MySQL -- 标记除 1 个匹配行之外的所有行

Posted

技术标签:

【中文标题】MySQL -- 标记除 1 个匹配行之外的所有行【英文标题】:MySQL -- mark all but 1 matching row 【发布时间】:2008-10-28 02:40:20 【问题描述】:

这类似于this question,但似乎其中的一些答案与 mysql 不太兼容(或者我做得不对),而且我花了很多时间弄清楚我需要的改变。显然我的 SQL 比我想象的更生疏。我也希望更改列值而不是删除,但我认为至少 那个 部分很简单......

我有一张这样的桌子:

rowid SERIAL
指纹文本
重复布尔值
内容 TEXT
created_date DATETIME

我想通过指纹为每个组的第一个(按 created_date)设置重复=true。很容易将具有重复指纹的所有行标记为欺骗行。我坚持的部分是保持第一。

其中一个填充表格的应用会大量加载数据,多个工作人员从不同来源加载数据,工作人员的数据不一定按日期分区,因此尝试将这些都标记为很痛苦他们进来(插入的第一个不一定是按日期排列的第一个)。此外,我已经有一堆数据,我需要清理任何一种方式。所以我宁愿有一个相对有效的查询,我可以在批量加载后运行以进行清理,而不是尝试将其构建到该应用程序中。

谢谢!

【问题讨论】:

(指纹,created_date)是唯一的吗? 【参考方案1】:

如果您分组的数据大于 1024 字节,则需要明确告知 MySQL(有关详细信息,请参阅this link)。因此,如果您的指纹列中的数据大于 1024 字节,您应该使用将 max_sort_length 变量设置为更大的数字(有关允许的值的详细信息,请参阅 this link,以及有关如何设置它的 this link),以便group by 不会默默地仅使用您的部分数据进行分组。

一旦您确定 MySQL 将正确分组您的数据,以下查询将设置重复标志,以便第一个指纹记录的重复设置为 FALSE/0,任何后续指纹记录的重复设置为 TRUE/1:

    UPDATE mytable m1
INNER JOIN (SELECT fingerprint
                 , MIN(rowid) AS minrow 
              FROM mytable m2 
          GROUP BY fingerprint) m3 
        ON m1.fingerprint = m3.fingerprint
       SET m1.duplicate = m3.minrow != m1.rowid;

请记住,此解决方案不考虑 NULL,如果指纹字段可能为 NULL,那么您将需要额外的逻辑来处理这种情况。

【讨论】:

【参考方案2】:

假设您可以在数据加载期间离线,那么两步方法怎么样:

将每个项目标记为重复。 从每个组中选择最早的行,并清除重复标志。

不优雅,但可以完成工作。

【讨论】:

这可以通过一个相当简单的查询轻松完成。没有理由不遗余力地让事情复杂化。【参考方案3】:

这是一个有趣的方法:

SET @rowid := 0;

UPDATE mytable
SET duplicate = (rowid = @rowid), 
    rowid = (@rowid:=rowid)
ORDER BY rowid, created_date;
首先将用户变量设置为零,假设它小于表中的任何 rowid。 然后使用 MySQL UPDATE...ORDER BY 功能确保行按顺序更新rowid,然后按created_date。 对于每一行,如果当前rowid不等于用户变量@rowid,则将duplicate设置为0(假)。只有在遇到rowid 的给定值的第一行才会出现这种情况。 然后将rowid 的虚拟集添加到它自己的值,将@rowid 设置为该值作为副作用。 当你UPDATE 下一行时,如果它与上一行重复,rowid 将等于用户变量@rowid,因此duplicate 将设置为 1 (true)。

编辑:现在我已经对此进行了测试,并更正了设置duplicate 的行中的一个错误。

【讨论】:

【参考方案4】:

这是另一种方法,使用 MySQL 的多表 UPDATE 语法:

UPDATE mytable m1
  JOIN mytable m2 ON (m1.rowid = m2.rowid AND m1.created_date < m2.created_date)
SET m2.duplicate = 1;

【讨论】:

哦,是的,你是对的。它假定每个日期都是唯一的。嗯嗯。 没错,但你可以做 m1.primary_key @Chris:是的,通常确实如此。但是您可能无法假设行是按时间顺序插入的。也就是说,m1.primary_key &lt; m2.primary_key 可能无法保证 m1.created_date &lt; m2.created_date。 YMMV。【参考方案5】:

我不知道 MySQL 的语法,但在 PLSQL 中你只需要:

UPDATE t1
SET duplicate = 1
FROM MyTable t1
WHERE rowid != (
  SELECT TOP 1 rowid FROM MyTable t2
  WHERE t2.fingerprint = t1.fingerprint ORDER BY created_date DESC
)

这可能有一些语法错误,因为我只是随便打字/无法测试它,但这就是它的要点。


MySQL 版本(未测试):

UPDATE t1
  SET duplicate = 1
FROM MyTable t1
WHERE rowid != (
  SELECT rowid FROM MyTable t2
  WHERE t2.fingerprint = t1.fingerprint
  ORDER BY created_date DESC
  LIMIT 1
)

【讨论】:

SELECT TOP 是 Microsoft SQL Server 的一项功能。 Oracle 或 MySQL 不支持它。 刚刚查了一下 MySQL 的语法,是 LIMIT。【参考方案6】:

未经测试...

UPDATE TheAnonymousTable
   SET duplicate = TRUE
 WHERE rowid NOT IN
       (SELECT rowid
          FROM (SELECT MIN(created_date) AS created_date, fingerprint
                  FROM TheAnonymousTable
                 GROUP BY fingerprint
               ) AS M,
               TheAnonymousTable AS T
         WHERE M.created_date = T.created_date
           AND M.fingerprint  = T.fingerprint
       );

逻辑是最里面的查询返回每个不同指纹的最早created_date作为表别名M。中间查询确定每行的rowid值;必须这样做(但必要)很麻烦,并且代码假定您不会获得相同指纹和时间戳的两条记录。这为您提供了每个单独指纹的最早记录的 rowid。然后外部查询(UPDATE)在所有那些 rowid 不是最早行之一的行上设置“重复”标志。

一些 DBMS 可能对在正在更新的表上执行(嵌套)子查询不满意。

【讨论】:

以上是关于MySQL -- 标记除 1 个匹配行之外的所有行的主要内容,如果未能解决你的问题,请参考以下文章

选择除基于 MySQL 中的两个字段之外的所有行

如何更新除该组中最新项目之外的一组行

模式匹配并删除除最后一次出现的所有行

TSQL - 每组删除除 1 之外的所有行

MS-ACCESS:删除除 top 1 之外的所有行并从查询中更新表

删除除具有最大值的行之外的所有行