รับข้อมูลจำเพาะของคุณ (รวมถึงข้อมูลเพิ่มเติมในความคิดเห็น)
- คุณมีคอลัมน์ ID ตัวเลข (ตัวเลขจำนวนเต็ม) ที่มีช่องว่างเพียงไม่กี่ (หรือน้อยมาก)
- เห็นได้ชัดว่าการดำเนินการเขียนไม่มากหรือน้อย
- คอลัมน์ ID ของคุณจะต้องได้รับการจัดทำดัชนี! คีย์หลักทำหน้าที่อย่างดี
แบบสอบถามด้านล่างไม่จำเป็นต้องสแกนตามลำดับของตารางขนาดใหญ่เพียงสแกนดัชนี
ก่อนอื่นรับประมาณการสำหรับคำค้นหาหลัก:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
ส่วนที่มีราคาแพงเพียงอย่างเดียวคือcount(*)
(สำหรับโต๊ะขนาดใหญ่) รับข้อกำหนดข้างต้นคุณไม่ต้องการมัน การประมาณการจะทำได้ดีเกือบไม่มีค่าใช้จ่าย ( คำอธิบายโดยละเอียดที่นี่ ):
SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
ตราบใดที่ct
ไม่ได้มากขนาดเล็กกว่าid_span
แบบสอบถามจะดีกว่าวิธีการอื่น ๆ
WITH params AS (
SELECT 1 AS min_id -- minimum id <= current min id
, 5100000 AS id_span -- rounded up. (max_id - min_id + buffer)
)
SELECT *
FROM (
SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
FROM params p
,generate_series(1, 1100) g -- 1000 + buffer
GROUP BY 1 -- trim duplicates
) r
JOIN big USING (id)
LIMIT 1000; -- trim surplus
สร้างตัวเลขสุ่มในid
ช่องว่าง คุณมี "ช่องว่างไม่กี่" ดังนั้นเพิ่ม 10% (เพียงพอที่จะครอบคลุมช่องว่าง) ลงในจำนวนแถวที่จะเรียกคืน
แต่ละคนid
สามารถเลือกได้หลายครั้งโดยบังเอิญ (แม้ว่าจะมีพื้นที่ id ไม่มาก) ดังนั้นจัดกลุ่มตัวเลขที่สร้างขึ้น (หรือใช้DISTINCT
)
เข้าร่วมid
กับตารางใหญ่ สิ่งนี้ควรจะเร็วมากเมื่อดัชนีอยู่ในตำแหน่ง
ในที่สุดก็ตัดแต่งส่วนเกินid
ที่ยังไม่ได้กินโดยการหลอกและช่องว่าง ทุกแถวมีโอกาสเท่าเทียมกันในการเลือก
เวอร์ชั่นสั้น
คุณสามารถทำให้แบบสอบถามนี้ง่ายขึ้น CTE ในแบบสอบถามด้านบนมีวัตถุประสงค์เพื่อการศึกษาเท่านั้น:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
ปรับแต่งด้วย rCTE
โดยเฉพาะอย่างยิ่งถ้าคุณไม่แน่ใจเกี่ยวกับช่องว่างและการประมาณการ
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM generate_series(1, 1030) -- 1000 + few percent - adapt to your needs
LIMIT 1030 -- hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
UNION -- eliminate dupe
SELECT b.*
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM random_pick r -- plus 3 percent - adapt to your needs
LIMIT 999 -- less than 1000, hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
เราสามารถทำงานกับส่วนเกินที่มีขนาดเล็กลงในแบบสอบถามพื้นฐาน หากมีช่องว่างมากเกินไปเราจึงไม่พบแถวมากพอในการทำซ้ำครั้งแรก rCTE จะยังคงทำซ้ำกับคำที่เรียกซ้ำ เรายังต้องการช่องว่างค่อนข้างน้อยในพื้นที่ ID หรือการเรียกซ้ำอาจแห้งก่อนถึงขีด จำกัด - หรือเราต้องเริ่มต้นด้วยบัฟเฟอร์ที่มีขนาดใหญ่พอซึ่งท้าทายวัตถุประสงค์ในการเพิ่มประสิทธิภาพ
รายการที่ซ้ำกันจะถูกกำจัดโดยUNION
ใน rCTE
ด้านนอกLIMIT
ทำให้ CTE หยุดทันทีที่เรามีแถวมากพอ
แบบสอบถามนี้ถูกร่างขึ้นอย่างรอบคอบเพื่อใช้ดัชนีที่มีอยู่สร้างแถวสุ่มจริง ๆ และไม่หยุดจนกว่าเราจะบรรลุขีด จำกัด (เว้นแต่ว่าการเรียกซ้ำจะแห้ง) มีข้อผิดพลาดมากมายที่นี่ถ้าคุณจะเขียนใหม่
ห่อเป็นฟังก์ชั่น
สำหรับการใช้ซ้ำกับพารามิเตอร์ที่แตกต่าง:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big AS
$func$
DECLARE
_surplus int := _limit * _gaps;
_estimate int := ( -- get current estimate from system
SELECT c.reltuples * _gaps
FROM pg_class c
WHERE c.oid = 'big'::regclass);
BEGIN
RETURN QUERY
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM generate_series(1, _surplus) g
LIMIT _surplus -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
UNION -- eliminate dupes
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM random_pick -- just to make it recursive
LIMIT _limit -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
)
SELECT *
FROM random_pick
LIMIT _limit;
END
$func$ LANGUAGE plpgsql VOLATILE ROWS 1000;
โทร:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
คุณสามารถทำให้ยาชื่อสามัญนี้ใช้กับตารางใดก็ได้: นำชื่อคอลัมน์ PK และตารางเป็นประเภท polymorphic และใช้EXECUTE
... แต่นั่นไม่ใช่ขอบเขตของคำถามนี้ ดู:
ทางเลือกที่เป็นไปได้
ถ้าความต้องการของคุณช่วยให้ชุดเหมือนกันสำหรับซ้ำโทร (และเรามีการพูดคุยเกี่ยวกับการโทรซ้ำ) ฉันจะพิจารณาดู materialized ดำเนินการแบบสอบถามข้างต้นหนึ่งครั้งและเขียนผลลัพธ์ลงในตาราง ผู้ใช้จะได้รับการเลือกแบบกึ่งสุ่มที่ความเร็วแสง รีเฟรชการสุ่มเลือกของคุณตามช่วงเวลาหรือเหตุการณ์ที่คุณเลือก
n
เป็นเปอร์เซ็นต์อยู่ที่ไหน คู่มือ:
BERNOULLI
และSYSTEM
การสุ่มตัวอย่างแต่ละวิธียอมรับอาร์กิวเมนต์เดียวซึ่งเป็นส่วนของตารางเพื่อตัวอย่างแสดงเป็น
เปอร์เซ็นต์ระหว่าง 0 และ 100 อาร์กิวเมนต์นี้สามารถเป็นreal
นิพจน์ที่มีค่าใด ๆ
เหมืองเน้นหนัก มันเป็นไปอย่างรวดเร็วมากแต่ผลที่ได้คือไม่สุ่มว่า คู่มืออีกครั้ง:
SYSTEM
วิธีการอย่างมีนัยสำคัญได้เร็วกว่าBERNOULLI
วิธีการเมื่อเปอร์เซ็นต์ตัวอย่างเล็ก ๆ ที่ระบุไว้ แต่มันก็อาจจะกลับมาตัวอย่างน้อยสุ่มของตารางเป็นผลมาจากผลกระทบของการจัดกลุ่ม
จำนวนแถวที่ส่งคืนอาจแตกต่างกันอย่างมาก ตัวอย่างของเราในการรับประมาณ 1,000 แถว:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
ที่เกี่ยวข้อง:
หรือติดตั้งโมดูลเพิ่มเติมtsm_system_rowsเพื่อรับจำนวนแถวที่ร้องขออย่างแน่นอน (หากมีเพียงพอ) และอนุญาตให้ใช้ไวยากรณ์ที่สะดวกยิ่งขึ้น:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
ดูคำตอบของอีวานเพื่อดูรายละเอียด
แต่นั่นก็ยังไม่สุ่ม