สำหรับการประมวลผลชุดข้อมูลขนาดใหญ่ที่ซับซ้อน (ต่างกัน
งานทั่วไปประเภทนี้มักจะมีลักษณะดังนี้: "ที่นี่
แต่เมื่อปริมาณของ "บางสิ่ง" นี้เริ่มวัดเป็นร้อยเมกะไบต์ และบริการจะต้องทำงานกับฐานข้อมูลต่อไปตลอด 24 ชั่วโมงทุกวัน ผลข้างเคียงมากมายก็เกิดขึ้นที่จะทำลายชีวิตของคุณ
หากต้องการจัดการกับสิ่งเหล่านี้ใน PostgreSQL (และไม่เพียงแต่ในนั้น) คุณสามารถใช้การเพิ่มประสิทธิภาพบางอย่างที่จะช่วยให้คุณประมวลผลทุกอย่างได้เร็วขึ้นและใช้ทรัพยากรน้อยลง
1. จัดส่งที่ไหน?
ขั้นแรก มาตัดสินใจว่าเราจะอัปโหลดข้อมูลที่เราต้องการ "ประมวลผล" ได้ที่ไหน
1.1. ตารางชั่วคราว (ตารางชั่วคราว)
ตามหลักการแล้ว สำหรับตารางชั่วคราวของ PostgreSQL จะเหมือนกับตารางอื่นๆ ดังนั้นไสยศาสตร์เช่น “ทุกสิ่งในนั้นถูกเก็บไว้ในความทรงจำเท่านั้น และมันสามารถจบลงได้”. แต่ก็มีความแตกต่างที่สำคัญหลายประการเช่นกัน
“เนมสเปซ” ของคุณเองสำหรับการเชื่อมต่อกับฐานข้อมูลแต่ละครั้ง
หากการเชื่อมต่อทั้งสองพยายามเชื่อมต่อพร้อมกัน CREATE TABLE x
แล้วจะมีคนได้แน่นอน ข้อผิดพลาดที่ไม่ซ้ำกัน วัตถุฐานข้อมูล
แต่ถ้าทั้งสองพยายามดำเนินการ CREATE TEMPORARY TABLE x
จากนั้นทั้งคู่ก็จะทำตามปกติและทุกคนจะได้ สำเนาของคุณ ตาราง และจะไม่มีอะไรที่เหมือนกันระหว่างพวกเขา
"ทำลายตัวเอง" เมื่อตัดการเชื่อมต่อ
เมื่อการเชื่อมต่อปิด ตารางชั่วคราวทั้งหมดจะถูกลบโดยอัตโนมัติ ดังนั้นด้วยตนเอง DROP TABLE x
ไม่มีประเด็นใดนอกจาก...
หากคุณกำลังทำงานผ่าน pgbouncer ในโหมดธุรกรรมจากนั้นฐานข้อมูลยังคงเชื่อว่าการเชื่อมต่อนี้ยังคงทำงานอยู่ และในนั้นยังมีตารางชั่วคราวนี้อยู่
ดังนั้นการพยายามสร้างมันใหม่อีกครั้งจากการเชื่อมต่ออื่นไปยัง pgbouncer จะส่งผลให้เกิดข้อผิดพลาด แต่สิ่งนี้สามารถหลีกเลี่ยงได้โดยใช้ CREATE TEMPORARY TABLE IF NOT EXISTS x
.
จริงอยู่ ยังไงก็ดีกว่าไม่ทำเช่นนี้เพราะคุณ "ทันใด" จะสามารถค้นหาข้อมูลที่เหลือจาก "เจ้าของคนก่อน" ได้ที่นั่น แต่จะเป็นการดีกว่ามากหากอ่านคู่มือและดูว่าเมื่อสร้างตารางคุณสามารถเพิ่มได้ ON COMMIT DROP
- นั่นคือเมื่อธุรกรรมเสร็จสิ้น ตารางจะถูกลบโดยอัตโนมัติ
ไม่ใช่การจำลองแบบ
เนื่องจากเป็นของการเชื่อมต่อเฉพาะ ตารางชั่วคราวจะไม่ถูกจำลองแบบ แต่ ทำให้ไม่จำเป็นต้องบันทึกข้อมูลซ้ำซ้อน ในฮีป + WAL ดังนั้น INSERT/UPDATE/DELETE ลงไปจึงเร็วกว่ามาก
แต่เนื่องจากตารางชั่วคราวยังคงเป็นตารางที่ "เกือบธรรมดา" จึงไม่สามารถสร้างขึ้นบนแบบจำลองได้เช่นกัน อย่างน้อยตอนนี้ถึงแม้ว่าแพตช์ที่เกี่ยวข้องจะมีการเผยแพร่มาเป็นเวลานานแล้วก็ตาม
1.2. ตารางที่ไม่ได้บันทึก
แต่สิ่งที่คุณควรทำ เช่น หากคุณมีกระบวนการ ETL ที่ยุ่งยากซึ่งไม่สามารถดำเนินการได้ภายในธุรกรรมเดียว แต่คุณยังคงมี pgbouncer ในโหมดธุรกรรม? ..
หรือกระแสข้อมูลมีขนาดใหญ่ขนาดนั้น มีแบนด์วิธไม่เพียงพอในการเชื่อมต่อครั้งเดียว จากฐานข้อมูล (อ่าน หนึ่งกระบวนการต่อ CPU)?..
หรือมีการดำเนินการบางอย่างเกิดขึ้น แบบอะซิงโครนัส ในการเชื่อมต่อที่แตกต่างกัน?..
มีทางเลือกเดียวเท่านั้นที่นี่ - สร้างตารางที่ไม่ใช่ชั่วคราวชั่วคราว. ปัน ใช่ครับ นั่นคือ:
- สร้างตาราง "ของฉันเอง" โดยมีชื่อแบบสุ่มสูงสุดเพื่อไม่ให้ตัดกับใครเลย
- สารสกัด: เติมข้อมูลจากแหล่งภายนอก
- แปลง: แปลงแล้ว กรอกข้อมูลในช่องลิงก์คีย์แล้ว
- โหลด: เทข้อมูลที่พร้อมลงในตารางเป้าหมาย
- ลบตาราง "ของฉัน"
และตอนนี้ - แมลงวันในครีม ในความเป็นจริง, การเขียนทั้งหมดใน PostgreSQL เกิดขึ้นสองครั้ง - COMMIT
'กังวลและ ROLLBACK
'ธุรกรรมที่เป็นโมฆะ
แต่เราไม่ต้องการสิ่งนี้! เรามีกระบวนการทั้งหมด ไม่ว่าจะประสบความสำเร็จอย่างสมบูรณ์หรือไม่ก็ตาม. มันไม่สำคัญว่าจะมีธุรกรรมตัวกลางจำนวนเท่าใด - เราไม่สนใจที่จะ "ดำเนินกระบวนการต่อจากตรงกลาง" โดยเฉพาะอย่างยิ่งเมื่อไม่ชัดเจนว่ามันอยู่ที่ไหน
ในการทำเช่นนี้นักพัฒนา PostgreSQL ในเวอร์ชัน 9.1 ได้แนะนำสิ่งนี้เช่น
ด้วยข้อบ่งชี้นี้ ตารางจะถูกสร้างขึ้นเป็นไม่ได้เข้าสู่ระบบ ข้อมูลที่เขียนลงในตารางที่ไม่ได้บันทึกจะไม่ผ่านบันทึกการเขียนล่วงหน้า (ดูบทที่ 29) ทำให้ตารางดังกล่าว ทำงานเร็วกว่าปกติมาก. อย่างไรก็ตาม พวกเขาไม่สามารถต้านทานความล้มเหลวได้ ในกรณีที่เซิร์ฟเวอร์ขัดข้องหรือปิดระบบฉุกเฉิน ตารางที่ไม่ได้บันทึก ถูกตัดทอนโดยอัตโนมัติ. นอกจากนี้ เนื้อหาของตารางที่ไม่ได้บันทึก ไม่ได้ทำซ้ำ ไปยังเซิร์ฟเวอร์ทาส ดัชนีใดๆ ที่สร้างบนตารางที่ไม่ได้บันทึกจะถูกยกเลิกการบันทึกโดยอัตโนมัติ
ในระยะสั้น มันจะเร็วขึ้นมากแต่ถ้าเซิร์ฟเวอร์ฐานข้อมูล "ตก" ก็จะไม่เป็นที่พอใจ แต่สิ่งนี้เกิดขึ้นบ่อยแค่ไหน และกระบวนการ ETL ของคุณรู้วิธีแก้ไขอย่างถูกต้อง “จากตรงกลาง” หลังจาก “ฟื้นฟู” ฐานข้อมูลหรือไม่..
ถ้าไม่ และกรณีข้างต้นก็คล้ายกับของคุณ ให้ใช้ UNLOGGED
แต่ไม่เคย อย่าเปิดใช้งานคุณลักษณะนี้บนตารางจริงข้อมูลที่เป็นที่รักของคุณ
1.3. เมื่อกระทำการ { ลบแถว | หยด}
โครงสร้างนี้ช่วยให้คุณสามารถระบุพฤติกรรมอัตโนมัติเมื่อธุรกรรมเสร็จสมบูรณ์เมื่อสร้างตาราง
เกี่ยวกับ ON COMMIT DROP
ฉันได้เขียนไว้ข้างต้นแล้วมันเกิดขึ้น DROP TABLE
, แต่ด้วย ON COMMIT DELETE ROWS
สถานการณ์น่าสนใจยิ่งขึ้น - มันถูกสร้างขึ้นที่นี่ TRUNCATE TABLE
.
เนื่องจากโครงสร้างพื้นฐานทั้งหมดสำหรับการจัดเก็บคำอธิบายเมตาของตารางชั่วคราวจึงเหมือนกับของตารางปกติทุกประการ การสร้างและการลบตารางชั่วคราวอย่างต่อเนื่องทำให้เกิด "การบวม" ของตารางระบบอย่างรุนแรง pg_class, pg_attribute, pg_attrdef, pg_depend,...
ทีนี้ลองจินตนาการว่าคุณมีผู้ปฏิบัติงานที่เชื่อมต่อโดยตรงกับฐานข้อมูล ซึ่งเปิดธุรกรรมใหม่ทุก ๆ วินาที สร้าง เติม ประมวลผลและลบตารางชั่วคราว... จะมีขยะส่วนเกินสะสมอยู่ในตารางระบบ และ ซึ่งจะทำให้เกิดการเบรกเพิ่มเติมสำหรับการทำงานแต่ละครั้ง
โดยทั่วไปอย่าทำเช่นนี้! ในกรณีนี้จะมีประสิทธิภาพมากกว่ามาก CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS
นำมันออกจากวงจรธุรกรรม - จากนั้นเมื่อเริ่มต้นธุรกรรมใหม่แต่ละรายการ ตารางก็พร้อมแล้ว จะมีอยู่ (บันทึกการโทร CREATE
), แต่ จะว่างเปล่า, ขอบคุณ TRUNCATE
(เรายังบันทึกการโทรไว้ด้วย) เมื่อทำธุรกรรมครั้งก่อนเสร็จสิ้น
1.4. ชอบ...รวมทั้ง...
ฉันได้กล่าวไว้ในตอนต้นว่าหนึ่งในกรณีการใช้งานทั่วไปสำหรับตารางชั่วคราวคือการนำเข้าหลายประเภท - และนักพัฒนาก็เหนื่อยที่จะคัดลอกรายการฟิลด์ของตารางเป้าหมายลงในการประกาศชั่วคราว...
แต่ความเกียจคร้านเป็นกลไกของความก้าวหน้า! นั่นเป็นเหตุผล สร้างตารางใหม่ "ตามตัวอย่าง" มันง่ายกว่ามาก:
CREATE TEMPORARY TABLE import_table(
LIKE target_table
);
เนื่องจากคุณสามารถสร้างข้อมูลจำนวนมากลงในตารางนี้ได้ การค้นหาผ่านข้อมูลจึงไม่รวดเร็ว แต่มีวิธีแก้ไขปัญหาแบบดั้งเดิมสำหรับสิ่งนี้ - ดัชนี! และใช่, ตารางชั่วคราวก็สามารถมีดัชนีได้เช่นกัน.
เนื่องจากบ่อยครั้งที่ดัชนีที่ต้องการจะตรงกับดัชนีของตารางเป้าหมาย คุณจึงสามารถเขียนได้ง่ายๆ LIKE target_table INCLUDING INDEXES
.
หากคุณต้องการด้วย DEFAULT
-ค่า (เช่น เพื่อกรอกค่าคีย์หลัก) คุณสามารถใช้ได้ LIKE target_table INCLUDING DEFAULTS
. หรือเพียงแค่ - LIKE target_table INCLUDING ALL
— คัดลอกค่าเริ่มต้น ดัชนี ข้อจำกัด...
แต่ที่นี่คุณต้องเข้าใจว่าถ้าคุณสร้าง นำเข้าตารางทันทีพร้อมดัชนี จากนั้นข้อมูลจะใช้เวลาโหลดนานขึ้นกว่าถ้าคุณเติมทุกอย่างให้เต็มก่อนแล้วจึงสะสมดัชนี - ดูว่ามันทำอย่างไรเป็นตัวอย่าง
โดยทั่วไปแล้ว
2.จะเขียนยังไง?
ให้ฉันพูด - ใช้มัน
-ไหลแทน "แพ็ค" INSERT
,
3. จะดำเนินการอย่างไร?
มาดู intro ของเรากันดีกว่า:
- คุณมีตารางที่มีข้อมูลลูกค้าเก็บไว้ในฐานข้อมูลของคุณ 1M บันทึก
- ทุกวันลูกค้าจะส่งอันใหม่ให้คุณ "ภาพ" เต็มๆ
- จากประสบการณ์ท่านก็ทราบมาบ้างเป็นบางครั้งบางคราว มีการเปลี่ยนแปลงบันทึกไม่เกิน 10K
ตัวอย่างคลาสสิกของสถานการณ์เช่นนี้คือ
3.1. อัลกอริธึมการซิงโครไนซ์แบบเต็ม
เพื่อความง่าย สมมติว่าคุณไม่จำเป็นต้องปรับโครงสร้างข้อมูลใหม่ด้วยซ้ำ เพียงนำตารางมาในรูปแบบที่ต้องการ ซึ่งก็คือ:
- เอาออก ทุกสิ่งที่ไม่มีอยู่อีกต่อไป
- เพื่ออัปเดต ทุกสิ่งที่มีอยู่แล้วและจำเป็นต้องได้รับการอัปเดต
- แทรก ทุกสิ่งที่ยังไม่เกิดขึ้น
เหตุใดจึงต้องดำเนินการตามลำดับนี้ เพราะนี่คือขนาดโต๊ะที่จะขยายน้อยที่สุด (
ลบออกจากวันที่
ไม่ แน่นอน คุณสามารถดำเนินการได้เพียงสองขั้นตอนเท่านั้น:
- เอาออก (
DELETE
) ทุกอย่างโดยทั่วไป - แทรก ทั้งหมดจากภาพใหม่
แต่ในขณะเดียวกัน ต้องขอบคุณ MVCC ขนาดของตารางจะเพิ่มขึ้นสองเท่าอย่างแน่นอน! การได้รับอิมเมจ +1 ล้านอิมเมจในตารางเนื่องจากการอัปเดต 10K ถือเป็นความซ้ำซ้อน...
ตัดทอน
นักพัฒนาที่มีประสบการณ์มากกว่ารู้ว่าสามารถทำความสะอาดแท็บเล็ตทั้งหมดได้ในราคาถูก:
- เพื่อล้าง (
TRUNCATE
) ทั้งตาราง - แทรก ทั้งหมดจากภาพใหม่
วิธีการนี้ได้ผล
ซึ่งหมายความว่า:
- เรากำลังเริ่มต้น การทำธุรกรรมระยะยาว
TRUNCATE
กำหนด การเข้าถึงพิเศษ-การปิดกั้น- เราทำการแทรกมาเป็นเวลานานและทุกคนในเวลานี้ ไม่สามารถด้วยซ้ำ
SELECT
มีบางอย่างไม่ค่อยดีนัก...
แก้ไขตาราง… เปลี่ยนชื่อ… / วางตาราง…
อีกทางเลือกหนึ่งคือการเติมทุกอย่างลงในตารางใหม่แยกกัน จากนั้นเปลี่ยนชื่อแทนตารางเก่า สิ่งเล็กๆ น้อยๆ ที่น่ารังเกียจสองสามอย่าง:
- ยังเหมือนกัน การเข้าถึงพิเศษแม้ว่าจะใช้เวลาน้อยกว่ามากก็ตาม
- แผนการสืบค้น/สถิติทั้งหมดสำหรับตารางนี้ถูกรีเซ็ต
จำเป็นต้องเรียกใช้การวิเคราะห์ - กุญแจต่างประเทศทั้งหมดเสีย (FK) ไปที่โต๊ะ
มีแพตช์ WIP จาก Simon Riggs ที่แนะนำให้ทำ ALTER
-การดำเนินการเพื่อแทนที่เนื้อหาของตารางที่ระดับไฟล์โดยไม่ต้องแตะสถิติและ FK แต่ไม่ได้รวบรวมองค์ประชุม
ลบ อัปเดต แทรก
ดังนั้นเราจึงเลือกตัวเลือกที่ไม่ปิดกั้นของการดำเนินการทั้งสามรายการ เกือบสาม... ทำอย่างไรให้ได้ผลสูงสุด?
-- все делаем в рамках транзакции, чтобы никто не видел "промежуточных" состояний
BEGIN;
-- создаем временную таблицу с импортируемыми данными
CREATE TEMPORARY TABLE tmp(
LIKE dst INCLUDING INDEXES -- по образу и подобию, вместе с индексами
) ON COMMIT DROP; -- за рамками транзакции она нам не нужна
-- быстро-быстро вливаем новый образ через COPY
COPY tmp FROM STDIN;
-- ...
-- .
-- удаляем отсутствующие
DELETE FROM
dst D
USING
dst X
LEFT JOIN
tmp Y
USING(pk1, pk2) -- поля первичного ключа
WHERE
(D.pk1, D.pk2) = (X.pk1, X.pk2) AND
Y IS NOT DISTINCT FROM NULL; -- "антиджойн"
-- обновляем оставшиеся
UPDATE
dst D
SET
(f1, f2, f3) = (T.f1, T.f2, T.f3)
FROM
tmp T
WHERE
(D.pk1, D.pk2) = (T.pk1, T.pk2) AND
(D.f1, D.f2, D.f3) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- незачем обновлять совпадающие
-- вставляем отсутствующие
INSERT INTO
dst
SELECT
T.*
FROM
tmp T
LEFT JOIN
dst D
USING(pk1, pk2)
WHERE
D IS NOT DISTINCT FROM NULL;
COMMIT;
3.2. นำเข้าหลังการประมวลผล
ใน KLADR เดียวกัน บันทึกที่เปลี่ยนแปลงทั้งหมดจะต้องดำเนินการเพิ่มเติมผ่านการประมวลผลภายหลัง - ทำให้เป็นมาตรฐาน เน้นคำสำคัญ และลดขนาดให้เหลือโครงสร้างที่จำเป็น แต่คุณรู้ได้อย่างไร - สิ่งที่เปลี่ยนแปลงไปอย่างแน่นอนโดยไม่ทำให้รหัสการซิงโครไนซ์ซับซ้อน โดยหลักการแล้วไม่ต้องแตะเลยใช่ไหม
หากมีเพียงกระบวนการของคุณเท่านั้นที่มีสิทธิ์เขียนในขณะที่ทำการซิงโครไนซ์ คุณสามารถใช้ทริกเกอร์ที่จะรวบรวมการเปลี่ยนแปลงทั้งหมดให้เรา:
-- целевые таблицы
CREATE TABLE kladr(...);
CREATE TABLE kladr_house(...);
-- таблицы с историей изменений
CREATE TABLE kladr$log(
ro kladr, -- тут лежат целые образы записей старой/новой
rn kladr
);
CREATE TABLE kladr_house$log(
ro kladr_house,
rn kladr_house
);
-- общая функция логирования изменений
CREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$
DECLARE
dst varchar = TG_TABLE_NAME || '$log';
stmt text = '';
BEGIN
-- проверяем необходимость логгирования при обновлении записи
IF TG_OP = 'UPDATE' THEN
IF NEW IS NOT DISTINCT FROM OLD THEN
RETURN NEW;
END IF;
END IF;
-- создаем запись лога
stmt = 'INSERT INTO ' || dst::text || '(ro,rn)VALUES(';
CASE TG_OP
WHEN 'INSERT' THEN
EXECUTE stmt || 'NULL,$1)' USING NEW;
WHEN 'UPDATE' THEN
EXECUTE stmt || '$1,$2)' USING OLD, NEW;
WHEN 'DELETE' THEN
EXECUTE stmt || '$1,NULL)' USING OLD;
END CASE;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
ตอนนี้เราสามารถใช้ทริกเกอร์ก่อนที่จะเริ่มการซิงโครไนซ์ (หรือเปิดใช้งานผ่านทาง ALTER TABLE ... ENABLE TRIGGER ...
):
CREATE TRIGGER log
AFTER INSERT OR UPDATE OR DELETE
ON kladr
FOR EACH ROW
EXECUTE PROCEDURE diff$log();
CREATE TRIGGER log
AFTER INSERT OR UPDATE OR DELETE
ON kladr_house
FOR EACH ROW
EXECUTE PROCEDURE diff$log();
จากนั้นเราจะแยกการเปลี่ยนแปลงทั้งหมดที่เราต้องการออกจากตารางบันทึกอย่างใจเย็น และเรียกใช้ผ่านตัวจัดการเพิ่มเติม
3.3. การนำเข้าชุดที่เชื่อมโยง
ข้างต้นเราได้พิจารณากรณีที่โครงสร้างข้อมูลของต้นทางและปลายทางเหมือนกัน แต่จะเกิดอะไรขึ้นหากการอัพโหลดจากระบบภายนอกมีรูปแบบที่แตกต่างจากโครงสร้างการจัดเก็บข้อมูลในฐานข้อมูลของเรา?
มาดูตัวอย่างการจัดเก็บลูกค้าและบัญชีของพวกเขา ตัวเลือก "หลายต่อหนึ่ง" แบบคลาสสิก:
CREATE TABLE client(
client_id
serial
PRIMARY KEY
, inn
varchar
UNIQUE
, name
varchar
);
CREATE TABLE invoice(
invoice_id
serial
PRIMARY KEY
, client_id
integer
REFERENCES client(client_id)
, number
varchar
, dt
date
, sum
numeric(32,2)
);
แต่การดาวน์โหลดจากแหล่งภายนอกมาหาเราในรูปแบบของ "ออลอินวัน":
CREATE TEMPORARY TABLE invoice_import(
client_inn
varchar
, client_name
varchar
, invoice_number
varchar
, invoice_dt
date
, invoice_sum
numeric(32,2)
);
แน่นอนว่าข้อมูลลูกค้าสามารถทำซ้ำได้ในเวอร์ชันนี้ และบันทึกหลักคือ “บัญชี”:
0123456789;Вася;A-01;2020-03-16;1000.00
9876543210;Петя;A-02;2020-03-16;666.00
0123456789;Вася;B-03;2020-03-16;9999.00
สำหรับโมเดล เราจะแทรกข้อมูลการทดสอบของเรา แต่จำไว้ว่า - COPY
มีประสิทธิภาพมากกว่า!
INSERT INTO invoice_import
VALUES
('0123456789', 'Вася', 'A-01', '2020-03-16', 1000.00)
, ('9876543210', 'Петя', 'A-02', '2020-03-16', 666.00)
, ('0123456789', 'Вася', 'B-03', '2020-03-16', 9999.00);
ขั้นแรก เรามาเน้นที่ "การตัดทอน" เหล่านั้นซึ่ง "ข้อเท็จจริง" ของเราอ้างถึง ในกรณีของเรา ใบแจ้งหนี้หมายถึงลูกค้า:
CREATE TEMPORARY TABLE client_import AS
SELECT DISTINCT ON(client_inn)
-- можно просто SELECT DISTINCT, если данные заведомо непротиворечивы
client_inn inn
, client_name "name"
FROM
invoice_import;
เพื่อเชื่อมโยงบัญชีกับรหัสลูกค้าได้อย่างถูกต้อง เราต้องค้นหาหรือสร้างตัวระบุเหล่านี้ก่อน มาเพิ่มฟิลด์ข้างใต้:
ALTER TABLE invoice_import ADD COLUMN client_id integer;
ALTER TABLE client_import ADD COLUMN client_id integer;
ลองใช้วิธีซิงโครไนซ์ตารางที่อธิบายไว้ข้างต้นพร้อมกับการแก้ไขเล็กน้อย เราจะไม่อัปเดตหรือลบสิ่งใดในตารางเป้าหมาย เนื่องจากเรานำเข้าไคลเอ็นต์ "ต่อท้ายเท่านั้น":
-- проставляем в таблице импорта ID уже существующих записей
UPDATE
client_import T
SET
client_id = D.client_id
FROM
client D
WHERE
T.inn = D.inn; -- unique key
-- вставляем отсутствовавшие записи и проставляем их ID
WITH ins AS (
INSERT INTO client(
inn
, name
)
SELECT
inn
, name
FROM
client_import
WHERE
client_id IS NULL -- если ID не проставился
RETURNING *
)
UPDATE
client_import T
SET
client_id = D.client_id
FROM
ins D
WHERE
T.inn = D.inn; -- unique key
-- проставляем ID клиентов у записей счетов
UPDATE
invoice_import T
SET
client_id = D.client_id
FROM
client_import D
WHERE
T.client_inn = D.inn; -- прикладной ключ
จริงๆแล้วทุกอย่างอยู่ในนั้น invoice_import
ตอนนี้เราได้กรอกข้อมูลในช่องติดต่อแล้ว client_id
ซึ่งเราจะแทรกใบแจ้งหนี้
ที่มา: will.com