如何删除Postgres 9表中的重复行,这些行在每个字段上都是完全重复的,并且没有单独的字段可以用作唯一键,所以我不能只使用GROUP BY
列并使用{ {1}}陈述。
我正在寻找一个单独的SQL语句,而不是一个需要我创建临时表并将记录插入其中的解决方案。我知道如何做到这一点,但需要更多的工作来适应我的自动化过程。
表格定义:
NOT IN
示例数据:
jthinksearch=> \d releases_labels;
Unlogged table "discogs.releases_labels"
Column | Type | Modifiers
------------+---------+-----------
label | text |
release_id | integer |
catno | text |
Indexes:
"releases_labels_catno_idx" btree (catno)
"releases_labels_name_idx" btree (label)
Foreign-key constraints:
"foreign_did" FOREIGN KEY (release_id) REFERENCES release(id)
答案 0 :(得分:7)
如果你有能力重写整个表格,这可能是最简单的方法:
WITH Deleted AS (
DELETE FROM discogs.releases_labels
RETURNING *
)
INSERT INTO discogs.releases_labels
SELECT DISTINCT * FROM Deleted
如果您需要专门定位重复记录,则可以使用唯一标识行的内部ctid
字段:
DELETE FROM discogs.releases_labels
WHERE ctid NOT IN (
SELECT MIN(ctid)
FROM discogs.releases_labels
GROUP BY label, release_id, catno
)
对ctid
要非常小心;它随着时间而变化。但是你可以依赖它在单一陈述的范围内保持不变。
答案 1 :(得分:4)
这是一个删除重复项的解决方案:
DELETE FROM releases_labels r
WHERE EXISTS (
SELECT 1
FROM releases_labels r1
WHERE r1 = r
AND r1.ctid < r.ctid
);
由于没有唯一键,因此我(ab)使用元组ID ctid
。第一排在第一排中存活下来。
ctid
是一个不属于关联行类型的系统列,因此当在表达式r1 = r
中引用包含表别名的整行时,只有可见列比较(不是ctid
或其他)。这就是为什么整行可以相等,一个ctid
仍然比另一行小。
只有少数重复项,这也是所有解决方案中最快的 使用 lot 重复项,其他解决方案更快。
然后我建议:
ALTER TABLE discogs.releases_labels ADD COLUMN releases_labels_id serial PRIMARY KEY;
这有点令人惊讶。原因在chapter Composite Type Comparison in the manual:
中解释SQL规范要求按行进行比较以返回NULL 结果取决于比较两个NULL值或NULL和a 非NULL。 PostgreSQL仅在比较两者的结果时才这样做 行构造函数(如第9.23.5节)或比较行构造函数 到子查询的输出(如第9.22节)。在其他情况下 比较两个复合类型值,两个NULL字段值 被认为是等于,并且认为NULL大于非NULL。 这是必要的,以便进行一致的排序和索引 复合类型的行为。
大胆强调我的。
我删除了该部分,因为solution with a data-modifying CTE provided by @Nick更好。
答案 2 :(得分:0)
您可以尝试这样:
CREATE TABLE temp
INSERT INTO temp SELECT DISTINCT * FROM discogs.releases_labels;
DROP TABLE discogs.releases_labels;
ALTER TABLE temp RENAME TO discogs.releases_labels;
答案 3 :(得分:0)
由于您没有主键,因此没有简单的方法可以将一个重复的行与任何其他行区分开来。这就是为什么强烈建议任何表都有主键(*)的原因之一。
所以你只剩下2个解决方案:
使用过程SQL和游标来自过程语言,如Python或[放在这里你的首选语言]或PL / pgSQL。像(小心未经测试)的东西:
CREATE OR REPLACE FUNCTION deduplicate() RETURNS integer AS $$
DECLARE
curs CURSOR FOR SELECT * FROM releases_labels ORDER BY label, release_id, catno;
r releases_labels%ROWTYPE;
old releases_labels%ROWTYPE;
n integer;
BEGIN
n := 0;
old := NULL;
FOR rec IN curs LOOP
r := rec;
IF r = old THEN
DELETE FROM releases_labels WHERE CURRENT OF curs;
n := n + 1;
END IF;
old := rec;
END LOOP;
RETURN n;
END;
$$ LANGUAGE plpgsql;
SELECT deduplicate();
应删除重复的行并返回实际删除的行数。它不一定是最有效的方式,但您只需触摸需要删除的行,这样您就不必锁定整个表。
(*)希望PostgreSQL提供可用作密钥的ctid
伪列。如果您的表格包含oid
列,您也可以使用它,因为它永远不会改变。
(**)PostgreSQL WITH
允许您在单个SQL语句中执行此操作
这两点来自Nick Barnes的回答
答案 4 :(得分:0)
由于您将来还需要避免重复,因此您可以在重复数据删除时添加代理键和唯一约束:
-- add surrogate key
ALTER TABLE releases_labels
ADD column id SERIAL NOT NULL PRIMARY KEY
;
-- verify
SELECT * FROM releases_labels;
DELETE FROM releases_labels dd
WHERE EXISTS (SELECT *
FROM releases_labels x
WHERE x.label = dd.label
AND x.release_id = dd.release_id
AND x.catno = dd.catno
AND x.id < dd.id
);
-- verify
SELECT * FROM releases_labels;
-- add unique constraint for the natural key
ALTER TABLE releases_labels
ADD UNIQUE (label,release_id,catno)
;
-- verify
SELECT * FROM releases_labels;