《SQL学习记录之一》

        运营活动需求:1、新用户注册的话就送6元红包、2、用户当天的交易满3笔的话就送给用户8元红包,3、用户连续登录6天就送10元红包。

        连续6天就送代金卷==》连续N天就送代金==》动态可配置的,可根据运营现状即使调整策略。

        实现方式:

                1、新用户注册(注册接口)

                2、用户当天交易满3笔的话,那我直接查询交易记录是否当天记录总数超过3笔

                3、那连续6天如何实现呢?

        首先用户登录表结构设计:sys_user_login

IDUMNAMELOGIN_TIME
1ZHANGAN张三2022-09-01 00:00:00
2LISI李四2022-09-01 00:00:00
--创建表结构
CREATE TABLE `sys_user_logins` (
  `Id` varchar(32) NOT NULL DEFAULT '1' COMMENT '自增主键',
  `um` varchar(255) DEFAULT NULL COMMENT '用户账号',
  `login_time` timestamp NULL DEFAULT NULL COMMENT '登陆时间',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

CREATE DEFINER=`root`@`localhost` TRIGGER `id_trigger` BEFORE INSERT ON `sys_user_logins` FOR EACH ROW BEGIN
	SET new.ID=REPLACE(UUID(),'-','');
END;
--动态生成日期

SELECT
	date(
	from_unixtime( unix_timestamp( '2017-01-01' ) + floor( rand() * ( unix_timestamp( '2018-08-08' ) - unix_timestamp( '2017-01-01' ) + 1 ) ) )) AS date;

--动态生成日期(日期:时分秒)

set global log_bin_trust_function_creators=TRUE;
DROP FUNCTION
IF
	EXISTS func_RandomDateTime;
#USAGE:
#SELECT func_RandomDateTime(DATE_FORMAT('2010-12-1 10:10:10','%Y-%m-%d %H:%i:%s'),DATE_FORMAT('2010-12-1 11:10:13','%Y-%m-%d %H:%i:%s')) AS t;
CREATE FUNCTION func_RandomDateTime(sd DATETIME,ed DATETIME) RETURNS DATETIME BEGIN
	DECLARE
		sub INT DEFAULT 0;
	DECLARE
		ret DATETIME;
	
	SET sub = ABS(
	UNIX_TIMESTAMP( ed )- UNIX_TIMESTAMP( sd ));
	
	SET ret = DATE_ADD( sd, INTERVAL FLOOR( 1+RAND ()*( sub - 1 )) SECOND );
	RETURN DATE_ADD( sd, INTERVAL FLOOR( 1+RAND ()*(( ABS( UNIX_TIMESTAMP( ed )- UNIX_TIMESTAMP( sd )))- 1 )) SECOND );

END;

show create function func_RandomDateTime;

SELECT func_RandomDateTime(DATE_FORMAT('2010-12-1 10:10:10','%Y-%m-%d %H:%i:%s'),DATE_FORMAT('2010-12-1 11:10:13','%Y-%m-%d %H:%i:%s')) AS t;

DROP PROCEDURE p01 ;

CREATE PROCEDURE p01 () BEGIN
	DECLARE
		i INT;	
		SET i = 1001;
	
		WHILE
				i <= 2000 DO
				INSERT INTO sys_user_logins ( um, login_time )
			VALUES
				( CONCAT( 'zhangsan00', i ), func_RandomDateTime ( DATE_FORMAT( '2020-12-1 10:10:10', '%Y-%m-%d %H:%i:%s' ), DATE_FORMAT( '2022-9-1 11:10:13', '%Y-%m-%d %H:%i:%s' )) );
			
			SET i = i + 1;
		END WHILE;
	
END;

delete from sys_user_logins;

DROP PROCEDURE p02 ;

CREATE PROCEDURE p02 () BEGIN
	DECLARE
		i INT;	
		SET i = 1;
		WHILE
				i <= 100 DO
				CALL p01();
				SET i = i + 1;
		END WHILE;
	
END;

CALL p02();

select * from sys_user_logins;

select count(*) from sys_user_logins;

select um,count(*) from sys_user_logins a group by um;


CALL p02()
> OK
> 时间: 460.159s

业务实现​

计算连续登陆天数的两种情况:①计算用户连续登录天数,以用户和登陆时间为指标。②计算用户在某个时间段内连续登录天数>N的情况

1 查看每位用户连续登陆的情况

2 查出在某个时间段内连续登录天数>=5天的用户

没有具体需求,用户在某一段日期内可能出现多次连续登录,比如出现这周连续登录三天,上周连续登录四天的情况,需要将这些信息全部输出,最后结果输出四个字段,分别是用户ID、首次登录日期、结束登录日期、连续登录天数。

首先对用户登录数据进行排序,这里使用窗口函数rank(),为了后续代码简洁,将执行的结果存到新表user_login_temp_01里面,代码如下:

create table user_login_temp_01(
    select *,rank() over(partition by um order by login_time) 排序
        from sys_user_logins);

#查看结果
select * from user_login_temp_01;

代码解读:针对um分区,把um一样的分到一个窗口里,每个窗口在针对登录日期升序排列,每个窗口内进行排序后最后拼接再一起,结果如下:

接下来是最重要的一步,用login_time - 排序。比如1号用户第一次登录是2016-10-31,减去,1天是2016-10-30,第二次登录是2016-11-01,减去2天是2016-10-30,说明这两天是连续登录的;第四次登录是2016-11-10,减去4天是2016-11-6不等于2016-10-30,说明这次登录与第一次登录就不是连续的了,代码如下(结果还是存到新表user_login_temp_02里):

create table user_login_temp_02(

select *,date_sub(login_time, interval 排序 day) 辅助日期列

from user_login_temp_01);

#查看结果

select * from user_login_temp_02;

date_sub()是个日期运算函数,第一个参数是起始日期,第二个参数是起始日期减去几天,结果如下:

最后一步是根据上面得到的结果,用um和辅助日期列做分组字段,um和辅助日期列完全一样的被分到一个组,这个组是连续登录的,针对login_time取最小值就是起始登陆日期,取最大值就是连续登录的最后一天日期,每个组进行计数得到的是连续登录的天数。这样就完成需求了。把所有代码整合在一起,不需要创建临时表的代码如下,比较长...

select um,

    min(login_time) 起始登录日期,

    max(login_time) 结束登录日期,

    count(login_time) 连续登录天数

from (select *,date_sub(login_time, interval 排序 day) 辅助日期列

        from (select *,rank() over(partition by um order by login_time) 排序

          from (select distinct um, date(login_time) login_time  from sys_user_logins) as a

        )as b

) as c

group by um,辅助日期列

having 连续登录天数>3;

第二种情况:查出在某个时间段内连续登录天数>=5天的用户,这个需求可以用上面查询的结果进行筛选。但是仅针对这个需求,用上面的代码就特别麻烦,下面介绍一个简单的办法,引用一个新的静态窗口函数lead()

select *,lead(login_time,4) over(partition by um order by login_time) 第五次登录日期  from sys_user_logins

lead函数有三个参数,第一个参数是指定的列(这里用登陆日期),第二个参数是当前行向后几行的值,这里用的是4,也就是第五次登录的日期,第三个参数是如果返回的空值可以用指定值替代,这里没有使用第三个参数。 over语句里面是针对UM分窗,每个窗口针对登录日期升序。代码得到结果如下,可以看到新列的第四行为空值,因为在UM为1的窗口里,第四行是不能往后移动四行的,数据行数不够,所以返回空值。 

用第五次登录日期 - login_time+1,如果等于5,说明是连续登录五天的,如果得到空值或者大于5,说明没有连续登录五天,代码和结果如下:

select *,datediff(第五次登录日期,login_date) 相差天数

from (select *,lead(login_date,4) over(partition by user_id order by login_date) 第五次登录日期

from user_login_date) as a;

最后一步,筛选相差天数=5的,针对user_id去重,完成需求

select 
    distinct um
from (
        select *,datediff(第五次登录日期,login_time)+1 相差天数
                from (select *,lead(login_time,4) over(partition by um order by login_time) 
                    第五次登录日期    from sys_user_logins) as a
        ) as b  where 相差天数=8;

备注:

 001、1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in_肖广彪的博客-CSDN博客

 002、 使用MySQL窗口函数巧妙的解决用户连续登录天数等问题 - 知乎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值