Възможно е в таблицата някакво поле, което има повтарящи се стойности, да е необходимо, за да го остави като уникално.
И как да процедираме с повтарящи се стойности, без да ги елиминирате всички?
Възможно ли е да оставите само най-актуалните ?
системна колона 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_);