【问题标题】:Keep only fresh 200 records for each group每组只保留新的 200 条记录
【发布时间】:2020-02-28 02:05:31
【问题描述】:

我有一个表格,其中包含类似的列 编号 |姓名 |日期 |组..

我想要做的是删除每组计数超过 200 的所有旧记录。

例如,我有一个名为“shoes”的组,它有 400 条记录 “giftcard”有 300 条记录,“electronics”有 100 条记录,等等

所以在运行 SQL 查询后,我想要的是每组(鞋子、礼品卡、电子产品等)的计数小于或等于 200。 要删除的记录是按日期或 ID(自动递增)标识的旧记录。 因此,“shoes”组中的 200 条记录将被删除,这些记录比保留的记录更旧或 id 小于保留的记录。

【问题讨论】:

  • 获取最高的id,减去200,删除该id以下的所有记录。
  • @Maximin,你假设 id 是连续的。

标签: mysql sql


【解决方案1】:

这类问题在 MySQL 中有点不方便,因为他们没有实现像 ROW_NUMBER() 这样的 SQL-99 窗口函数。 MySQL 直到 8.0 版本才支持此功能。

这是一个适用于 MySQL 5.7 及更早版本的单个 SQL 语句的解决方案,并且只能选择每个组中大于第 200 个的成员。它使用了一个名为user variables 的 MySQL 功能,当您的查询过程逐行处理时,它会保持其价值。

DELETE f FROM foo AS f
JOIN (SELECT id, IF(@g = `group`, @rn:=@rn+1, @rn:=1) AS row_number, @g:=grp
        FROM foo, (SELECT @g:=null, @rn:=0) _init
        ORDER BY `group`, date desc) AS r
ON f.id = r.id AND r.row_number > 200;

在您运行此程序(或任何删除数据的程序!)之前,我建议您了解它的工作原理,并使用等效的 SELECT 对其进行测试,以确保它正在选择您要删除的行。

我用较小的数据集对此进行了测试。这是我在没有过滤的情况下运行它时的数据:

SELECT f.id, f.`group`, r.row_number FROM foo AS f
JOIN (SELECT id, IF(@g = `group`, @rn:=@rn+1, @rn:=1) AS row_number, @g:=grp
        FROM foo, (SELECT @g:=null, @rn:=0) _init
        ORDER BY `group`, date desc) AS r
ON f.id = r.id;

+----+--------+------------+
| id | group  | row_number |
+----+--------+------------+
|  1 |      1 |          1 |
|  2 |      1 |          2 |
|  3 |      1 |          3 |
|  5 |      1 |          4 |
| 11 |      1 |          5 |
|  4 |      2 |          1 |
| 10 |      2 |          2 |
|  8 |      2 |          3 |
|  7 |      3 |          1 |
|  6 |      3 |          2 |
| 12 |      3 |          3 |
|  9 |      4 |          1 |
+----+--------+------------+

这是跳过每组前 2 个的 SELECT:

SELECT f.id, f.`group`, r.row_number FROM foo AS f
JOIN (SELECT id, IF(@g = `group`, @rn:=@rn+1, @rn:=1) AS row_number, @g:=grp
        FROM foo, (SELECT @g:=null, @rn:=0) _init
        ORDER BY `group`, date desc) AS r
ON f.id = r.id AND r.row_number > 2;

+----+-------+------------+
| id | group | row_number |
+----+-------+------------+
|  3 |     1 |          3 |
|  5 |     1 |          4 |
| 11 |     1 |          5 |
|  8 |     2 |          3 |
| 12 |     3 |          3 |
+----+-------+------------+

【讨论】:

  • 谢谢。这行得通。对于像我这样的新手:第二行最后一个字grp也应该改为group
【解决方案2】:

运行这个伪 SQL

SELECT shoes.id FROM shoes ORDER BY Date DESC LIMIT 200

然后从中解析结果(一个数组..(1、2 等)——称之为 $IDS)

DELETE FROM shoes WHERE ID NOT IN ($IDS)

编辑:要将这一切都作为 SQL 查询来完成,有两种可能的方法。

1DELETE FROM shoes WHERE ID NOT IN (SELECT shoes.id FROM shoes ORDER BY Date DESC LIMIT 200) -- 是的,你可以这样做。当心。正如比尔建议的那样,首先以SELECT * FROM shoes WHERE ID NOT IN (SELECT shoes.id FROM shoes ORDER BY Date DESC LIMIT 200) 的身份运行它,以确保它选择了正确的东西[您要删除的内容!]

2。对 DECLARE 不太了解,但您可以声明 @IDs = SELECT shoes.id FROM shoes ORDER BY Date DESC LIMIT 200,然后声明 DELETE FROM shoes WHERE ID NOT IN (@IDS)

两者都未经测试。顺便说一句,您应该使用SQLFiddle 设置模拟模式信息,以便人们来帮助他们测试他们的查询。

【讨论】:

  • 您建议如何完全在 SQL 查询中执行此操作
  • OP 希望每组保留前 200 个,而不是整个表中的前 200 个。此外,MySQL 用户定义的变量不能保存整个数据集,它们只能保存一个标量值。也不能使用变量作为子查询的代码。
【解决方案3】:

这将是一个 SQL Server 解决方案

Select * from (
Select *, ROW_NUMBER() OVER (Partition By [Group] order by Date) RN  
from table) t1
inner join (
Select [GROUP], COUNT(*) as Cnt
from table
group by [Group]
) a on a.[Group] = t1.[Group]
where t1.RN <= 200
and a.Cnt >= 200

编辑:

这里使用的是 CTE

With CTE as 
(
    Select [GROUP], COUNT(*) as cnt
    from tbl
    group by [Group]
)

Select t1.* 
from (Select *, ROW_NUMBER() OVER (Partition By [Group] order by Date) RN  
      from tbl) t1
inner join CTE a on a.[Group] = t1.[Group]
where t1.RN <= 200 and 
      a.Cnt >= 200

【讨论】:

  • 微软 SQL Server 的不错的、正确的解决方案,但不幸的是 MySQL 还不支持窗口函数或 CTE。
  • 查询报错。靠近隔断的开口圆括号
  • @dipamchange,正如比尔所说,这是一个 MS SQL Server 解决方案,我发现你使用的是 MySQL。也许有办法把它翻译成 MySQL
  • Ohh k ,实际上我不知道我们如何将其转换为 MySQL 支持的查询,这里有人有任何指针吗?
  • @CSharper,不,没有。自 2008 年以来已要求开窗函数,但未实现。 bugs.mysql.com/bug.php?id=35893
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多