سؤال

كيف يمكنك تحديد صف جدول بشكل عشوائي في T-SQL بناءً على الوزن المطبق لجميع الصفوف المرشحة؟

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

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

المحلول

تشتمل إجابة Dane على انضمام الذات بطريقة تقدم قانونًا مربعًا. (n*n/2) الصفوف بعد الصلة حيث يوجد صفوف n في الجدول.

ما سيكون أكثر مثالية هو أن تكون قادرًا على تحليل الجدول مرة واحدة فقط.

DECLARE @id int, @weight_sum int, @weight_point int
DECLARE @table TABLE (id int, weight int)

INSERT INTO @table(id, weight) VALUES(1, 50)
INSERT INTO @table(id, weight) VALUES(2, 25)
INSERT INTO @table(id, weight) VALUES(3, 25)

SELECT @weight_sum = SUM(weight)
FROM @table

SELECT @weight_point = FLOOR(((@weight_sum - 1) * RAND() + 1), 0)

SELECT
    @id = CASE WHEN @weight_point < 0 THEN @id ELSE [table].id END,
    @weight_point = @weight_point - [table].weight
FROM
    @table [table]
ORDER BY
    [table].Weight DESC

وهذا سوف يمر عبر الجدول، والإعداد @id لكل سجل id القيمة وفي نفس الوقت تتناقص @weight نقطة.في نهاية المطاف، @weight_point سوف تصبح سلبية.وهذا يعني أن SUM لجميع الأوزان السابقة أكبر من القيمة المستهدفة التي تم اختيارها عشوائيًا.هذا هو الرقم القياسي الذي نريده، لذا من تلك النقطة فصاعدًا قمنا بوضعه @id لنفسه (تجاهل أي معرفات في الجدول).

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

DECLARE @id int, @weight_sum int, @weight_point int, @next_weight int, @row_count int
DECLARE @table TABLE (id int, weight int)

INSERT INTO @table(id, weight) VALUES(1, 50)
INSERT INTO @table(id, weight) VALUES(2, 25)
INSERT INTO @table(id, weight) VALUES(3, 25)

SELECT @weight_sum = SUM(weight)
FROM @table

SELECT @weight_point = ROUND(((@weight_sum - 1) * RAND() + 1), 0)

SELECT @next_weight = MAX(weight) FROM @table
SELECT @row_count   = COUNT(*)    FROM @table
SET @weight_point = @weight_point - (@next_weight * @row_count)

WHILE (@weight_point > 0)
BEGIN
    SELECT @next_weight = MAX(weight) FROM @table WHERE weight < @next_weight
    SELECT @row_count   = COUNT(*)    FROM @table WHERE weight = @next_weight
    SET @weight_point = @weight_point - (@next_weight * @row_count)
END

-- # Once the @weight_point is less than 0, we know that the randomly chosen record
-- # is in the group of records WHERE [table].weight = @next_weight

SELECT @row_count = FLOOR(((@row_count - 1) * RAND() + 1), 0)

SELECT
    @id = CASE WHEN @row_count < 0 THEN @id ELSE [table].id END,
    @row_count = @row_count - 1
FROM
    @table [table]
WHERE
    [table].weight = @next_weight
ORDER BY
    [table].Weight DESC

نصائح أخرى

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

DECLARE @id int, @weight_sum int, @weight_point int
DECLARE @table TABLE (id int, weight int)

INSERT INTO @table(id, weight) VALUES(1, 50)
INSERT INTO @table(id, weight) VALUES(2, 25)
INSERT INTO @table(id, weight) VALUES(3, 25)

SELECT @weight_sum = SUM(weight)
FROM @table

SELECT @weight_point = ROUND(((@weight_sum - 1) * RAND() + 1), 0)

SELECT TOP 1 @id = t1.id
FROM @table t1, @table t2
WHERE t1.id >= t2.id
GROUP BY t1.id
HAVING SUM(t2.weight) >= @weight_point
ORDER BY t1.id

SELECT @id

ال "تحمل بشكل متزايد مجموع الوزن [كذا]" الجزء مكلف إذا كان لديك الكثير من السجلات.إذا كان لديك بالفعل نطاق واسع من الدرجات/الأوزان (على سبيل المثال:النطاق واسع بما يكفي بحيث تكون معظم الأوزان القياسية فريدة من نوعها.من 1 إلى 5 نجوم ربما لن تكفي)، يمكنك القيام بشيء كهذا لاختيار قيمة الوزن.أنا أستخدم VB.Net هنا للتوضيح، ولكن يمكن القيام بذلك بسهولة باستخدام لغة Sql أيضًا:

Function PickScore()
    'Assume we have a database wrapper class instance called SQL and seeded a PRNG already
    'Get count of scores in database
    Dim ScoreCount As Double = SQL.ExecuteScalar("SELECT COUNT(score) FROM [MyTable]")
    ' You could also approximate this with just the number of records in the table, which might be faster.

    'Random number between 0 and 1 with ScoreCount possible values
    Dim rand As Double = Random.GetNext(ScoreCount) / ScoreCount

    'Use the equation y = 1 - x^3 to skew results in favor of higher scores
    ' For x between 0 and 1, y is also between 0 and 1 with a strong bias towards 1
    rand = 1 - (rand * rand * rand)

    'Now we need to map the (0,1] vector to [1,Maxscore].
    'Just find MaxScore and mutliply by rand
    Dim MaxScore As UInteger = SQL.ExecuteScalar("SELECT MAX(Score) FROM Songs")
    Return MaxScore * rand
End Function

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

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

في قاعدة البيانات، يجب تحديث القيم اللاحقة بعد الإدراج.إذا كان التكرار النسبي للتحديثات وحجم مجموعة البيانات لا يجعل تكلفة القيام بذلك باهظة، فهذا يعني أنه يمكن الحصول على القيمة المناسبة من استعلام واحد s-argable (مسند يمكن حله عن طريق بحث فهرس) .

إذا كنت بحاجة إلى الحصول على مجموعة من العينات (على سبيل المثال، تريد أخذ عينات من 50 صفًا من مجموعة مكونة من 5 ملايين صف) حيث يحتوي كل صف على عمود يسمى Weight وهو int وحيثما تعني القيم الأكبر وزنًا أكبر، يمكنك استخدام هذه الوظيفة:

SELECT * 
FROM 
(
    SELECT TOP 50 RowData, Weight 
    FROM MyTable 
    ORDER BY POWER(RAND(CAST(NEWID() AS VARBINARY)), (1.0/Weight)) DESC
) X 
ORDER BY Weight DESC

المفتاح هنا هو استخدام الدالة POWER() كما هو موضح هنا

مرجع على اختيار وظيفة عشوائية هنا و هنا

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

1.0 * ABS(CAST(CHECKSUM(NEWID()) AS bigint)) / CAST(0x7FFFFFFF AS INT) 

لقد قمت بإلقاء المجموع الاختباري كـ BIGINT بدلاً من INT بسبب هذا مشكلة:

نظرًا لأن checksum إرجاع int ، ونطاق int IS -2^31 (-2،147،483،648) إلى 2^31-1 (2،147،483،647) ، يمكن أن تُرجع وظيفة ABS () خطأ في التدفق إذا حدثت النتيجة تمامًا -2،147،483،648 !من الواضح أن الفرص منخفضة للغاية ، حوالي 1 من كل 4 مليارات ، ومع ذلك كنا نديرها على طاولة صف ~ 1.8b كل يوم ، لذلك كان يحدث مرة واحدة في الأسبوع!الإصلاح هو إلقاء الفحص إلى Bigint قبل ABS.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top