แทรกธรรมดา
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);
การใช้LEFT [OUTER] JOIN
แทน[INNER] JOIN
หมายความว่าแถวจากval
ยังไม่ได้ปรับตัวลดลงfoo
เมื่อไม่ตรงกับที่พบใน แต่NULL
ถูกป้อนfoo_id
แทน
การVALUES
แสดงออกในแบบสอบถามย่อยทำเช่นเดียวกับCTE ของ @ ypercube Common Table Expressionsมีคุณสมบัติเพิ่มเติมและอ่านได้ง่ายขึ้นในคิวรีขนาดใหญ่ แต่ก็เป็นอุปสรรคในการเพิ่มประสิทธิภาพเช่นกัน ดังนั้นโดยทั่วไปเคียวรีย่อยจะเร็วขึ้นเล็กน้อยเมื่อไม่จำเป็น
id
เป็นชื่อคอลัมน์เป็นรูปแบบการป้องกันการแพร่กระจายกว้าง ควรจะเป็นfoo_id
และbar_id
หรือสิ่งที่สื่อความหมาย เมื่อเข้าร่วมกลุ่มของตารางคุณจะมีหลายคอลัมน์ที่มีชื่อid
...
พิจารณาธรรมดาtext
หรือแทนvarchar
varchar(n)
หากคุณต้องการจำกัดความยาวอย่างแท้จริงให้เพิ่มCHECK
ข้อ จำกัด :
คุณอาจต้องเพิ่มการส่งประเภทที่ชัดเจน เนื่องจากVALUES
นิพจน์ไม่ได้แนบกับตารางโดยตรง (เช่นในINSERT ... VALUES ...
) จึงไม่สามารถรับชนิดและใช้ชนิดข้อมูลเริ่มต้นได้หากไม่มีการประกาศประเภทที่ชัดเจนซึ่งอาจไม่สามารถใช้งานได้ในทุกกรณี ก็พอที่จะทำในแถวแรกส่วนที่เหลือจะตกหลุม
INSERT หายไปแถว FK ในเวลาเดียวกัน
หากคุณต้องการสร้างรายการที่ไม่มีอยู่ในfoo
ทันทีในคำสั่ง SQL เดียว CTEs เป็นเครื่องมือ:
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);
สังเกตแถวจำลองใหม่สองแถวที่จะแทรก ทั้งสองเป็นสีม่วงซึ่งไม่ได้อยู่ในfoo
แต่ สองแถวเพื่อแสดงความต้องการDISTINCT
ในINSERT
คำสั่งแรก
คำอธิบายทีละขั้นตอน
CTE ที่ 1 sel
จัดเตรียมข้อมูลอินพุตจำนวนหลายแถว แบบสอบถามย่อยที่val
มีการVALUES
แสดงออกสามารถถูกแทนที่ด้วยตารางหรือแบบสอบถามย่อยเป็นแหล่งที่มา ทันทีLEFT JOIN
เพื่อfoo
ต่อท้ายแถวfoo_id
ที่มีอยู่ล่วงหน้า type
แถวอื่นทั้งหมดได้มาfoo_id IS NULL
ทางนี้
CTE ที่ 2 ins
แทรกประเภทใหม่ที่แตกต่าง ( foo_id IS NULL
) ลงในfoo
และส่งกลับที่สร้างขึ้นใหม่foo_id
- พร้อมกับtype
เพื่อเข้าร่วมกลับไปที่แทรกแถว
ด้านนอกขั้นสุดท้ายในINSERT
ขณะนี้สามารถแทรก foo.id สำหรับทุกแถว: ประเภทที่มีอยู่แล้วหรือมันถูกแทรกในขั้นตอนที่ 2
พูดอย่างเคร่งครัดแทรกทั้งสองเกิดขึ้น "ขนาน" แต่เนื่องจากนี่เป็นคำสั่งเดียวFOREIGN KEY
ข้อ จำกัดเริ่มต้นจะไม่บ่น Referential integrity ถูกบังคับใช้เมื่อสิ้นสุดคำสั่งโดยค่าเริ่มต้น
ซอ Fiddleสำหรับ Postgres 9.3 (ทำงานเหมือนกันใน 9.1.)
มีเงื่อนไขการแข่งขันขนาดเล็กถ้าคุณเรียกใช้แบบสอบถามเหล่านี้หลายรายการพร้อมกัน อ่านคำถามที่เกี่ยวข้องมากขึ้นภายใต้ที่นี่และที่นี่และที่นี่ เกิดขึ้นจริงภายใต้การโหลดพร้อมกันอย่างหนักเท่านั้นถ้าเคย เมื่อเปรียบเทียบกับโซลูชั่นแคชเช่นโฆษณาในคำตอบอื่นโอกาสมีน้อยมาก
ฟังก์ชั่นสำหรับการใช้งานซ้ำ ๆ
สำหรับการใช้งานซ้ำ ๆ ฉันจะสร้างฟังก์ชัน SQL ที่ใช้อาร์เรย์ของเร็กคอร์ดเป็นพารามิเตอร์และใช้unnest(param)
แทนVALUES
นิพจน์
_param
หรือถ้าไวยากรณ์สำหรับอาร์เรย์ของระเบียนจะยุ่งเกินไปสำหรับคุณใช้สตริงคั่นด้วยเครื่องหมายจุลภาคเป็นพารามิเตอร์ ตัวอย่างของแบบฟอร์ม:
'description1,type1;description2,type2;description3,type3'
จากนั้นใช้สิ่งนี้เพื่อแทนที่VALUES
นิพจน์ในคำสั่งด้านบน:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
ทำงานกับ UPSERT ใน Postgres 9.5
สร้างประเภทแถวที่กำหนดเองสำหรับการส่งพารามิเตอร์ เราสามารถทำได้โดยไม่มีมัน แต่มันง่ายกว่า:
CREATE TYPE foobar AS (description text, type text);
ฟังก์ชั่น:
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;
โทร:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
รวดเร็วและแข็งแกร่งสำหรับสภาพแวดล้อมที่มีการทำธุรกรรมพร้อมกัน
นอกเหนือจากข้อความค้นหาข้างต้นสิ่งนี้ ...
... ใช้SELECT
หรือINSERT
เปิดfoo
: type
สิ่งที่ไม่มีอยู่ในตาราง FK จะถูกแทรก สมมติว่าประเภทส่วนใหญ่มีอยู่แล้ว เพื่อให้แน่ใจและออกกฎการแข่งขันอย่างแท้จริงแถวที่เราต้องการจะถูกล็อค (เพื่อให้การทำธุรกรรมที่เกิดขึ้นพร้อมกันไม่สามารถแทรกแซงได้) หากเป็นสิ่งที่หวาดระแวงเกินไปสำหรับกรณีของคุณคุณสามารถแทนที่:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
กับ
ON CONFLICT(type) DO NOTHING
... ใช้INSERT
หรือUPDATE
(จริง "UPSERT") บนbar
: หากdescription
มีอยู่แล้วจะมีการtype
ปรับปรุง:
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
แต่ถ้าtype
มีการเปลี่ยนแปลงจริง:
... ส่งผ่านค่าชนิดแถวที่รู้จักกันดีพร้อมVARIADIC
พารามิเตอร์ สังเกตค่าเริ่มต้นสูงสุด 100 พารามิเตอร์! เปรียบเทียบ:
มีหลายวิธีในการส่งผ่านหลายแถว ...
ที่เกี่ยวข้อง: