【问题标题】:SQL - sum data for all time, 30 days and 90 days for multiple columns indiviuallySQL - 单独为多列汇总所有时间、30 天和 90 天的数据
【发布时间】:2020-06-02 13:53:19
【问题描述】:

背景:

我的数据看起来像这样

date        src    subsrc   subsubsrc   param1  param2
2020-02-01  src1    ksjd    dfd8        47      31    
2020-02-02  src1    djsk    zmnc        44      95    
2020-02-03  src2    skdj    awes        92      100   
2020-02-04  src2    mxsf    kajs        80      2     
2020-02-05  src3    skdj    asio        46      53    
2020-02-06  src3    dekl    jdqo        19      18    
2020-02-07  src3    dskl    dqqq        69      18    
2020-02-08  src4    sqip    riow        64      46    
2020-02-09  src5    ss01    qwep        34      34    

我正在尝试汇总过去 30 天和过去 90 天的所有时间(无滚动总和)

所以我的最终数据应该是这样的:

src     subsrc  subsubsrc   p1_all  p1_30   p1_90   p2_all  p2_30   p2_90
src1    ksjd    dfd8        7       1       7       98      7        98
src1    djsk    zmnc        0       0       0       0       0         0
src2    skdj    awes        12      12      12      4       4         4
src2    mxsf    kajs        6       6       6       31      31       31
src3    skdj    asio        0       0       0       0       0         0
src3    dekl    jdqo        20      20      20      17      17        17
src3    dskl    dqqq        3       3       3       4       4         4
src4    sqip    qwep        0       0       0       0       0         0
src5    ss01    qwes        15      15      15      2       2         2

关于数据:

  • 这只是虚拟数据,因此不正确。
  • 我的数据中有数万行。
  • 有十几个 src 列构成表的键。
  • 有十几个参数列,我必须对 30 和 90 以及所有时间求和。
  • param 列中也有空值。
  • 另外,同一天和 src 列可能有多行。
  • 每天都在添加新数据,查询可能会每天运行以获取最新的 30、90、所有时间数据。

我的尝试:

这是我想出的:

SELECT src, subsubsrc, subsubsrc,
SUM(param1) as param1_all,
SUM(CASE WHEN DATE_DIFF(CURRENT_DATE,date,day) <= 30 THEN param1 END) as param1_30,
SUM(CASE WHEN DATE_DIFF(CURRENT_DATE,date,day) <= 90 THEN param1 END) as param1_90,
SUM(param2) as param2_all,
SUM(CASE WHEN DATE_DIFF(CURRENT_DATE,date,day) <= 30 THEN param2 END) as param2_30,
SUM(CASE WHEN DATE_DIFF(CURRENT_DATE,date,day) <= 90 THEN param2 END) as param2_90,
FROM `MY_TABLE`
GROUP BY src
ORDER BY src

这确实有效,但我可以预计对于多个源甚至更多参数列,此查询将变为多长时间。

我一直在尝试一种叫做“过滤聚合函数(或手动数据透视)”的方法,HERE 解释道。但我无法理解/实施它。

我还查看了几十个答案,其中大多数是每天的总和,或者是这个基本计算的复杂案例。也许我没有正确搜索它。

如您所见,我是 SQL 新手,非常感谢任何帮助。

【问题讨论】:

  • 您使用的是 MySQL 还是 Big Query?这是两种不同的数据库产品,请仅标记相关的产品。
  • 它的 MySQL,我将在 Google Cloud SQL 中编写新表。由于界面和速度的原因,我在 BigQuery 中测试了查询,并且错误地标记了 bigquery。
  • 我如何知道问题被标记为关闭的原因。
  • “数万”?我怀疑它是否需要超过 1 秒。够快吗?
  • @RickJames。作为一名编码员,很多重复困扰着我。我只是觉得它可以改进。此外,数据会随着时间的推移而增长。

标签: mysql sql group-by pivot query-optimization


【解决方案1】:

我建议您为此使用 3 个不同的查询:

  1. 总和
  2. 30 天的总和
  3. 90 天的总和

因为当您尝试进行多合一查询时,由于CASE-WHEN-END(顺便说一句,MySQL 中有紧凑形式IF()),您最终会进行全表扫描。这是极不理想的。

如果将其拆分为 3 个不同的查询并向 date 列添加索引,则它不会对第二个和第三个查询进行全扫描。仅适用于第一个查询,可以单独优化(例如通过缓存)。

还有这种方法:DATE_DIFF(CURRENT_DATE,date,day) &lt;= 90

应改为:date &gt;= 'date-90-days-ago'(其中'date-90-days-ago' 是固定日期)

因此,您不必为每一行计算 2 个日期的差异。您只需计算 2 个日期:30 天前和 90 天前,并将所有其他日期与这两个日期进行比较。这种方法将受益于date 列索引。

【讨论】:

  • 您认为不是创建 3 个查询,而是可以将单个子查询用作多个列的过滤器。我发现了类似Filtered aggregate functions (or manual pivot)(上面共享的链接)之类的东西,看起来很有希望。
  • 感谢IF() 声明而不是CASE-WHEN-END
  • 最后我不能使用固定日期,因为数据每天都会更新。
  • 对于您提到的 3 个查询中的每一个,都将具有相同的 src 列并在相同的参数列上求和,甚至在日期上具有相同的条件(30、90 和所有值除外)。我只是觉得应该有一种简洁明了的方式。
  • 您无需提供date-90-days-ago,MySQL 很乐意通过CURDATE() - INTERVAL 90 DAY 提供该常量——而且速度一样快。
【解决方案2】:

如果您能够以编程方式构建查询,那么您的第一种方法也不错。一种替代方法可能是首先为 30 天和 90 天的案例创建边表,以便您可以有效地从每个案例中选择所有列。这也可以在子查询中完成,但需要考虑性能。

希望澄清一些未经测试的伪代码:

SELECT 
 src,
 subsrc,
 subsubsrc,
 SUM(param1) as param1_all,
 -- other "all" sums here
 SUM(t30.param1) as param1_30,
 -- other "30" sums here
 SUM(t90.param1) as param1_90,
 -- other "90" sums here
FROM MY_TABLE
LEFT JOIN (
  SELECT *
  FROM MY_TABLE
  WHERE date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
) as t30 on t30.src = MY_TABLE.src
LEFT JOIN (
  SELECT *
  FROM MY_TABLE
  WHERE date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
) as t90 on t90.src = MY_TABLE.src
GROUP BY MY_TABLE.src
ORDER BY MY_TABLE.src

请注意,日期条件已切换为不使用日期列上的函数,而是与日期值进行比较。您最初的方法将击败日期上的任何索引(您希望使其更有效)。

如果您首先将这些子查询放入在 src 上具有键的边表中,则连接也会更有效率。您甚至可以先对这些边表进行分组/求和,而不是创建更大的数据副本,然后将汇总的数据连接在一起。

【讨论】:

  • 谢谢你。但如前所述,我有 20 个 src 列和 20 个 param 列(相加)。我只是觉得,鉴于这项工作更像是重复性工作,可能会有更聪明的方法来做到这一点。现在我只管这个。谢谢。
  • 是的,这减少了一些重复(没有到处放置日期条件),但可能不能解决您的主要问题。有可能使用存储过程或构建查询的准备好的语句来做到这一点,这是我试图避免的事情,因为我发现它有点陌生,并且觉得它使代码更难理解/共享。也许其他人会向我们展示一种非常干净的方法来做到这一点。我假设您是直接编写此 SQL,而不是从某些应用程序代码中查询。
  • 是的,日级别表在 Cloud SQL 中,我也在 Cloud SQL 中创建一个 30 90 的新表。如果那是你要问的。我自己不是专家,但让我们看看我们是否可以通过这个问题学到新的东西。
【解决方案3】:

您的代码看起来不错。您的 RDBMS 需要在后台循环所有记录并进行一些计算。您可以改进的一件事是您正在计算所有记录的日期差异。分别提前计算 30 天前和 90 天前的时刻并仅将日期与这些日期进行比较是有意义的。

由于您已经知道行数和参数的数量在未来会增加,因此创建一个每天按以下方式计算的 cron 作业是有意义的:

  • 第一次计算值时,它应该存储所有结果以及运行日期(可能存储到专门用于此分析的表中)
  • 在随后的几天中,您可以通过加载自上次检查后创建的项目来计算所有时间总和
  • 您仍然需要计算 30 天和 90 天的数据,但这比一直计算这个问题要小得多

如果你这样做得当并且掌握了每日信息,那么以后你也可以分析历史趋势。

【讨论】:

    【解决方案4】:

    您的查询看起来不错;条件聚合是透视数据集的规范方法。

    一种可能提高性能的方法是更改​​条件表达式中的日期过滤器:使用日期函数会排除使用索引。

    相反,您可以将其表述为:

    select 
        src, 
        subsrc, 
        subsubsrc,
        sum(param1) as param1_all,
        sum(case when date >= current_date - interval 30 day then param1 end) as param1_30,
        sum(case when date >= current_date - interval 90 day then param1 end) as param1_90,
        sum(param2) as param2_all,
        sum(case when date >= current_date - interval 30 day then param2 end) as param2_30,
        sum(case when date >= current_date - interval 90 day then param2 end) as param2_90
    from my_table
    group by src, subsrc, subsubsrc
    order by src, subsrc, subsubsrc
    

    对于性能,以下索引可能会有所帮助:(src, subsrc, subsubsrc, date)

    请注意,我在 group by 子句中包含了所有三个非聚合列 (src, subsrc, subsubsrc):从 MySQL 5.7 开始,默认情况下这是强制性的(尽管您可以使用 sql 模式来改变这种行为) - 和大多数其他数据库实现相同的约束。

    【讨论】:

    • 赞成,因为这个答案比其他答案更快和/或更干净。
    • @RickJames:来自像你这样的 MySQL 性能专家,非常感谢!
    猜你喜欢
    • 2013-09-08
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 2021-03-24
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    相关资源
    最近更新 更多