【问题标题】:MySQL 8 - Calculating Year-over-Year MetricsMySQL 8 - 计算年度指标
【发布时间】:2020-08-19 09:57:37
【问题描述】:

我一直在尝试计算每月回报的同比增长,并且一直在重写相同的查询数小时,但没有运气。我见过解决方案,但它们都是其他数据库解决方案。

我正在尝试基本上实现以下目标:

这是我构建的查询,尽管由于子查询每行运行,我从未真正完成过运行(运行了 15 分钟以上)。

这个表有 2m+ 行,索引很好,速度很快,但子查询会杀死它。

可能是完全错误的方法,但这就是我所拥有的。

SELECT
    YEAR(thisyear.trandte) AS `Year`,
    MONTH(thisyear.trandte) AS `YearMonth`,
    SUM(lastyear.totamount) AS LastYearSales,
    SUM(thisyear.totamount) AS ThisYearSales
FROM
    sync_invoice_lines thisyear
LEFT JOIN
    sync_invoice_lines lastyear ON
        MONTH(thisyear.trandte) = (MONTH(lastyear.trandte)) AND
        YEAR(thisyear.trandte) = (YEAR(lastyear.trandte) - 1)
WHERE
    thisyear.type = 'IN' AND
    lastyear.type = 'IN' AND
    thisyear.sync_active = 1 AND
    lastyear.sync_active = 1 AND
GROUP BY `Year`, `YearMonth`

【问题讨论】:

  • 您当前的查询效率非常低,并且会返回完全错误的数字。您以相同的年份/月份连接所有行,即如果您在 2020 年 7 月获得 1000 行,则在 SUM 之前的连接之后您将获得 1000*1000 行。

标签: mysql sql sum e-commerce window-functions


【解决方案1】:

如果表中所有月份的数据没有任何间隔,那么你只需要窗口函数LAG() 来获取去年同月totamount 的总和:

SELECT YEAR(trandte) AS Year, 
       MONTH(trandte) AS Month, 
       SUM(totamount) AS ThisYearSales,
       LAG(SUM(totamount), 12) OVER (ORDER BY YEAR(trandte), MONTH(trandte)) AS LastYearSales
FROM sync_invoice_lines 
WHERE type = 'IN' AND sync_active = 1
GROUP BY Year, Month

如果月份之间有间隔,则从上述查询中创建一个CTE 并对其执行LEFT self join:

WITH cte AS (
  SELECT YEAR(trandte) AS Year, 
         MONTH(trandte) AS Month, 
         SUM(totamount) AS Sales
  FROM sync_invoice_lines 
  WHERE type = 'IN' AND sync_active = 1
  GROUP BY Year, Month
)
SELECT c1.Year, 
       c1.Month,
       c1.Sales AS ThisYearSales,
       c2.Sales AS LastYearSales 
FROM cte c1 LEFT JOIN cte c2
ON c2.Year = c1.Year - 1 AND c2.Month = c1.Month

【讨论】:

  • 为什么不OVER (PARTITION BY MONTH(trandte) ORDER BY YEAR(trandte))上年同月
  • 当然,在 LAG() 中没有12 也可以这样写。
【解决方案2】:

您可以在单个表扫描中执行此操作(无需连接或 CTE),并考虑可能缺少的月份。为此,请使用 lag()range 子句,该子句正好针对去年的同一个月,如下所示:

select
    year(trandte) as `year`,
    month(trandte) as `yearmonth`,
    lag(sum(totamount)) over(
        order by concat(year(trandte), '-', month(trandte), '-01') 
        range between interval 1 year preceding and interval 1 year preceding
    ) as lastyearsales,
    sum(totamount) as thisyearsales
from sync_invoice_lines
where type = 'IN' and sync_active = 1
group by year(trandte), month(trandte)
order by year(trandte), month(trandte)

【讨论】:

  • 这看起来是一个不错的解决方案,但是会产生错误:Window '<unnamed window>' with RANGE N PRECEDING/FOLLOWING frame requires exactly one ORDER BY expression, of numeric or temporal type。我会尝试解决它 - 只是 LAG 和 OVER 函数的新手。
【解决方案3】:

您可以使用 CASE 表达式分别计算去年销售额和今年销售额的总额。 这很简单。

查询如下:

SELECT
    YEAR(CURRENT_DATE) AS `Year`,
    MONTH(trandte) AS `YearMonth`,
    SUM(CASE YEAR(trandte) WHEN YEAR(CURRENT_DATE)-1 THEN totamount END) AS LastYearSales,
    SUM(CASE YEAR(trandte) WHEN YEAR(CURRENT_DATE) THEN totamount END) AS ThisYearSales
FROM
    sync_invoice_lines
WHERE type = 'IN' AND sync_active = 1
GROUP BY `YearMonth`
ORDER BY `YearMonth`;

DB Fiddle

您可以在 YEAR(CURRENT_DATE) 部分中指定任何年份。

【讨论】:

    【解决方案4】:

    您可以使用数据透视表逐年显示每个月的销售额。

    with monthly_sales as  
    (SELECT
        YEAR(trandte) AS year,
        MONTH(trandte) AS month,
        SUM(totamount) AS sales
    FROM
        sync_invoice_lines 
    WHERE
        type = 'IN' AND
        sync_active = 1
    GROUP BY YEAR(trandte), MONTH(trandte)) 
    
    Select * from 
    (select month, year from monthly_sales)
    pivot
    (sum(sales) 
    for month in (2013, 2014, 2015)
    )
    order by month
    

    【讨论】:

      【解决方案5】:

      第 1 步:计算所有每月小计,没有逐年对比

      SELECT  LEFT(trandte, 7) AS yyyy_mm,
              SUM(totamount) AS sales
          FROM sync_invoice_lines
          WHERE ...
          GROUP BY 1;
      

      首先,看看这是否得到了正确的数字,尽管不是按照所需的顺序。看看它跑得有多快。

      这可能就是你所需要的。

      第 2 步:这将使用大约 30 行,因此效率不是问题。将上述内容放在另一个表中,或者,因为您有 MySQL 8.0(或 MariaDB 10.2),所以在 WITH 中使用它来完成其余的工作。第 2 步可能是使用自联接计算年复一年。

      第三步:输出的顺序——还是图形包重新排列数据得到12组多年?

      从长远来看,考虑建立和维护一个“汇总表”,也许是每日小计。这就像第 1 步,但有数千行,而不是数百万或数十行。有了它,您可以非常快速地计算每月的金额。或每周。或其他范围。这样,庞大的任务(第 1 步)以每日块的形式构建,速度将提高一千倍。

      关于小计的更多信息:http://mysql.rjweb.org/doc.php/summarytables

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-19
        • 1970-01-01
        • 2016-04-05
        • 2012-06-02
        • 1970-01-01
        • 2022-01-24
        相关资源
        最近更新 更多