PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

PostgreSQL – Как да елиминирате повтарящи се стойности

Възможно е в таблицата някакво поле, което има повтарящи се стойности, да е необходимо, за да го остави като уникално.
И как да процедираме с повтарящи се стойности, без да ги елиминирате всички?
Възможно ли е да оставите само най-актуалните ?

системна колона ctid

Всяка таблица има някои колони, имплицитно дефинирани от системата, чиито имена са запазени.
В момента колоните на системата са:tableoid, xmin, cmin, xmax, cmax и ctid. Всеки от тях има метаданни от таблицата, към която принадлежи.
Системната колона ctid е предназначена да съхранява версията на физическото местоположение на реда. Тази версия може да се промени, ако редът
се актуализира (АКТУАЛИЗИРАНЕ) или таблицата премине през ВАКУУМ ПЪЛЕН.
Типът данни на ctid е tid, което означава идентификатор на кортежа (или идентификатор на ред), който е двойка (номер на блок, индекс на кортежа в блока)
която идентифицира физическото местоположение на реда в таблицата.
Тази колона винаги има своята уникална стойност в таблицата, така че когато има редове с повтарящи се стойности може да се използва като критерий за тяхното елиминиране.

Създаване на тестова таблица:

CREATE TABLE tb_test_ctid (
    col1 int,
    col2 text);

Вмъкнете някои данни:

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

Проверете текущите редове:

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

Актуализиране на ред:

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

Проверете отново таблицата:

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

Можем да забележим, че на актуализирания ред също е променен ctid...

Прост ВАКУУМЕН ПЪЛЕН тест:

VACUUM FULL tb_test_ctid;

Проверка на таблицата след ВАКУУМ:

SELECT ctid, * FROM tb_test_ctid;

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

Актуализирайте отново същия ред, като използвате клаузата RETURNING:

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

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

Проверете отново таблицата:

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

Елиминиране на повтарящи се стойности с ctid

Представете си таблица, която има повтарящи се стойности в поле и същото поле е решено да го направи уникално по-късно.
Не забравяйте, че полето ПЪРВЕН КЛЮЧ също е уникално.
Добре, беше решено повтарящите се стойности в това поле ще бъде изтрито.
Сега е необходимо да се установи критерий, за да се реши между тези повтарящи се стойности кои ще останат.
В следващия случай критерият е най-актуалният ред, тоест този с най-високата стойност на ctid.

Създаване на нова тестова таблица:

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

Вмъкнете 10 записа:

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

Проверете таблицата:

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
Вмъкнете още 3 записа:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';

Проверете повтарящи се стойности:

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

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

В полето id_ на таблицата има повтарящи се стойности...

Опит да направите полето id_ първичен ключ:

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.

Използвайки CTE и функциите на прозореца, разберете кои повтарящи се стойности ще бъдат запазени:

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)

Оставяне на таблицата с уникални стойности за полето id_, премахване на по-старите редове:

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)

Проверка на стойностите на таблицата без дублирани стойности за 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

Вече можете да промените таблицата, за да оставите полето id_ като ПРАВИЛЕН КЛЮЧ:

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

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да архивирате и възстановите PostgreSQL база данни

  2. Моите любими разширения на PostgreSQL - първа част

  3. Връщане на редове от INSERT с ON CONFLICT без необходимост от актуализиране

  4. Oracle еквивалент на Postgres' DISTINCT ON?

  5. gem install pg не може да се свърже с libpq