时间序列错位还原之SQL实现案例详解

Posted ShenLiang2025

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了时间序列错位还原之SQL实现案例详解相关的知识,希望对你有一定的参考价值。

SQL时间错位与还原生成案例

需求描述

1 原表T1某条记录(记做r1,相邻下一条为r2)的下一行记录的STARTDATE小于上一行ENDDATE,针对这样的记录做转换即:

r1的STARTDATE保持不变,ENDDATE为r1的STARTDATE-1

r2的STARTDATE为r1的ENDDATE,ENDDATE为r1的ENDDATE

2 如果原表T1不存在相邻行“时间重叠”(即为1的定义)时保持原有数据不变。

 

# 文本版
#T1
seq id  startdate   enddate     num
1	1	2021-04-20	2021-05-03	200
2	1	2021-05-01	2021-05-24	100
3	1	2021-05-18	2021-05-31	69
4	1	2021-05-20	2021-07-31	34
5	1	2021-08-05	2021-08-25	45
6	1	2021-08-15	2021-09-25	65


#输出结果
ID STARTDATE    ENDDATE     NUM
1  2021-04-20	2021-04-30	200
1  2021-05-01	2021-05-02	300
1  2021-05-03	2021-05-17	100
1  2021-05-18	2021-05-19	169
1  2021-05-20	2021-05-23	203
1  2021-05-24	2021-05-30	103
1  2021-05-31	2021-07-30	34
1  2021-08-05	2021-08-14	45
1  2021-08-15	2021-08-25	110
1  2021-08-26	2021-09-25	65

 

思路概述

#1 需求延展
SEQ     ID      STARTDATE       ENDDATE         NUM
1	1	2021-04-20	2021-05-03	200
2	1	2021-05-01	2021-05-24	100
3	1	2021-05-18	2021-05-31	69
4	1	2021-05-20	2021-07-31	34
这里第4条记录同时叠加在第2和3条记录里。

#2 思路概述 
1) T0 通过上下行函数生成的时间序列
id      new_DATE        nextSTARTDATE   preEndDATE     rn      
1	2021-05-24		        2021-05-03	1
1	2021-05-03	2021-05-24	2021-05-01	2
1	2021-05-01	2021-05-03	2021-04-20	3
1	2021-04-20	2021-05-01		        4

2) last 取出T0里的最后一条记录,为后面的矫正做准备。
new_Date        preENDDATE      id
2021-05-24	2021-05-03	1

3) normal 取出原始数据里不会出现时间叠加的记录,为后面的矫正做准备。
当前演示数据无记录,代码加注释可浮现。

4)T_Serial 统一定义STARTDATE、ENDDATE,首次修正T0。
id      STARTDATE       ENDDATE
1	2021-04-20	2021-04-30
1	2021-05-01	2021-05-03
1	2021-05-04	2021-05-24

5) T2 对时间没有重叠的记录进行修正(删除T0对应值,更新对应ENDDATE)。
当前示例结果集为空,即无需要修正。

6) T2关联T1(原始表),汇总后取得最终值
STARTDATE   ENDDATE     NUM
2021-04-20	2021-04-30	200
2021-05-01	2021-05-03	300
2021-05-04	2021-05-24	100

SQL代码

-- 当前演示版本是mysql 8.0.23,支持CTE、窗口函数的SQL Server、Oracle需要修改Order by和ADDDATE处语法。
-- Step0 创建表并初始化数据
DROP TABLE IF EXISTS test_ShenLiang2025;
CREATE TABLE test_ShenLiang2025 (
  seq int DEFAULT NULL,
  id int DEFAULT NULL,
  STARTDATE date DEFAULT NULL,
  ENDDATE date DEFAULT NULL,
  NUM int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO test_ShenLiang2025 VALUES ('1', '1', '2021-04-20', '2021-05-03', '200');
INSERT INTO test_ShenLiang2025 VALUES ('2', '1', '2021-05-01', '2021-05-24', '100');
INSERT INTO test_ShenLiang2025 VALUES ('3', '1', '2021-05-18', '2021-05-31', '69');
INSERT INTO test_ShenLiang2025 VALUES ('4', '1', '2021-05-20', '2021-07-31', '34');
INSERT INTO test_ShenLiang2025 VALUES ('5', '1', '2021-08-05', '2021-08-25', '45');
INSERT INTO test_ShenLiang2025 VALUES ('6', '1', '2021-08-15', '2021-09-25', '65');


-- Step1 构建临时结果集以生成时间序列。
WITH T0 AS(
SELECT id, 
			new_DATE,
			LEAD(NEW_DATE,1) OVER (PARTITION BY ID ORDER BY NEW_DATE ) nextSTARTDATE,
			LAG(NEW_DATE,1) OVER (PARTITION BY ID ORDER BY NEW_DATE ) preENDDATE,
			ROW_NUMBER()OVER(PARTITION BY ID ORDER BY new_DATE DESC) rn
			FROM
		(
		SELECT DISTINCT ID,STARTDATE new_DATE  FROM test_ShenLiang2025    
			WHERE seq in (1,2) -- 可加注释验证,当前仅取原表里2条记录
		UNION
		SELECT DISTINCT ID,ENDDATE new_DATE FROM test_ShenLiang2025
			WHERE seq in (1,2) -- 可加注释验证,当前仅取原表里2条记录
						ORDER BY new_DATE 
		)A
),last AS
( SELECT new_DATE,preENDDATE,id
FROM T0 
WHERE nextSTARTDATE IS NULL
),normal AS
(
	SELECT * FROM
	(
	SELECT id, 
				ENDDATE,
				LEAD(STARTDATE,1) OVER (PARTITION BY ID ORDER BY ENDDATE ) nextSTARTDATE,
				LAG(ENDDATE,1) OVER (PARTITION BY ID ORDER BY ENDDATE ) preENDDATE
				FROM test_ShenLiang2025
	)A
	WHERE ENDDATE > preENDDATE AND ENDDATE < nextSTARTDATE
),T_Serial AS (

SELECT ID,ADDDATE(preENDDATE, INTERVAL 1 DAY ) STARTDATE,
new_DATE ENDDATE
FROM last 

UNION

SELECT bottom_2.ID,bottom_2.new_DATE STARTDATE,
CASE WHEN rn =3 THEN bottom_2.nextSTARTDATE 
	ELSE ADDDATE(bottom_2.nextSTARTDATE, INTERVAL -1 DAY ) END ENDDATE
FROM last 
JOIN T0 bottom_2
ON bottom_2.nextSTARTDATE<=last.preENDDATE AND bottom_2.id = last.id
),T2 AS(
SELECT B.ID,B.STARTDATE,B.ENDDATE FROM
		(
			SELECT A.*,ROW_NUMBER()OVER(PARTITION BY ID,STARTDATE ORDER BY ENDDATE) rn
			FROM
			(
			SELECT A.ID,A.STARTDATE,A.ENDDATE
			FROM T_Serial A
			LEFT JOIN normal B
			ON A.STARTDATE = B.ENDDATE AND A.ID = B.ID
			WHERE B.ENDDATE IS NULL

			UNION 
			 
			SELECT A.ID,A.STARTDATE,B.ENDDATE   
			FROM T_Serial A
			INNER JOIN normal B
			ON ADDDATE(A.ENDDATE, INTERVAL 1 DAY ) = B.ENDDATE AND A.ID = B.ID			 
			)A
		)B WHERE rn =1
)

-- Step2 时间序列关联原表生成NUM字段。
SELECT T2.STARTDATE,T2.ENDDATE,SUM(T1.NUM) TOTAL FROM T2
JOIN test_ShenLiang2025 T1
ON T2.STARTDATE>=T1.STARTDATE 
	AND T2.ENDDATE<=T1.ENDDATE
GROUP BY T2.STARTDATE,T2.ENDDATE
ORDER BY T2.STARTDATE

-- Step4 查看结果
/* 
STARTDATE   ENDDATE     NUM
2021-04-20	2021-04-30	200
2021-05-01	2021-05-03	300
2021-05-04	2021-05-24	100
*/

-- 注,注释记录过滤后结果见下图:

执行结果

 

以上是关于时间序列错位还原之SQL实现案例详解的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis之Mapper XML 文件详解-sql和入参

猿创征文|时间序列分析算法之二次指数平滑法和三次指数平滑法详解+Python代码实现

SQL优化万能公式:5 大步骤 + 10 个案例

MySQL增量备份与还原详解

《全网最强》详解机器学习分类算法之决策树(附可视化和代码)

处理字符串_14_SQL处理IN和合并后字符串案例详解