มันขึ้นอยู่กับสถานการณ์และข้อกำหนดที่แน่นอน พิจารณาความคิดเห็นของฉันกับคำถาม
ทางออกที่ง่าย
ด้วยDISTINCT ON
ใน Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
ผลการสั่งซื้อ
หรือด้วยNOT EXISTS
มาตรฐาน SQL (ใช้ได้กับ RDBMS ทุกตัวที่ฉันรู้จัก):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
ผลเดียวกัน แต่มีการเรียงลำดับโดยพลการ - ORDER BY
ถ้าคุณเพิ่ม
ข้อกำหนดและดัชนีที่แน่นอนหนึ่งในนั้นอาจเร็วกว่า
โดยทั่วไปแล้วDISTINCT ON
จะเป็นผู้ชนะและคุณจะได้รับผลลัพธ์ที่เรียงลำดับอยู่ด้านบน แต่สำหรับบางกรณีเทคนิคการสืบค้นอื่น ๆ นั้นเร็วกว่ามาก ดูด้านล่าง
โซลูชันที่มีคิวรีย่อยเพื่อคำนวณค่าสูงสุด / นาทีโดยทั่วไปจะช้ากว่า โดยทั่วไปแล้วตัวแปรที่มี CTE จะช้ากว่า
มุมมองธรรมดา (เช่นเสนอโดยคำตอบอื่น) ไม่ได้ช่วยประสิทธิภาพเลยใน Postgres
ซอ Fiddle
ทางออกที่เหมาะสม
เงื่อนไขและการเรียง
ก่อนอื่นคุณต้องทนทุกข์ทรมานจากเค้าโครงตารางย่อยที่เหมาะสมที่สุด มันอาจดูเล็กน้อย แต่การทำให้สคีมาของคุณเป็นปกติสามารถไปได้ไกล
การเรียงลำดับตามประเภทตัวอักษร ( text
,, varchar
... )จะต้องดำเนินการตามภาษา - โดยเฉพาะอย่างยิ่งCOLLATION ส่วนใหญ่มีแนวโน้ม DB ของคุณใช้บางชุดท้องถิ่นของกฎ (เช่นในกรณีของฉัน: de_AT.UTF-8
) ค้นหาด้วย:
SHOW lc_collate;
นี้จะทำให้การเรียงลำดับและดัชนีดูอัพช้า ยิ่งสายยาว (ชื่อสินค้า) ยิ่งแย่ หากคุณไม่สนใจกฎการเรียงในเอาต์พุตของคุณ (หรือลำดับการเรียงเลย) สิ่งนี้อาจเร็วขึ้นหากคุณเพิ่มCOLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
โปรดสังเกตว่าฉันเพิ่มการเปรียบเทียบในสองแห่งได้อย่างไร
เร็วขึ้นเป็นสองเท่าในการทดสอบของฉันด้วยชื่อ 20k แถวและชื่อพื้นฐาน ('good123')
ดัชนี
หากแบบสอบถามของคุณควรใช้ดัชนีคอลัมน์ที่มีข้อมูลตัวอักษรจะต้องใช้การเปรียบเทียบที่ตรงกัน ( good
ในตัวอย่าง):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
อย่าลืมอ่านสองบทสุดท้ายของคำตอบที่เกี่ยวข้องใน SO:
คุณสามารถมีดัชนีหลายดัชนีที่มีการเรียงหน้าแตกต่างกันในคอลัมน์เดียวกัน - หากคุณต้องการสินค้าที่เรียงตามการเปรียบเทียบอื่น (หรือค่าเริ่มต้น) ในการสืบค้นอื่น ๆ
ทำให้ปกติ
สตริงที่ซ้ำซ้อน (ชื่อที่ดี) ยังขยายตารางและดัชนีของคุณซึ่งทำให้ทุกอย่างช้าลง ด้วยเค้าโครงตารางที่เหมาะสมคุณสามารถหลีกเลี่ยงปัญหาส่วนใหญ่ที่จะเริ่มต้นด้วย อาจมีลักษณะเช่นนี้:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
ปุ่มหลักมีดัชนีเกือบทั้งหมดที่เราต้องการโดยอัตโนมัติ
ทั้งนี้ขึ้นอยู่กับรายละเอียดที่ขาดหายไปเป็นดัชนีหลายคอลัมน์ในprice
ที่มีลำดับถัดลงมาในคอลัมน์ที่สองอาจปรับปรุงประสิทธิภาพการทำงาน:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
อีกครั้งการเปรียบเทียบจะต้องตรงกับคำค้นหาของคุณ (ดูด้านบน)
ใน Postgres 9.2 หรือใหม่กว่า"การครอบคลุมดัชนี" สำหรับการสแกนดัชนีเท่านั้นสามารถช่วยได้มากขึ้นโดยเฉพาะถ้าตารางของคุณมีคอลัมน์เพิ่มเติมทำให้ตารางมีขนาดใหญ่กว่าดัชนีครอบคลุม
ข้อความค้นหาที่เป็นผลลัพธ์เหล่านี้เร็วกว่ามาก:
ไม่มีอยู่
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
ปิดกั้น
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
ซอ Fiddle
โซลูชั่นที่เร็วขึ้น
หากยังไม่เร็วพออาจมีวิธีแก้ปัญหาที่เร็วกว่า
JOIN LATERAL
แบบสอบถามย่อยCTE / / ที่สัมพันธ์กันแบบเรียกซ้ำ
โดยเฉพาะอย่างยิ่งสำหรับการแจกแจงข้อมูลที่มีราคามากมายต่อดี :
มุมมองที่ปรากฏ
หากคุณต้องการเรียกใช้งานนี้บ่อยครั้งและเร็วฉันขอแนะนำให้คุณสร้างมุมมองที่เป็นรูปธรรม ฉันคิดว่ามันปลอดภัยที่จะสมมติว่าราคาและสินค้าคงเหลือสำหรับวันที่ผ่านมาไม่ค่อยมีการเปลี่ยนแปลง คำนวณผลลัพธ์หนึ่งครั้งและเก็บสแน็ปช็อตเป็นมุมมองที่ปรากฏ
Postgres 9.3+ มีการสนับสนุนอัตโนมัติสำหรับมุมมองที่ปรากฏ คุณสามารถใช้เวอร์ชันพื้นฐานในเวอร์ชันที่เก่ากว่าได้อย่างง่ายดาย