Het is mogelijk dat in een tabel een veld met herhaalde waarden nodig is om het als uniek te laten.
En hoe verder te gaan met herhaalde waarden zonder ze allemaal te elimineren?
Zou het mogelijk zijn om alleen de meest actuele ?
ctid-systeemkolom
Elke tabel heeft enkele kolommen die impliciet zijn gedefinieerd door het systeem, waarvan de namen zijn gereserveerd.
Momenteel zijn de systeemkolommen:tableoid, xmin, cmin, xmax, cmax en ctid. Elk heeft metadata uit de tabel waartoe ze behoren.
De ctid-systeemkolom is bedoeld om de versie van de fysieke locatie van de rij op te slaan. Deze versie kan veranderen als de rij
wordt bijgewerkt (UPDATE) of de tabel door een VACUUM FULL gaat.
Het gegevenstype van ctid is tid, dat wil zeggen tuple-ID (of rij-ID), wat een paar (bloknummer, tuple-index binnen het blok)
dat de fysieke locatie van de rij in de tabel identificeert.
Deze kolom heeft altijd zijn unieke waarde in de tabel, dus als er rijen zijn met herhaalde waarden het kan worden gebruikt als criterium voor hun eliminatie.
Aanmaken van een testtabel:
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Voeg wat gegevens in:
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Controleer huidige rijen:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Een rij bijwerken:
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Controleer de tafel opnieuw:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
We kunnen zien dat de geüpdatete rij ook zijn ctid had veranderd...
Een eenvoudige VACUM VOL-test:
VACUUM FULL tb_test_ctid;
De tafel controleren na het VACUM:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Werk dezelfde rij opnieuw bij met behulp van de RETURNING-component:
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Controleer de tafel opnieuw:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Herhaalde waarden elimineren met ctid
Stel je een tabel voor met herhaalde waarden in een veld en datzelfde veld wordt later uniek gemaakt.
Onthoud dat een PRIMARY KEY-veld ook uniek is.
OK, er is besloten dat de herhaalde waarden in dat veld wordt verwijderd.
Het is nu nodig om een criterium vast te stellen om te beslissen tussen deze herhaalde waarden die overblijven.
In het volgende geval is het criterium de meest actuele regel, dat wil zeggen die met de hoogste ctid-waarde.
Nieuwe testtabel aanmaken:
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
10 records invoegen:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Bekijk de 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 | aVoeg nog 3 records toe:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Controleer herhaalde waarden:
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
Er zijn herhaalde waarden in het veld id_ van de tabel...
Poging om van het veld id_ een primaire sleutel te maken:
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.
Ontdek met behulp van CTE- en vensterfuncties welke herhaalde waarden behouden blijven:
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)
De tabel verlaten met unieke waarden voor het veld id_, de oudere rijen verwijderen:
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)
Tabelwaarden controleren zonder dubbele waarden voor 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
U kunt nu de tabel wijzigen om het veld id_ als PRIMAIRE SLEUTEL te laten:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);