PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

PostgreSQL – So beseitigen Sie wiederholte Werte

Es ist möglich, dass in einer Tabelle einige Felder mit wiederholten Werten eindeutig bleiben müssen.
Und wie kann man mit wiederholten Werten fortfahren, ohne sie alle zu löschen?
Wäre es möglich, nur die aktuellsten zu belassen ?

ctid-Systemspalte

Jede Tabelle hat einige vom System implizit definierte Spalten, deren Namen reserviert sind.
Derzeit sind die Systemspalten:tableoid, xmin, cmin, xmax, cmax und ctid. Jeder hat Metadaten aus der Tabelle, zu der er gehört.
Die Systemspalte ctid soll die Version des physischen Standorts der Zeile speichern. Diese Version kann sich ändern, wenn die Zeile
aktualisiert wird (UPDATE) oder die Tabelle ein VACUUM FULL durchläuft.
Der Datentyp von ctid ist tid, das bedeutet Tupelkennung (oder Zeilenkennung), was a ist Paar (Blocknummer, Tupelindex innerhalb des Blocks)
das die physische Position der Zeile innerhalb der Tabelle identifiziert.
Diese Spalte hat immer ihren eindeutigen Wert in der Tabelle, also wenn es Zeilen mit sich wiederholenden Werten gibt es kann als Kriterium für deren Eliminierung herangezogen werden.

Tabellenerstellung testen:

CREATE TABLE tb_test_ctid (
    col1 int,
    col2 text);

Geben Sie einige Daten ein:

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

Aktuelle Zeilen prüfen:

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

Zeile aktualisieren:

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

Überprüfen Sie die Tabelle erneut:

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

Wir können feststellen, dass die ctid der aktualisierten Zeile ebenfalls geändert wurde…

Ein einfacher VAKUUM-VOLL-Test:

VACUUM FULL tb_test_ctid;

Überprüfung der Tabelle nach VACUUM:

SELECT ctid, * FROM tb_test_ctid;

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

Aktualisieren Sie dieselbe Zeile erneut mit der RETURNING-Klausel:

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

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

Überprüfen Sie die Tabelle erneut:

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

Beseitigung wiederholter Werte mit ctid

Stellen Sie sich eine Tabelle vor, die wiederholte Werte in einem Feld enthält, und dasselbe Feld soll später eindeutig werden.
Denken Sie daran, dass ein PRIMARY KEY-Feld ebenfalls eindeutig ist.
OK, es wurde entschieden, dass die wiederholten Werte in dieses Feld wird gelöscht.
Es ist nun notwendig, ein Kriterium festzulegen, um unter diesen wiederholten Werten zu entscheiden, welche verbleiben.
Im folgenden Fall ist das Kriterium die aktuellste Zeile, dh diejenige mit der höchste ctid-Wert.

Erstellung einer neuen Testtabelle:

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

10 Datensätze einfügen:

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

Prüfen Sie die Tabelle:

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
Fügen Sie 3 weitere Datensätze ein:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';

Überprüfen Sie wiederholte Werte:

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

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

Es gibt wiederholte Werte im id_-Feld der Tabelle…

Versuchen Sie, das Feld id_ zu einem Primärschlüssel zu machen:

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.

Finden Sie mithilfe von CTE- und Fensterfunktionen heraus, welche wiederholten Werte beibehalten werden:

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)

Belassen der Tabelle mit eindeutigen Werten für das Feld id_ und Entfernen der älteren Zeilen:

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)

Überprüfen von Tabellenwerten ohne doppelte Werte 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

Sie können die Tabelle jetzt so ändern, dass sie das Feld id_ als PRIMARY KEY:

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