Postgres में त्वरित यादृच्छिक पंक्ति चयन


98

मेरे पास पोस्टग्रेज में एक मेज है जिसमें लाखों पंक्तियों के जोड़े हैं। मैंने इंटरनेट पर जाँच की है और मैंने निम्नलिखित पाया है

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

यह काम करता है, लेकिन यह वास्तव में धीमा है ... क्या उस तालिका को बनाने का एक और तरीका है, या सभी तालिका को पढ़े बिना एक यादृच्छिक पंक्ति का चयन करने का एक सीधा तरीका है? वैसे 'मायिड' एक पूर्णांक है, लेकिन यह एक खाली क्षेत्र हो सकता है।


1
यदि आप कई रैंडम पंक्तियों का चयन करना चाहते हैं, तो यह प्रश्न देखें: stackoverflow.com/q/8674718/247696
फ्लिम

जवाबों:


99

आप के OFFSETरूप में के साथ प्रयोग करना चाहते हो सकता है

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

Nमें पंक्तियों की संख्या है mytable। आपको पहले SELECT COUNT(*)मूल्य का पता लगाने के लिए एक करने की आवश्यकता हो सकती है N

अपडेट (एंटनी हैचकिंस द्वारा)

आपको floorयहाँ उपयोग करना चाहिए :

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 पंक्तियों की एक तालिका पर विचार करें; random()*Nउत्पन्न करता है 0 <= x < 2और उदाहरण के SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;लिए निकटतम int के लिए निहित गोलाई के कारण 0 पंक्तियाँ देता है।


इसे कम से कम एन का उपयोग करने के लिए समझें SELECT COUNT(*)? मेरा मतलब है कि तालिका में सभी मूल्यों का उपयोग न करें, लेकिन उनमें से केवल एक हिस्सा है?
जुआन

@ जुआन जो आपकी आवश्यकताओं पर निर्भर करता है।
एनपीई

EXPLAIN SELECT ...एन के विभिन्न मूल्यों के साथ उपयोग करना क्वेरी के लिए समान लागत देता है, फिर मुझे लगता है कि एन के अधिकतम मूल्य के लिए जाना बेहतर है।
जुआन

3
नीचे मेरे जवाब में एक
बगफिक्स

2
यह एक त्रुटि से बंद है। यह पहली पंक्ति को कभी नहीं लौटाएगा और त्रुटि 1 / COUNT (*) उत्पन्न करेगा क्योंकि यह अंतिम पंक्ति के बाद पंक्ति को वापस करने का प्रयास करेगा।
इयान

62

PostgreSQL 9.5 ने बहुत तेज़ नमूना चयन के लिए एक नया दृष्टिकोण पेश किया: TABLESAMPLE

वाक्य-विन्यास है

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

यह इष्टतम समाधान नहीं है यदि आप केवल एक पंक्ति को चुनना चाहते हैं, क्योंकि आपको सटीक प्रतिशत की गणना करने के लिए तालिका के COUNT को जानना होगा।

धीमी गति से बचने के लिए और 1 पंक्ति से लेकर अरबों पंक्तियों तक की तालिकाओं के लिए तेज तालिका का उपयोग करें, आप यह कर सकते हैं:

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

यह इतना सुंदर नहीं लग सकता है, लेकिन शायद किसी भी अन्य उत्तर की तुलना में तेज है।

यह तय करने के लिए कि क्या आप बर्नोली ओडर सिस्टम का उपयोग करना चाहते हैं, http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/ पर अंतर के बारे में पढ़ें


2
यह किसी भी अन्य उत्तर की तुलना में बहुत तेज़ और आसान है - यह सबसे ऊपर होना चाहिए।
हेडन शिफ

1
आप गिनती प्राप्त करने के लिए सिर्फ एक सबक्वेरी का उपयोग क्यों नहीं कर सकते? SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;?
मशीनगॉस्ट

2
@ machineghost "धीमी गति से बचने के लिए ..." ... यदि आपका डेटा इतना छोटा है, कि आप उचित समय में गिन सकते हैं, तो इसके लिए जाएं! :-)
अल्फोंक्स

2
@machineghost SELECT reltuples FROM pg_class WHERE relname = 'my_table'गणना अनुमान के लिए उपयोग करें ।
हायनेक -पिची- विचोदिल

@ Hynek-Pichi-Vychodil बहुत अच्छा इनपुट! यह सुनिश्चित करने के लिए कि अनुमान पुराना नहीं है, इसे हाल ही में मधुमक्खी VALUUM ANALYZEd करना होगा .. लेकिन एक अच्छे डेटाबेस का वैसे भी ठीक से विश्लेषण किया जाना चाहिए .. और यह सब विशिष्ट उपयोग-मामले पर निर्भर करता है। आमतौर पर विशाल टेबल इतनी तेजी से नहीं बढ़ती हैं ... धन्यवाद!
अल्फोंक्स

34

मैंने एक सबकुछ के साथ यह कोशिश की और यह ठीक काम कर गया। ऑफसेट, कम से कम Postgresql v8.4.4 में ठीक काम करता है।

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

वास्तव में, यह काम करने के लिए v8.4 आवश्यक है, <= 8.3 के लिए काम नहीं करता है।
एंटनी हैचकिंस

1
नीचे मेरे जवाब में एक
बगफिक्स

32

आपको उपयोग करने की आवश्यकता है floor:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 पंक्तियों की एक तालिका पर विचार करें; random()*N0 <= x <2 उत्पन्न करता है और उदाहरण के SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;लिए निकटतम int के लिए निहित गोलाई के कारण 0 पंक्तियाँ देता है।
एंटनी हैचकिंस

यदि आप एक उच्च सीमा का उपयोग करना चाहते हैं तो दुर्भाग्य से यह काम नहीं करता है ... मुझे 3 आइटम प्राप्त करने की आवश्यकता है इसलिए मुझे ORDER BY RANDOM () सिंटैक्स का उपयोग करने की आवश्यकता है।
एलेक्सिस विल्के

1
लगातार तीन प्रश्न अभी भी एक से अधिक तेज़ होंगे order by random(), लगभग 3*O(N) < O(NlogN)- वास्तविक जीवन के आंकड़े सूचकांकों के कारण थोड़े अलग होंगे।
एंटनी हैचकिंस

मेरे समस्या यह है कि 3 आइटम अलग और एक होने की जरूरत है WHERE myid NOT IN (1st-myid)और WHERE myid NOT IN (1st-myid, 2nd-myid)नहीं होगा काम के बाद से निर्णय OFFSET द्वारा किया जाता है। हम्म् ... मुझे लगता है कि मैं दूसरे और तीसरे चयन में 1 और 2 से एन को कम कर सकता हूं।
एलेक्सिस विल्के

क्या आप या कोई भी इस उत्तर का विस्तार इस उत्तर के साथ कर सकता है कि मुझे इसका उपयोग करने की आवश्यकता क्यों हैfloor() ? क्या लाभ प्रदान करता है?
ADTC

14

कुछ अलग विकल्पों के लिए इस लिंक को देखें। http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

अपडेट करें: (A.Hatchkins)

(बहुत) लंबे लेख का सारांश इस प्रकार है।

लेखक चार दृष्टिकोणों को सूचीबद्ध करता है:

1) ORDER BY random() LIMIT 1; - धीमा

2) ORDER BY id where id>=random()*N LIMIT 1- अगर कोई अंतराल हो तो नॉनफॉर्म

3) यादृच्छिक कॉलम - अब और फिर हर अपडेट किए जाने की आवश्यकता है

4) कस्टम यादृच्छिक कुल - चालाक विधि, धीमी हो सकती है: यादृच्छिक () एन बार उत्पन्न करने की आवश्यकता है

और विधि # 2 का उपयोग करके सुधार करने का सुझाव देता है

5) ORDER BY id where id=random()*N LIMIT 1 यदि परिणाम खाली है, तो बाद की आवश्यकताओं के साथ।


मुझे आश्चर्य है कि उन्होंने OFFSET को कवर क्यों नहीं किया? एक यादृच्छिक पंक्ति प्राप्त करने के लिए ORDER का उपयोग करना प्रश्न से बाहर है। सौभाग्य से, OFFSET जवाबों में अच्छी तरह से शामिल है।
androidguy

4

tsm_system_rowsविस्तार का उपयोग करने के लिए यादृच्छिक पंक्ति लाने का सबसे आसान और तेज़ तरीका है :

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

तब आप इच्छित पंक्तियों की सही संख्या का चयन कर सकते हैं:

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

यह PostgreSQL 9.5 और बाद में उपलब्ध है।

देखें: https://www.postgresql.org/docs/current/static/tsm-system-rows.html


1
निष्पक्ष चेतावनी, यह पूरी तरह से यादृच्छिक नहीं है। छोटी तालिकाओं पर, मैंने हमेशा क्रम में पहली पंक्तियों को वापस किया है।
बेन ऑबिन

1
हाँ यह स्पष्ट रूप से प्रलेखन में स्पष्ट किया गया है (ऊपर लिंक): «बिल्ट-इन सिस्टम नमूना विधि की तरह, SYSTEM_ROWS ब्लॉक-स्तरीय नमूना प्रदर्शन करता है, ताकि नमूना पूरी तरह से यादृच्छिक न हो, लेकिन क्लस्टरिंग प्रभाव के अधीन हो सकता है, खासकर यदि केवल एक छोटा सा पंक्तियों की संख्या का अनुरोध किया जाता है। » यदि आपके पास एक छोटा डेटासेट है, तो ORDER BY random() LIMIT 1;पर्याप्त उपवास होना चाहिए।
डेमियन

मैंने देखा। बस जो इसे लिंक पर क्लिक नहीं करता है या भविष्य में लिंक की मृत्यु हो जाती है, उसे यह स्पष्ट करना चाहता था।
बेन ऑबिन

1
यह भी ध्यान देने योग्य है कि यह केवल एक तालिका से बाहर यादृच्छिक पंक्तियों को चुनने के लिए काम करेगा और जब तक कि एक क्वेरी को चलाने की तुलना में विरोध किया जाता है, तब यादृच्छिक रूप से एक या कुछ रिकॉर्ड उठाता है।
नौ

3

मैं बिना किसी बहुत तेजी से समाधान के साथ आया हूं TABLESAMPLE। से बहुत तेज OFFSET random()*N LIMIT 1। इसके लिए टेबल काउंट की भी आवश्यकता नहीं होती है।

उदाहरण के लिए, यादृच्छिक लेकिन पूर्वानुमानित डेटा के साथ एक अभिव्यक्ति सूचकांक बनाने का विचार है md5(primary key)

यहाँ 1M पंक्तियों के नमूने का परीक्षण किया गया है:

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

परिणाम:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

यह क्वेरी कभी-कभी (लगभग 1 / Number_of_rows संभावना के साथ) 0 पंक्तियाँ लौटाती है, इसलिए इसे जाँचने और पुन: चलाने की आवश्यकता होती है। इसके अलावा संभाव्यताएं समान नहीं हैं - कुछ पंक्तियाँ दूसरों की तुलना में अधिक संभावित हैं।

तुलना के लिए:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

परिणाम व्यापक रूप से भिन्न होते हैं, लेकिन बहुत खराब हो सकते हैं:

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

2
तेज, हां। सचमुच यादृच्छिक, नहीं। एक md5 मान जो किसी अन्य मौजूदा मान के बाद अगला बड़ा मान होता है, को चुनने का एक बहुत पतला मौका होता है, जबकि संख्या स्थान में एक बड़े अंतराल के बाद के मानों में एक बहुत बड़ा मौका होता है (बीच में संभावित मानों की संख्या से बड़ा) । परिणामस्वरूप वितरण यादृच्छिक नहीं है।
एरविन ब्रान्डस्टेट्टर

बहुत दिलचस्प है, क्या यह लॉटरी जैसी क्वेरी के usecase में काम कर सकता है: क्वेरी को सभी उपलब्ध टिकटों पर ध्यान देना चाहिए और अनियमित रूप से केवल एक ही टिकट वापस करना चाहिए। क्या मैं आपकी तकनीक के साथ निराशावादी लॉक (चयन ... अपडेट के लिए) का उपयोग कर सकता हूं?
मैथ्यू

संबंधित कुछ लॉटरी के लिए आपको वास्तव में निष्पक्ष और क्रिप्टोग्राफिक रूप से सुरक्षित यादृच्छिक नमूने का उपयोग करना चाहिए - उदाहरण के लिए 1 और अधिकतम (आईडी) के बीच एक यादृच्छिक संख्या चुनें जब तक आप मौजूदा आईडी नहीं पाते। इस उत्तर से विधि न तो उचित है और न ही सुरक्षित है - यह तेज़ है। उन चीजों के लिए जो 'कुछ पर परीक्षण करने के लिए पंक्तियों की यादृच्छिक 1% प्राप्त करें', या 'यादृच्छिक 5 प्रविष्टियां दिखाएं'।
Tometzky
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.