lag与lead函数 mysql 8.0版本前实现方式

发布时间 2023-04-01 00:00:54作者: 何苦->

lag与lead函数 mysql 8.0版本前实现方式

mysql 8.0版本前实现方式

MySQL5.7.25和 8.0.16 环境中实现类似Oracle的分析函数(8.0版本中已支持,直接使用即可)。

create table test2(
  cus_no varchar(10) DEFAULT NULL COMMENT '客户编号',
  tran_month varchar(6) DEFAULT NULL COMMENT '交易月份',
  tran_amt DECIMAL(20,2) DEFAULT NULL COMMENT '交易金额'
);

-- 插入测试数据
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','201910',1415.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','201911',39.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','201912',580.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','202010',915.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','202011',1200.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101028','202012',800.00);

insert into test2(cus_no,tran_month,tran_amt) values('cus_101029','201910',540.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101029','201911',495.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101029','201912',360.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101029','202001',990.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101029','202011',190.00);

insert into test2(cus_no,tran_month,tran_amt) values('cus_101030','201910',990.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101030','201911',330.00);
insert into test2(cus_no,tran_month,tran_amt) values('cus_101030','202001',560.00);

image-20230331160338877

需求:统计每个客户月度消费的环比增长率,同比增长率

例:

月度消费环比增长率 =(当月消费金额-上月消费金额)/上月消费金额 ×100%

月度消费同比增长率 =(当月消费金额-去年同期消费金额)/去年同期消费金额 ×100%

为了计算环比、同比增长率,需要获取每个月对应的上个月、去年同期的消费金额

SELECT
	t2.cus_no '客户编号',
	t2.tran_month '交易月份',
	t1.tran_amt '交易金额',
	-- 上一个月、上一年调整时间区间
	( SELECT sum(tran_amt) FROM test1 WHERE cus_no = t1.cus_no and date_format(date_add(concat(tran_month,'01'), interval '1' month),'%Y%m') = t1.tran_month ) AS 'lag上个月',-- lag上个月
	( SELECT sum(tran_amt) FROM test1 WHERE cus_no = t1.cus_no and date_format(date_add(concat(tran_month,'01'), interval '12' month),'%Y%m') = t1.tran_month ) AS 'lag上一年同月' -- lag上一年同月
FROM
	test2 AS t1
RIGHT JOIN (
	SELECT * FROM ( 
	-- <1> 生成一个时间区间的月份列表 
		SELECT
			date_format( date_add( '20191001', INTERVAL @s MONTH ), '%Y%m' ) AS tran_month,
			 @s := @s + 1 AS `index`
		FROM
			mysql.help_topic,
			( SELECT @s := 0 ) temp 
		WHERE
			-- 两个日期间相差的月份
			@s <= TIMESTAMPDIFF(MONTH,'20191001','20201201')
	-- <1>
	) t_1 
	INNER JOIN ( 
		SELECT DISTINCT cus_no FROM test2 
	) t_2
) AS t2 ON t2.tran_month = t1.tran_month and t2.cus_no = t1.cus_no
ORDER BY t2.cus_no, t2.tran_month; 

image-20230331175416309

[oracle]first_value、last_value与nth_value函数

函数语法如下,他们都支持使用窗口子句:

first_value( col ) over([分区子句] [排序子句] [开窗子句] )

last_value( col ) over( [分区子句] [排序子句] [开窗子句] )

nth_value( col ,n ) [ FROM FIRST | FROM LAST ] [ RESPECT NOLLS | IGNORE NOLLS ] over( [分区子句] [排序子句] [开窗子句] )

注:
	first_value和last_value函数常用在计算排过序的结果集中的第一行和最后一行数据,或者说是最大值和最小值(依排序而定)
	nth_value则可以获取任意行的数据

需求:统计每个客户在所有消费月份中的最大消费金额与最小消费金额

说明:

统计每个客户在过往所有消费月份中,消费金额最大月份的消费金额与消费金额最小月份的消费金额

-- 统计每个客户在所有消费月份中的最大消费金额与最小消费金额
SELECT
	CUS_NO 客户编号,
	TRAN_MONTH 交易月份,
	TRAN_AMT 交易金额,
	first_value ( TRAN_AMT ) over ( partition BY CUS_NO ORDER BY TRAN_AMT ROWS BETWEEN unbounded preceding AND unbounded following ) AS 最小消费金额,
	last_value ( TRAN_AMT ) over ( partition BY CUS_NO ORDER BY TRAN_AMT ROWS BETWEEN unbounded preceding AND unbounded following ) AS 最大消费金额,
	nth_value ( TRAN_AMT, 2 ) over ( partition BY CUS_NO ORDER BY TRAN_AMT ROWS BETWEEN unbounded preceding AND unbounded following ) AS 第2小的消费金额
FROM
	test2;

窗口子句 rows between unbounded preceding and unbounded following 表示滑动窗口范围是整个分区,否则默认的滑动窗口将是分区中第一行到当前行 因为中间用了order by。

扩展需求:统计每个客户在当前月以及前两个月这三个月中的最大消费金额与最小消费金额

SELECT
	CUS_NO 客户编号,
	TRAN_MONTH 交易月份,
	TRAN_AMT 交易金额,
	min( TRAN_AMT ) over ( PARTITION BY CUS_NO ORDER BY month_num rows BETWEEN 2 preceding AND 0 following ) AS 当前至前两个月最小值,
	max( TRAN_AMT ) over ( PARTITION BY CUS_NO ORDER BY month_num rows BETWEEN 2 preceding AND 0 following ) AS 当前至两个月最大值 
FROM( 
	-- 对行进行排序并为每一行增加一个唯一编号
	SELECT CUS_NO, TRAN_MONTH, TRAN_AMT, row_number ( ) over ( PARTITION BY CUS_NO ORDER BY TRAN_MONTH ) AS month_num FROM (
		SELECT 
			COALESCE( a.CUS_NO, b.CUS_NO ) AS CUS_NO,
			COALESCE ( a.TRAN_MONTH, b.TRAN_MONTH ) AS TRAN_MONTH,
			a.TRAN_AMT TRAN_AMT
		FROM
			test2 a
			FULL JOIN (
				-- 内联接表 笛卡尔 客户编号+日期月份 排重
				SELECT DISTINCT
					b.CUS_NO,
					to_char ( a.date_list, 'YYYYMM' ) AS TRAN_MONTH 
				FROM (
						-- 给定日期开始根据日期天数生成日期
						SELECT
							to_date ( '20191001', 'YYYYMMDD' ) + rownum - 1 AS date_list 
						FROM
							DUAL 
							-- 生成数字序列结果集  日期天数
						connect BY rownum <= ( to_date ( '20201201', 'YYYYMMDD' ) - to_date ( '20191001', 'YYYYMMDD' ) ) + 1 
					) a, ( SELECT DISTINCT CUS_NO FROM test2 ) b 
			) b ON a.CUS_NO = b.CUS_NO 
			AND a.TRAN_MONTH = b.TRAN_MONTH
	) 
);