मैं MySQL के आदेश को RAND () फ़ंक्शन से कैसे अनुकूलित कर सकता हूं?


90

मैं अपने प्रश्नों को अनुकूलित करना चाहूंगा, इसलिए मैं देखता हूं mysql-slow.log

मेरे अधिकांश धीमे प्रश्नों में सम्‍मिलित है ORDER BY RAND()। मुझे इस समस्या को हल करने के लिए एक वास्तविक समाधान नहीं मिल रहा है। Theres MySQLPerformanceBlog पर एक संभावित समाधान है लेकिन मुझे नहीं लगता कि यह पर्याप्त है। खराब रूप से अनुकूलित (या अक्सर अपडेट किए गए, उपयोगकर्ता प्रबंधित) टेबल पर यह काम नहीं करता है या मुझे दो-या अधिक प्रश्नों को चलाने की आवश्यकता है इससे पहले कि मैं अपनी- PHPयादृच्छिक यादृच्छिक पंक्ति का चयन कर सकूं।

क्या इस मुद्दे का कोई हल है?

एक डमी उदाहरण:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
ORDER BY
        RAND()
LIMIT 1

जवाबों:


67

इसे इस्तेमाल करे:

SELECT  *
FROM    (
        SELECT  @cnt := COUNT(*) + 1,
                @lim := 10
        FROM    t_random
        ) vars
STRAIGHT_JOIN
        (
        SELECT  r.*,
                @lim := @lim - 1
        FROM    t_random r
        WHERE   (@cnt := @cnt - 1)
                AND RAND(20090301) < @lim / @cnt
        ) i

इस पर विशेष रूप से कुशल है MyISAM(के बाद से COUNT(*)तत्काल है), लेकिन फिर भी में InnoDBयह 10तुलना में अधिक कुशल बार ORDER BY RAND()

यहां मुख्य विचार यह है कि हम सॉर्ट नहीं करते हैं, लेकिन इसके बजाय दो चर रखते हैं और running probabilityवर्तमान चरण पर चयनित होने वाली एक पंक्ति की गणना करते हैं।

इस लेख को और अधिक विस्तार से मेरे ब्लॉग में देखें:

अपडेट करें:

यदि आपको एकल यादृच्छिक रिकॉर्ड का चयन करने की आवश्यकता है, तो यह प्रयास करें:

SELECT  aco.*
FROM    (
        SELECT  minid + FLOOR((maxid - minid) * RAND()) AS randid
        FROM    (
                SELECT  MAX(ac_id) AS maxid, MIN(ac_id) AS minid
                FROM    accomodation
                ) q
        ) q2
JOIN    accomodation aco
ON      aco.ac_id =
        COALESCE
        (
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_id > randid
                AND ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        ),
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        )
        )

यह मानता है कि आपके ac_idसमान रूप से समान रूप से वितरित किए जाते हैं।


हैलो, क्वासोई! सबसे पहले, आपकी तेज़ प्रतिक्रिया के लिए धन्यवाद! शायद यह मेरी गलती है लेकिन यह अभी भी आपके समाधान को स्पष्ट नहीं कर रहा है। मैं अपनी मूल पोस्ट को एक ठोस उदाहरण के साथ अपडेट करूंगा और यदि आप इस उदाहरण पर अपना समाधान बताते हैं तो मुझे खुशी होगी।
फैब्रिक

वहाँ पर एक टाइपो था "JOIN आवास में aco.id =" जहाँ aco.id वास्तव में aco.ac_id है। दूसरी ओर सही क्वेरी ने मेरे लिए काम नहीं किया क्योंकि यह # 1241 त्रुटि फेंकता है - ऑपरेंड में पांचवें चयन (चौथे उप-चयन) में 1 कॉलम (एस) होना चाहिए। मैंने कोष्ठक के साथ समस्या को खोजने की कोशिश की (अगर मैं गलत नहीं हूँ), लेकिन मैं अभी तक समस्या नहीं ढूँढ सकता।
फेब्रिक

@fabrik: अब कोशिश करो। यह वास्तव में मददगार होगा यदि आप टेबल स्क्रिप्ट पोस्ट करते हैं ताकि मैं पोस्ट करने से पहले उनकी जांच कर सकूं।
क्वासोनी

धन्यवाद, यह काम करता है! :) क्या आप JOIN को एडिट कर सकते हैं ... aco.id वाले हिस्से को JOIN करें ... aco.ac_id पर तो मैं आपके समाधान को स्वीकार कर सकता हूं। एक बार फिर धन्यवाद! एक सवाल: मुझे आश्चर्य है कि यदि संभव हो तो यह ORDER BY RAND () की तरह एक यादृच्छिक यादृच्छिक है? सिर्फ इसलिए कि यह क्वेरी कई बार कुछ परिणाम दोहरा रही है।
फेब्रिक

1
@ एडम: नहीं, यह जानबूझकर है, ताकि आप परिणामों को पुन: पेश कर सकें।
क्वासोनि

12

यह इस बात पर निर्भर करता है कि आपको कितना यादृच्छिक होना चाहिए। आपके द्वारा जोड़ा गया समाधान बहुत अच्छी तरह से IMO काम करता है। जब तक आपके पास ID फ़ील्ड में बड़े अंतराल नहीं हैं, तब भी यह बहुत यादृच्छिक है।

हालाँकि, आपको इसका उपयोग करके एक क्वेरी में करने में सक्षम होना चाहिए (एक मान का चयन करने के लिए):

SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*MAX(id)) LIMIT 1

अन्य समाधान:

  • randomतालिका में कहा गया एक स्थायी फ़्लोट फ़ील्ड जोड़ें और इसे यादृच्छिक संख्याओं के साथ भरें। फिर आप PHP में एक यादृच्छिक संख्या उत्पन्न कर सकते हैं और कर सकते हैं"SELECT ... WHERE rnd > $random"
  • आईडी की पूरी सूची को पकड़ो और उन्हें एक पाठ फ़ाइल में कैश करें। फ़ाइल पढ़ें और उसमें से एक यादृच्छिक आईडी चुनें।
  • HTML के रूप में क्वेरी के परिणामों को कैश करें और इसे कुछ घंटों के लिए रखें।

8
क्या यह सिर्फ मेरे लिए है या यह क्वेरी काम नहीं करती है? मैंने इसे कई विविधताओं के साथ आज़माया और उन्होंने सभी "ग्रुप फंक्शन का अमान्य उपयोग" फेंक दिया ..
सोफीवॉर्स

आप इसे एक उपशम के साथ कर सकते हैं SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1लेकिन यह ठीक से काम नहीं करता है क्योंकि यह पिछले रिकॉर्ड को कभी नहीं लौटाता है
मार्क

11
SELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1मेरे लिए चाल करने के लिए लगता है
मार्क

1

यहाँ है कि मैं यह कैसे करूँगा:

SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*)
  FROM    accomodation a
  JOIN    accomodation_category c
    ON (a.ac_category = c.acat_id)
  WHERE   a.ac_status != 'draft'
        AND c.acat_slug != 'vendeglatohely'
        AND a.ac_images != 'b:0;';

SET @sql := CONCAT('
  SELECT  a.ac_id,
        a.ac_status,
        a.ac_name,
        a.ac_status,
        a.ac_images
  FROM    accomodation a
  JOIN    accomodation_category c
    ON (a.ac_category = c.acat_id)
  WHERE   a.ac_status != ''draft''
        AND c.acat_slug != ''vendeglatohely''
        AND a.ac_images != ''b:0;''
  LIMIT ', @r, ', 1');

PREPARE stmt1 FROM @sql;

EXECUTE stmt1;


मेरी तालिका निरंतर नहीं है क्योंकि यह अक्सर संपादित होती है। उदाहरण के लिए वर्तमान में पहली आईडी 121 है।
फेब्रीक

3
उपरोक्त तकनीक आईडी मूल्यों के निरंतर होने पर निर्भर नहीं करती है। यह कुछ अन्य समाधानों की तरह 1 और COUNT (*) के बीच एक यादृच्छिक संख्या चुनता है, न कि 1 और MAX (id)।
बिल कार्विन

1
का उपयोग करना OFFSET(जो है के @rलिए है) एक स्कैन से बचने नहीं है - एक पूर्ण टेबल स्कैन तक।
रिक जेम्स

@ रिकजम्स, यह सही है। अगर मुझे आज इस सवाल का जवाब देना होता, तो मैं प्राथमिक कुंजी द्वारा प्रश्न करता। LIMIT के साथ एक ऑफसेट का उपयोग करने से बहुत सी पंक्तियाँ स्कैन होती हैं। प्राथमिक कुंजी द्वारा क्वेरी करना, हालांकि बहुत तेज़ है, प्रत्येक पंक्ति को चुनने की एक समान संभावना की गारंटी नहीं देता है - यह अंतराल का पालन करने वाली पंक्तियों का पक्षधर है।
बिल कार्विन

1

(हाँ, मैं यहाँ पर्याप्त मांस नहीं रखने के लिए डिंग करूँगा, लेकिन क्या आप एक दिन के लिए शाकाहारी नहीं हो सकते?)

मामला: अंतराल के बिना लगातार AUTO_INCREMENT, 1 पंक्ति लौटाया गया
मामला: बिना अंतराल के लगातार AUTO_INCREMENT, 10 पंक्तियाँ
मामला: अंतराल के साथ AUTO_INCREMENT, 1 पंक्ति लौटी
केस: यादृच्छिक के लिए अतिरिक्त FLOAT स्तंभ
प्रकरण: UUID या MD5 स्तंभ

उन 5 मामलों को बड़ी तालिकाओं के लिए बहुत कुशल बनाया जा सकता है। देखें मेरे ब्लॉग जानकारी के लिए।


0

यह आपको सिंगल सब क्वेरी देगा जो इंडेक्स का उपयोग रैंडम आईडी प्राप्त करने के लिए करेगा, फिर दूसरी क्वेरी आपके ज्वाइन टेबल को फायर करेगी।

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
AND accomodation.ac_id IS IN (
        SELECT accomodation.ac_id FROM accomodation ORDER BY RAND() LIMIT 1
)

0

आपके डमी-उदाहरण के लिए समाधान होगा:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation,
        JOIN 
            accomodation_category 
            ON accomodation.ac_category = accomodation_category.acat_id
        JOIN 
            ( 
               SELECT CEIL(RAND()*(SELECT MAX(ac_id) FROM accomodation)) AS ac_id
            ) AS Choices 
            USING (ac_id)
WHERE   accomodation.ac_id >= Choices.ac_id 
        AND accomodation.ac_status != 'draft'
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
LIMIT 1

विकल्प के बारे में अधिक पढ़ने के लिए ORDER BY RAND(), आपको यह लेख पढ़ना चाहिए ।


0

मैं अपनी परियोजना में बहुत सारे मौजूदा प्रश्नों का अनुकूलन कर रहा हूं। Quassnoi के समाधान ने मुझे प्रश्नों को बहुत तेज करने में मदद की है! हालाँकि, मुझे सभी प्रश्नों में उक्त समाधान को शामिल करना कठिन लगता है, विशेषकर जटिल प्रश्नों के लिए जिसमें कई बड़ी मेज पर कई उपश्रेणियाँ शामिल हैं।

इसलिए मैं कम अनुकूलित समाधान का उपयोग कर रहा हूं। मौलिक रूप से यह उसी तरह काम करता है जैसे क्वासोई के समाधान।

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
        AND rand() <= $size * $factor / [accomodation_table_row_count]
LIMIT $size

$size * $factor / [accomodation_table_row_count]एक यादृच्छिक पंक्ति लेने की संभावना को पूरा करता है। रैंड () एक यादृच्छिक संख्या उत्पन्न करेगा। यदि रैंड छोटा है या संभाव्यता के बराबर है, तो पंक्ति का चयन किया जाएगा। यह प्रभावी रूप से तालिका आकार को सीमित करने के लिए एक यादृच्छिक चयन करता है। चूंकि एक मौका है कि यह परिभाषित सीमा की तुलना में कम वापस आएगा, हमें यह सुनिश्चित करने की आवश्यकता है कि हम पर्याप्त पंक्तियों का चयन कर रहे हैं। इसलिए हम $ आकार को एक $ कारक से गुणा करते हैं (मैं आमतौर पर $ कारक = 2 ​​सेट करता हूं, ज्यादातर मामलों में काम करता है)। अंत में हम करते हैंlimit $size

समस्या अब साथ काम कर रही है outodation_table_row_count । यदि हमें तालिका आकार पता है, तो हम तालिका आकार को कठिन कोड बना सकते हैं। यह सबसे तेज़ चलेगा, लेकिन जाहिर है कि यह आदर्श नहीं है। यदि आप Myisam का उपयोग कर रहे हैं, तो टेबल काउंट प्राप्त करना बहुत कुशल है। चूँकि मैं innodb का उपयोग कर रहा हूँ, मैं केवल एक साधारण गणना + चयन कर रहा हूँ। आपके मामले में, यह इस तरह दिखेगा:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
        AND rand() <= $size * $factor / (select (SELECT count(*) FROM `accomodation`) * (SELECT count(*) FROM `accomodation_category`))
LIMIT $size

मुश्किल हिस्सा सही संभावना बाहर काम कर रहा है। जैसा कि आप निम्न कोड देख सकते हैं वास्तव में केवल रफ टेम्प टेबल की गणना करता है (वास्तव में, बहुत मोटा!): (select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))लेकिन आप इस तर्क को एक करीबी टेबल आकार का अनुमान देने के लिए परिष्कृत कर सकते हैं। ध्यान दें कि OVER- सेलेक्ट की गई पंक्तियों को चुनना बेहतर है। यानी यदि संभावना बहुत कम सेट की जाती है, तो आप पर्याप्त पंक्तियों का चयन नहीं करने का जोखिम उठाते हैं।

यह समाधान क्वासोई के समाधान की तुलना में धीमा चलता है क्योंकि हमें तालिका के आकार को पुनर्गणना करने की आवश्यकता है। हालाँकि, मुझे यह कोडिंग बहुत अधिक प्रबंधनीय लगती है। यह सटीकता + प्रदर्शन बनाम कोडिंग जटिलता के बीच एक व्यापार बंद है । कहा जाता है कि, बड़े तालिकाओं पर यह ऑर्डर ऑफ रैंड () की तुलना में अभी भी बहुत तेज है।

नोट: यदि क्वेरी तर्क अनुमति देता है, तो रैंडम सेलेक्शन को किसी भी जॉइन ऑपरेशंस से पहले करें।


-1
function getRandomRow(){
    $id = rand(0,NUM_OF_ROWS_OR_CLOSE_TO_IT);
    $res = getRowById($id);
    if(!empty($res))
    return $res;
    return getRandomRow();
}

//rowid is a key on table
function getRowById($rowid=false){

   return db select from table where rowid = $rowid; 
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.