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

时间:2015-08-17 13:18:07

标签: mysql

我有三个表:monthly_revenuecurrenciesforeign_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 |
|------------------------------------------------------|

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 |
|---------------------------------------|

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

  • GBP> 1月美元为1.61
  • EUR> 1月美元为1.33
  • GBP> 1月份的欧元为1.43

没有美元作为基础货币的汇率,2月份没有汇率。

货币表

|-----------|
| id | name |
|-----------|
| 1  | GBP  |
| 2  | USD  |
| 3  | EUR  |
|-----------|

我想要实现的目标

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 |
|------------------------------------------------------|

但是,第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     |
|-------------------------------------------------------------------------|

我在

的地方

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

  • 如果没有可用的外汇汇率(例如,对于未来日期,当然外汇汇率不可用)则忽略整行。在这种情况下,我喜欢的是使用最近一个月的平均值。

  • 如果正在执行目标货币与基础货币相同的计算,则忽略整行(因为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

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

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

感谢。

编辑 Here is a SQFiddle,其中包含示例模式和突出显示问题的代码。

4 个答案:

答案 0 :(得分: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 ;

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

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

为了表现,这将是有益的:

INDEX(base, target, rate_date, rate)

答案 1 :(得分: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

加入两次,一次为当月,一次为前一次

 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

此外,我个人不喜欢这种编写查询的方式,并且喜欢使用显式连接,因为您在逻辑上对条件进行分组并且在where子句中没有混乱。即:

 ... 
 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

答案 2 :(得分:0)

http://sqlfiddle.com/#!9/6a41a/1

这个小提琴基于你原来的小提琴,但我在2月和3月添加了一些费率值来测试并展示它是如何工作的。

WHERE

如果您需要特定月份的记录,可以在ORDER之前添加WHERE r.month = 1 and r.year = 2015 ORDER BY r.id, fx.avg_date DESC) t ,如下所示:

.row-fluid

答案 3 :(得分:0)

您可以在您提供的小提琴链接上测试此查询:http://sqlfiddle.com/#!9/33def/2

select id,product_id,currency_id,currency_name,
    value,month,year,@prev_fx_avg:=ifnull(fx_avg,@prev_fx_avg) fx_avg,
    value*@prev_fx_avg as converted 
from (SELECT 
    r.id,r.product_id,r.currency_id,c.name as currency_name,
    r.value,r.month,r.year,if(c.name="USD",1,temp.avg_rate) as fx_avg

FROM 
    monthly_revenue r
    left join currencies c on r.currency_id=c.id
    left join

    (select base , avg(rate) as avg_rate, MONTH(fx.rate_date) month,
     YEAR(fx.rate_date) year
     from foreign_exchange fx
     where target="USD"
     group by base,target,MONTH(fx.rate_date),
    YEAR(fx.rate_date)) temp on(r.month=temp.month and r.year=temp.year and c.name=temp.base)

    group by r.id
    order by r.currency_id,r.month ASC, r.year ASC) final,(select @prev_fx_avg:=-1) temp2;