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 < m2.primary_key
可能无法保证 m1.created_date < 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 个匹配行之外的所有行的主要内容,如果未能解决你的问题,请参考以下文章