在具有以下格式和示例数据的大型用户数据库中,我们正在尝试识别重复的人:
id first_name last_name email
---------------------------------------------------
1 chris baker
2 chris baker chris@gmail.com
3 chris baker chris@hotmail.com
4 chris baker crayzyguy@crazy.com
5 carl castle castle@npr.org
6 mike rotch fakeuser@sample.com
我使用以下查询:
SELECT
GROUP_CONCAT(id) AS "ids",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "duplicate_count"
FROM
users
GROUP BY
name
HAVING
duplicate_count > 1
这很有效;我得到一个重复列表,其中包含所涉及行的ID号。
我们会将与重复相关的任何相关数据重新分配给实际的人(set user_id = 2 where user_id = 3
),然后删除重复的用户行。
在我们第一次发布此报告之后出现问题,因为我们在手动验证它们确实是重复之后清理列表 - 有些不重复。有两个Chris Bakers是合法用户。
我们不希望在随后的重复报告中看到Chris Baker,直到时间结束,所以我正在寻找一种方法来标记用户ID 1和用户ID 4不是彼此重复的未来报告,但是他们可以被以后添加的新用户复制。
我尝试了什么
我在用户表中添加了is_not_duplicate
字段,但是如果新的重复“Chris Baker”被添加到数据库中,则会导致此情况不会显示在重复的报告上; is_not_duplicate
不正确地排除其中一个帐户。在有两个重复的Chris Baker加上标记为HAVING
的“真实”之前,我的> 1
语句将不会达到is_not_duplicate
阈值。
总结的问题
如何在不循环结果或多个查询的情况下在上述查询中构建异常?
子查询很好,但数据集的大小使每个查询都很重要,我希望解决方案尽可能高效。
答案 0 :(得分:7)
尝试添加is_not_duplicate
布尔字段并修改代码,如下所示:
SELECT
GROUP_CONCAT(id) AS "ids",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "duplicate_count",
SUM(is_not_duplicate) AS "real_count"
FROM
users
GROUP BY
name
HAVING
duplicate_count > 1
AND
duplicate_count - real_count > 0
新添加的重复项将有is_not_duplicate=0
,因此该名称的real_count
将小于duplicate_count
,并且该行将显示
答案 1 :(得分:2)
我的大脑太油腻了,目前无法提出实际的查询,但我可能会在一条应该有效的路径上给你一个推动:)
如果你确实添加了另一列(也许是一个有效的重复用户表?...两者都会完成相同的事情),并运行一个子查询来计算所有有效的重复项,然后你可以比较当前查询中的计数。您将排除任何具有匹配计数的用户,并且会使用更高的计数来提取任何用户。希望这是有道理的;我将创建一个用例:
*您甚至可以修改查询,使其甚至不会列出重复的ID(即使您只获得1个ID的重复标记)。而不是必须重新检查哪些是valids。这会有点复杂。没有它,至少你会忽略Chris Baker,直到另一个人进入系统
我已经写了基本查询,处理排除特定ID我将尝试在今晚滚动。但是,这至少解决了你的初始需求。如果您不需要更复杂的查询,请告诉我,以便我不会浪费时间在上面:)
SELECT
GROUP_CONCAT(id) AS "ids",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "duplicate_count"
FROM
users
WHERE NOT EXISTS
(
SELECT 1
FROM
(
SELECT
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "valid_duplicate_count"
FROM
users
WHERE
is_valid_duplicate = 1 --true
GROUP BY
name
HAVING
valid_duplicate_count > 1
) AS duplicate_users
WHERE
duplicate_users.name = users.name
AND valid_duplicate_count = duplicate_count
)
GROUP BY
name
HAVING
duplicate_count > 1
以下是应执行上述操作的查询,但最终列表将仅打印不在有效列表中的ID。实际上这比我想象的要简单得多。并且,它与上面的大致相同,但我保持上面的唯一原因是保留两个选项,以防我搞砸了上面的...它确实变得复杂,因为它是许多嵌套查询。如果您可以使用CTE,甚至临时表。它可能会使查询更具表现力,将其分解为临时表:)。希望这有助于您正在寻找
SELECT GROUP_CONCAT(id) AS "ids",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "final_duplicate_count"
--This count could actually be 1 due to the nature of the query
FROM
users
--get the list of duplicated user names
WHERE EXISTS
(
SELECT
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "total_duplicate_count"
FROM
users AS total_dup_users
--ignore valid_users whose count still matches
WHERE NOT EXISTS
(
SELECT 1
FROM
(
SELECT
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "valid_duplicate_count"
FROM
users AS valid_users
WHERE
is_valid_duplicate = 1 --true
GROUP BY
name
HAVING
valid_duplicate_count > 1
) AS duplicate_users
WHERE
--join inner table to outer table
duplicate_users.name = total_dup_users.name
--valid count check
AND valid_duplicate_count = total_duplicate_count
)
--join inner table to outer table
AND total_dup_users.Name = users.Name
GROUP BY
name
HAVING
duplicate_count > 1
)
--ignore users that are valid when doing the actual counts
AND NOT EXISTS
(
SELECT 1
FROM users AS valid
WHERE
--join inner table to outer table
users.name =
CONCAT(UPPER(valid.first_name), UPPER(valid.last_name))
--only valid users
AND valid.is_valid_duplicate = 1 --true
)
GROUP BY
FinalDuplicates.Name
答案 2 :(得分:2)
由于这基本上是多对多关系,我会添加一个新表not_duplicate
,其中包含字段user1
和user2
。
我可能会为每个not_duplicate
关系添加两行,这样我有2 -> 3
的一行和3 -> 2
的对称行来简化查询,但这可能会导致数据不一致确保同时删除这两行(或只有一行并在脚本中进行正确的查询)。
答案 3 :(得分:1)
在我看来,is_not_duplicate列不够复杂,无法容纳您想要存储的信息 - 据我所知,您希望手动告诉您的检测,两个不同的用户不是彼此重复的。所以你要么创建一个像is_not_duplicate_of = other-user-id这样的列,要么你想保持开放的可能性,即一个用户可以手动定义而不是多个用户的副本,你需要一个带有两个user-id列的单独表。
查询告诉你非重写的重复项可能必须比你建议的重复一点复杂,我想不出一个适用于group by和逻辑的那个。我想到的唯一一件事就是
SELECT u1.* FROM users u1
INNER JOIN users u2
ON u1.id <> u2.id
AND u2.name = u1.name
WHERE NOT EXISTS (
SELECT *
FROM users_non_dups un
WHERE (un.id1 = u1.id AND un.id2 = u2.id)
OR (un.id1 = u2.id AND un.id2 = u1.id)
)
答案 4 :(得分:1)
如果您每次运行报告时都要更正所有重复项,那么一个非常简单的解决方案可能是修改查询:
SELECT
GROUP_CONCAT(id) AS "ids",
MAX(id) AS "max_id",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
COUNT(*) AS "duplicate_count"
FROM
users
GROUP BY
name
HAVING
duplicate_count > 1
AND
max_id > MAX_ID_LAST_TIME_DUPLICATE_REPORT_WAS_GENERATED;
答案 5 :(得分:0)
我会继续制作“confirmed_unique”专栏,默认为“False”。
为了避免你提到的问题,
然后我会选择所有看起来像重复的元素,并为“confirmed_unique”添加“False”条目。
答案 6 :(得分:0)
我不确定这是否有效,但您能否考虑添加* is_duplicate_of *列的相反逻辑?这样,您可以通过在此列输入第一个记录的ID来标记重复项,该记录将大于零。您希望保留的记录在此字段中的值为0。您可以将默认(未经检查的记录)设置为-1,以跟踪每条记录的验证状态。
之后,您可以继续执行一个SQL,该SQL只会将新记录与 is_duplicate_of = 0 的正确记录进行比较。
答案 7 :(得分:0)
如果您可以稍微更改报告的格式。你可以像这样进行自我加入 -
SELECT
CONCAT(u1.id,",", u2.id) AS "ids",
CONCAT(UPPER(u1.first_name), UPPER(u1.last_name)) AS "name"
FROM
users u1, users u2
WHERE
u1.id < u2.id AND
UPPER(u1.first_name) = UPPER(u2.first_name) AND
UPPER(u1.last_name) = UPPER(u2.last_name) AND
CONCAT(u1.id,",", u2.id) NOT IN (SELECT ids from not_dupe)
报告重复项如下:
ids | name
----|--------
1,2 | CHRISBAKER
1,3 | CHRISBAKER
...
并且not_dupe表将具有如下行:
ids
------
1,2
3,4
...
答案 8 :(得分:0)
我认为创建一个存储不重复的ID的查找表是有意义的。因此,删除了已确认的非重复项,查询只需要查找在查找表上找到的重复项。
例如在这个例子中我们有
id 1 | id 2
2 4
如果crayzyguy@crazy.com和chris@gmail.com是不同的人。
答案 9 :(得分:0)
如果我是你,我会在我的数据库架构中添加一些地理定位表/字段。
两个最终用户具有相同名称且生活在同一个地方的可能性非常低 - 除非在非常大的城镇 - 但您也可以将地理定位分割为小区域 - 这与粒度有关。
祝你好运。答案 10 :(得分:0)
我建议你创建一些东西:
然后构建一个报告,查找重复的true并解码字符串字段以匹配可能的重复
答案 11 :(得分:0)
我给Justin Pihony +1作为第一个建议将重复计数与非重复计数进行比较,而Hrant Khachatrian +1则表示第一个显示有效的方法。
这是一个稍微不同的方法,加上一些重命名使一切更加自我解释,加上查询中的一些额外列,以明确哪些记录需要作为潜在的重复进行比较。
我会调用新列“CONFIRMED_UNIQUE”而不是“IS_NOT_DUPLICATE”。和Hrant一样,我会把它设为布尔值(tinyint(1),0 = FALSE,1 = TRUE)。
“potential_duplicate_count”是必须删除的最大记录数。
select
group_concat(case when not confirmed_unique then id end) as potential_duplicate_ids,
group_concat(case when confirmed_unique then id end) as confirmed_unique_ids,
concat(upper(first_name), upper(last_name)) as name,
sum( case when not confirmed_unique then 1 end ) - (not max(confirmed_unique)) as potential_duplicate_count
from
users
group by
name
having
potential_duplicate_count > 0
答案 12 :(得分:0)
我看到其他人因为合并的建议而被投票否决了,但是你的问题陈述没有说数据需要到位。 OP跟进了他们的解决方案,这恰好是一个put SQL,这并不意味着每个解决方案都需要限制在那个。
据我所知,问题在于数据库中有多个相似但不一定相同的记录,这些记录具有成本和声誉影响,因此您需要对这些记录进行重复数据删除。
我会编写一个批处理作业来搜索可能的重复项(这可能会像你想的那样复杂或简单),然后关闭它找到的两个记录是dupes并创建一个新记录。
要启用它,您需要四个新列:
打开将是默认状态 合并将合并记录(有效关闭和替换) 如果合并被颠倒,拆分
因此,作为示例,请浏览所有记录,例如,具有相同的名称。将它们成对合并。因此,如果你有三个Chris Bakers,记录1,2和3,合并1和2来制作记录4然后3和4来制作记录5.你的表最终会像:
ID NAME STATUS RELATEDID CHAINID DATESTATUSCHANGED [other rows omitted]
1 Chris Baker MERGED 2 4 27-AUG-2012
2 Chris Baker MERGED 1 4 27-AUG-2012
3 Chris Baker MERGED 4 5 28-AUG-2012
4 Chris Baker MERGED 3 5 28-AUG-2012
5 Chris Baker OPEN
通过这种方式,您可以完整记录您的数据发生了什么,可以通过取消合并来反转任何更改,如果例如联系人1和2不相同则反转合并3和4,反转合并1 2,你最终会得到这个:
ID NAME STATUS RELATEDID CHAINID DATESTATUSCHANGED
1 Chris Baker SPLIT 2 4 29-AUG-2012
2 Chris Baker SPLIT 1 4 29-AUG-2012
3 Chris Baker SPLIT 4 5 29-AUG-2012
4 Chris Baker CLOSED 3 5 29-AUG-2012
5 Chris Baker CLOSED 29-AUG-2012
然后您可以手动合并,因为您可能不希望您的作业自动重新合并拆分记录。
答案 13 :(得分:-1)
是否有充分的理由不将重复的帐户合并到一个帐户中?
从评论中看,信息似乎主要用于联系信息,因此合并应该相对轻松,风险低。合并用户后,他们将不再出现在您的重复报告中。此外,您的用户表实际上会缩小,这有助于提高性能。
答案 14 :(得分:-1)
将数据类型位is_not_duplicate
添加到您的表中,并在设置is_not_duplicate
数据值后使用以下查询:
SELECT GROUP_CONCAT(id) AS "ids",
CONCAT(UPPER(first_name), UPPER(last_name)) AS "name"
FROM users
GROUP BY name
HAVING COUNT(*) > SUM(CAST(is_not_duplicate AS INT))
以上查询将总重复行与总有效重复行进行比较。
答案 15 :(得分:-1)
为什么不在这种情况下将电子邮件列设置为唯一标识符,并且在您清理一次记录后,您不允许从那里开始重复?