删除没有主键的重复项

Posted

技术标签:

【中文标题】删除没有主键的重复项【英文标题】:Delete duplicates with no primary key 【发布时间】:2012-07-15 11:36:39 【问题描述】:

此处要删除具有重复列值 (Product) 的行,然后将其用作主键

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

在查询所有重复项期间,我们希望保留第一项并删除第二项作为重复项。

目前还没有主键,我们希望在删除重复项的活动之后创建它。 那么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

【问题讨论】:

数据库有多大。我们在这里谈论数百万行吗?数十亿? 大约 200,000 条记录和 3000 次重复,没有那么多:D 当您有两条记录,Product 的数据相同,但其他列的数据不同,您怎么知道要保留哪条正确? 即使您的查询有效,它也会删除所有版本的副本,而不仅仅是留下一个。在完整的 SQL Server 中,您将使用 ROW_NUMBER,怀疑是支持的。可能很容易将不同的重复项插入另一个表,然后从主表中删除重复项并从第二个表中重新插入。 @Walter Mitty 因为它基于代码执行中的导入过程,我正在探索数据 【参考方案1】:

如果您有两个具有相同产品列的不同记录,那么您可以使用某些标准选择不需要的记录,例如

 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。

或者您也可以仅选择 Product,然后对其他 JOIN 条件执行 DELETE,例如具有无效的 CustomerId、EntryDate NULL 或其他任何内容。如果您知道只有一个有效的 Product 副本,并且所有其他副本都可以通过无效数据识别,则此方法有效。

假设您有 IDENTICAL 记录(或者您有相同的和不相同的记录,或者您可能对某些产品有多个欺骗,但您不知道是哪一个)。您运行完全相同的查询。然后,您在 ProductsTable 上运行 SELECT 查询并 SELECT DISTINCT 所有与要删除的产品代码匹配的产品,按产品分组,并为所有字段选择合适的聚合函数(如果相同,任何聚合都应该这样做。否则我通常尝试使用 MAX或最小值)。这将为每个产品“保存”一行。

此时您运行 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.

注意:如果您想区分“好记录”和“无效重复”,上述方法不起作用。仅当您有多余的 DUPLICATE 记录,或者您不关心 保留哪行 行以及丢弃哪一行时,它才有效!

编辑: 您说“重复项”具有无效字段。在这种情况下,您可以使用排序技巧修改上述内容:

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

然后,如果您只有一排产品,一切都很好,它将被选中。如果您有更多,将首先选择并插入 (FieldWhichShouldNeverBeNull IS NULL) 为 FALSE 的那个(即 FieldWhichShouldNeverBeNull 实际上不为 null 的那个)。由于 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;

我们假设:“每组不良记录至少有一个良好记录”。

我们通过提出相反的假设并查询它来检查这个假设。如果一切都是 copacci,我们预计此查询不会返回任何内容。

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 );

现在“检查”查询确实返回了,

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 是不正确的。所有其他人 - 噗。

让我再次声明,如果一条记录没有有效记录它是重复的,那么所有副本 该记录将被杀死 - 不会有幸存者

为了避免这种情况,可以先选择(使用上面的查询,检查“which should return nothing”)代表这种失败模式的行到另一个临时表中,然后在删除后将它们插入回主表中(使用交易可能是有序的)。

【讨论】:

朋友,我正在根据您的方法进行尝试,如果可能,请根据您的想法和总结提供示例 3-5 行代码。我们将不胜感激。 可以。我将包括一个小例子,以确保我理解你的问题。删除大量数据总是让我不知何故感到紧张:-) 嘿 :D ,我和你一样,真正让我恼火的是 SQL CE 的残疾,在我发现一个小时的错误后,它甚至无法进行嵌套查询,正在健身房迟到了,我会在第二天试试这个,然后会给你反馈,你真的帮了大忙。【参考方案2】:

通过编写旧表的脚本并重命名它来创建一个新表。还将所有对象(索引等)从旧表写入新表。将守门员插入新表中。如果您的数据库处于批量记录或简单恢复模式,则此操作将被最低限度地记录。删除旧表,然后将新表重命名为旧名称。

与删除相比,这样做的优势在于可以最少地记录插入。删除会做双重工作,因为不仅数据会被删除,而且删除必须写入事务日志。对于大表,最少记录的插入将比删除快得多。

【讨论】:

【参考方案3】:

如果它不是那么大并且您有一些停机时间,并且您有 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
)

【讨论】:

您好,谢谢您试试这个,您认为它是精简版吗? 在语言方面应该无关紧要,如果需要,可以通过脚本轻松添加标识行。【参考方案4】:

试试这个:

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

这有一个缺陷,即它删除了具有重复产品的所有记录。您可能想要做的是删除给定产品的每组记录中的所有记录。首先将所有重复项复制到单独的表中,然后以某种方式从该表中删除重复项,然后应用上述内容,然后将剩余的产品复制回原始表,这可能是值得的。

【讨论】:

Walter,如果你知道它会删除表中所有重复的产品(包括操作员需要保留的产品),你为什么还要发布代码?希望 Sypress 要么在点击执行之前阅读代码下方的段落,要么有最近的完整备份...... @brian。好点子。请参阅我对 OP 的评论,询问如何知道要保留哪一个。当该评论的答案是非承诺性的时,我认为可以发布一些删除每个重复组的所有成员的内容。 @WalterMitty 请删除此内容。就像它删除了我表中的所有行

以上是关于删除没有主键的重复项的主要内容,如果未能解决你的问题,请参考以下文章

从没有主键的临时表中删除重复字段 [重复]

错误 #1062 主键的 Mysql 重复条目

如何避免没有主键和唯一键的重复条目?

很菜的数据库问题,主键的值允许重复吗?外键啥作用?

sql语句查询出的两行数据除了主键其他都一样,怎么去掉重复数据

维度建模:如何创建没有代理主键的表?