Je možné, že v tabulce je nutné některé pole, které má opakované hodnoty, ponechat jako jedinečné.
A jak postupovat s opakovanými hodnotami, aniž bychom je všechny odstranili?
Bylo by možné ponechat pouze nejaktuálnější ?
ctid Systémový sloupec
Každá tabulka má nějaké sloupce implicitně definované systémem, jejichž jména jsou vyhrazena.
V současné době jsou systémové sloupce:tableoid, xmin, cmin, xmax, cmax a ctid. Každý z nich má metadata z tabulky, ke které patří.
Systémový sloupec ctid je určen k uložení verze fyzického umístění řádku. Tato verze se může změnit, pokud je řádek
aktualizován (UPDATE) nebo pokud tabulka prochází VACUUM FULL.
Datový typ ctid je tid, to znamená identifikátor n-tice (nebo identifikátor řádku), což je pár (číslo bloku, index n-tice v bloku)
který identifikuje fyzické umístění řádku v tabulce.
Tento sloupec má v tabulce vždy svou jedinečnou hodnotu, takže když existují řádky s opakujícími se hodnotami může být použito jako kritérium pro jejich eliminaci.
Vytvoření testovací tabulky:
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Vložte nějaká data:
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Zkontrolujte aktuální řádky:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Aktualizovat řádek:
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Znovu zkontrolujte tabulku:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Můžeme si všimnout, že aktualizovaný řádek měl také změněno své ctid…
Jednoduchý test VACUUM FULL:
VACUUM FULL tb_test_ctid;
Kontrola stolu po VACUUM:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Aktualizujte stejný řádek znovu pomocí klauzule RETURNING:
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Znovu zkontrolujte tabulku:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Eliminace opakovaných hodnot pomocí ctid
Představte si tabulku, která má v poli opakované hodnoty a stejné pole se později rozhodne, že bude jedinečné.
Nezapomeňte, že pole PRIMARY KEY je také jedinečné.
OK, bylo rozhodnuto, že opakované hodnoty v toto pole bude smazáno.
Nyní je nutné stanovit kritérium pro rozhodování mezi těmito opakovanými hodnotami, které zůstanou.
V následujícím případě je kritériem nejaktuálnější řádek, tj. nejvyšší hodnotu ctid.
Vytvoření nové testovací tabulky:
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
Vložte 10 záznamů:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Zkontrolujte tabulku:
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 | aVložte 3 další záznamy:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Zkontrolujte opakované hodnoty:
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
V poli id_ tabulky jsou opakované hodnoty…
Pokuste se udělat z pole id_ primární klíč:
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.
Pomocí CTE a okenních funkcí zjistěte, které opakované hodnoty budou zachovány:
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)
Opuštění tabulky s jedinečnými hodnotami pro pole id_, odstranění starších řádků:
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)
Kontrola hodnot tabulky bez duplicitních hodnot pro 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
Nyní můžete změnit tabulku tak, aby pole id_ ponechalo jako PRIMÁRNÍ KLÍČ:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);