MASUKKAN polos
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
Penggunaan LEFT [OUTER] JOINbukan [INNER] JOINberarti bahwa baris dari val tidak dijatuhkan ketika tidak ada kecocokan ditemukan di foo. Sebagai gantinya, NULLdimasukkan untuk foo_id.
The VALUESekspresi dalam subquery tidak sama dengan @ ypercube ini CTE. Common Table Expressions menawarkan fitur-fitur tambahan dan lebih mudah dibaca dalam permintaan besar, tetapi mereka juga berpose sebagai hambatan optimisasi. Jadi subqueries biasanya sedikit lebih cepat ketika tidak ada yang di atas diperlukan.
idsebagai nama kolom adalah anti-pola yang tersebar luas. Harus foo_iddan bar_idatau apa pun deskriptif. Saat bergabung dengan banyak tabel, Anda berakhir dengan beberapa kolom yang semuanya bernama id...
Pertimbangkan yang polos textatau varcharbukan varchar(n). Jika Anda benar-benar perlu menerapkan batasan panjang, tambahkan CHECKbatasan:
Anda mungkin perlu menambahkan gips tipe eksplisit. Karena VALUESekspresi tidak secara langsung dilampirkan ke tabel (seperti dalam INSERT ... VALUES ...), tipe tidak dapat diturunkan dan tipe data default digunakan tanpa deklarasi tipe eksplisit, yang mungkin tidak berfungsi dalam semua kasus. Cukup dengan melakukannya di baris pertama, sisanya akan jatuh dalam barisan.
MASUKKAN baris FK yang hilang pada saat yang sama
Jika Anda ingin membuat entri yang tidak ada foodengan cepat, dalam satu pernyataan SQL , CTE berperan:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
Perhatikan dua baris boneka baru untuk dimasukkan. Keduanya berwarna ungu , yang belum ada foo. Dua baris untuk menggambarkan kebutuhan DISTINCTdalam INSERTpernyataan pertama .
Penjelasan langkah demi langkah
CTE 1 selmenyediakan beberapa baris input data. Subquery valdengan VALUESekspresi dapat diganti dengan tabel atau subquery sebagai sumber. Segera LEFT JOINuntuk foomenambahkan baris yang foo_idsudah ada sebelumnya type. Semua baris lainnya mendapatkan foo_id IS NULLcara ini.
2 CTE insmenyisipkan berbeda jenis baru ( foo_id IS NULL) ke dalam foo, dan kembali yang baru dihasilkan foo_id- bersama-sama dengan typeuntuk bergabung kembali untuk menyisipkan baris.
Bagian luar terakhir INSERTsekarang dapat menyisipkan foo.id untuk setiap baris: baik tipe yang sudah ada sebelumnya, atau sudah dimasukkan pada langkah 2.
Sebenarnya, kedua sisipan terjadi "secara paralel", tetapi karena ini adalah pernyataan tunggal , FOREIGN KEYbatasan default tidak akan mengeluh. Integritas referensial diberlakukan di akhir pernyataan secara default.
SQL Fiddle untuk Postgres 9.3. (Bekerja sama di 9.1.)
Ada kondisi balapan kecil jika Anda menjalankan beberapa dari pertanyaan ini secara bersamaan. Baca lebih lanjut di bawah pertanyaan terkait di sini dan di sini dan di sini . Benar-benar hanya terjadi di bawah beban bersamaan yang berat, jika pernah. Dibandingkan dengan solusi caching seperti yang diiklankan dalam jawaban lain, peluangnya sangat kecil .
Fungsi untuk penggunaan berulang
Untuk penggunaan berulang saya akan membuat fungsi SQL yang mengambil array catatan sebagai parameter dan digunakan unnest(param)sebagai ganti VALUESekspresi.
Atau, jika sintaks untuk array rekaman terlalu berantakan untuk Anda, gunakan string yang dipisahkan koma sebagai parameter _param. Misalnya bentuk:
'description1,type1;description2,type2;description3,type3'
Kemudian gunakan ini untuk mengganti VALUESekspresi dalam pernyataan di atas:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Berfungsi dengan UPSERT di Postgres 9.5
Buat jenis baris khusus untuk melewati parameter. Kita bisa melakukannya tanpa itu, tetapi lebih sederhana:
CREATE TYPE foobar AS (description text, type text);
Fungsi:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
Panggilan:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Cepat dan solid untuk lingkungan dengan transaksi bersamaan.
Selain pertanyaan di atas, ini ...
... berlaku SELECTatau INSERTaktif foo: Apa pun typeyang tidak ada dalam tabel FK, belum dimasukkan. Dengan asumsi sebagian besar tipe sudah ada. Untuk benar-benar yakin dan mengesampingkan kondisi balapan, baris yang kita butuhkan dikunci (sehingga transaksi bersamaan tidak dapat mengganggu). Jika itu terlalu paranoid untuk kasus Anda, Anda dapat mengganti:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
dengan
ON CONFLICT(type) DO NOTHING
... berlaku INSERTatau UPDATE(benar "UPSERT") pada bar: Jika yang descriptionsudah ada typediperbarui:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
Tetapi hanya jika typebenar-benar berubah:
... melewati nilai juga tipe baris yang dikenal dengan VARIADICparameter. Perhatikan maksimum default 100 parameter! Membandingkan:
Ada banyak cara lain untuk melewati banyak baris ...
Terkait: