MySQL 8-计算年度指标

问题描述

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

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

Year over Year

这是我构建的查询,尽管由于子查询按行运行,所以我从未真正完成过该查询(运行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`

解决方法

假设您在表中所有月份的数据都没有任何间隙,那么您所需要的就是窗口函数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自联接:

WITH cte AS (
  SELECT YEAR(trandte) AS Year,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
,

您可以在单个表扫描中执行此操作(不进行联接或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)
,

您可以使用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)部分中指定任何年份。

,

您可以使用数据透视表按年份显示您的销售额。

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
,

第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步可能是使用自联接来计算年复一年。

第3步:输出顺序-还是图形包重新排列数据以获取12套多年的数据?

从长远来看,考虑建立和维护一个“汇总表”,也许是每日小计。就像第1步,但要成千上万行,而不是数百万或数十行。这样,您就可以很快计算出每月金额。或每周一次。或其他范围。这样,庞大的任务(第1步)便建立在日常数据块中,其速度将快一千倍。

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