PostgreSQL에서 중복 레코드 삭제


113

PostgreSQL 8.3.8 데이터베이스에 키 / 제약 조건이없고 정확히 동일한 값을 가진 여러 행이있는 테이블이 있습니다.

모든 중복을 제거하고 각 행의 사본을 1 개만 유지하고 싶습니다.

특히 중복을 식별하는 데 사용할 수있는 하나의 열 ( "키"라고 함)이 있습니다 (즉, 각 고유 "키"에 대해 하나의 항목 만 있어야 함).

어떻게 할 수 있습니까? (이상적으로는 단일 SQL 명령 사용)이 경우 속도는 문제가되지 않습니다 (행이 몇 개만 있음).

답변:


80
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

20
사용하지 마십시오. 너무 느립니다!
Paweł Malisak

5
이 솔루션은 확실히 작동하지만 아래 @rapimo의 솔루션 은 훨씬 빠르게 실행됩니다. 나는 이것이 다른 솔루션에서 진행되는 그룹화가 아닌 N 번 실행되는 내부 select 문과 관련이 있다고 생각합니다 (복제 테이블의 모든 N 행에 대해).
David

거대한 테이블 (수백만 개의 레코드)의 경우 @rapimo의 솔루션과 달리 실제로 메모리에 적합합니다. 따라서 이러한 경우에는 이것이 더 빠릅니다 (스와핑 없음).
Giel '

1
설명 추가 : ctid가 행의 물리적 위치를 나타내는 특별한 postgres 열이기 때문에 작동합니다. 테이블에 고유 ID가 없더라도이를 고유 ID로 사용할 수 있습니다. postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

194

더 빠른 솔루션은

DELETE FROM dups a USING (
      SELECT MIN(ctid) as ctid, key
        FROM dups 
        GROUP BY key HAVING COUNT(*) > 1
      ) b
      WHERE a.key = b.key 
      AND a.ctid <> b.ctid

20
a_horse_with_no_name의 솔루션보다 빠른 이유는 무엇입니까?
Roberto

3
이것은 2 개의 쿼리 만 실행하기 때문에 더 빠릅니다. 먼저 모든 중복 항목을 선택한 다음 하나는 테이블에서 모든 항목을 삭제합니다. @a_horse_with_no_name에 의한 쿼리는 테이블의 모든 단일 항목에 대해 다른 항목과 일치하는지 확인하는 쿼리를 수행합니다.
Aeolun

5
무엇 ctid입니까?
techkuz

6
문서에서 : ctid. 테이블 내 행 버전의 물리적 위치입니다. ctid를 사용하여 행 버전을 매우 빠르게 찾을 수 있지만 행의 ctid는 VACUUM FULL에 의해 업데이트되거나 이동할 때마다 변경됩니다. 따라서 ctid는 장기 행 식별자로 쓸모가 없습니다.
Saim

1
중복 행이 2 개 이상일 때는 한 번에 하나의 중복 만 삭제하기 때문에 작동하지 않는 것 같습니다.
프랭키 드레이크

73

이것은 빠르고 간결합니다.

DELETE FROM dupes T1
    USING   dupes T2
WHERE   T1.ctid < T2.ctid  -- delete the older versions
    AND T1.key  = T2.key;  -- add more columns if needed

자세한 정보를 포함하는 고유 식별자없이 중복 행을 삭제하는 방법의 내 대답을 참조하십시오.


ct는 무엇을 의미합니까? 카운트?
techkuz

4
@trthhrtz ctid는 테이블에서 레코드의 실제 위치를 가리 킵니다. 내가 주석에서 썼던 것과는 달리, less than 연산자를 사용하는 것은 ct가 래핑 될 수 있고 ctid가 더 낮은 값이 실제로 더 최신 버전 일 수 있으므로 반드시 이전 버전을 가리키는 것은 아닙니다.
isapir 19

1
참고로이 솔루션을 시도한 후 15 분 후에 중단했습니다. rapimo의 솔루션을 시도했고 약 10 초 만에 완료되었습니다 (~ 700,000 행 삭제).
Patrick

@Patrick은 rapimo의 답변이이 경우 작동하지 않기 때문에 db에 고유 식별자가 없는지 상상할 수 없습니다.
stucash

@isapir 나는 단지 궁금합니다, 위의 답변, 그들은 그들이 선택한대로 이전 기록을 유지하고 min(ctid)있습니까? 당신은 새로운 것을 유지하고 있습니까? 감사!
stucash

17

나는 이것을 시도했다 :

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

Postgres 위키에서 제공 :

https://wiki.postgresql.org/wiki/Deleting_duplicates


@rapimo의 답변과 수락 된 답변 (@a_horse_with_no_name)과 비교하여 성능에 대한 아이디어가 있습니까?
tuxayo

3
질문과 같이 모든 열이 동일한 경우에는 작동하지 id않습니다.
ibizaman

이 쿼리는 원본과 중복을 모두 삭제합니다. 문제는 적어도 하나의 행을 유지하는 것입니다.
pyBomb

@pyBomb이 잘못되었습니다 id. column1 ... 3이 중복 되는 첫 번째 항목이 유지됩니다
Jeff

postgresql 12에서 이것은 BY FAR이 가장 빠른 솔루션입니다 (3 억 행에 대해). 나는 받아 들여진 대답을 포함하여이 질문에서 제안 된 모든 것을 테스트했고,이 "공식적인"솔루션은 실제로 가장 빠르고 OP (그리고 내)의 모든 요구 사항을 충족합니다
Jeff

7

나만의 버전을 만들어야했습니다. @a_horse_with_no_name에 의해 작성된 버전은 내 테이블에서 너무 느립니다 (21M 행). 그리고 @rapimo는 단순히 dups를 삭제하지 않습니다.

PostgreSQL 9.5에서 사용하는 것은 다음과 같습니다.

DELETE FROM your_table
WHERE ctid IN (
  SELECT unnest(array_remove(all_ctids, actid))
  FROM (
         SELECT
           min(b.ctid)     AS actid,
           array_agg(ctid) AS all_ctids
         FROM your_table b
         GROUP BY key1, key2, key3, key4
         HAVING count(*) > 1) c);

6

임시 테이블을 사용합니다.

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;

그런 다음, 삭제 tab및 이름 변경 tab_temptab.


8
이 접근 방식은 트리거, 인덱스 및 통계를 고려하지 않습니다. 확실히 추가 할 수 있지만 작업도 더 많이 추가됩니다.
Jordan

모든 사람이 그것을 필요로하는 것은 아닙니다. 이 접근 방식은 매우 빠르고 인덱스가없는 20 만 개의 이메일 (varchar 250)에서 나머지보다 훨씬 더 잘 작동합니다.
Sergey Telshevsky 2011

전체 코드 :DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
Eric Burel

1

id열별로 모든 고유 ID를 찾고 고유 목록에없는 다른 ID를 제거하는 또 다른 방법 ( 테이블에 있는 고유 필드가있는 경우에만 작동 )

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);

문제는 내 질문에 테이블에 고유 ID가 없다는 것입니다. "중복"은 모든 열에서 정확히 동일한 값을 가진 여러 행이었습니다.
André Morujão

그래, 나는 몇 가지 메모 추가
Zaytsev 드미트리을

1

어때 :

와
  u AS (선택 DISTINCT * FROM your_table),
  x AS (your_table에서 삭제)
INSERT INTO your_table SELECT * FROM u;

나는 실행 순서에 대해 걱정하고 있었는데, SELECT DISTINCT 전에 DELETE가 발생할 것이지만 잘 작동합니다. 그리고 테이블 구조에 대한 지식이 필요 없다는 추가 보너스가 있습니다.


유일한 단점은 동등성을 지원하지 않는 데이터 유형 (예 :) json이있는 경우 작동하지 않는다는 것입니다.
a_horse_with_no_name

0

이것은 나를 위해 잘 작동했습니다. 중복 값이 ​​포함 된 테이블, 용어가 있습니다. 모든 중복 행으로 임시 테이블을 채우는 쿼리를 실행했습니다. 그런 다음 임시 테이블에서 해당 ID로 a delete 문을 실행했습니다. value는 중복 항목이 포함 된 열입니다.

        CREATE TEMP TABLE dupids AS
        select id from (
                    select value, id, row_number() 
over (partition by value order by value) 
    as rownum from terms
                  ) tmp
                  where rownum >= 2;

delete from [table] where id in (select id from dupids)

0

다음은 사용하는 솔루션입니다 PARTITION BY.

DELETE FROM dups
USING (
  SELECT
    ctid,
    (ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])) AS is_duplicate
  FROM dups 
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.