PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL:come eliminare i valori ripetuti

È possibile che in una tabella sia necessario un campo che ha valori ripetuti per lasciarla come unica.
E come procedere con valori ripetuti senza eliminarli tutti?
Sarebbe possibile lasciare solo i più attuali ?

colonna del sistema ctid

Ogni tabella ha delle colonne definite implicitamente dal sistema, i cui nomi sono riservati.
Attualmente le colonne di sistema sono:tableoid, xmin, cmin, xmax, cmax e ctid. Ognuno ha i metadati della tabella a cui appartiene.
La colonna di sistema ctid ha lo scopo di memorizzare la versione della posizione fisica della riga. Questa versione può cambiare se la riga
viene aggiornata (UPDATE) o la tabella passa attraverso un VACUUM FULL.
Il tipo di dati di ctid è tid, ciò significa identificatore di tupla (o identificatore di riga), che è un coppia (numero di blocco, indice tupla all'interno del blocco)
che identifica la posizione fisica della riga all'interno della tabella.
Questa colonna ha sempre il suo valore univoco nella tabella, quindi quando ci sono righe con valori ripetuti può essere utilizzato come criterio per la loro eliminazione.

Creazione della tabella di prova:

CREATE TABLE tb_test_ctid (
    col1 int,
    col2 text);

Inserisci alcuni dati:

INSERT INTO tb_test_ctid VALUES 
(1, 'foo'),
(2, 'bar'),
(3, 'baz');

Controlla le righe correnti:

SELECT ctid, * FROM tb_test_ctid;
 ctid  | col1 | col2 
-------+------+------
 (0,1) |    1 | foo
 (0,2) |    2 | bar
 (0,3) |    3 | baz

Aggiorna una riga:

UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;

Controlla di nuovo la tabella:

SELECT ctid, * FROM tb_test_ctid;
 ctid  | col1 | col2 
-------+------+------
 (0,2) |    2 | bar
 (0,3) |    3 | baz
 (0,4) |    1 | spam

Possiamo notare che anche la riga aggiornata ha cambiato il suo ctid...

Un semplice test VUOTO PIENO:

VACUUM FULL tb_test_ctid;

Controllo della tabella dopo VACUUM:

SELECT ctid, * FROM tb_test_ctid;

ctid   | col1 | col2 
-------+------+------
(0,1)  | 2    | bar
(0,2)  | 3    | baz
(0,3)  | 1    | spam

Aggiorna nuovamente la stessa riga utilizzando la clausola RETURNING:

UPDATE tb_test_ctid
    SET col2 = 'eggs'
    WHERE col1 = 1
    RETURNING ctid;

 ctid  
-------
 (0,4)

Controlla di nuovo la tabella:

SELECT ctid, * FROM tb_test_ctid;
 ctid  | col1 | col2 
-------+------+------
 (0,2) |    2 | bar
 (0,3) |    3 | baz
 (0,4) |    1 | spam

Eliminazione dei valori ripetuti con ctid

Immagina una tabella che ha valori ripetuti in un campo e che lo stesso campo viene deciso per renderlo univoco in un secondo momento.
Ricorda che anche un campo CHIAVE PRIMARIA è univoco.
OK, è stato deciso che i valori ripetuti in quel campo verrà cancellato.
Ora è necessario stabilire un criterio per decidere tra questi valori ripetuti che rimarranno.
Nel caso seguente il criterio è la riga più attuale, cioè quella con il valore ctid più alto.

Creazione nuova tabella di test:

CREATE TABLE tb_foo(
    id_ int,  --This field will be the primary key in the future!
    letter char(1)
);

Inserisci 10 record:

INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';

Controlla la tabella:

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
Inserisci altri 3 record:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';

Controlla i valori ripetuti:

SELECT id_, letter FROM tb_foo WHERE id_ <= 3;

 id_ | letter  
-----+--------
   1 | a
   2 | a
   3 | a
   1 | b
   2 | b
   3 | b

Ci sono valori ripetuti nel campo id_ della tabella...

Tentativo di rendere il campo id_ una chiave primaria:

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.

Usando CTE e le funzioni della finestra, scopri quali valori ripetuti verranno mantenuti:

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)

Lasciando la tabella con valori univoci per il campo id_, rimuovendo le righe precedenti:

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)

Controllo dei valori della tabella senza valori duplicati per 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

Ora puoi modificare la tabella per lasciare il campo id_ come CHIAVE PRIMARIA:

ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);