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_);