SQL Server 查询连续月份的方法

本文介绍了一种查询连续月份登录用户的方法,通过SQL语句实现跨年查询,并给出具体实现步骤。

       最近一个朋友那有这么一个需求:需要查询出连续6个月有登陆的用户,并且查询的时间段可能会有跨年的情况。今天就分享一下我帮朋友解决的查询连续月份的方法。

首先模拟一个用户登陆表

CREATE TABLE UserLogin (
	UserName VARCHAR(10), --用户名
	LoginTime DATETIME    --登陆日期
)

 

插入一些模拟数据,简单模拟一下用户的登陆信息,其中 A 用户连续每个月都有登陆,B 用户的登陆时间中间有间断。B用户有不连续6个月的情况。

 

INSERT INTO dbo.UserLogin ( UserName, LoginTime )
VALUES  ( 'A', '2018-01-01 09:23:32'),( 'B', '2018-01-01 12:34:34'),
	( 'A', '2018-02-04 14:43:21'),( 'B', '2018-02-15 21:56:23'),
	( 'A', '2018-03-25 08:54:12'),( 'B', '2018-03-01 09:42:42'),
	( 'A', '2018-04-05 10:02:41'),
	( 'A', '2018-05-06 07:35:52'),
	( 'A', '2018-06-20 11:23:12'),( 'B', '2018-06-06 10:15:36'),
	( 'A', '2018-07-01 14:50:56'),
	( 'A', '2018-08-20 09:21:32'),( 'B', '2018-08-15 10:15:36'),
	( 'A', '2018-09-12 10:14:21'),( 'B', '2018-09-02 12:30:45'),
	( 'A', '2018-10-02 11:50:58'),( 'B', '2018-10-10 13:20:54'),
	( 'A', '2018-11-26 08:05:41'),( 'B', '2018-11-26 10:25:40'),
	( 'A', '2018-12-16 11:26:35'),( 'B', '2018-12-18 14:15:26'),
	( 'A', '2019-01-01 12:21:32'),( 'B', '2019-01-15 12:35:36'),
	( 'A', '2019-02-05 08:19:26'),( 'B', '2019-02-08 10:45:34'),
	( 'A', '2019-03-20 10:34:38'),( 'B', '2019-03-06 11:15:48')

    其实实现查询连续月份的方法很简单,就是先 Group By 出来每个用户的登陆月份,使用 ROW_NUMBER() 函数给每个用户一个排序的序号。然后用用户的登陆月份与序号相减,如果月份连续,则相减后的数就是一样的。这样就能找到连续的月份了。

    如果查询时间跨年了怎么办呢?如果遇到跨年的情况,我就在取用户登陆月份的时候先判断 LoginTime 里面的年份是否小于查询的起始时间的年份,如果小于,则在获取的月份上加上12 。比如查询“2017-10-15”到 “2018-04-20”之间,我的处理方式获取2018年1月,则在1的基础上加上12,变成13 ,2月变成14,以此类推。这样就能保证月份数与排序数相减,月份连续值相等了。

看下面的图示,大家应该就能清楚是什么原理了。

得到上图的结果后,再根据“用户名”和“相减的值”来Group By,最后Count一下“相减的值”出现多少行,大于等于6就表明连续6个月都有登陆了。

    具体的实现语句如下:

-- 第三步:计算出现连续的月份数量。HAVING 筛选出出现6次以上的用户,出现6次以上即表示连续6月以上有登陆
SELECT aa.UserName,COUNT(aa.Con_M) AS '连续登录月份数量' 
FROM (
	-- 第二步:月份 减去 排序的序号 。如果是连续的月份,则相减的值是相同的。
	SELECT a.UserName 
	,( a.MM - ROW_NUMBER() OVER(PARTITION BY a.UserName ORDER BY a.MM) ) AS 'Con_M'
	FROM (
		--第一步:根据用户名,月份Group ,跨年的情况在月份上加12
		SELECT  UserName
		,CASE WHEN YEAR(@B_Time)<YEAR(LoginTime) THEN MONTH(LoginTime)+12 ELSE MONTH(LoginTime) END AS 'MM' --跨年的情况,月份+12
		FROM dbo.UserLogin 
		WHERE LoginTime >= @B_Time AND LoginTime <= @E_Time
		GROUP BY UserName
		,CASE WHEN YEAR(@B_Time)<YEAR(LoginTime) THEN MONTH(LoginTime)+12 ELSE MONTH(LoginTime) END
	) a 
) aa 
GROUP BY aa.UserName,Con_M
HAVING COUNT(aa.Con_M)>=6  --连续6个月有登陆,如果只要连续3个月,则此条件改为3

 

查询结果如下所示:

 

可以看到,在18年6月到19年3月之间,A用户连续登录了10个月,B用户连续的月份只有8个月。

如果查询 18年1月到6月,则只能查出A用户来,B用户18年1月到6月没有连续登录。

 

 

 

 

 

 

 

 

 

 

 

 

SQL Server中,对比两个不同月份的数据通常涉及筛选、聚合和连接操作。根据数据表的结构和业务需求,可以采用不同的SQL查询方法实现对比。以下是几种常见的实现方式: ### 1. 使用 `WHERE` 子句分别查询不同月份的数据 通过 `DATEADD` 和 `DATEPART` 函数可以提取指定月份的数据。例如,查询当前月份和上个月份的销售记录数量和总销售额: ```sql -- 查询当前月份数据 SELECT COUNT(*) AS current_month_count, SUM(sales) AS current_month_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, GETDATE()) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, GETDATE()); -- 查询上个月份数据 SELECT COUNT(*) AS previous_month_count, SUM(sales) AS previous_month_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, DATEADD(MONTH, -1, GETDATE())) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, DATEADD(MONTH, -1, GETDATE())); ``` ### 2. 使用 `UNION ALL` 合并两个月份查询结果 将两个不同月份的数据合并到一个结果集中,便于后续处理和展示: ```sql SELECT 'Current Month' AS month_label, COUNT(*) AS record_count, SUM(sales) AS total_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, GETDATE()) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, GETDATE()) UNION ALL SELECT 'Previous Month' AS month_label, COUNT(*), SUM(sales) FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, DATEADD(MONTH, -1, GETDATE())) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, DATEADD(MONTH, -1, GETDATE())); ``` ### 3. 使用子查询进行横向对比 通过子查询分别获取两个不同月份的汇总数据,并使用 `LEFT JOIN` 实现横向对比: ```sql SELECT curr.month_label AS current_month, curr.total_sales AS current_sales, prev.month_label AS previous_month, prev.total_sales AS previous_sales, (curr.total_sales - prev.total_sales) AS sales_difference FROM ( SELECT DATEPART(YEAR, add_time) * 100 + DATEPART(MONTH, add_time) AS month_label, SUM(sales) AS total_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, GETDATE()) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, GETDATE()) GROUP BY DATEPART(YEAR, add_time), DATEPART(MONTH, add_time) ) AS curr LEFT JOIN ( SELECT DATEPART(YEAR, add_time) * 100 + DATEPART(MONTH, add_time) AS month_label, SUM(sales) AS total_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = DATEPART(YEAR, DATEADD(MONTH, -1, GETDATE())) AND DATEPART(MONTH, add_time) = DATEPART(MONTH, DATEADD(MONTH, -1, GETDATE())) GROUP BY DATEPART(YEAR, add_time), DATEPART(MONTH, add_time) ) AS prev ON curr.month_label = prev.month_label; ``` ### 4. 使用自定义函数生成时间范围并进行对比 如果需要对比多个连续月份的数据,可以使用自定义函数生成时间范围,然后与主表进行关联查询。例如,使用以下函数生成指定时间段内的月份列表: ```sql CREATE FUNCTION f_GetMonthRange ( @startTime DATETIME, @endTime DATETIME ) RETURNS @table TABLE ( DateMonth VARCHAR(7) ) AS BEGIN WHILE @startTime <= @endTime BEGIN INSERT INTO @table SELECT CONVERT(VARCHAR(7), DATEADD(MM, DATEDIFF(MM, 0, @startTime), 0), 120); SET @startTime = CONVERT(VARCHAR(7), DATEADD(MM, 1, @startTime), 120) + '-01'; END RETURN; END; ``` 调用该函数并结合主表进行对比查询: ```sql SELECT m.DateMonth, ISNULL(SUM(s.sales), 0) AS total_sales FROM dbo.f_GetMonthRange('2023-01-01', '2023-12-01') m LEFT JOIN sales_data s ON m.DateMonth = CONVERT(VARCHAR(7), s.add_time, 120) GROUP BY m.DateMonth ORDER BY m.DateMonth; ``` ### 5. 使用窗口函数计算环比增长 对于时间序列数据,可以使用窗口函数 `LAG` 来获取前一个时间段的数据,从而计算环比增长率: ```sql WITH monthly_sales AS ( SELECT CONVERT(VARCHAR(7), add_time, 120) AS month_label, SUM(sales) AS total_sales FROM sales_data GROUP BY CONVERT(VARCHAR(7), add_time, 120) ) SELECT month_label, total_sales, LAG(total_sales, 1) OVER (ORDER BY month_label) AS previous_month_sales, ((total_sales - LAG(total_sales, 1) OVER (ORDER BY month_label)) / LAG(total_sales, 1) OVER (ORDER BY month_label)) * 100 AS growth_rate FROM monthly_sales ORDER BY month_label; ``` ### 6. 对比不同年份的相同月份 如果需要对比不同年份的相同月份(如2023年10月与2022年10月),可以分别筛选并连接两个时间段的数据: ```sql SELECT curr.month_label AS current_month, curr.total_sales AS current_sales, prev.month_label AS previous_year_month, prev.total_sales AS previous_year_sales, (curr.total_sales - prev.total_sales) AS sales_difference FROM ( SELECT DATEPART(YEAR, add_time) * 100 + DATEPART(MONTH, add_time) AS month_label, SUM(sales) AS total_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = 2023 AND DATEPART(MONTH, add_time) = 10 GROUP BY DATEPART(YEAR, add_time), DATEPART(MONTH, add_time) ) AS curr LEFT JOIN ( SELECT DATEPART(YEAR, add_time) * 100 + DATEPART(MONTH, add_time) AS month_label, SUM(sales) AS total_sales FROM sales_data WHERE DATEPART(YEAR, add_time) = 2022 AND DATEPART(MONTH, add_time) = 10 GROUP BY DATEPART(YEAR, add_time), DATEPART(MONTH, add_time) ) AS prev ON curr.month_label = prev.month_label; ``` ###
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值