MySQL重复行

4
我有一个表格,其中包含一些重复的信息:Id、Name、Lastname、Birth、PersonalKey、Personal Info、Direction和Source。
其中Source告诉我信息来自哪里。
这些重复的信息具有唯一的id,并且我需要删除重复的信息。但是,我对某些Source信息具有优先权,我需要保留它们并删除其他信息。
另外,另一个Source信息包含一些想要保留的信息所没有的信息,因此我需要重新填写PersonalKey以保留那个信息并删除重复的信息。
表名为Pruebas。
---Id, Name, Firstname, Lastname, Birth, RFC, Source, PersonalKey---
---2,Juan,Garcia,Escobeddo,1983-08-04,GAED87393, DRV484930, 34233--
---3,Juan,Garcia,Escobedo,1987-08-04,GAED87393, FIN484930, --
---4,Juan,Garcia,Escobedo,1987-08-04,GAED87393, SA484930, --

如您所见:

  • 这些ID是唯一的。
  • 名称、姓氏和名字都是重复的。
  • 第二个ID(id 2)具有个人密钥值,但是第三个和第四个没有。
    • 我希望保留“FIN%”来源的那一行,删除其他行,但首先需要确保保留的行获得PersonalKey值(换句话说,我不想丢失PersonalKey值)。

提前感谢。


所以,如果姓名、名字、姓氏、出生日期和RFC都相同,则将个人密钥从DRV来源的那个复制到FIN来源的那个,然后删除所有非FIN? - Sarah Mei
你说你通过使用名字、姓氏和出生日期来识别"重复"记录。那么姓名和RFC呢?在比较数据时是否会忽略它们?此外,一个人总是有一个FIN源记录吗?一个人可能有多个FIN源记录吗? - Dipin
我通过名字、姓氏和出生日期来识别重复项。 并非所有的注册表都有RFC,因此无法识别重复项。 但是所有的注册表都有姓名、名字、姓氏和出生日期,因此我可以知道哪些是重复项。 - Granger
FIN源记录怎么样?您保证每个DUP集合都有一个吗?是否可能有多个? - Dipin
不,我不能保证每个DUP集都有一个,但如果我有一个FIN,我必须拥有另一个的PersonalKey,如果我没有FIN,那么我只需要删除DUPS并留下一个。也许是填充更多信息的那个。 - Granger
显示剩余6条评论
4个回答

3
我会在这个查询上运行一个光标(使用MySQL SP编程语言、Java、Python、.NET)。
select Name, Firstname, Lastname, count(1)
  from Pruebas
 group by Name, Firstname, Lastname
having count(1) > 1

然后,对于游标返回的行,你可以根据需要进行操作:检查FIN%实例、检查PersonalKey是否存在,并相应地进行更新。

对于游标中的每一行,你可以使用以下方法打开一个不同的游标:

select *
  from Pruebas
 where Name = the_Name
   and Firstname = the_Firstname
   and Lastname = the_Lastname

现在,您将拥有一个包含您将修改的所有行的内部光标。如果这是您需要的光标,请保留它并使用您提到的KEY值对其进行更新。否则,请删除它。

在Oracle中,您可以通过一次查询实现您想要的结果,但我认为用这种方法可能无法获得相同的性能。

希望这可以帮到您。


好的,我需要更多的方向。。。我不知道是否正确,当我用SP制作光标(我不知道但我正在阅读相关资料),基于查询,我将如何知道哪些ID是重复的?以及如何处理重复名称的循环? - Granger

3
我能想到的最简单的解决方案是将PersonalKey复制到其他重复的行中,然后删除所有不匹配'FIN%'的行。
UPDATE Pruebas p1 JOIN Pruebas p2
 ON (SOUNDEX(CONCAT(p1.Name, p2.Firstname, p3.Lastname)) 
   = SOUNDEX(CONCAT(p2.Name, p2.Firstname, p2.Lastname)))
SET p1.PersonalKey = p2.PersonalKey
WHERE p2.PersonalKey IS NOT NULL;

DELETE FROM Pruebas WHERE Source NOT LIKE 'FIN%';

我展示了一个使用SOUNDEX()的近似匹配表达式来进行连接。


从您留下的其他评论中,我看到您有很多变化和不确定性。在这种情况下,没有办法自动清理和去重 - 或者至少自动清理将比手动清理更复杂,更难以正确执行。


关于您的评论,查询需要很多小时:是的,它确实不会很有效率。JOIN表达式不是可搜索的 - 也就是说,它不能利用索引。通过添加一个额外的列来存储名字、姓氏和名称的SOUNDEX()值,然后在该列上创建索引,可以使其更加高效。

但是SOUNDEX()也不能保证找到所有可能的拼写错误。你面临着一项无法完全自动化的数据清理任务。任何数据清理的解决方案都需要人工操作。


很抱歉,Bill Karwin,你的回答看起来很好,但我有一个疑问,如果一些完整的名字是同音词怎么办?这就是为什么我想知道与姓名、名字、姓氏以及出生日期的比较。提前感谢! - Granger
我明白,但你说有些情况下拼写错误。你需要一种方法将它们与近似表达式匹配起来。SOUNDEX()是MySQL中的内置函数。 - Bill Karwin
另外唯一的选择是手动清理名称,然后您可以使用简单的相等比较而不是近似比较。 - Bill Karwin
不,我认为你的解决方案可能很好,但我并不是在谈论拼写错误的名称,我只是希望将出生日期与重复的条目进行比较,因为它们是相同的。这样同音词就会随着出生日期而消失了。你觉得呢? - Granger
当然是可能的。您比我更了解数据的状态。一种策略是编写SQL来自动执行易于重复的情况,然后需要手动修复的剩余情况将会更少。 - Bill Karwin
抱歉耽误了,但是我做出的查询和你完全一样,已经过去16个小时了,但仍在运行。有没有什么方法可以提高大型数据库的查询效率?或者如何检查时间或确认查询是否走向正确的方向? - Granger

2
我会这样做:

创建一个名为Pruebas_new的表,并从Pruebas表中选择所有数据,按照name、firstname和lastname进行分组,在Source字段中筛选以“FIN%”开头的数据;

如果你想让它更快,可以使用临时表来重写原始表中的内容,但这是最简单的获取所需数据的方法。

那么我可以这样做,复制我的Prueba数据库,并称之为Prueba2,然后从prueba运行查询到prueba2?抱歉,我不理解查询的逻辑。 - Granger
我认为这将创建一个包含FIN的源表。然后,您可以将其复制回原始Prueba表中。 - jimiyash

1

抱歉回答有些晚了,最近几天有点忙。

以下是我的答案,基于以下假设:

1)您将使用其他机制(您在原始问题的评论中提到要使用正则表达式进行清理)来清除名称拼写问题。

2)可以使用名字、姓氏和出生日期来识别重复记录集(您在原始问题的评论中提到了这一点)。

3)名字、姓氏和出生日期不能为空。

4)在一个重复记录集中不能有多个FIN记录(您在原始问题的评论中提到了这一点)。

如果以上任何假设无效,则我的答案将需要修改。

请执行以下步骤:

1)将所有FIN记录更新为从非-FIN记录中复制PersonalKey:

    UPDATE Pruebas p1
INNER JOIN Pruebas p2
        ON p1.Firstname = p2.Firstname
       AND p1.Lastname = p2.Lastname
       AND p1.Birth = p2.Birth
       SET p1.PersonalKey = p2.PersonalKey
     WHERE p1.Source like 'FIN%'
       AND p1.PersonalKey is null
       AND p2.PersonalKey is not null;

2) 删除所有非FIN记录,如果我们有FIN记录:

    DELETE p2
      FROM Pruebas p1
INNER JOIN Pruebas p2
        ON p1.Firstname = p2.Firstname
       AND p1.Lastname = p2.Lastname
       AND p1.Birth = p2.Birth
     WHERE p1.Source like 'FIN%'
       AND p2.Source not like 'FIN%';

此时所有带有 FIN 记录的 DUP 已经被清除,只剩下 FIN 记录。

3) 如果我们决定对于所有其他情况都使用 DRV 记录,则需要将 PersonalKey 从另一个记录复制到 DRV 记录中:

    UPDATE Pruebas p1
INNER JOIN Pruebas p2
        ON p1.Firstname = p2.Firstname
       AND p1.Lastname = p2.Lastname
       AND p1.Birth = p2.Birth
       SET p1.PersonalKey = p2.PersonalKey
     WHERE p1.Source like 'DRV%'
       AND p1.PersonalKey is null
       AND p2.PersonalKey is not null;

4) 删除所有非DRV记录,但保留存在DRV记录的记录:

    DELETE p2
      FROM Pruebas p1
INNER JOIN Pruebas p2
        ON p1.Firstname = p2.Firstname
       AND p1.Lastname = p2.Lastname
       AND p1.Birth = p2.Birth
     WHERE p1.Source like 'DRV%'
       AND p2.Source not like 'DRV%';

此时,所有带有DRV记录的DUP已被清理,只剩下DRV记录。

如果唯一的其他记录类型是SA记录,则不应再有DUP,我们完成了。

5)如果我们想选择填充最多信息的记录,或者如果我们完成了3和4,并且仍然有多个记录类型导致DUP。对于所有非FIN记录,我们需要将任何DUP集中具有PersonalKey的记录复制到任何没有PersonalKey的记录中:

    UPDATE Pruebas p1
INNER JOIN Pruebas p2
        ON p1.Firstname = p2.Firstname
       AND p1.Lastname = p2.Lastname
       AND p1.Birth = p2.Birth
       SET p1.PersonalKey = p2.PersonalKey
     WHERE p1.Source not like 'FIN%'
       AND p1.PersonalKey is null
       AND p2.PersonalKey is not null;

6) 删除除了信息得分计算列定义的最具信息量记录之外的所有记录:

    DELETE p5
      FROM Pruebas p5
INNER JOIN (SELECT p3.Firstname
                 , p3.Lastname
                 , p3.Birth
                 , MIN(p3.Id) AS min_id
              FROM Pruebas p3
        INNER JOIN (SELECT p1.Firstname
                         , p1.Lastname
                         , p1.Birth
                         , count(*) AS c
                         , MAX((p1.Name is not null) + (p1.RFC is not null) + (p1.Source is not null) + (p1.PersonalKey is not null)) AS info_score
                      FROM Pruebas p1
                  GROUP BY p1.Firstname
                         , p1.Lastname
                         , p1.Birth 
                    HAVING count(*) > 1) p2
                ON p3.Firstname = p2.Firstname
               AND p3.Lastname = p2.Lastname
               AND p3.Birth = p2.Birth
               AND ((p3.Name is not null) + (p3.RFC is not null) + (p3.Source is not null) + (p3.PersonalKey is not null)) = p2.info_score
          GROUP BY p3.Firstname
                 , p3.Lastname
                 , p3.Birth) p4
        ON p4.Firstname = p5.Firstname
       AND p4.Lastname = p5.Lastname
       AND p4.Birth = p5.Birth
       AND p4.min_id <> p5.Id;

此时,如果可用,则已保存PersonalKey,并保存了FIN记录(如果存在),否则将保存DRV记录或具有最多信息的记录。

如果您对上述任何内容有疑问,请告诉我。

希望能帮到您,

- Dipin


和Bill Karwin一样。很抱歉耽搁了,但是我已经像你一样查询了16个小时,仍然在工作中。有没有什么方法可以改进对大型数据库的查询?或者如何检查时间或检查查询是否正确? - Granger
这与Bill的答案相同。即使您没有该DUP的FIN记录,他的答案也会删除所有非FIN记录。表有多大?你对它有什么索引?由于where子句的差异,我的查询应该比Bill的更有效。附注:希望你在测试中运行它。 - Dipin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接