Mungkin saja dalam sebuah tabel, beberapa bidang yang memiliki nilai berulang diperlukan untuk menjadikannya unik.
Dan bagaimana cara melanjutkan dengan nilai yang berulang tanpa menghilangkan semuanya?
Apakah mungkin hanya menyisakan yang terbaru ?
Kolom Sistem ctid
Setiap tabel memiliki beberapa kolom yang didefinisikan secara implisit oleh sistem, yang namanya dicadangkan.
Saat ini kolom sistem adalah:tableoid, xmin, cmin, xmax, cmax dan ctid. Masing-masing memiliki metadata dari tabel tempat mereka berada.
Kolom sistem ctid dimaksudkan untuk menyimpan versi lokasi fisik baris. Versi ini dapat berubah jika baris
diperbarui (UPDATE) atau tabel melewati VACUUM FULL.
Tipe data ctid adalah tid, artinya pengidentifikasi tuple (atau pengidentifikasi baris), yang merupakan pair (nomor blok, indeks tuple di dalam blok)
yang mengidentifikasi lokasi fisik baris dalam tabel.
Kolom ini selalu memiliki nilai unik dalam tabel, jadi ketika ada baris dengan nilai berulang itu dapat digunakan sebagai kriteria untuk eliminasi mereka.
Pembuatan tabel uji:
CREATE TABLE tb_test_ctid ( col1 int, col2 text);
Masukkan beberapa data:
INSERT INTO tb_test_ctid VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');
Periksa baris saat ini:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 1 | foo (0,2) | 2 | bar (0,3) | 3 | baz
Perbarui satu baris:
UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
Periksa tabel lagi:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Kami dapat melihat bahwa baris yang diperbarui telah mengubah ctidnya juga…
Tes PENUH VAKUM sederhana:
VACUUM FULL tb_test_ctid;
Memeriksa tabel setelah VACUUM:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,1) | 2 | bar (0,2) | 3 | baz (0,3) | 1 | spam
Perbarui baris yang sama lagi menggunakan klausa RETURNING:
UPDATE tb_test_ctid SET col2 = 'eggs' WHERE col1 = 1 RETURNING ctid;
ctid ------- (0,4)
Periksa tabel lagi:
SELECT ctid, * FROM tb_test_ctid;
ctid | col1 | col2 -------+------+------ (0,2) | 2 | bar (0,3) | 3 | baz (0,4) | 1 | spam
Menghilangkan Nilai Berulang dengan ctid
Bayangkan sebuah tabel yang memiliki nilai berulang dalam sebuah bidang dan bidang yang sama diputuskan untuk membuatnya unik nanti.
Ingat bahwa bidang KUNCI UTAMA juga unik.
OK, diputuskan bahwa nilai yang diulang dalam bidang itu akan dihapus.
Sekarang perlu menetapkan kriteria untuk memutuskan di antara nilai-nilai berulang yang akan tetap ada.
Dalam kasus berikut, kriterianya adalah baris terbaru, yaitu satu dengan nilai ctid tertinggi.
Pembuatan tabel pengujian baru:
CREATE TABLE tb_foo( id_ int, --This field will be the primary key in the future! letter char(1) );
Sisipkan 10 record:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
Periksa tabelnya:
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 | aMasukkan 3 catatan lagi:
INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
Periksa nilai berulang:
SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
id_ | letter -----+-------- 1 | a 2 | a 3 | a 1 | b 2 | b 3 | b
Ada nilai berulang di bidang id_ tabel…
Mencoba menjadikan bidang id_ sebagai kunci utama:
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.
Menggunakan fungsi CTE dan jendela, cari tahu nilai berulang mana yang akan disimpan:
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)
Meninggalkan tabel dengan nilai unik untuk bidang id_, menghapus baris yang lebih lama:
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)
Memeriksa nilai tabel tanpa nilai duplikat untuk 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
Anda sekarang dapat mengubah tabel untuk meninggalkan bidang id_ sebagai KUNCI UTAMA:
ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);