订阅更新

按年度拆分销售总额:一个区间跨多年

SQL 实战 123 题 · 第 102 题 困难 约 4 分钟读完

LeetCode 1384 · 区间拆分、和年份表关联、区间重叠计算

本文目录
  1. 面试怎么回答
  2. 题意
  3. 表结构
  4. 思路
  5. 参考解法
  6. 易错点

面试怎么回答

我会构造一张年份表,用「区间重叠」条件(开始 <= 年末 且 结束 >= 年初)和销售区间关联,每一年的天数 = LEAST(结束, 年末) - GREATEST(开始, 年初) + 1,再乘日均额。区间按日历拆分在数仓里很常见,比如订阅收入按月分摊,都是这个套路。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

题意

销售表记录每个产品的一段销售期(起止日期都包含在内)和这段时间的日均销售额。销售期可能跨越 2018 到 2020 中的多个年份。按产品和年份输出每年的销售总额,按产品 ID 和年份排序。

表结构

  • Product:product_id(主键)、product_name
  • Sales:product_id(主键)、period_start、period_end、average_daily_sales

思路

把一个跨年的区间拆到各年:构造一张年份表(2018、2019、2020),让销售区间和每一年的区间做重叠判断,有重叠才关联。重叠天数 = MIN(两个结束日) - MAX(两个开始日) + 1,再乘以日均销售额。原解法为每一年写一段 UNION,逻辑相同但更冗长。

参考解法

(
 select Sales.product_id, product_name, '2018' as 'report_year', if(period_start<'2019-01-01', (datediff(if(period_end<'2019-01-01', period_end, date('2018-12-31')), if(period_start>='2018-01-01', period_start, date('2018-01-01')))+1)*average_daily_sales, 0) as total_amount
from Sales  
join Product on Sales.product_id = Product.product_id 
having  total_amount>0
)
union(
select Sales.product_id, product_name, '2019' as 'report_year', if( period_start<'2020-01-01', (datediff(if(period_end<'2020-01-01', period_end, date('2019-12-31')), if(period_start>='2019-01-01', period_start, date('2019-01-01')))+1)*average_daily_sales , 0) as total_amount
from Sales  
join Product on (Sales.product_id = Product.product_id )
having  total_amount>0
)
union(
select Sales.product_id, product_name, '2020' as 'report_year', (datediff(if(period_end<'2021-01-01', period_end, date('2020-12-31')), if(period_start>='2020-01-01', period_start, date('2020-01-01')))+1)*average_daily_sales as total_amount
from Sales  
join Product  on (Sales.product_id = Product.product_id)
having total_amount>0
) 
order by product_id, report_year

另一种写法:和年份表做区间重叠关联

SELECT s.product_id, p.product_name, y.yr AS report_year,
       (DATEDIFF(LEAST(s.period_end, DATE(CONCAT(y.yr, '-12-31'))),
                 GREATEST(s.period_start, DATE(CONCAT(y.yr, '-01-01')))) + 1) * s.average_daily_sales AS total_amount
FROM Sales s
JOIN Product p ON p.product_id = s.product_id
JOIN (SELECT '2018' AS yr UNION ALL SELECT '2019' UNION ALL SELECT '2020') y
  ON s.period_start <= DATE(CONCAT(y.yr, '-12-31')) AND s.period_end >= DATE(CONCAT(y.yr, '-01-01'))
ORDER BY s.product_id, report_year

易错点

  • 天数要 +1,因为起止日期都包含在内。
  • 只和销售期有重叠的年份关联,否则会算出负数天数。
刷题进度保存在当前浏览器