วิธีที่ดีที่สุดในการเลือกแถวสุ่ม PostgreSQL


345

ฉันต้องการสุ่มเลือกแถวใน PostgreSQL ฉันลองทำสิ่งนี้:

select * from table where random() < 0.01;

แต่บางคนก็แนะนำสิ่งนี้:

select * from table order by random() limit 1000;

ฉันมีตารางขนาดใหญ่มากที่มีแถว 500 ล้านแถวฉันต้องการให้มันเร็ว

วิธีไหนดีกว่ากัน อะไรคือความแตกต่าง? วิธีที่ดีที่สุดในการเลือกแถวแบบสุ่มคืออะไร


1
สวัสดีแจ็คขอบคุณสำหรับการตอบกลับของคุณเวลาดำเนินการจะช้าลงตามลำดับ แต่ฉันอยากจะรู้ว่าถ้าหากแตกต่างกันจะมีใคร ...
nanounanue

เอ่อ ... ยินดีต้อนรับ คุณเคยลองเปรียบเทียบวิธีที่แตกต่างกันหรือไม่?

นอกจากนี้ยังมีวิธีที่เร็วกว่ามาก ทุกอย่างขึ้นอยู่กับความต้องการของคุณและสิ่งที่คุณต้องทำงานด้วย คุณต้องการ 1,000 แถวหรือไม่ ตารางมีรหัสตัวเลขหรือไม่ ด้วยช่องว่างไม่กี่ / มาก? ความเร็วสำคัญแค่ไหน? มีการร้องขอกี่ครั้งต่อหน่วย? ทุกคำขอต้องการชุดที่แตกต่างกันหรือพวกเขาสามารถเหมือนกันสำหรับชิ้นเวลาที่กำหนด?
Erwin Brandstetter

6
ตัวเลือกแรก "(สุ่ม () <0.01)" ไม่ถูกต้องทางคณิตศาสตร์เนื่องจากคุณไม่ได้รับแถวตอบกลับหากไม่มีหมายเลขสุ่มต่ำกว่า 0.01 ซึ่งอาจเกิดขึ้นได้ในทุกกรณี หรือสูงกว่าเกณฑ์ ตัวเลือกที่สองถูกต้องเสมอ
Herme

1
หากคุณต้องการเลือกเพียงหนึ่งแถวให้ดูคำถามนี้: stackoverflow.com/q/5297396/247696
Flimm

คำตอบ:


230

รับข้อมูลจำเพาะของคุณ (รวมถึงข้อมูลเพิ่มเติมในความคิดเห็น)

  • คุณมีคอลัมน์ 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 ดำเนินการแบบสอบถามข้างต้นหนึ่งครั้งและเขียนผลลัพธ์ลงในตาราง ผู้ใช้จะได้รับการเลือกแบบกึ่งสุ่มที่ความเร็วแสง รีเฟรชการสุ่มเลือกของคุณตามช่วงเวลาหรือเหตุการณ์ที่คุณเลือก

Postgres 9.5 แนะนำ TABLESAMPLE SYSTEM (n)

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);

ดูคำตอบของอีวานเพื่อดูรายละเอียด

แต่นั่นก็ยังไม่สุ่ม


อยู่ที่ไหนกำหนดทีโต๊ะ? ก็ควรrแทนที ?
Luc M

1
@LucM: มันถูกกำหนดไว้ที่นี่: ซึ่งเป็นสั้นสำหรับJOIN bigtbl t เป็นนามแฝงของตารางสำหรับ มีวัตถุประสงค์คือเพื่อร่นไวยากรณ์ แต่จะไม่จำเป็นในกรณีนี้โดยเฉพาะ ฉันทำให้คำค้นหาของฉันง่ายขึ้นและเพิ่มเวอร์ชันที่เรียบง่าย JOIN bigtbl AS ttbigtbl
Erwin Brandstetter

วัตถุประสงค์ของช่วงของค่าจาก generate_series (1,1100) คืออะไร
Awesome-o

@ Awesome-o: เป้าหมายคือการดึง 1,000 แถวฉันเริ่มต้นด้วย 10% พิเศษเพื่อชดเชยช่องว่างเล็กน้อยหรือ (สุ่ม แต่ไม่น่าจะเป็นไปได้) ตัวเลขสุ่มซ้ำ ... คำอธิบายคือคำตอบของฉัน
Erwin Brandstetter

เออร์วินผมโพสต์รูปแบบของ "ทางเลือกที่เป็นไปได้" ของคุณ: stackoverflow.com/a/23634212/430128 จะสนใจในความคิดของคุณ
Raman

100

คุณสามารถตรวจสอบและเปรียบเทียบแผนการดำเนินการของทั้งสองโดยใช้

EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;

การทดสอบอย่างรวดเร็วบนโต๊ะขนาดใหญ่1แสดงว่าORDER BYอันดับแรกเรียงลำดับตารางที่สมบูรณ์แล้วเลือก 1,000 รายการแรก การเรียงลำดับตารางขนาดใหญ่ไม่เพียง แต่อ่านตารางนั้น แต่ยังเกี่ยวข้องกับการอ่านและเขียนไฟล์ชั่วคราว where random() < 0.1สแกนเพียงครั้งเดียวตารางสมบูรณ์

สำหรับตารางขนาดใหญ่สิ่งนี้อาจไม่ใช่สิ่งที่คุณต้องการเนื่องจากการสแกนตารางที่สมบูรณ์แบบหนึ่งอาจใช้เวลานาน

ข้อเสนอที่สามจะเป็น

select * from table where random() < 0.01 limit 1000;

อันนี้จะหยุดการสแกนตารางทันทีที่พบ 1,000 แถวแล้วจึงกลับมาเร็วกว่า แน่นอนสิ่งนี้จะทำให้ความสุ่มลงมาเล็กน้อย แต่อาจจะดีพอในกรณีของคุณ

แก้ไข:นอกเหนือจากข้อควรพิจารณานี้คุณอาจตรวจสอบคำถามที่ถามแล้วสำหรับสิ่งนี้ การใช้คิวรี[postgresql] randomส่งคืนการค้นหาค่อนข้างน้อย

และบทความที่เชื่อมโยงของ depez สรุปแนวทางอีกหลาย:


1 "ใหญ่" ดังใน "ตารางที่สมบูรณ์จะไม่พอดีกับหน่วยความจำ"


1
จุดที่ดีเกี่ยวกับการเขียนไฟล์ชั่วคราวสำหรับการสั่งซื้อ นั่นเป็นเพลงยอดฮิตอย่างแน่นอน ฉันเดาว่าเราทำได้random() < 0.02แล้วสลับรายการนั้นแล้วlimit 1000! การเรียงลำดับจะมีราคาถูกกว่าในไม่กี่พันแถว (ฮ่า ๆ )
Donald Miner

"select * จากตารางที่มีการสุ่ม () <0.05 จำกัด 500;" เป็นหนึ่งในวิธีที่ง่ายกว่าสำหรับ postgresql เราใช้สิ่งนี้ในหนึ่งในโครงการของเราที่เราต้องการเลือก 5% ของผลลัพธ์และไม่เกิน 500 แถวต่อครั้งสำหรับการประมวลผล
tgharold

ทำไมในโลกที่คุณจะพิจารณาการสแกนแบบเต็ม O (n) เพื่อดึงตัวอย่างบนตารางแถวขนาด 500 ม. มันช้าลงอย่างน่าขันบนโต๊ะขนาดใหญ่และไม่จำเป็นเลย
mafu

77

คำสั่ง postgresql โดยการสุ่ม () เลือกแถวในลำดับแบบสุ่ม:

select your_columns from your_table ORDER BY random()

คำสั่ง postgresql โดยการสุ่ม () ที่มีความแตกต่าง:

select * from 
  (select distinct your_columns from your_table) table_alias
ORDER BY random()

คำสั่ง postgresql โดย จำกัด แบบสุ่มหนึ่งแถว:

select your_columns from your_table ORDER BY random() limit 1

1
select your_columns from your_table ORDER BY random() limit 1ใช้เวลา 2 ~ นาทีในการดำเนินการในแถว
45mil

มีวิธีเร่งความเร็วนี้ไหม
CpILL

43

เริ่มต้นด้วย PostgreSQL 9.5 มีไวยากรณ์ใหม่สำหรับการรับองค์ประกอบแบบสุ่มจากตาราง:

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);

ตัวอย่างเช่นนี้จะให้ 5% mytableขององค์ประกอบจาก

ดูคำอธิบายเพิ่มเติมเกี่ยวกับโพสต์บล็อกนี้: http://www.postgresql.org/docs/current/static/sql-select.html


3
หมายเหตุสำคัญจากเอกสาร: "วิธีการของระบบจะทำการสุ่มตัวอย่างระดับบล็อกกับแต่ละบล็อกที่มีโอกาสระบุในการเลือกแถวทั้งหมดในแต่ละบล็อกที่เลือกจะถูกส่งคืนวิธีการของระบบนั้นเร็วกว่าวิธี BERNOULLI อย่างมีนัยสำคัญเมื่อเปอร์เซ็นต์การสุ่มตัวอย่างเล็ก ๆ ถูกระบุ แต่อาจส่งกลับตัวอย่างแบบสุ่มน้อยของตารางซึ่งเป็นผลมาจากการจัดกลุ่มผลกระทบ "
ทิม

1
มีวิธีการระบุจำนวนแถวแทนที่จะเป็นเปอร์เซ็นต์หรือไม่?
Flimm

4
คุณสามารถใช้TABLESAMPLE SYSTEM_ROWS(400)เพื่อรับตัวอย่างของ 400 แถวสุ่ม คุณต้องเปิดใช้งานส่วนขยายในตัวtsm_system_rowsเพื่อใช้คำสั่งนี้
Mickaël Le Baillif

27

อันที่มี ORDER BY จะเป็นอันที่ช้ากว่า

select * from table where random() < 0.01;ไปบันทึกโดยบันทึกและตัดสินใจที่จะกรองแบบสุ่มหรือไม่ สิ่งนี้จะเป็นO(N)เพราะต้องการตรวจสอบแต่ละระเบียนเพียงครั้งเดียว

select * from table order by random() limit 1000;เป็นไปได้ในการจัดเรียงทั้งตารางแล้วเลือกแรก 1000 นอกเหนือมหัศจรรย์ของขึ้นใด ๆ O(N * log N)ที่อยู่เบื้องหลังสั่งซื้อโดยการเป็น

ข้อเสียrandom() < 0.01อย่างหนึ่งคือคุณจะได้รับจำนวนระเบียนผลลัพธ์ที่เปลี่ยนแปลงได้


หมายเหตุมีวิธีที่ดีกว่าที่จะสับชุดของข้อมูลมากกว่าการเรียงลำดับโดยการสุ่ม: ฟิชเชอร์เยตส์สลับO(N)ซึ่งทำงานใน อย่างไรก็ตามการใช้การสลับใน SQL นั้นค่อนข้างท้าทาย


3
ไม่มีเหตุผลที่คุณไม่สามารถเพิ่มขีด จำกัด 1 ในตอนท้ายของตัวอย่างแรกของคุณ ปัญหาเดียวคือมีความเป็นไปได้ที่คุณจะไม่ได้รับการบันทึกกลับคืนดังนั้นคุณต้องพิจารณาในรหัสของคุณ
Relequestual

ปัญหาของ Fisher-Yates ก็คือคุณต้องมีชุดข้อมูลทั้งหมดในหน่วยความจำเพื่อที่จะทำการเลือก ไม่เป็นไปได้สำหรับชุดข้อมูลที่มีขนาดใหญ่มาก :(
CpILL

16

นี่คือการตัดสินใจที่เหมาะกับฉัน ฉันเดาว่ามันง่ายมากที่จะเข้าใจและดำเนินการ

SELECT 
  field_1, 
  field_2, 
  field_2, 
  random() as ordering
FROM 
  big_table
WHERE 
  some_conditions
ORDER BY
  ordering 
LIMIT 1000;

6
ฉันคิดว่าวิธีนี้ใช้ORDER BY random()งานได้ แต่อาจไม่ได้ผลเมื่อทำงานกับโต๊ะขนาดใหญ่
Anh Cao

15
select * from table order by random() limit 1000;

tsm_system_rowsหากคุณทราบจำนวนแถวที่คุณต้องการตรวจสอบ

tsm_system_rows

โมดูลจัดเตรียมวิธีการสุ่มตัวอย่างตาราง SYSTEM_ROWS ซึ่งสามารถใช้ในส่วนคำสั่ง TABLESAMPLE ของคำสั่ง SELECT

วิธีการสุ่มตัวอย่างตารางนี้ยอมรับอาร์กิวเมนต์จำนวนเต็มเดียวที่เป็นจำนวนแถวสูงสุดที่จะอ่าน ตัวอย่างผลลัพธ์จะมีจำนวนแถวที่แน่นอนเสมอเว้นแต่ว่าตารางจะไม่มีแถวมากพอในกรณีที่เลือกทั้งตาราง เช่นเดียวกับวิธีการสุ่มตัวอย่าง SYSTEM ในตัว SYSTEM_ROWS ทำการสุ่มตัวอย่างระดับบล็อกเพื่อให้ตัวอย่างไม่สุ่มสมบูรณ์ แต่อาจอยู่ภายใต้เอฟเฟกต์การจัดกลุ่มโดยเฉพาะอย่างยิ่งหากมีการร้องขอแถวจำนวนน้อย

ก่อนติดตั้งส่วนขยาย

CREATE EXTENSION tsm_system_rows;

ถ้าอย่างนั้นคำถามของคุณ

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);

2
ฉันได้เพิ่มลิงก์ไปยังคำตอบที่คุณเพิ่มเข้ามามันเป็นการปรับปรุงที่โดดเด่นเหนือSYSTEMวิธีการในตัว
Erwin Brandstetter

ผมได้ตอบเพียงคำถามที่นี่ (ระเบียนเดียวสุ่ม) ในระหว่างที่ผมดำเนินการมากเปรียบเทียบและการทดสอบของtsm_system_rowsและtsm_system_timeส่วนขยาย เท่าที่ผมสามารถมองเห็นพวกเขาแทบจะไร้ประโยชน์สำหรับอะไร แต่อย่างน้อยที่สุดการเลือกของแถวสุ่ม ฉันจะขอบคุณถ้าคุณสามารถดูอย่างรวดเร็วและแสดงความคิดเห็นเกี่ยวกับความถูกต้องหรือการวิเคราะห์ของฉัน
Vérace

6

หากคุณต้องการเพียงหนึ่งแถว, คุณสามารถใช้คำนวณมาจากoffsetcount

select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));

2

การเปลี่ยนแปลงของมุมมองที่เป็นรูปธรรม "ทางเลือกที่เป็นไปได้" ที่ร่างโดย Erwin Brandstetterนั้นเป็นไปได้

ตัวอย่างเช่นพูดว่าคุณไม่ต้องการให้ข้อมูลซ้ำในค่าสุ่มที่ส่งคืน ดังนั้นคุณจะต้องตั้งค่าบูลีนในตารางหลักที่มีชุดของค่า (แบบไม่สุ่ม) ของคุณ

สมมติว่านี่เป็นตารางอินพุต:

id_values  id  |   used
           ----+--------
           1   |   FALSE
           2   |   FALSE
           3   |   FALSE
           4   |   FALSE
           5   |   FALSE
           ...

เติมID_VALUESตารางตามต้องการ จากนั้นอธิบายโดย Erwin สร้างมุมมอง materialized ที่สุ่มID_VALUESตารางหนึ่งครั้ง:

CREATE MATERIALIZED VIEW id_values_randomized AS
  SELECT id
  FROM id_values
  ORDER BY random();

โปรดทราบว่ามุมมองที่ปรากฏขึ้นนั้นไม่ได้มีคอลัมน์ที่ใช้งานเพราะสิ่งนี้จะล้าสมัยอย่างรวดเร็ว มุมมองไม่จำเป็นต้องมีคอลัมน์อื่น ๆ ที่อาจอยู่ในid_valuesตาราง

เพื่อให้ได้ (และ "กิน") ค่าสุ่มใช้ UPDATE-กลับมาในid_valuesการเลือกid_valuesจากid_values_randomizedความเป็นไปได้ด้วยการเข้าร่วมและใช้เกณฑ์ที่ต้องการที่จะได้รับที่เกี่ยวข้องเท่านั้น ตัวอย่างเช่น:

UPDATE id_values
SET used = TRUE
WHERE id_values.id IN 
  (SELECT i.id
    FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id
    WHERE (NOT i.used)
    LIMIT 5)
RETURNING id;

เปลี่ยนLIMITตามความจำเป็น - ถ้าคุณต้องการเพียงหนึ่งค่าสุ่มในเวลาที่เปลี่ยนแปลงไปLIMIT1

ด้วยดัชนีที่เหมาะสมid_valuesฉันเชื่อว่า UPDATE-RETURNING ควรดำเนินการอย่างรวดเร็วด้วยการโหลดเพียงเล็กน้อย มันส่งคืนค่าแบบสุ่มด้วยฐานข้อมูลเดียวไปกลับ เกณฑ์สำหรับแถว "มีสิทธิ์" อาจซับซ้อนตามที่ต้องการ แถวใหม่สามารถเพิ่มลงในid_valuesตารางได้ตลอดเวลาและพวกเขาจะสามารถเข้าถึงแอปพลิเคชันได้ทันทีที่มีการรีเฟรชมุมมองที่เป็นรูปธรรม (ซึ่งน่าจะสามารถเรียกใช้ในเวลาที่มีงานไม่มาก) การสร้างและรีเฟรชมุมมองที่ปรากฏจะช้า แต่จะต้องดำเนินการก็ต่อเมื่อมีการเพิ่มรหัสใหม่ลงในid_valuesตาราง


น่าสนใจมาก. จะใช้งานได้หรือไม่หากฉันต้องการไม่เพียง แต่จะต้องเลือกเท่านั้น แต่ยังอัปเดตโดยใช้ select .. เพื่ออัปเดตด้วย pg_try_advisory_xact_lock (เช่นฉันต้องการจำนวนมากพร้อมกันอ่านและเขียน)
Mathieu

1

บทเรียนหนึ่งจากประสบการณ์ของฉัน:

offset floor(random() * N) limit 1order by random() limit 1ไม่ได้เร็วกว่า

ฉันคิดว่าoffsetวิธีการจะเร็วขึ้นเพราะควรประหยัดเวลาในการเรียงลำดับใน Postgres ปรากฎว่ามันไม่ใช่


0

เพิ่มคอลัมน์ที่เรียกว่ามีประเภทr ดัชนีserialr

สมมติว่าเรามี 200,000 แถวเราจะสร้างตัวเลขสุ่มnโดยที่ 0 n<<= 200, 000

เลือกแถวด้วยr > nเรียงลำดับASCและเลือกแถวที่เล็กที่สุด

รหัส:

select * from YOUR_TABLE 
where r > (
    select (
        select reltuples::bigint AS estimate
        from   pg_class
        where  oid = 'public.YOUR_TABLE'::regclass) * random()
    )
order by r asc limit(1);

รหัสอธิบายตนเอง แบบสอบถามย่อยที่อยู่ตรงกลางจะใช้เพื่อประเมินจำนวนแถวของตารางจากhttps://stackoverflow.com/a/7945274/1271094อย่างรวดเร็ว

ในระดับแอปพลิเคชันคุณต้องดำเนินการคำสั่งอีกครั้งหากn> จำนวนแถวหรือจำเป็นต้องเลือกหลายแถว


ฉันชอบสิ่งนี้เพราะมันสั้นและสง่างาม :) และฉันก็พบวิธีที่จะปรับปรุง: อธิบายการวิเคราะห์บอกฉันว่าเช่นนี้ดัชนี PKEY จะไม่ถูกใช้เนื่องจากการสุ่ม () ส่งกลับสองเท่าในขณะที่ PKEY ต้องการ BIGINT
fxtentacle

เลือก * จาก YOUR_TABLE โดยที่ r> (เลือก (เลือก reltuples :: bigint AS ประมาณจาก pg_class โดยที่ oid = 'public.YOUR_TABLE' :: regclass) * สุ่ม ()) :: ลำดับ BIGINT โดย r asc จำกัด (1);
fxtentacle

0

ฉันรู้ว่าฉันมาช้าไปงานปาร์ตี้ แต่ฉันเพิ่งพบเครื่องมือที่ยอดเยี่ยมนี้ชื่อpg_sample :

pg_sample - แยกชุดข้อมูลตัวอย่างขนาดเล็กจากฐานข้อมูล PostgreSQL ที่มีขนาดใหญ่ขึ้นในขณะที่ยังคงความสมบูรณ์ของการอ้างอิง

ฉันพยายามนี้กับฐานข้อมูล 350M แถวและมันก็เป็นจริงอย่างรวดเร็วไม่ทราบเกี่ยวกับการสุ่ม

./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.