แทรกธรรมดา
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 พารามิเตอร์! เปรียบเทียบ:
มีหลายวิธีในการส่งผ่านหลายแถว ...
ที่เกี่ยวข้อง: