Di PostgreSQL 9.1 atau lebih baru Anda dapat melakukannya dengan satu pernyataan menggunakan CTE pengubah data . Ini umumnya kurang rawan kesalahan. Ini meminimalkan kerangka waktu antara dua DELETE di mana kondisi balapan dapat menghasilkan hasil yang mengejutkan dengan operasi bersamaan:
WITH del_child AS (
DELETE FROM child
WHERE child_id = 1
RETURNING parent_id, child_id
)
DELETE FROM parent p
USING del_child x
WHERE p.parent_id = x.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
SQL Fiddle.
Anak itu dihapus dalam hal apa pun. Saya mengutip manualnya:
Pernyataan pengubahan data dalam
WITH
dieksekusi tepat sekali, danselalu sampai selesai , terlepas dari apakah kueri utama membaca semua (atau memang ada) keluarannya. Perhatikan bahwa ini berbeda dari aturan untukSELECT
diWITH
:seperti yang dinyatakan di bagian sebelumnya, eksekusiSELECT
dijalankan hanya sejauh kueri utama menuntut keluarannya.
Induk hanya dihapus jika tidak memiliki lain anak-anak.
Perhatikan kondisi terakhir. Bertentangan dengan apa yang diharapkan, ini perlu, karena:
Sub-pernyataan dalam
WITH
dieksekusi bersamaan dengan satu sama lain dan dengan permintaan utama. Oleh karena itu, saat menggunakan pernyataan modifikasi data diWITH
, urutan di mana pembaruan yang ditentukan benar-benar terjadi tidak dapat diprediksi. Semua pernyataan dieksekusi dengan snapshot yang sama (lihat Bab 13), sehingga mereka tidak dapat "melihat" efek satu sama lain pada tabel target.
Penekanan tebal milik saya.
Saya menggunakan nama kolom parent_id
sebagai pengganti id
non non-deskriptif .
Hilangkan kondisi balapan
Untuk menghilangkan kemungkinan kondisi balapan yang saya sebutkan di atas sepenuhnya , kunci baris induk pertama . Tentu saja, semua operasi serupa harus mengikuti prosedur yang sama untuk membuatnya bekerja.
WITH lock_parent AS (
SELECT p.parent_id, c.child_id
FROM child c
JOIN parent p ON p.parent_id = c.parent_id
WHERE c.child_id = 12 -- provide child_id here once
FOR NO KEY UPDATE -- locks parent row.
)
, del_child AS (
DELETE FROM child c
USING lock_parent l
WHERE c.child_id = l.child_id
)
DELETE FROM parent p
USING lock_parent l
WHERE p.parent_id = l.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
Dengan cara ini hanya satu transaksi pada suatu waktu dapat mengunci orang tua yang sama. Jadi tidak mungkin terjadi banyak transaksi menghapus anak dari orang tua yang sama, masih melihat anak-anak lain dan menyelamatkan orang tua, sementara semua anak hilang setelahnya. (Pembaruan pada kolom non-kunci masih diperbolehkan dengan FOR NO KEY UPDATE
.)
Jika kasus seperti itu tidak pernah terjadi atau Anda dapat menghadapinya (hampir tidak pernah) terjadi - kueri pertama lebih murah. Jika tidak, ini adalah jalur aman.
FOR NO KEY UPDATE
diperkenalkan dengan Postgres 9.4. Detail dalam manual. Dalam versi yang lebih lama, gunakan kunci yang lebih kuat FOR UPDATE
sebagai gantinya.