如何删除没有唯一标识符的重复行
Posted
技术标签:
【中文标题】如何删除没有唯一标识符的重复行【英文标题】:How to delete duplicate rows without unique identifier 【发布时间】:2015-01-02 08:50:23 【问题描述】:我的表中有重复的行,我想以最有效的方式删除重复的行,因为表很大。经过一番研究,我想出了这个查询:
WITH TempEmp AS
(
SELECT name, ROW_NUMBER() OVER(PARTITION by name, address, zipcode ORDER BY name) AS duplicateRecCount
FROM mytable
)
-- Now Delete Duplicate Records
DELETE FROM TempEmp
WHERE duplicateRecCount > 1;
但它只适用于 SQL,而不适用于 Netezza。好像不喜欢WITH
子句后面的DELETE
?
【问题讨论】:
如果它是一次性工作 - 为什么不在 postgresql 控制台中运行它? 这不是一次工作,而是每周一次,我们总是得到一些重复的值。谢谢 为什么会得到重复值?如果你只是不把它放在第一位呢? 列(name, address, zipcode)
定义了重复项吗?还有其他列吗?那些无关紧要吗?不同的?列的任何组合是唯一的吗?如果某些列在重复项之间存在差异,您要保留每组中的哪一行?
适用于 POSTGRESQL(也适用于 AWS REDSHIFT)View the answer to this question on another page
【参考方案1】:
我喜欢@erwin-brandstetter 的解决方案,但想展示一个带有USING
关键字的解决方案:
DELETE FROM table_with_dups T1
USING table_with_dups T2
WHERE T1.ctid < T2.ctid -- delete the "older" ones
AND T1.name = T2.name -- list columns that define duplicates
AND T1.address = T2.address
AND T1.zipcode = T2.zipcode;
如果您想在删除记录之前查看记录,只需将DELETE
替换为SELECT *
并将USING
替换为逗号,
,即
SELECT * FROM table_with_dups T1
, table_with_dups T2
WHERE T1.ctid < T2.ctid -- select the "older" ones
AND T1.name = T2.name -- list columns that define duplicates
AND T1.address = T2.address
AND T1.zipcode = T2.zipcode;
更新:我在这里测试了一些不同的解决方案以提高速度。如果您不希望有很多重复项,那么此解决方案的性能比具有 NOT IN (...)
子句的解决方案要好得多,因为它们会在子查询中生成大量行。
如果您重写查询以使用IN (...)
,那么它的执行方式与此处提供的解决方案相似,但 SQL 代码变得不那么简洁。
更新 2:如果您在其中一个关键列中有 NULL
值(IMO 确实不应该这样做),那么您可以在该列的条件中使用 COALESCE()
,例如
AND COALESCE(T1.col_with_nulls, '[NULL]') = COALESCE(T2.col_with_nulls, '[NULL]')
【讨论】:
Erwin 的答案更好,因为它可以正确处理NULL
值,并且不需要输入两次列名。
正如我在答案开头所写的那样:I like @erwin-brandstetter 's solution, but wanted to show a solution ...
。不过,在发现性能优势后,我更喜欢USING
解决方案,尤其是对于大型表。我添加了一个示例,说明如何处理 NULL
值。
非常好,尤其是可以先看看。为了检查数据列中的 NULL 值,我根据表的 \dS
输出为每列生成了一个 T1.col = T2.col OR (T1.col IS NULL AND T2.col IS NULL)
标准。现在我可以添加我的主键约束了。
谢谢,事实证明这比其他解决方案快得多。我在 1 小时后放弃了一些版本,这几乎是立即完成的
对我很有帮助的解决方案,因为我可以在执行之前目视检查删除列表。【参考方案2】:
如果没有其他唯一标识,可以使用ctid
:
delete from mytable
where exists (select 1
from mytable t2
where t2.name = mytable.name and
t2.address = mytable.address and
t2.zip = mytable.zip and
t2.ctid > mytable.ctid
);
在每个表中都有一个唯一的、自动递增的 id 是个好主意。像这样发送delete
是其中一个重要原因。
【讨论】:
我的表中没有任何名为 ctid 的字段,您能解释一下您从哪里得到的吗?谢谢 ctid 是一个隐藏字段。当您检索表定义时,它不会显示。它是一种内部行号。where not exists
将删除没有重复的行。应该是where exists (select 1
`
@GordonLinoff - 感谢您的澄清。我知道这是题外话;这就是我问题前缀中OT:
的含义;)
在我的小桌子上我做了:select ctid, * from table
。 ctid
表示为 (0,1)、(0,2) 等。所以我能够对重复行执行简单的删除语句:delete from table where ctid = '(0,1)'
【参考方案3】:
在完美世界中,每个表都有某种唯一标识符。
如果没有任何唯一列(或其组合),请使用the ctid
column:
DELETE FROM tbl
WHERE ctid NOT IN (
SELECT min(ctid) -- ctid is NOT NULL by definition
FROM tbl
GROUP BY name, address, zipcode); -- list columns defining duplicates
上面的查询很简短,方便地只列出一次列名。当可能涉及 NULL 值时,NOT IN (SELECT ...)
是一种棘手的查询样式,但系统列 ctid
永远不会为 NULL。见:
将EXISTS
用作demonstrated by @Gordon 通常更快。 USING
子句like @isapir added later 的自联接也是如此。两者都应该产生相同的查询计划。
重要区别:这些其他查询将 NULL 值视为不等于,而 GROUP BY
(或DISTINCT
或 DISTINCT ON ()
) 将 NULL 值视为相等。对于定义为NOT NULL
的列无关紧要。否则,根据您对“重复”的定义,您将需要一种方法或另一种方法。 或使用IS NOT DISTINCT FROM
比较值(可能会排除某些索引)。
免责声明:
ctid
是 Postgres 的一个实现细节,它不在 SQL 标准中,并且可以在没有警告的情况下在主要版本之间进行更改(即使这不太可能)。由于后台进程或并发写入操作(但不在同一命令内),其值可能会在命令之间发生变化。
相关:
How do I (or can I) SELECT DISTINCT on multiple columns?
How to use the physical location of rows (ROWID) in a DELETE statement
旁白:
DELETE
语句的目标不能是 CTE,只能是基础表。这是 SQL Server 的溢出效应——你的整个方法也是如此。
【讨论】:
我喜欢这个解决方案,因为它非常简洁。关于我在下面发布的解决方案的性能有什么想法吗? ***.com/a/46775289/968244 我实际上能够测试它。我有一个大约有 350k 行的表,它在 7 列中有 39 个重复项,没有索引。我首先尝试了GROUP BY
解决方案,它花费了 30 多秒,所以我杀了它。然后我尝试了USING
解决方案,它在大约 16 秒内完成。
@isapir:就像我在 2014 年提到的那样:NOT IN
语法很短,但EXISTS
更快。 (与使用 USING
子句的完全有效的查询相同。)但是有一个细微的差别。我在上面添加了一条注释。
酷。感谢您的澄清。【参考方案4】:
这是我想出的,使用group by
DELETE FROM mytable
WHERE id NOT in (
SELECT MIN(id)
FROM mytable
GROUP BY name, address, zipcode
)
它会删除重复项,保留有重复项的最旧记录。
【讨论】:
我的表中没有 id,这是 netezza 数据库,它们没有像 sql server 这样的自动递增数字 是否有另一列唯一标识行?HAVING
子句对于这个查询来说是噪音。在 any 情况下,每个现有 id
的计数 >= 1。你可以删除它。【参考方案5】:
我们可以使用窗口函数来非常有效地删除重复行:
DELETE FROM tab
WHERE id IN (SELECT id
FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), id
FROM tab) x
WHERE x.row_number > 1);
一些PostgreSQL的优化版本(带ctid):
DELETE FROM tab
WHERE ctid = ANY(ARRAY(SELECT ctid
FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), ctid
FROM tab) x
WHERE x.row_number > 1));
【讨论】:
【参考方案6】:http://www.postgresql.org/docs/current/static/sql-delete.html 指定了有效语法
我会更改您的表以添加唯一的自动递增主键 ID,以便您可以运行如下查询,该查询将保留每组重复项中的第一个(即具有最低 id 的那个)。请注意,在 Postgres 中添加密钥比其他一些数据库要复杂一些。
DELETE FROM mytable d USING (
SELECT min(id), name, address, zip
FROM mytable
GROUP BY name, address, zip HAVING COUNT() > 1
) AS k
WHERE d.id <> k.id
AND d.name=k.name
AND d.address=k.address
AND d.zip=k.zip;
【讨论】:
【参考方案7】:如果您想在表格中保留一行重复行。
create table some_name_for_new_table as
(select * from (select *,row_number() over (partition by pk_id) row_n from
your_table_name_where_duplicates_are_present) a where row_n = 1);
这将创建一个您可以复制的表格。
复制表格前请删除'row_n'列
【讨论】:
【参考方案8】:如果您希望每一行都有一个唯一标识符,您只需添加一个(序列号或 guid),并将其视为代理键。
CREATE TABLE thenames
( name text not null
, address text not null
, zipcode text not null
);
INSERT INTO thenames(name,address,zipcode) VALUES
('James', 'main street', '123' )
,('James', 'main street', '123' )
,('James', 'void street', '456')
,('Alice', 'union square' , '123')
;
SELECT*FROM thenames;
-- add a surrogate key
ALTER TABLE thenames
ADD COLUMN seq serial NOT NULL PRIMARY KEY
;
SELECT*FROM thenames;
DELETE FROM thenames del
WHERE EXISTS(
SELECT*FROM thenames x
WHERE x.name=del.name
AND x.address=del.address
AND x.zipcode=del.zipcode
AND x.seq < del.seq
);
-- add the unique constrain,so that new dupplicates cannot be created in the future
ALTER TABLE thenames
ADD UNIQUE (name,address,zipcode)
;
SELECT*FROM thenames;
【讨论】:
Netezza 不支持主键或唯一键约束 没有。【参考方案9】:来自文档delete duplicate rows
IRC 中的一个常见问题是如何删除在一组列中重复的行,只保留 ID 最低的行。 此查询对所有具有相同 column1、column2 和 column3 的 tablename 行执行此操作。
DELETE FROM tablename
WHERE id IN (SELECT id
FROM (SELECT id,
ROW_NUMBER() OVER (partition BY column1, column2, column3 ORDER BY id) AS rnum
FROM tablename) t
WHERE t.rnum > 1);
有时使用时间戳字段代替 ID 字段。
【讨论】:
【参考方案10】:对于较小的表,我们可以使用rowid伪列来删除重复的行。
您可以在下面使用此查询:
delete from table1 t1 where t1.rowid > (select min(t2.rowid) from table1 t2 where t1.column = t2.column)
【讨论】:
以上是关于如何删除没有唯一标识符的重复行的主要内容,如果未能解决你的问题,请参考以下文章