【问题标题】:JOINING two tables a WHERE and a SUM加入两个表一个 WHERE 和一个 SUM
【发布时间】:2020-01-25 10:12:08
【问题描述】:

我有这张表叫销售:

vat,    activation
12345   2017-07-28
567892  2018-08-21
224124  2016-08-22

这张表叫做发票:

cif,   billdate,    amount
12345  2017-07-19   200
12345  2017-07-29   250
12345  2017-08-02   3000
224124 2016-08-24   400
224124 2018-09-08   2499

我想要一张这样的桌子:

vat,   activation, first_month_amount
12345  2017-07-28  450
224124 2016-08-22  400
567892 2018-08-21  200

基本上是激活当月计费的所有发票的总和。

到目前为止,我尝试过这个:

select * from  sales
LEFT JOIN ( 
    SELECT vat,sum(amount) as first_month_amount
    FROM invoices 
    WHERE month(billdate)=month(activation) 
    Group by cif
    ) as salesdata on sales.vat = salesdata.cif 

查询失败,因为找不到激活列。

第二次尝试是:

select * from  sales
LEFT JOIN ( 
    SELECT vat,sum(amount) as first_month_amount
    FROM invoices 
    WHERE month(billdate)=month(activation) 
    Group by cif
    ) as salesdata on sales.vat = salesdata.cif 

再次失败,因为找不到 billdate 列

如何做到这一点?

【问题讨论】:

    标签: mysql join sum where-clause


    【解决方案1】:

    此查询将为您提供所需的结果。它按年月和cif 对销售额进行分组,并在年月和cif 值上生成JOIN

    SELECT s.vat, s.activation, COALESCE(i.amount, 0) AS amount
    FROM sales s
    LEFT JOIN (SELECT cif, DATE_FORMAT(billdate, '%Y%m') AS billmonth, SUM(amount) AS amount
               FROM invoices
               GROUP BY cif, billmonth) i ON i.cif = s.vat AND i.billmonth = DATE_FORMAT(s.activation, '%Y%m')
    

    输出(注意 vat = 567892 的值为 0,因为 invoices 中没有数据):

    vat     activation  amount
    12345   2017-07-28  450
    567892  2018-08-21  0
    224124  2016-08-22  400
    

    Demo on dbfiddle

    【讨论】:

    • 成功了!非常感谢尼克,我已经为此苦苦挣扎了几天。
    • @Mamut 不用担心。我很高兴能帮上忙。
    【解决方案2】:

    你可以试试下面-

    select vat, activation,first_month_amount
    from sales s
    (
    SELECT cif,month(billdate) mbill,year(billdate) ybill,sum(amount) as first_month_amount
        FROM invoices 
        Group by cif,month(billdate),year(billdate)
    )s1 on s.vat=s1.cif and month(activation)=mbill and year(activation)=ybill
    

    【讨论】:

      【解决方案3】:

      您可以简单地在表之间进行连接:

      select s.vat, s.activation, sum(i.amount)
      from sales s
       left join invoices i on last_day(i.billdate)=last_day(s.activation)
      group by s.vat, s.activation;
      

      SQLFiddle

      不确定最后一个预期行 (567892 2018-08-21 200) 的逻辑,因为没有数据可以生成。

      【讨论】:

        【解决方案4】:

        一种方法是使用Correlated subquery;此外,仅匹配月份是不够的,您还需要匹配年份,才能正确获得“第一个月”金额:

        select s.*, 
               (select sum(i.amount) 
                from invoices i 
                where month(i.billdate) = month(s.activation) 
                      and year(i.billdate) = year(s.activation) 
                      and i.cif = s.vat
               ) first_month_amount
        from  sales s 
        

        通过在invoices 表上设置索引(cif),上述查询可以执行。所以,如果你还没有定义这个索引;您可以使用以下内容:

        ALTER TABLE invoices ADD INDEX (cif);
        

        但是,这个查询仍然没有得到足够好的优化(至少,在我看来)。目前,对日期值使用Month()Year() 函数会禁止在invoices 表的billdate 字段上使用索引。

        因此,我们可以使查询 sargeable 如下:

        select s.*, 
               (select sum(i.amount) 
                from invoices i 
                where i.cif = s.vat 
                      and i.billdate 
                            between date_format(s.activation, '%Y-%m-01') 
                                    and last_day(s.activation)
               ) first_month_amount
        from  sales s 
        

        如果我们在invoices 表上定义以下复合索引,则此查询将性能足够。请注意,billdate 应位于索引末尾,因为它是 Range 条件:

        ALTER TABLE invoices ADD INDEX (cif, billdate);
        

        【讨论】:

        • 感谢 Madhur 提供的所有知识和建议!
        • @Mamut 欢迎您!在得出结论之前,您应该在此处对所有答案中的各种方法进行基准测试。在具有数百万行的表上拥有(并不断获得)实际经验,基于Derived Table 的技术会导致不必要的临时表创建并最终影响生产服务器上运行的其他查询。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-11
        • 2012-11-25
        • 1970-01-01
        相关资源
        最近更新 更多