Możliwe, że w tabeli jakieś pole, które ma powtarzające się wartości, jest konieczne, aby pozostawić je jako unikalne.
A jak postępować z powtarzającymi się wartościami bez ich eliminowania?
Czy byłoby możliwe pozostawienie tylko najbardziej aktualnych ?
ctid Kolumna systemowa
Każda tabela ma kilka kolumn domyślnie zdefiniowanych przez system, których nazwy są zastrzeżone.
Obecnie kolumny systemowe to:tableoid, xmin, cmin, xmax, cmax i ctid. Każda z nich ma metadane z tabeli, do której należą.
Kolumna systemowa ctid jest przeznaczona do przechowywania wersji fizycznej lokalizacji wiersza. Ta wersja może się zmienić, jeśli wiersz
zostanie zaktualizowany (UPDATE) lub tabela przejdzie przez PRÓŻNIĘ PEŁNĄ.
Typ danych ctid to tid, co oznacza identyfikator krotki (lub identyfikator wiersza), który jest para (numer bloku, indeks krotki w bloku)
która identyfikuje fizyczne położenie wiersza w tabeli.
Ta kolumna zawsze ma swoją unikalną wartość w tabeli, więc jeśli istnieją wiersze z powtarzającymi się wartościami może służyć jako kryterium ich eliminacji.
Test tworzenia tabeli:
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Wstaw jakieś dane:
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Sprawdź bieżące wiersze:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Zaktualizuj wiersz:
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Sprawdź ponownie tabelę:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Możemy zauważyć, że zaktualizowany wiersz miał zmieniony identyfikator ctid…
Prosty test PRÓŻNIOWY PEŁNY:
VACUUM FULL tb_test_ctid;
Sprawdzenie tabeli po PRÓŻNIU:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Zaktualizuj ponownie ten sam wiersz, używając klauzuli RETURNING:
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Sprawdź ponownie tabelę:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Eliminowanie powtarzających się wartości za pomocą ctid
Wyobraź sobie tabelę, która zawiera powtarzające się wartości w polu, a to samo pole zostało później uznane za unikatowe.
Pamiętaj, że pole PRIMARY KEY również jest unikatowe.
OK, zdecydowano, że powtarzające się wartości w polu to pole zostanie usunięte.
Teraz konieczne jest ustalenie kryterium, aby wybrać spośród tych powtarzających się wartości, które pozostaną.
W następującym przypadku kryterium jest najbardziej aktualny wiersz, to znaczy ten z najwyższa wartość ctid.
Tworzenie nowej tabeli testowej:
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
Wstaw 10 rekordów:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Sprawdź tabelę:
SELECT id_, letter FROM tb_foo;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 4 | a 5 | a 6 | a 7 | a 8 | a 9 | a 10 | aWstaw jeszcze 3 rekordy:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Sprawdź powtarzające się wartości:
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
W polu id_ tabeli występują powtarzające się wartości…
Spróbuj ustawić pole id_ jako klucz podstawowy:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);
ERROR: could not create unique index "tb_foo_pkey" DETAIL: Key (id_)=(3) is duplicated.
Korzystając z funkcji CTE i okna, sprawdź, które powtarzające się wartości zostaną zachowane:
WITH t AS ( SELECT id_, count(id_) OVER (PARTITION BY id_) AS count_id, -- Count ctid, max(ctid) OVER (PARTITION BY id_) AS max_ctid -- Most current ctid FROM tb_foo ) SELECT t.id_, t.max_ctid FROM t WHERE t.count_id > 1 -- Filters which values repeat GROUP by id_, max_ctid;
id_ | max_ctid -----+---------- 3 | (0,13) 1 | (0,11) 2 | (0,12)
Opuszczenie tabeli z unikalnymi wartościami pola id_, usunięcie starszych wierszy:
WITH t1 AS ( SELECT id_, count(id_) OVER (PARTITION BY id_) AS count_id, ctid, max(ctid) OVER (PARTITION BY id_) AS max_ctid FROM tb_foo ), t2 AS ( -- Virtual table that filters repeated values that will remain SELECT t1.id_, t1.max_ctid FROM t1 WHERE t1.count_id > 1 GROUP by t1.id_, t1.max_ctid) DELETE -- DELETE with JOIN FROM tb_foo AS f USING t2 WHERE f.id_ = t2.id_ AND -- tb_foo has id_ equal to t2 (repeated values) f.ctid < t2.max_ctid; -- ctid is less than the maximum (most current)
Sprawdzanie wartości tabeli bez zduplikowanych wartości dla id_:
SELECT id_, letter FROM tb_foo;
id_ | letter -----+-------- 4 | a 5 | a 6 | a 7 | a 8 | a 9 | a 10 | a 1 | b 2 | b 3 | b
Możesz teraz zmienić tabelę, aby pozostawić pole id_ jako KLUCZ PODSTAWOWY:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);