如果count为零,MySQL将执行子查询

Amo*_*Amo 12 mysql

我有三个表:monthly_revenue,currenciesforeign_exchange.

monthly_revenue表

|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1  | 1          | 1           | 100   | 1     | 2015 |
| 2  | 1          | 2           | 125   | 1     | 2015 |
| 3  | 1          | 3           | 115   | 1     | 2015 |
| 4  | 1          | 1           | 100   | 2     | 2015 |
| 5  | 1          | 2           | 125   | 2     | 2015 |
| 6  | 1          | 3           | 115   | 2     | 2015 |
|------------------------------------------------------|
Run Code Online (Sandbox Code Playgroud)

foreign_exchange表

|---------------------------------------|
| id | base | target | rate | rate_date |
|---------------------------------------|
| 1  | GBP  | USD    | 1.6  |2015-01-01 |
| 2  | GBP  | USD    | 1.62 |2015-01-15 |
| 3  | GBP  | USD    | 1.61 |2015-01-31 |
| 4  | EUR  | USD    | 1.2  |2015-01-01 |
| 5  | EUR  | USD    | 1.4  |2015-01-15 |
| 6  | EUR  | USD    | 1.4  |2015-01-31 |
| 7  | GBP  | EUR    | 1.4  |2015-01-01 |
| 8  | GBP  | EUR    | 1.45 |2015-01-15 |
| 9  | GBP  | EUR    | 1.44 |2015-01-31 |
|---------------------------------------|
Run Code Online (Sandbox Code Playgroud)

由此,我们可以看到平均fx率:

  • 英镑> 1月美元兑1.61
  • 欧元> 1月份的美元是1.33
  • 英镑> 1月欧元兑1.43

美元作为基础货币没有价格,2月没有价格.

货币表

|-----------|
| id | name |
|-----------|
| 1  | GBP  |
| 2  | USD  |
| 3  | EUR  |
|-----------|
Run Code Online (Sandbox Code Playgroud)

我想要实现的目标

monthly_revenue表中的每一行都可以有不同的currency_id,因为订单放置的是不同的货币.我想以一种共同货币查看给定月份的所有收入.因此,我不想一月份以英镑计算1月份的所有收入,而是单独查看1月份美元的所有收入,而是希望1月份所有收入都能获得一个值 - 转换为美元(例如).

这可以使用以下内容计算每一行(本例中使用1月):

收入价值x基准货币与目标货币之间1月份的平均汇率

如果我在1月份有5个订单,有4种不同的货币,这让我看到任何单一货币的所有收入.

示例 - 获取1月份的所有收入,以美元计算

这应该返回:

|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1  | 1          | 1           | 100   | 1     | 2015 |
| 2  | 1          | 2           | 125   | 1     | 2015 |
| 3  | 1          | 3           | 115   | 1     | 2015 |
|------------------------------------------------------|
Run Code Online (Sandbox Code Playgroud)

但是,第1行和第3行不是美元(分别是GBP和EUR).

我想看到的是返回的每一行都有正在转换的平均汇率和一converted列.例如:

|-------------------------------------------------------------------------|
| id | prod_id | currency_id | value | month | year | fx_avg | converted  |
|-------------------------------------------------------------------------|
| 1  | 1       | 1           | 100   | 1     | 2015 | 1.61   | 161        |
| 2  | 1       | 2           | 125   | 1     | 2015 | 1      | 125        |
| 3  | 1       | 3           | 115   | 1     | 2015 | 1.33   | 152.95     |
|-------------------------------------------------------------------------|
Run Code Online (Sandbox Code Playgroud)

我在哪里

我现在可以使用下面的查询完成基本计算,但缺少一些关键功能:

  • 如果没有可用的外汇汇率(例如,对于未来日期当然没有外汇汇率),则忽略整行.在这个例子中我想要的是使用最近一个月的平均值.

  • 如果在目标货币与基础货币相同的情况下执行计算,则忽略整行(因为FX表中没有基数等于目标的记录).在这种情况下,速率应该硬定义为1.

查询到目前为止

SELECT 
    r.value * IFNULL(AVG(fx.rate),1) as converted, AVG(fx.rate) as averageFx, 
    r.*, fx.*
FROM 
    foreign_exchange fx, monthly_revenue r, order_headers h
WHERE 
    fx.base IN (SELECT name FROM currencies WHERE id = r.currency_id) AND 
    r.order_header_id = h.id AND 
    fx.target = 'USD' AND 
    MONTH(fx.rate_date) = r.month AND
    YEAR(fx.rate_date) = r.year AND
    r.year = 2015
GROUP BY r.id
ORDER BY month ASC
Run Code Online (Sandbox Code Playgroud)

如果没有可用于FX的记录,则应该执行单独的子查询以获得最近一个月的平均费率.

任何输入将不胜感激.如果需要进一步的信息,请发表评论.

谢谢.

编辑 这是一个SQFiddle,它具有示例模式和突出问题的代码.

Ric*_*mes 6

以下是计算特定货币和月初的交易所的函数的近似值:

DELIMITER //
CREATE FUNCTION MonthRate(IN _curr CHAR(3) CHARACTER SET ascii,
                          IN _date DATE)
    RETURNS FLOAT
    DETERMINISTIC
BEGIN
    -- Note:  _date must be the first of some month, such as '2015-02-01'
    DECLARE _avg FLOAT;
    DECLARE _prev FLOAT;
    -- First, try to get the average for the month:
    SELECT AVG(rate) INTO _avg FROM foreign_exchange
        WHERE base = _curr
          AND target = 'USD'
          AND rate_date >= _date
          AND rate_date  < _date + INTERVAL 1 MONTH;
    IF _avg IS NOT NULL THEN
        RETURN _avg;
    END;
    -- Fall back onto the last rate before the month:
    SELECT rate INTO _prev
        FROM foreign_exchange
        WHERE base = _curr
          AND target = 'USD'
          AND rate_date < _date
        ORDER BY _date
        LIMIT 1;
    IF _prev IS NOT NULL THEN
        RETURN _prev;
    END;
    SELECT "Could not get value -- ran off start of Rates table";
END;
DELIMITER ;
Run Code Online (Sandbox Code Playgroud)

可能存在语法错误等.但希望您可以使用它.

从其余代码调用函数应该很容易.

为了性能,这将是有益的:

INDEX(base, target, rate_date, rate)
Run Code Online (Sandbox Code Playgroud)


vit*_*ore 0

创建一个视图:

 create view avg_rate as
   select base, target, year(fx.rate_date) year, month(fx.rate_date) month,
          avg(rate) avg_rate 
   from  foreign_exchange group by base, target
Run Code Online (Sandbox Code Playgroud)

加入两次,一次是当前月份,一次是上个月

 select r.id, r.month, 
        r.value * avg(coalesce(cr.avg_rate, pr.avg_rate, 1)) converted,
        avg(coalesce(cr.avg_rate, pr.avg_rate), 0) rate

 from monthly_revenue r, avg_rate cr, avg_rate pr, order_headers h

 where
  r.year = 2015 and
  cr.year = r.year and cr.month = r.month  and cr.target='USD' and 
  pr.year = r.year and pr.month = r.month - 1  and pr.target='USD' and
  r.order_header_id = h.id
 group by r.id
 order by r.month
Run Code Online (Sandbox Code Playgroud)

另外,我个人不喜欢这种编写查询的方式,更喜欢使用显式联接,因为您可以对条件进行逻辑分组,并且不会在 where 子句中出现混乱。IE:

 ... 
 from monthly_revenue r
    inner join order_headers h on r.order_header_id = h.id
    left join avg_rate cr on cr.year = r.year and cr.month = r.month  and cr.target='USD'
    left join avg_rate pr on pr.year = r.year and pr.month = r.month - 1 and pr.target='USD'

 where r.year = 2015
Run Code Online (Sandbox Code Playgroud)