답변:
예를 들어 다음과 같이 할 수 있습니다.
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
CREATE TABLE tmp AS SELECT ...;
. 그러면 레이아웃이 무엇인지 알아낼 필요조차 없습니다 tmp
. :)
이러한 접근 방식 중 일부는 약간 복잡해 보이며 일반적으로 다음과 같이 수행합니다.
주어진 table table
에서 최대 field3 행을 유지하면서 (field1, field2)에서 고유하고 싶습니다.
DELETE FROM table USING table alias
WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
table.max_field < alias.max_field
예를 들어, 테이블이 있고 user_accounts
이메일에 고유 한 제약 조건을 추가하고 싶지만 일부 중복 항목이 있습니다. 또한 가장 최근에 만든 항목 (중복 항목 중 최대 ID)을 유지하고 싶다고 말합니다.
DELETE FROM user_accounts USING user_accounts ua2
WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
USING
표준 SQL이 아니라 PostgreSQL 확장 (그러나 매우 유용한 확장)이지만 원래 질문에는 특히 PostgreSQL이 언급되어 있습니다.USING
postgresql에서 무엇을하는지 더 잘 설명 할 수 있습니까?
WHERE table1.ctid<table2.ctid
-직렬 열을 추가 할 필요 없음
새 테이블을 생성하는 대신 자른 후 동일한 테이블에 고유 행을 다시 삽입 할 수도 있습니다. 한 번의 트랜잭션으로 모든 작업 을 수행하십시오 . 선택적으로를 사용하여 트랜잭션이 끝날 때 임시 테이블을 자동으로 삭제할 수 있습니다 ON COMMIT DROP
. 아래를 참조하십시오.
이 접근 방식은 테이블 전체에서 삭제할 행이 많은 경우에만 유용합니다. 몇 개의 중복에 대해서는 일반 DELETE
.
수백만 개의 행을 언급하셨습니다. 작업을 빠르게 하려면 세션에 충분한 임시 버퍼 를 할당 해야합니다. 현재 세션에서 임시 버퍼를 사용 하기 전에 설정을 조정 해야 합니다. 테이블의 크기를 확인하십시오.
SELECT pg_size_pretty(pg_relation_size('tbl'));
temp_buffers
그에 따라 설정하십시오 . 메모리 내 표현에는 약간 더 많은 RAM이 필요하므로 충분히 반올림하십시오.
SET temp_buffers = 200MB; -- example value
BEGIN;
-- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit
CREATE TEMPORARY TABLE t_tmp AS -- retain temp table after commit
SELECT DISTINCT * FROM tbl; -- DISTINCT folds duplicates
TRUNCATE tbl;
INSERT INTO tbl
SELECT * FROM t_tmp;
-- ORDER BY id; -- optionally "cluster" data while being at it.
COMMIT;
이 방법은 종속 개체가있는 경우 새 테이블을 만드는 것보다 우수 할 수 있습니다 . 테이블을 참조하는 뷰, 인덱스, 외래 키 또는 기타 개체. TRUNCATE
어쨌든 깨끗한 슬레이트 (백그라운드의 새 파일)로 시작하고 큰 테이블 보다 훨씬 빠릅니다 DELETE FROM tbl
( DELETE
실제로 작은 테이블에서 더 빠를 수 있음).
큰 테이블의 경우 인덱스와 외래 키를 삭제하고 테이블을 다시 채우고 이러한 개체를 다시 만드는 것이 정기적으로 더 빠릅니다 . fk 제약 조건에 관한 한 새 데이터가 물론 유효한지 확인해야합니다. 그렇지 않으면 fk를 만들려고 할 때 예외가 발생합니다.
주 TRUNCATE
보다 더 공격적으로 잠금이 필요합니다 DELETE
. 동시로드가 많은 테이블의 경우 문제가 될 수 있습니다.
경우 TRUNCATE
일반적 대한 옵션 아닌지 매체 작은 테이블 과 유사한 기술이있는 데이터 수정 CTE (포스트 그레스 9.1 +)를 :
WITH del AS (DELETE FROM tbl RETURNING *)
INSERT INTO tbl
SELECT DISTINCT * FROM del;
-- ORDER BY id; -- optionally "cluster" data while being at it.
큰 테이블의 경우 TRUNCATE
더 느리기 때문에 더 빠릅니다. 그러나 작은 테이블의 경우 더 빠르고 간단 할 수 있습니다.
종속 개체가 전혀없는 경우 새 테이블을 만들고 이전 테이블을 삭제할 수 있지만이 보편적 인 접근 방식을 통해 얻을 수있는 것은 거의 없습니다.
사용 가능한 RAM에 맞지 않는 매우 큰 테이블의 경우 새 테이블을 만드는 것이 훨씬 빠릅니다. 개체에 따라 발생할 수있는 문제 / 오버 헤드와 비교하여이 값을 측정해야합니다.
TRUNCATE
. 이후 임시 테이블이 존재하지 않았기 때문에 데이터를 다시 가져와야 합니다. Erwin이 말했듯이 테이블을 자르기 전에 존재하는지 확인하십시오. codebykat의 대답 @ 참조
ON COMMIT DROP
"하나의 트랜잭션으로"쓴 부분을 놓친 사람들이 데이터를 잃지 않도록. 그리고 "하나의 트랜잭션"을 명확히하기 위해 BEGIN / COMMIT를 추가했습니다.
일반적으로 테이블에서 "표시되지 않는"열인 oid 또는 ctid를 사용할 수 있습니다.
DELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
NOT EXISTS
상당히 빨라야합니다 . DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
또는 다른 열 또는 열 집합을 사용하여 정렬하여 생존자를 선택합니다.
NOT EXISTS
합니까?
EXISTS
. 다음과 같이 읽으십시오. "값이 dist_col
같지만 더 큰 다른 행이있는 모든 행을 삭제하십시오 ctid
." 사기 그룹당 유일한 생존자는 가장 큰 ctid
.
LIMIT
중복 수를 알고 있으면 함께 사용할 수 있습니다 .
PostgreSQL 창 함수는이 문제에 편리합니다.
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);
중복 삭제를 참조하십시오 .
에서 오래된 postgresql.org 메일 링리스트 :
create table test ( a text, b text );
insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
insert into test values ( 'x', 'y');
select oid, a, b from test;
select o.oid, o.a, o.b from test o
where exists ( select 'x'
from test i
where i.a = o.a
and i.b = o.b
and i.oid < o.oid
);
참고 : PostgreSQL from
은 삭제 절에 언급 된 테이블의 별칭을 지원하지 않습니다.
delete from test
where exists ( select 'x'
from test i
where i.a = test.a
and i.b = test.b
and i.oid < test.oid
);
중복 삭제를위한 일반화 된 쿼리 :
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
열 ctid
은 모든 테이블에 사용할 수있는 특수 열이지만 특별히 언급하지 않는 한 표시되지 않습니다. ctid
열의 값은 테이블의 모든 행에 대해 고유 한 것으로 간주된다.
GROUP BY
절 을 올바르게 지정하는 것이 필수적이라는 점을 추가 할 가치 가 있습니다. 이것은 현재 위반 된 '고유성 기준'이어야합니다. 또는 키가 중복을 감지하기를 원하는 경우입니다. 잘못 지정된 경우 제대로 작동하지 않습니다
방금 Erwin Brandstetter의 답변을 사용 하여 조인 테이블 (자체 기본 ID가없는 테이블)에서 중복을 제거했지만 중요한 경고가 하나 있음을 발견했습니다.
포함 ON COMMIT DROP
은 트랜잭션이 끝날 때 임시 테이블이 삭제됨을 의미합니다. 나를 위해 그것은 내가 그것을 삽입하려고 할 때 임시 테이블을 더 이상 사용할 수 없음 을 의미 했습니다!
나는 방금 CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
했고 모든 것이 잘 작동했습니다.
세션이 끝날 때 임시 테이블이 삭제되지 않습니다.
이 함수는 인덱스를 제거하지 않고 중복을 제거하고 테이블에 대해 수행합니다.
용법: select remove_duplicates('mytable');
--- --- remove_duplicates (tablename)은 테이블에서 중복 레코드를 제거합니다 (세트에서 고유 세트로 변환). --- 함수 생성 또는 교체 remove_duplicates (text) $$로 void 반환 알리다 테이블 이름 ALIAS FOR $ 1; 시작 EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_'|| 테이블 이름 || 'AS (SELECT DISTINCT * FROM'|| 테이블 이름 || ');'; EXECUTE 'DELETE FROM'|| 테이블 이름 || ';'; 'INSERT INTO'실행 || 테이블 이름 || '(SELECT * FROM _DISTINCT_'|| 테이블 이름 || ');'; EXECUTE 'DROP TABLE _DISTINCT_'|| 테이블 이름 || ';'; 반환; 종료; $$ LANGUAGE plpgsql;
중복 된 항목이 하나 또는 몇 개만 있고 실제로 중복 된 경우 (즉, 두 번 표시됨) ctid
위에 제안 된대로 "hidden" 열을 LIMIT
다음 과 함께 사용할 수 있습니다 .
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
선택한 행 중 첫 번째 행만 삭제됩니다.
먼저, 어떤 "중복"을 유지할 것인지 결정해야합니다. 모든 열이 같으면 그 중 하나를 삭제할 수 있습니다 ...하지만 가장 최근의 기준 만 유지하거나 다른 기준을 유지 하시겠습니까?
가장 빠른 방법은 위의 질문에 대한 답변과 테이블의 중복 비율에 따라 다릅니다. 행의 50 %를 버리면을 수행하는 CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
것이 더 좋고 행의 1 %를 삭제하는 경우 DELETE를 사용하는 것이 좋습니다.
또한 이와 같은 유지 관리 작업의 경우 일반적으로 work_mem
RAM의 좋은 덩어리 로 설정 하는 것이 좋습니다. 속도면에서 좋습니다. 동시 연결이 하나만있는 한 ...
PostgreSQL 8.4로 작업하고 있습니다. 제안 된 코드를 실행했을 때 실제로 중복을 제거하지 않는 것으로 나타났습니다. 일부 테스트를 실행하면서 "DISTINCT ON (duplicate_column_name)"과 "ORDER BY duplicate_column_name"을 추가하는 것이 트릭임을 발견했습니다. 저는 SQL 전문가가 아니며 PostgreSQL 8.4 SELECT ... DISTINCT 문서에서 이것을 발견했습니다.
CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$
DECLARE
tablename ALIAS FOR $1;
duplicate_column ALIAS FOR $2;
BEGIN
EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);';
EXECUTE 'DELETE FROM ' || tablename || ';';
EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
RETURN;
END;
$$ LANGUAGE plpgsql;
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가 가장 낮은 행을 유지합니다. 패턴은 postgres 위키 에서 가져 왔습니다.
CTE를 사용하면이를 통해 위의 더 읽기 쉬운 버전을 얻을 수 있습니다.
WITH duplicate_ids as (
SELECT id, rnum
FROM num_of_rows
WHERE rnum > 1
),
num_of_rows as (
SELECT id,
ROW_NUMBER() over (partition BY column1,
column2,
column3 ORDER BY id) AS rnum
FROM tablename
)
DELETE FROM tablename
WHERE id IN (SELECT id from duplicate_ids)
CREATE TABLE test (col text);
INSERT INTO test VALUES
('1'),
('2'), ('2'),
('3'),
('4'), ('4'),
('5'),
('6'), ('6');
DELETE FROM test
WHERE ctid in (
SELECT t.ctid FROM (
SELECT row_number() over (
partition BY col
ORDER BY col
) AS rnum,
ctid FROM test
ORDER BY col
) t
WHERE t.rnum >1);