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

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

我有一张这样的桌子:

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

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

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

谢谢!

【问题讨论】:

  • (指纹,created_date)是唯一的吗?

标签: sql mysql


【解决方案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 可能对在正在更新的表上执行(嵌套)子查询不满意。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多