sql >> Databasteknik >  >> RDS >> PostgreSQL

PostgreSQL – Hur man eliminerar upprepade värden

Det är möjligt att i en tabell är något fält som har upprepade värden nödvändigt för att lämna det som unikt.
Och hur går man vidare med upprepade värden utan att eliminera dem alla?
Skulle det vara möjligt att lämna bara de mest aktuella ?

ctid System Column

Varje tabell har några kolumner som är implicit definierade av systemet, vars namn är reserverade.
För närvarande är systemkolumnerna:tableoid, xmin, cmin, xmax, cmax och ctid. Var och en har metadata från tabellen som de tillhör.
Ctid-systemkolumnen är avsedd att lagra versionen av den fysiska platsen för raden. Den här versionen kan ändras om raden
uppdateras (UPPDATERA) under tabellen går igenom en VAKUUM FULL.
Datatypen för ctid är tid, det vill säga tuppelidentifierare (eller radidentifierare), vilket är en par (blocknummer, tuppelindex inom blocket)
som identifierar den fysiska platsen för raden i tabellen.
Denna kolumn har alltid sitt unika värde i tabellen, så när det finns rader med upprepade värden det kan användas som ett kriterium för deras eliminering.

Testa att skapa tabeller:

CREATE TABLE tb_test_ctid (
    col1 int,
    col2 text);

Infoga några data:

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

Kontrollera aktuella rader:

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

Uppdatera en rad:

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

Kontrollera 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 märka att den uppdaterade raden hade ändrat sin ctid också...

Ett enkelt VAKUUM FULL-test:

VACUUM FULL tb_test_ctid;

Kontrollera tabellen efter VAKUUM:

SELECT ctid, * FROM tb_test_ctid;

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

Uppdatera samma rad igen med RETURNING-satsen:

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

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

Kontrollera tabellen igen:

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

Eliminera upprepade värden med ctid

Föreställ dig en tabell som har upprepade värden i ett fält och samma fält bestäms för att göra det unikt senare.
Kom ihåg att ett PRIMARY KEY-fält också är unikt.
OK, det bestämdes att de upprepade värdena i det fältet kommer att raderas.
Det är nu nödvändigt att upprätta ett kriterium för att avgöra bland dessa upprepade värden som kommer att finnas kvar.
I följande fall är kriteriet den mest aktuella raden, det vill säga den med det högsta ctid-värdet.

Skapa ny testtabell:

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

Infoga 10 poster:

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

Kontrollera 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
Infoga ytterligare 3 poster:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';

Kontrollera upprepade värden:

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

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

Det finns upprepade värden i tabellens id_-fält...

Försök att göra id_-fältet till en primärnyckel:

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.

Ta reda på vilka upprepade värden som kommer att behållas med hjälp av CTE och fönsterfunktioner:

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)

Lämnar tabellen med unika värden för id_-fältet, tar bort de äldre raderna:

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)

Kontrollerar tabellvärden utan duplicerade värden för 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 ändra tabellen för att lämna id_-fältet som PRIMÄRNYCKEL:

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

  1. Oracle:funktionsbaserad indexselektiv unikhet

  2. När ska jag använda MySQLi istället för MySQL?

  3. Tillkännager repmgr 2.0RC2

  4. 8 sätt att lägga till sekunder till ett Datetime-värde i MariaDB