请求用窗口函数改写SQL以正确统计近30天内房产类广告最多的用户ID
用窗口函数改写SQL以正确统计近30天内房产类广告最多的用户ID
首先我得先帮你拆解下原SQL的问题:你说的完全没错,原查询的逻辑是把用户的每一条广告发布时间当成30天窗口的起始点,然后统计这个窗口内的广告数——这其实没覆盖所有可能的连续30天周期。比如要是某个用户在1月15号没发广告,但1月10日到2月9日这个窗口里的广告数是最多的,原查询就直接漏掉这个情况了,而且原逻辑的关联和分组还会导致广告被重复计算,结果肯定不准。
接下来我用窗口函数帮你重写SQL,同时把统计逻辑的问题也解决掉。这里默认你用的是支持窗口函数的数据库(比如MySQL 8.0+、PostgreSQL、SQL Server这些):
写法一:分层CTE+窗口函数,逻辑清晰易维护
WITH estate_ads AS ( -- 先筛出所有属于房产类的广告记录,减少后续计算量 SELECT a.IDUser, a.DateAdded FROM advertisements a INNER JOIN categories c ON a.IDCategory = c.ID WHERE c.Name LIKE '%Estate%' ), user_rolling_counts AS ( -- 对每个用户的每条广告,计算该广告时间前后30天窗口内的广告总数 -- 用RANGE窗口覆盖连续30天的时间范围,确保所有周期都被统计到 SELECT IDUser, COUNT(*) OVER ( PARTITION BY IDUser ORDER BY DateAdded RANGE BETWEEN INTERVAL 30 DAY PRECEDING AND CURRENT ROW ) AS rolling_30day_ad_count FROM estate_ads ), user_max_counts AS ( -- 找出每个用户的「最大30天广告数」 SELECT IDUser, MAX(rolling_30day_ad_count) AS max_30day_ads FROM user_rolling_counts GROUP BY IDUser ), ranked_users AS ( -- 给用户按最大广告数排名,并列第一的也能保留 SELECT IDUser, max_30day_ads, RANK() OVER (ORDER BY max_30day_ads DESC) AS user_rank FROM user_max_counts ) -- 取排名第一的用户(多个并列的话会全部返回,想只取一个加LIMIT 1就行) SELECT IDUser, max_30day_ads FROM ranked_users WHERE user_rank = 1;
写法二:适配部分对时间RANGE支持有限的数据库
如果你的数据库对RANGE窗口的时间间隔支持不太好,可以换这种写法,核心逻辑是一样的:
WITH estate_ads AS ( SELECT a.IDUser, a.DateAdded FROM advertisements a JOIN categories c ON a.IDCategory = c.ID WHERE c.Name LIKE '%Estate%' ), user_ads_ordered AS ( -- 给每个用户的广告按时间排个序,方便后续窗口计算 SELECT IDUser, DateAdded, ROW_NUMBER() OVER (PARTITION BY IDUser ORDER BY DateAdded) AS ad_seq FROM estate_ads ), user_30day_counts AS ( -- 统计每个用户每条广告对应的30天窗口内的广告数 SELECT u1.IDUser, COUNT(u2.IDUser) AS ad_count FROM user_ads_ordered u1 JOIN user_ads_ordered u2 ON u1.IDUser = u2.IDUser AND u2.DateAdded BETWEEN u1.DateAdded AND DATE_ADD(u1.DateAdded, INTERVAL 30 DAY) GROUP BY u1.IDUser, u1.ad_seq ), user_max_counts AS ( SELECT IDUser, MAX(ad_count) AS max_30day_ads FROM user_30day_counts GROUP BY IDUser ), ranked_users AS ( SELECT IDUser, max_30day_ads, DENSE_RANK() OVER (ORDER BY max_30day_ads DESC) AS user_rank FROM user_max_counts ) SELECT IDUser, max_30day_ads FROM ranked_users WHERE user_rank = 1;
为啥这个改写比原SQL靠谱?
- 真正覆盖了所有可能的连续30天周期,不再局限于用户发广告的时间点当窗口起点
- 用分层CTE把逻辑拆解开,每一步做什么一目了然,后续改需求也好调试
- 彻底避免了原SQL里重复计算广告的问题,统计结果更准确
- 用窗口函数的排名逻辑,比原SQL的
GROUP BY + MAX + LIMIT灵活多了——比如有多个用户并列第一的情况,原SQL只会返回一个,这个写法能把并列的都列出来
备注:内容来源于stack exchange,提问作者Mobin Ghanbari




