سؤال

كيف يمكنني طلب صف عشوائي (أو أقرب ما يكون إلى الصف العشوائي قدر الإمكان) في SQL خالص؟

هل كانت مفيدة؟

المحلول

انظر هذا المنصب: SQL لتحديد صف عشوائي من جدول قاعدة البيانات.يتم ذلك عبر طرق للقيام بذلك في MySQL وPostgreSQL وMicrosoft SQL Server وIBM DB2 وOracle (يتم نسخ ما يلي من هذا الرابط):

حدد صفًا عشوائيًا باستخدام MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

حدد صفًا عشوائيًا باستخدام PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

حدد صفًا عشوائيًا باستخدام Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

حدد صفًا عشوائيًا باستخدام IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

حدد سجلًا عشوائيًا باستخدام Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

نصائح أخرى

حلول مثل جيريمي:

SELECT * FROM table ORDER BY RAND() LIMIT 1

تعمل، ولكنها تحتاج إلى مسح تسلسلي للجدول بأكمله (لأن القيمة العشوائية المرتبطة بكل صف يجب حسابها - بحيث يمكن تحديد أصغرها)، والتي يمكن أن تكون بطيئة جدًا حتى بالنسبة للجداول متوسطة الحجم.توصيتي هي استخدام نوع من الأعمدة الرقمية المفهرسة (تحتوي العديد من الجداول على هذه المفاتيح كمفاتيح أساسية)، ثم كتابة شيء مثل:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

يعمل هذا في الوقت اللوغاريتمي، بغض النظر عن حجم الجدول، إذا num_value تمت فهرسته.تحذير واحد:هذا يفترض ذلك num_value يتم توزيعها بالتساوي في النطاق 0..MAX(num_value).إذا انحرفت مجموعة البيانات الخاصة بك بشدة عن هذا الافتراض، فستحصل على نتائج منحرفة (ستظهر بعض الصفوف أكثر من غيرها).

لا أعرف مدى فعالية هذا، ولكني استخدمته من قبل:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

نظرًا لأن المعرفات الفريدة العمومية (GUIDs) عشوائية جدًا، فإن الترتيب يعني أنك تحصل على صف عشوائي.

ORDER BY NEWID()

يأخذ 7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

يأخذ 0.0065 milliseconds!

سأذهب بالتأكيد مع الطريقة الأخيرة.

لم تذكر الخادم الذي تستخدمه.في الإصدارات الأقدم من SQL Server، يمكنك استخدام هذا:

select top 1 * from mytable order by newid()

في SQL Server 2005 والإصدارات الأحدث، يمكنك استخدام TABLESAMPLE للحصول على عينة عشوائية قابلة للتكرار:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;

لخادم SQL

سوف يعمل newid()/order by، ولكنه سيكون مكلفًا للغاية بالنسبة لمجموعات النتائج الكبيرة لأنه يجب عليه إنشاء معرف لكل صف، ثم فرزها.

يعد TABLESAMPLE() أمرًا جيدًا من وجهة نظر الأداء، ولكنك ستحصل على مجموعة من النتائج (سيتم إرجاع جميع الصفوف الموجودة في الصفحة).

للحصول على عينة عشوائية حقيقية ذات أداء أفضل، فإن أفضل طريقة هي تصفية الصفوف بشكل عشوائي.لقد وجدت نموذج التعليمات البرمجية التالي في مقالة كتب SQL Server عبر الإنترنت تحديد مجموعات النتائج باستخدام TABLESAMPLE:

إذا كنت تريد حقًا عينة عشوائية من الصفوف الفردية ، فقم بتعديل استعلامك لتصفية الصفوف بشكل عشوائي ، بدلاً من استخدام TableMample.على سبيل المثال ، يستخدم الاستعلام التالي الوظيفة الجديدة لإرجاع ما يقرب من واحد في المائة من صفوف المبيعات. جدول SalesorderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

يتم تضمين عمود SalesorDerID في تعبير Checksum بحيث يتم تقييم NewID () مرة واحدة لكل صف لتحقيق أخذ العينات على أساس كل صف.يقوم التعبير بالتعبير (checksum (newId () ، salesorderid) و 0x7ffffff كـ float / cast (0x7fffffff as int) إلى قيمة تعويم عشوائية بين 0 و 1.

عند تشغيل جدول يحتوي على 1,000,000 صف، فإليك نتائجي:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

إذا تمكنت من الإفلات من استخدام TABLESAMPLE، فسوف يمنحك أفضل أداء.بخلاف ذلك، استخدم طريقة newid()/filter.يجب أن يكون newid()/order by هو الملاذ الأخير إذا كان لديك مجموعة نتائج كبيرة.

إذا كان ذلك ممكنًا، استخدم البيانات المخزنة لتجنب عدم كفاءة كلا الفهرسين في RND() وإنشاء حقل رقم السجل.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1";
SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM table));
EXECUTE RandomRecord USING @n;

أفضل طريقة هي وضع قيمة عشوائية في عمود جديد لهذا الغرض فقط، واستخدام شيء مثل هذا (رمز مستعار + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

هذا هو الحل الذي يستخدمه كود ميدياويكي.بالطبع، هناك بعض التحيز ضد القيم الأصغر، لكنهم وجدوا أنه كان كافيًا لالتفاف القيمة العشوائية حول الصفر عندما لا يتم جلب أي صفوف.

قد يتطلب حل newid() فحصًا كاملاً للجدول بحيث يمكن تعيين دليل إرشادي جديد لكل صف، والذي سيكون أقل أداءً بكثير.

قد لا يعمل حل rand() على الإطلاق (أي.مع MSSQL) لأنه سيتم تقييم الوظيفة مرة واحدة فقط، و كل سيتم تعيين نفس الرقم "العشوائي" للصف.

بالنسبة لـ SQL Server 2005 و2008، إذا أردنا عينة عشوائية من الصفوف الفردية (من الكتب على الانترنت):

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)

بدلا من باستخدام RAND()، حيث لا يُنصح بذلك, ، يمكنك ببساطة الحصول على الحد الأقصى للمعرف (=Max):

SELECT MAX(ID) FROM TABLE;

احصل على عشوائي بين 1..Max (=My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

ثم قم بتشغيل SQL هذا:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

لاحظ أنه سيتحقق من وجود أي صفوف ذات معرفات مساوية أو أعلى من القيمة المختارة.من الممكن أيضًا البحث عن الصف الموجود في الجدول، والحصول على معرف مساوي أو أقل من My_Generated_Random، ثم تعديل الاستعلام مثل هذا:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1

كما تمت الإشارة إليه في تعليق @BillKarwin على إجابة @cnu...

عند الدمج مع LIMIT، وجدت أنه يعمل بشكل أفضل بكثير (على الأقل مع PostgreSQL 9.1) للانضمام بترتيب عشوائي بدلاً من ترتيب الصفوف الفعلية مباشرةً:على سبيل المثال

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

فقط تأكد من أن "r" ينشئ قيمة "rand" لكل قيمة مفتاح محتملة في الاستعلام المعقد المرتبط به ولكن لا يزال يحد من عدد صفوف "r" حيثما أمكن ذلك.

يعد CAST as Integer مفيدًا بشكل خاص لـ PostgreSQL 9.2 الذي يحتوي على تحسين فرز محدد للأنواع العائمة ذات الدقة الفردية والأعداد الصحيحة.

تهدف معظم الحلول هنا إلى تجنب الفرز، لكنها لا تزال بحاجة إلى إجراء فحص تسلسلي على الطاولة.

هناك أيضًا طريقة لتجنب الفحص التسلسلي عن طريق التبديل إلى فحص الفهرس.إذا كنت تعرف قيمة الفهرس لصفك العشوائي، فيمكنك الحصول على النتيجة على الفور تقريبًا.المشكلة هي كيفية تخمين قيمة الفهرس.

يعمل الحل التالي على PostgreSQL 8.4:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

أنا أعلاه الحل الذي تخمنه 10 قيم فهرس عشوائية مختلفة من النطاق 0 ..[القيمة الأخيرة للمعرف].

الرقم 10 عشوائي - يمكنك استخدام 100 أو 1000 لأنه (بشكل مدهش) ليس له تأثير كبير على وقت الاستجابة.

هناك أيضًا مشكلة واحدة - إذا كان لديك معرفات متفرقة قد تفوت.الحل هو لديك خطة احتياطية :) في هذه الحالة ترتيب قديم خالص بواسطة استعلام عشوائي ().عندما يبدو المعرف المدمج هكذا:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

ليس اتحاد الجميع بند.في هذه الحالة، إذا قام الجزء الأول بإرجاع أي بيانات، فلن يتم تنفيذ الجزء الثاني أبدًا!

في وقت متأخر، ولكنني وصلت إلى هنا عبر Google، لذا، ومن أجل الأجيال القادمة، سأضيف حلاً بديلاً.

هناك طريقة أخرى تتمثل في استخدام TOP مرتين، مع أوامر متناوبة.لا أعرف ما إذا كان "SQL خالصًا"، لأنه يستخدم متغيرًا في الجزء العلوي، ولكنه يعمل في SQL Server 2008.إليك مثال أستخدمه مقابل جدول كلمات القاموس، إذا كنت أريد كلمة عشوائية.

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

بالطبع، @idx عبارة عن عدد صحيح يتم إنشاؤه عشوائيًا ويتراوح من 1 إلى COUNT(*) في الجدول الهدف، بشكل شامل.إذا تمت فهرسة عمودك، فستستفيد منه أيضًا.ميزة أخرى هي أنه يمكنك استخدامه في وظيفة، حيث أن NEWID() غير مسموح به.

وأخيرًا، يتم تشغيل الاستعلام أعلاه في حوالي 1/10 من وقت exec لنوع الاستعلام NEWID() في نفس الجدول.YYMV.

يمكنك أيضًا تجربة استخدام new id() وظيفة.

ما عليك سوى كتابة استعلامك واستخدام الطلب حسب new id() وظيفة.انها عشوائية تماما.

لكي يحصل MySQL على سجل عشوائي

 SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

تفاصيل اكثر http://jan.kneschke.de/projects/mysql/order-by-rand/

لم أر هذا الاختلاف في الإجابات حتى الآن.كان لدي قيد إضافي حيث كنت بحاجة، في ضوء البذرة الأولية، لتحديد نفس مجموعة الصفوف في كل مرة.

بالنسبة لـ MS SQL:

مثال الحد الأدنى:

select top 10 percent *
from table_name
order by rand(checksum(*))

وقت التنفيذ الطبيعي:1.00

مثال NewId():

select top 10 percent *
from table_name
order by newid()

وقت التنفيذ الطبيعي:1.02

NewId() أبطأ بكثير من rand(checksum(*)), ، لذلك قد لا ترغب في استخدامه ضد مجموعات السجلات الكبيرة.

الاختيار مع البذور الأولية:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */

إذا كنت بحاجة إلى تحديد نفس المجموعة المعطاة للبذور، يبدو أن هذا يعمل.

في MSSQL (تم اختباره على 11.0.5569) باستخدام

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

أسرع بكثير من

SELECT TOP 100 * FROM employee ORDER BY NEWID()

في SQL Server، يمكنك دمج TABLESAMPLE مع NEWID() للحصول على عشوائية جيدة مع الحفاظ على السرعة.يعد هذا مفيدًا بشكل خاص إذا كنت تريد صفًا واحدًا فقط أو عددًا صغيرًا من الصفوف.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()
 SELECT * FROM table ORDER BY RAND() LIMIT 1

يجب أن أتفق مع CD-MaN:إن استخدام "ORDER BY RAND()" سيعمل بشكل جيد مع الجداول الصغيرة أو عندما تقوم بالتحديد عدة مرات فقط.

أستخدم أيضًا تقنية "num_value >= RAND() * ..."، وإذا كنت أرغب حقًا في الحصول على نتائج عشوائية، فلدي عمود "عشوائي" خاص في الجدول أقوم بتحديثه مرة واحدة يوميًا أو نحو ذلك.سيستغرق تشغيل UPDATE الفردي بعض الوقت (خاصة لأنه يجب أن يكون لديك فهرس في هذا العمود)، ولكنه أسرع بكثير من إنشاء أرقام عشوائية لكل صف في كل مرة يتم فيها تشغيل التحديد.

كن حذرًا لأن TableSample لا يُرجع فعليًا عينة عشوائية من الصفوف.فهو يوجه استعلامك للنظر في عينة عشوائية من الصفحات بحجم 8 كيلو بايت التي يتكون منها صفك.وبعد ذلك، يتم تنفيذ الاستعلام الخاص بك مقابل البيانات الموجودة في هذه الصفحات.ونظرًا لكيفية تجميع البيانات في هذه الصفحات (ترتيب الإدراج، وما إلى ذلك)، فقد يؤدي ذلك إلى بيانات ليست في الواقع عينة عشوائية.

يرى: http://www.mssqltips.com/tip.asp?tip=1308

تتضمن صفحة MSDN الخاصة بـ TableSample مثالاً لكيفية إنشاء عينة عشوائية من البيانات.

http://msdn.microsoft.com/en-us/library/ms189108.aspx

يبدو أن العديد من الأفكار المدرجة لا تزال تستخدم الترتيب

ومع ذلك، إذا كنت تستخدم جدولًا مؤقتًا، فيمكنك تعيين فهرس عشوائي (كما اقترحت العديد من الحلول)، ثم الحصول على الفهرس الأول الأكبر من رقم عشوائي بين 0 و1.

على سبيل المثال (لـ DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY

طريقة بسيطة وفعالة من http://akinas.com/pages/en/blog/mysql_random_row/

SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;

يوجد حل أفضل لـ Oracle بدلاً من استخدام dbms_random.value، بينما يتطلب فحصًا كاملاً لترتيب الصفوف حسب dbms_random.value وهو بطيء جدًا بالنسبة للجداول الكبيرة.

استخدم هذا بدلا من ذلك:

SELECT *
FROM employee sample(1)
WHERE rownum=1

بالنسبة لفايربيرد:

Select FIRST 1 column from table ORDER BY RAND()

مع SQL Server 2012+، يمكنك استخدام استعلام جلب الإزاحة للقيام بذلك لصف عشوائي واحد

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

حيث id هو عمود الهوية، وn هو الصف الذي تريده - يتم حسابه كرقم عشوائي بين 0 وcount()-1 من الجدول (الإزاحة 0 هي الصف الأول بعد كل شيء)

يعمل هذا مع الثغرات الموجودة في بيانات الجدول، طالما أن لديك فهرسًا للعمل معه من أجل جملة ORDER BY.كما أنه جيد جدًا بالنسبة للعشوائية - حيث تعمل على حل ذلك بنفسك لتمريره ولكن لا توجد صعوبات في الطرق الأخرى.بالإضافة إلى ذلك، فإن الأداء جيد جدًا، فهو يصمد جيدًا في مجموعة بيانات أصغر، على الرغم من أنني لم أجرب اختبارات أداء جادة مقابل عدة ملايين من الصفوف.

بالنسبة لـ SQL Server 2005 والإصدارات الأحدث، قم بتوسيع إجابة @GreyPanther للحالات التي يكون فيها num_value ليس لديه قيم مستمرة.يعمل هذا أيضًا في الحالات التي لا نقوم فيها بتوزيع مجموعات البيانات بالتساوي ومتى num_value ليس رقمًا ولكنه معرف فريد.

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)

وظيفة عشوائية من SQL يمكن أن تساعد.وأيضًا إذا كنت ترغب في الاقتصار على صف واحد فقط، فما عليك سوى إضافة ذلك في النهاية.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top