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] JOIN
bukan [INNER] JOIN
berarti bahwa baris dari val
tidak dijatuhkan ketika tidak ada kecocokan ditemukan di foo
. Sebagai gantinya, NULL
dimasukkan untuk foo_id
.
The VALUES
ekspresi 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.
id
sebagai nama kolom adalah anti-pola yang tersebar luas. Harus foo_id
dan bar_id
atau apa pun deskriptif. Saat bergabung dengan banyak tabel, Anda berakhir dengan beberapa kolom yang semuanya bernama id
...
Pertimbangkan yang polos text
atau varchar
bukan varchar(n)
. Jika Anda benar-benar perlu menerapkan batasan panjang, tambahkan CHECK
batasan:
Anda mungkin perlu menambahkan gips tipe eksplisit. Karena VALUES
ekspresi 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 foo
dengan 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 DISTINCT
dalam INSERT
pernyataan pertama .
Penjelasan langkah demi langkah
CTE 1 sel
menyediakan beberapa baris input data. Subquery val
dengan VALUES
ekspresi dapat diganti dengan tabel atau subquery sebagai sumber. Segera LEFT JOIN
untuk foo
menambahkan baris yang foo_id
sudah ada sebelumnya type
. Semua baris lainnya mendapatkan foo_id IS NULL
cara ini.
2 CTE ins
menyisipkan berbeda jenis baru ( foo_id IS NULL
) ke dalam foo
, dan kembali yang baru dihasilkan foo_id
- bersama-sama dengan type
untuk bergabung kembali untuk menyisipkan baris.
Bagian luar terakhir INSERT
sekarang 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 KEY
batasan 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 VALUES
ekspresi.
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 VALUES
ekspresi 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 SELECT
atau INSERT
aktif foo
: Apa pun type
yang 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 INSERT
atau UPDATE
(benar "UPSERT") pada bar
: Jika yang description
sudah ada type
diperbarui:
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 type
benar-benar berubah:
... melewati nilai juga tipe baris yang dikenal dengan VARIADIC
parameter. Perhatikan maksimum default 100 parameter! Membandingkan:
Ada banyak cara lain untuk melewati banyak baris ...
Terkait: