it-swarm.asia

ما هي أفضل طريقة للحصول على ترتيب عشوائي؟

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

أفهم أنه من المحتمل ألا يكون عشوائيًا "حقًا" ، كما أن العشوائية الزائفة جيدة بما يكفي لاحتياجاتي.

29
goric

ترتيب حسب NEWID () سيتم فرز السجلات بشكل عشوائي. مثال هنا

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()
23
Nomad

هذا سؤال قديم ، ولكن هناك جانب واحد من المناقشة مفقود ، في رأيي - الأداء. ORDER BY NewId() - الجواب العام. عندما يتوهم شخص ما ، يضيفون أنه يجب عليك حقاً التفاف NewID() في CheckSum() ، كما تعلمون ، من أجل الأداء!

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

enter image description here

لإعطائك فكرة عن كيفية هذا المقاييس ، سأعطيك مثالين من قاعدة بيانات أعمل معها.

  • TableA - يحتوي على 50000 صف عبر 2500 صفحة بيانات. يولد الاستعلام العشوائي 145 قراءة في 42 مللي ثانية.
  • الجدول ب - يحتوي على 1.2 مليون صف عبر 114000 صفحة بيانات. تشغيل Order By newid() على هذا الجدول يولد 53،700 قراءة ويستغرق 16 ثانية.

المعنوي للقصة هو أنه إذا كان لديك جداول كبيرة (فكر في مليارات الصفوف) أو كنت بحاجة إلى تشغيل هذا الاستعلام بشكل متكرر ، فإن طريقة newid() تنهار. إذن ماذا يفعل الصبي؟

مقابلة TABLESAMPLE ()

في SQL 2005 تم إنشاء قدرة جديدة تسمى TABLESAMPLE. لقد رأيت فقط مقال واحد يناقش استخدامه ... يجب أن يكون هناك المزيد. MSDN المستندات هنا . المثال الأول:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

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

فكيف استخدمه؟ حدد حجم مجموعة فرعية يغطي أكثر من عدد الصفوف التي تحتاجها ، ثم أضف Top(). الفكرة هي أنه يمكنك جعل جدولك الضخم يبدو أصغر سابق للفرز باهظ الثمن.

أنا شخصياً أستخدمه للحد فعلياً من حجم طاولتي. لذا في هذا الجدول الذي يعمل بالمليون صف top(20)...TABLESAMPLE(20 PERCENT) يسقط الاستعلام إلى 5600 قراءة في 1600 مللي ثانية. هناك أيضًا خيار REPEATABLE() حيث يمكنك تمرير "البذور" لاختيار الصفحة. هذا يجب أن يؤدي إلى اختيار عينة مستقرة.

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

16
EBarr

اقتراح براديب أديغا الأول ، ORDER BY NEWID() ، جيد وشيء استخدمته في الماضي لهذا السبب.

كن حذرًا عند استخدام Rand() - في العديد من السياقات يتم تنفيذه مرة واحدة فقط لكل عبارة ، لذا لن يكون لـ ORDER BY Rand() أي تأثير (حيث تحصل على نفس النتيجة من Rand () لكل صف).

على سبيل المثال:

SELECT display_name, Rand() FROM tr_person

إرجاع كل اسم من جدول الشخص ورقم "عشوائي" ، وهو نفسه لكل صف. يتغير الرقم في كل مرة تقوم فيها بتشغيل الاستعلام ، ولكنه نفس الرقم لكل صف في كل مرة.

لتوضيح أن نفس الشيء هو الحال مع Rand() المستخدم في بند ORDER BY ، أحاول:

SELECT display_name FROM tr_person ORDER BY Rand(), display_name

النتائج لا تزال مرتبة حسب الاسم الذي يشير إلى أن حقل الفرز السابق (الحقل المتوقع أن يكون عشوائيًا) ليس له أي تأثير لذا من المفترض دائمًا أن يكون له نفس القيمة.

الطلب بواسطة NEWID() يعمل على الرغم من ذلك ، لأنه إذا لم يكن NEWID () يعيد دائمًا إعادة النظر في الغرض من UUIDs فسيتم كسرها عند إدراج العديد من الجديد الصفوف في statemnt واحد مع معرفات فريدة لأنها مفتاح ، لذلك:

SELECT display_name FROM tr_person ORDER BY NEWID()

يرتب الأسماء "عشوائيًا".

DBMS أخرى

ما سبق ينطبق على MSSQL (2005 و 2008 على الأقل ، وإذا كنت أتذكر حقًا 2000 أيضًا). يجب أن يتم تقييم دالة UUID جديدة في كل مرة في جميع DBMSs NEWID () تحت MSSQL ولكن من الجدير التحقق من ذلك في الوثائق و/أو باختباراتك الخاصة. من المرجح أن يختلف سلوك وظائف النتائج التعسفية الأخرى ، مثل Rand () ، بين DBMSs ، لذا تحقق مرة أخرى من الوثائق.

كما رأيت أنه تم تجاهل الطلب من خلال قيم UUID في بعض السياقات حيث يفترض DB أن النوع ليس له ترتيب ذي معنى. إذا وجدت أن هذه هي الحالة التي يلقي بها UUID بشكل صريح إلى نوع سلسلة في جملة الطلب ، أو لف بعض الوظائف الأخرى حوله مثل CHECKSUM() في SQL Server (قد يكون هناك اختلاف طفيف في الأداء من هذا أيضًا سيتم إجراء الطلب على قيم 32 بت وليس 128 بت ، على الرغم من أن فائدة ذلك تفوق تكلفة تشغيل CHECKSUM() لكل قيمة أولاً سأتركك لاختبارها).

ملاحظة جانبية

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

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

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

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

الأداء

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

16
David Spillett

تحتوي العديد من الجداول على عمود معرّف رقمي مفهرس كثيف نسبيًا (عدد قليل من القيم المفقودة).

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

للتوضيح ، يختار الكود التالي 100 مستخدم عشوائي مختلف من جدول Stack Overflow للمستخدمين ، والذي يحتوي على 8،123،937 صفًا.

الخطوة الأولى هي تحديد نطاق قيم المعرفات ، وهي عملية فعالة بسبب المؤشر:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

Range query

تقرأ الخطة صفًا واحدًا من كل نهاية من الفهرس.

نقوم الآن بإنشاء 100 معرف عشوائي عشوائي في النطاق (مع صفوف متطابقة في جدول المستخدمين) وإرجاع هذه الصفوف:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

random rows query

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

 جدول "المستخدمون". مسح العد 1 ، يقرأ المنطقي 1937 ، يقرأ مادي 2 ، يقرأ القراءة 408 
 جدول "منضدة العمل". مسح عدد 0 ، قراءة منطقية 0 ، قراءة فعلية 0 ، قراءة قراءة 0 0 
 جدول "ملف العمل". مسح عدد 0 ، قراءة منطقية 0 ، قراءة فعلية 0 ، قراءة قراءة 0 
 
 أوقات تنفيذ SQL Server: 
 CPU CPU = 0 ms ، الوقت المنقضي = 9 مللي ثانية. 

جربه في Stack Exchange Data Explorer.

3
Paul White 9

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

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

إذا كان عليك تبديل مجموعة كبيرة من النتائج وتحديدها بعد ذلك ، فمن الأفضل استخدام SQL Server TABLESAMPLE في SQL Server بدلاً من وظيفة عشوائية في عبارة ORDER BY.

لذا ، بافتراض أن لدينا جدول قاعدة البيانات التالية:

enter image description here

والصفوف التالية في الجدول song:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

في SQL Server ، تحتاج إلى استخدام الوظيفة NEWID ، كما هو موضح في المثال التالي:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

عند تشغيل استعلام SQL المذكور على SQL Server ، سنحصل على مجموعة النتائج التالية:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

لاحظ أنه يتم سرد الأغاني بترتيب عشوائي ، وذلك بفضل استدعاء دالة NEWID المستخدمة في بند ORDER BY.

0
Vlad Mihalcea