MySQL 8 - 计算年度指标

Posted

技术标签:

【中文标题】MySQL 8 - 计算年度指标【英文标题】:MySQL 8 - Calculating Year-over-Year Metrics 【发布时间】: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 行。 【参考方案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

【讨论】:

以上是关于MySQL 8 - 计算年度指标的主要内容,如果未能解决你的问题,请参考以下文章

MySQL的性能指标计算和优化方法

MySQL性能指标计算方式

王道计算机组成原理笔记8 计算机性能指标—容量速度

性能测试指标都有哪些?

转载用sql语句计算出mysql数据库的qps,tps,iops性能指标

MySql主要性能指标说明