You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SQL中按三月窗口保留分组后单条数据并删除其余行?

当然可以实现!核心思路是先给每个(name, sex, id)组内的行按「三月时间窗口」分组,然后在每个窗口里只保留任意一行,删掉其他的。具体实现取决于你对「三月窗口」的定义,下面分两种常见情况说明:

情况1:按自然季度划分窗口(1-3月、4-6月、7-9月、10-12月)

这是最常用的方式,每个自然季度作为一个三个月窗口。用窗口函数ROW_NUMBER()给每个窗口内的行编号,然后只保留编号为1的行。

以MySQL 8.0+为例:

-- 先给每个组+季度内的行随机编号
WITH ranked_rows AS (
    SELECT 
        date, name, sex, id, status,
        ROW_NUMBER() OVER (
            PARTITION BY name, sex, id, YEAR(date), QUARTER(date)
            ORDER BY RAND() -- 随机选一行,也可以改成ORDER BY date选最早/最晚的
        ) AS row_num
    FROM your_table_name
)
-- 删除编号大于1的行(也就是每个窗口只留第一行)
DELETE FROM your_table_name
WHERE (date, name, sex, id) IN (
    SELECT date, name, sex, id 
    FROM ranked_rows 
    WHERE row_num > 1
);

如果你的表有主键(比如row_id),用主键判断更安全,避免因为重复的date/name/sex/id误删:

WITH ranked_rows AS (
    SELECT 
        row_id,
        ROW_NUMBER() OVER (
            PARTITION BY name, sex, id, YEAR(date), QUARTER(date)
            ORDER BY RAND()
        ) AS row_num
    FROM your_table_name
)
DELETE FROM your_table_name
WHERE row_id IN (
    SELECT row_id 
    FROM ranked_rows 
    WHERE row_num > 1
);

情况2:自定义滚动三个月窗口(从每个组的最早日期开始,每三个月一个窗口)

如果你的窗口不是自然季度,而是从每个组的第一条数据开始,每三个月划一个窗口(比如组内最早日期是2016-08-02,窗口就是2016-08-022016-11-02、2016-11-022017-02-02……),可以用递归CTE生成窗口,再给行分配窗口:

WITH RECURSIVE group_windows AS (
    -- 初始化:每个组的最早日期作为第一个窗口的起始
    SELECT 
        name, sex, id,
        MIN(date) AS window_start,
        DATE_ADD(MIN(date), INTERVAL 3 MONTH) AS window_end
    FROM your_table_name
    GROUP BY name, sex, id
    UNION ALL
    -- 递归生成后续窗口,每个窗口的起始是上一个窗口的结束
    SELECT 
        gw.name, gw.sex, gw.id,
        gw.window_end,
        DATE_ADD(gw.window_end, INTERVAL 3 MONTH)
    FROM group_windows gw
    JOIN (
        SELECT name, sex, id, MAX(date) AS max_date
        FROM your_table_name
        GROUP BY name, sex, id
    ) gd ON gw.name = gd.name AND gw.sex = gd.sex AND gw.id = gd.id
    WHERE gw.window_end < gd.max_date
),
row_assignments AS (
    -- 给每一行分配所属的窗口,并编号
    SELECT 
        t.*,
        ROW_NUMBER() OVER (
            PARTITION BY t.name, t.sex, t.id, gw.window_start
            ORDER BY RAND()
        ) AS row_num
    FROM your_table_name t
    JOIN group_windows gw 
        ON t.name = gw.name 
        AND t.sex = gw.sex 
        AND t.id = gw.id
        AND t.date >= gw.window_start 
        AND t.date < gw.window_end
)
-- 删除非首行的记录
DELETE FROM your_table_name
WHERE (date, name, sex, id) IN (
    SELECT date, name, sex, id 
    FROM row_assignments 
    WHERE row_num > 1
);

注意事项

  • 如果用ORDER BY RAND()是随机保留一行,你也可以换成ORDER BY date保留最早/最晚的,更可控。
  • 不同SQL方言(比如PostgreSQL、SQL Server)的函数语法略有差异,比如PostgreSQL用EXTRACT(YEAR FROM date)代替YEAR(date),random()代替RAND(),但逻辑是一样的。
  • 执行删除前建议先运行SELECT语句确认保留的行是否符合预期,避免误删数据。

内容的提问来源于stack exchange,提问作者Chen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:45:09