没有主键的重复记录删除

5

在此需要删除具有重复列值(Product)的行,该列将用作主键

该列的类型为nvarchar,我们不希望一个产品有两行记录。该数据库较大,约有数千行需要删除。

在查询所有重复项时,我们想保留第一项并删除第二个作为重复项。

当前还没有主键,在删除重复项之后,我们希望设置主键。然后,Product列可以成为我们的主键。

数据库是SQL Server CE。

我尝试过几种方法,但大多数得到类似以下错误的错误:

解析查询时出错。[标记行号=2,标记行偏移量=1,错误的标记=FROM]

我尝试过的一种方法:

DELETE FROM TblProducts
FROM TblProducts w
    INNER JOIN (
            SELECT Product
            FROM TblProducts
            GROUP BY Product
            HAVING COUNT(*) > 1
            )Dup ON w.Product = Dup.Product

首选方式是试图通过类似的方法来学习和调整我的代码(目前还不正确):

SELECT Product, COUNT(*) TotalCount
FROM TblProducts
GROUP BY Product
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC

--
;WITH cte   -- These 3 lines are the lines I have more doubt on them
     AS (SELECT ROW_NUMBER() OVER (PARTITION BY Product
                                       ORDER BY ( SELECT 0)) RN
         FROM   Word)
DELETE FROM cte
WHERE  RN > 1

数据库有多大?我们是在谈论百万行吗?十亿行? - Walter Mitty
大约有 200,000 条记录,其中有 3000 个重复项,不算太多:D - Kasrak
1
即使您的查询有效,它也会删除所有重复项的版本,而不仅仅是留下一个。在完整的SQL Server中,您将使用“ROW_NUMBER”,但我怀疑它是否受支持。可能最简单的方法是将不同的重复项插入到另一个表中,然后从主表中删除重复项,并从第二个表中重新插入它们。 - Martin Smith
@Martin Smith 是的,我也怀疑,类似的方法可能是首选,如果在 SSCE 中没有这样的选项,任何其他版本也可能有所帮助。 - Kasrak
@Walter Mitty 确实,在第一阶段由于某些原因很难避免这种情况,但在进行了去重处理并设置了新的限制条件后,代码本身将不再允许出现任何重复项,感谢您的建议。 - Kasrak
显示剩余4条评论
4个回答

4

如果您有两个“产品”列相同的不同记录,则可以使用某些条件选择不需要的记录,例如:

 CREATE TABLE victims AS
     SELECT MAX(entryDate) AS date, Product, COUNT(*) AS dups FROM ProductsTable WHERE ...
     GROUP BY Product HAVING dups > 1;

然后,您可以在ProductTable和Victims之间执行DELETE JOIN。
或者,您可以仅选择产品,然后针对其他JOIN条件执行DELETE操作,例如具有无效的CustomerId,EntryDate NULL或任何其他情况。如果您“知道”只有一个有效副本的产品,并且所有其他副本都可通过无效数据进行识别,则此方法适用。
假设您有相同的记录(或您既有相同的记录又有非相同的记录,或某些产品存在多个副本而您不知道哪个是哪个)。您需要运行完全相同的查询。然后,在ProductsTable上运行SELECT查询,并选择DISTINCT匹配要去重的产品代码的所有产品,按Product进行分组,并为所有字段选择合适的聚合函数(如果相同,则任何聚合都可以使用。否则,我通常尝试MAX或MIN)。这将“保存”每个产品的一行。
然后,在执行DELETE JOIN并删除所有重复产品之后,只需重新导入保存和去重的子集到主表中即可。
当然,在DELETE JOIN和INSERT SELECT之间,数据库将处于不稳定状态,其中至少有一个重复产品已经消失。
另一种在MySQL中应该有效的方法:
-- Create an empty table
CREATE TABLE deduped AS SELECT * FROM ProductsTable WHERE false;

CREATE UNIQUE INDEX deduped_ndx ON deduped(Product);

-- DROP duplicate rows, Joe the Butcher's way
INSERT IGNORE INTO deduped SELECT * FROM ProductsTable;

ALTER TABLE ProductsTable RENAME TO ProductsBackup;

ALTER TABLE deduped RENAME TO ProductsTable;
-- TODO: Copy all indexes from ProductsTable on deduped.

注意:如果你想区分“好记录”和“无效重复记录”,上述方法不起作用。它只在存在冗余重复记录或者你不关心保留哪一行而舍弃哪一行时才有效!

编辑: 你说“重复记录”有无效字段,这种情况下你可以使用排序技巧修改上述方法:

  SELECT * FROM ProductsTable ORDER BY Product, FieldWhichShouldNotBeNULL IS NULL;

如果您的产品只有一行,那么选择它就好了。如果有多个产品,则首先选择那个(FieldWhichShouldNeverBeNull IS NULL)为FALSE的产品(即实际上不为空的产品),并插入。由于IGNORE子句静默地反弹到产品的唯一性上,所有其他产品都会被忽略。这不是一个很好的解决方法(请检查我是否在子句中混淆了true和false!),但它应该可以工作。
编辑: 这是一个简单的表格,用于说明问题。
CREATE TABLE ProductTable ( Product varchar(10), Description varchar(10) );
INSERT INTO ProductTable VALUES ( 'CBPD10', 'C-Beam Prj' );
INSERT INTO ProductTable VALUES ( 'CBPD11', 'C Proj Mk2' );
INSERT INTO ProductTable VALUES ( 'CBPD12', 'C Proj Mk3' );

目前还没有索引,也没有主键。我们仍然可以将产品(Product)声明为主键。

但是会有一些问题。当两条新记录都具有NULL描述时,情况就不妙了。

然而,第二条记录是有效的产品,因为我们之前不知道CBPD14,因此我们不想完全丢失这条记录。但是我们确实想要删除虚假的CBPD10。

INSERT INTO ProductTable VALUES ( 'CBPD10', NULL );
INSERT INTO ProductTable VALUES ( 'CBPD14', NULL );

一个粗鲁的 DELETE FROM ProductTable WHERE Description IS NULL 并不可行,它会删除非重复项 CBPD14。

因此我们采用以下方式。首先获取重复项列表:

SELECT Product, COUNT(*) AS Dups FROM ProductTable GROUP BY Product HAVING Dups > 1;

我们假设:“每一组坏记录中至少有一条好记录”。
我们通过提出相反的假设并查询来检查这个假设。如果一切正常,我们希望此查询不返回任何结果。
SELECT Dups.Product FROM ProductTable
RIGHT JOIN ( SELECT Product, COUNT(*) AS Dups FROM ProductTable GROUP BY Product HAVING Dups > 1 ) AS Dups
ON (ProductTable.Product = Dups.Product
        AND ProductTable.Description IS NOT NULL)
WHERE ProductTable.Description IS NULL;

为了进一步验证,我插入了两条记录来表示这种故障模式;现在我确实希望上面的查询返回新代码。
INSERT INTO ProductTable VALUES ( "AC5", NULL ), ( "AC5", NULL );

现在,“check”查询确实返回,
AC5

因此,Dups的生成看起来不错。

我现在开始删除所有无效的重复记录。如果有重复但是有效的记录,它们将保持重复,除非找到某些条件来区分它们中的一个“好”记录并将所有其他记录声明为“无效”(可能使用与“描述”不同的字段再次重复此过程)。

但是,这里有个问题。目前,您不能从表中删除并选择子查询中的相同表http://dev.mysql.com/doc/refman/5.0/en/delete.html)。因此,需要进行一些小的解决方法:

CREATE TEMPORARY TABLE Dups AS
     SELECT Product, COUNT(*) AS Duplicates
         FROM ProductTable GROUP BY Product HAVING Duplicates > 1;

DELETE ProductTable FROM ProductTable JOIN Dups USING (Product)
    WHERE Description IS NULL;

现在,只要无效记录出现在Dups表中,它们就将被全部删除。因此,我们的CBPD14记录将不受影响,因为它没有在其中出现。CBPD10的“好”记录将保持原样,因为其描述不为NULL并非真实情况。其余所有记录都会消失。
请注意,如果一条记录没有有效记录,但是却是重复的,则该记录的所有副本都将被删除 - 没有一个能幸存下来
为了避免这种情况,您可以首先将表示此失败模式的行使用上述查询进行选择(检查应返回空),然后将它们插入到删除后的主表中(可能需要使用事务)。

朋友,我正在尝试按照你的方法进行操作,如果可能的话,请提供基于你所想和总结的3-5行代码示例。这将不胜感激。 - Kasrak
1
可以的。我会包含一个小例子,以确保我真正理解了你的问题。总是让我有点紧张删除大量数据 :-) - LSerni
嘿:D,我和你一样,这件事真的让我很烦恼,就是SQL CE的不足之处,它甚至不能做嵌套查询,在经历了一个小时的错误后,我发现了这一点。我晚了去健身房,我会在明天尝试并给你反馈,你真的帮了我很多。 - Kasrak

1
如果数据量不是很大,你有一些空闲时间,并且你有Sql Server Management Studio,你可以使用GUI在表上放置一个标识字段。现在你有了像CTE一样的情况,除了行本身是真正不同的。所以现在你可以做以下操作:
SELECT MIN(table_a.MyTempIDField)
FROM
table_a lhs
join table_1 rhs
 on lhs.field1 = rhs.field1
 and lhs.field2 = rhs.field2 [etc]
WHERE
 table_a.MyTempIDField <> table_b.MyTempIDField
GROUP BY
 lhs.field1, rhs.field2 etc

这将为您提供所有“好”的重复项。现在,您可以使用DELETE FROM查询包装此查询。

DELETE FROM lhs
FROM table_a lhs
join table_b rhs
 on lhs.field1 = rhs.field1
 and lhs.field2 = rhs.field2 [etc]
WHERE
 lhs.MyTempIDField <> rhs.MyTempIDField
 and lhs.MyTempIDField not in (

SELECT MIN(lhs.MyTempIDField)
FROM
table_a lhs
join table_a rhs
 on lhs.field1 = rhs.field1
 and lhs.field2 = rhs.field2 [etc]
WHERE
 lhs.MyTempIDField <> rhs.MyTempIDField
GROUP BY
  lhs.field1, lhs.field2 etc
)

嗨,谢谢,我会尝试一下。你考虑过这是紧凑版本吗? - Kasrak
如果涉及到语言问题,通过脚本添加标识行也很容易。 - Triple Gilaman

1
通过编写脚本将旧表导出并重命名来创建新表。还要从旧表中编写所有对象(索引等)到新表中。将保留者插入新表中。如果您的数据库处于批量日志记录或简单恢复模型中,则此操作将最小化记录。删除旧表,然后将新表重命名为旧名称。
与删除相比,这样做的优点在于插入可以最小化记录。删除会做双倍工作,因为不仅数据被删除,而且删除也必须写入事务日志。对于大型表,最小日志记录的插入速度比删除快得多。

-2

试试这个:

DELETE FROM TblProducts     
WHERE Product IN
      (
     SELECT Product
     FROM TblProducts
     GROUP BY Product
     HAVING COUNT(*) > 1)

这样做的缺点是它会删除所有拥有重复产品的记录。您可能想要做的是删除每个给定产品组中除了一个之外的所有记录。先将所有副本复制到另一个表中,然后以某种方式从该表中删除重复项,然后应用上述操作,最后将其余产品复制回原始表可能是值得的。


2
沃尔特,如果你知道这段代码会删除表中所有具有重复项的产品(包括OP需要保留的产品),为什么还要发布它呢?希望Sypress在执行之前阅读代码下面的段落或者有最近的完整备份... - brian
@brian。 很好的观点。请看我对原始帖子的评论,询问如何知道要保留哪一个。当那个评论没有做出承诺性回答时,我就认为可以发布删除每个重复组中所有成员的内容了。 - Walter Mitty
2
@WalterMitty 请删除这个。就像它删除了我的表中的所有行一样。 - Alex Gordon

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