You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

请求用窗口函数改写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

火山引擎 最新活动