Il est possible que dans une table, un champ qui a des valeurs répétées soit nécessaire pour le laisser unique.
Et comment procéder avec des valeurs répétées sans les éliminer toutes ?
Serait-il possible de ne laisser que les plus courantes ?
Colonne système ctid
Chaque table a des colonnes implicitement définies par le système, dont les noms sont réservés.
Actuellement, les colonnes système sont :tableoid, xmin, cmin, xmax, cmax et ctid. Chacun a des métadonnées de la table à laquelle il appartient.
La colonne système ctid est destinée à stocker la version de l'emplacement physique de la ligne. Cette version peut changer si la ligne
est mise à jour (UPDATE) ou si la table passe par un VACUUM FULL.
Le type de données de ctid est tid, c'est-à-dire l'identifiant de tuple (ou identifiant de ligne), qui est un paire (numéro de bloc, index de tuple dans le bloc)
qui identifie l'emplacement physique de la ligne dans la table.
Cette colonne a toujours sa valeur unique dans la table, donc quand il y a des lignes avec des valeurs répétées il peut être utilisé comme critère pour leur élimination.
Tester la création du tableau :
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Insérez quelques données :
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Vérifier les lignes actuelles :
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Mettre à jour une ligne :
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Vérifiez à nouveau le tableau :
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Nous pouvons remarquer que la ligne mise à jour a également changé son ctid…
Un simple test VIDE PLEIN :
VACUUM FULL tb_test_ctid;
Vérification de la table après VACUUM :
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Mettez à jour à nouveau la même ligne à l'aide de la clause RETURNING :
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Vérifiez à nouveau le tableau :
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Éliminer les valeurs répétées avec ctid
Imaginez une table qui a des valeurs répétées dans un champ et que ce même champ est décidé pour le rendre unique plus tard.
N'oubliez pas qu'un champ PRIMARY KEY est également unique.
OK, il a été décidé que les valeurs répétées dans ce champ sera supprimé.
Il faut maintenant établir un critère pour départager parmi ces valeurs répétées celles qui resteront.
Dans le cas suivant, le critère est la ligne la plus courante, c'est-à-dire celle avec la valeur ctid la plus élevée.
Création d'une nouvelle table de test :
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
Insérez 10 enregistrements :
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Consultez le tableau :
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 | aInsérez 3 enregistrements supplémentaires :
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Vérifier les valeurs répétées :
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
Il y a des valeurs répétées dans le champ id_ de la table…
Essayez de faire du champ id_ une clé primaire :
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.
A l'aide des CTE et des fonctions de fenêtrage, découvrez quelles valeurs répétées seront conservées :
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)
Quitter la table avec des valeurs uniques pour le champ id_, supprimer les anciennes lignes :
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)
Vérification des valeurs de table sans valeurs en double pour 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
Vous pouvez maintenant modifier la table pour laisser le champ id_ en tant que PRIMARY KEY :
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);