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

PostgreSQL – Sådan fjerner du gentagne værdier

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 | a
Indsæ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_);

  1. Cyklusdetektion med rekursiv subquery factoring

  2. Få den seneste dato fra grupperede MySQL-data

  3. returnere værdi ved en position fra STRING_SPLIT i SQL Server 2016

  4. Da SQL Server ikke har pakker, hvad gør programmører så for at komme uden om det?