sql >> Databáze >  >> RDS >> PostgreSQL

PostgreSQL – Jak eliminovat opakované hodnoty

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 | a
Vlož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_);

  1. Jak odstraním konkrétní řádky v databázi SQLite

  2. Syntaxe SQL CREATE TABLE – Zobrazeno podle DBMS

  3. Zobrazit všechny dotazy přicházející do databáze Oracle

  4. SQLite KROMĚ operátora