Det er muligt, at et felt i en tabel, der har gentagne værdier, er nødvendigt for at lade det være unikt.
Og hvordan fortsætter man med gentagne værdier uden at fjerne dem alle?
Ville det være muligt kun at lade de mest aktuelle ?
ctid System Column
Hver tabel har nogle kolonner, der er implicit defineret af systemet, hvis navne er reserveret.
I øjeblikket er systemkolonnerne:tableoid, xmin, cmin, xmax, cmax og ctid. Hver enkelt har metadata fra tabel, som de tilhører.
Ctid-systemkolonnen er beregnet til at gemme versionen af den fysiske placering af rækken. Denne version kan ændre sig, hvis rækken
opdateres (OPDATERING) under tabellen gennemgår en VACUUM FULL.
Datatypen for ctid er tid, det vil sige tupelidentifikator (eller rækkeidentifikator), som er en par (bloknummer, tupelindeks i blokken)
der identificerer den fysiske placering af rækken i tabellen.
Denne kolonne har altid sin unikke værdi i tabellen, så når der er rækker med gentagne værdier det kan bruges som et kriterium for deres eliminering.
Test tabeloprettelse:
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Indsæt nogle data:
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Tjek aktuelle rækker:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Opdater en række:
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Tjek tabellen igen:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Vi kan bemærke, at den opdaterede række også fik ændret sin ctid...
En simpel VAKUUM FULD test:
VACUUM FULL tb_test_ctid;
Kontrol af tabellen efter VACUUM:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Opdater den samme række igen ved at bruge RETURNING-sætningen:
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Tjek tabellen igen:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Eliminering af gentagne værdier med ctid
Forestil dig en tabel, der har gentagne værdier i et felt, og det samme felt besluttes for at gøre det unikt senere.
Husk, at et PRIMÆR NØGLE-felt også er unikt.
OK, det blev besluttet, at de gentagne værdier i det felt slettes.
Det er nu nødvendigt at etablere et kriterium for at afgøre blandt disse gentagne værdier, som vil forblive.
I det følgende tilfælde er kriteriet den mest aktuelle linje, det vil sige den med den højeste ctid-værdi.
Oprettelse af ny testtabel:
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
Indsæt 10 poster:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Tjek tabellen:
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 | aIndsæt 3 poster mere:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Kontroller gentagne værdier:
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
Der er gentagne værdier i tabellens id_-felt...
Forsøg på at gøre id_-feltet til en primær nøgle:
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.
Ved hjælp af CTE- og vinduesfunktioner kan du finde ud af, hvilke gentagne værdier der vil blive bevaret:
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)
Forlader tabellen med unikke værdier for id_-feltet, fjerner de ældre rækker:
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)
Kontrollerer tabelværdier uden duplikerede værdier for 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
Du kan nu ændre tabellen til at forlade id_-feltet som PRIMÆR NØGLE:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);