2008-09-12 32 views
21

Làm thế nào để bạn chọn ngẫu nhiên một hàng của bảng trong T-SQL dựa trên trọng số được áp dụng cho tất cả các hàng ứng cử viên? Ví dụ, tôi có một tập hợp các hàng trong một bảng có trọng số là 50, 25 và 25 (có thể tăng lên đến 100 nhưng không cần) và tôi muốn chọn một trong số chúng một cách ngẫu nhiên với kết quả thống kê tương đương với trọng lượng tương ứng.Lựa chọn trọng số ngẫu nhiên trong T-SQL

Trả lời

15

Câu trả lời của Dane bao gồm tự tham gia theo cách giới thiệu một luật hình vuông. (n*n/2) hàng sau khi tham gia có n hàng trong bảng.

Điều lý tưởng hơn là có thể phân tích cú pháp bảng một lầ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 

này sẽ đi qua bàn, thiết @id giá trị id mỗi kỷ lục trong khi cùng một lúc decrementing @weight điểm. Cuối cùng, số @weight_point sẽ âm. Điều này có nghĩa là SUM của tất cả các trọng số trước lớn hơn giá trị đích được chọn ngẫu nhiên. Đây là bản ghi chúng tôi muốn, do đó, từ thời điểm đó trở đi, chúng tôi đặt @id cho chính nó (bỏ qua bất kỳ ID nào trong bảng).

Điều này chạy qua bảng chỉ một lần, nhưng không phải chạy qua toàn bộ bảng ngay cả khi giá trị đã chọn là bản ghi đầu tiên. Bởi vì vị trí trung bình là một nửa thông qua bảng (và ít hơn nếu được sắp xếp theo trọng lượng tăng dần) viết một vòng lặp có thể có thể nhanh hơn ... (Đặc biệt nếu trọng số là trong các nhóm chung):

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 
+0

Tôi đã thực hiện một số thử nghiệm thực nghiệm và phát hiện ra rằng giải pháp của bạn rất nhạy cảm với dữ liệu đầu vào. Dữ liệu thử nghiệm của tôi - trọng số: 2, 998, lần lặp: 1 triệu. Trọng lượng 2 nên được chọn khoảng 2k lần. Nếu thứ tự của các trọng số trong bảng tăng dần (2, 998), nó sẽ tăng trọng số 2 chỉ khoảng 500 lần. Nếu bạn đảo ngược thứ tự, nó khoảng 2500 lần. Nếu bạn thay đổi 'ROUND' thành' FLOOR', cho thứ tự tăng dần, nó chọn trọng số 2 khoảng 1000 lần, giảm dần khoảng 2000 lần. Và đó là xác suất thích hợp. Tôi đã cập nhật câu trả lời của bạn. –

+0

Tôi không chắc chắn, tại sao 'FLOOR' hoạt động tốt hơn' ROUND'. Với 'ROUND', nó sẽ lấy trọng số nhỏ quá ít lần (1/4 lần) với thứ tự tăng dần hoặc quá nhiều lần với thứ tự giảm dần. 'FLOOR' cũng chọn trọng lượng nhỏ quá ít lần (1/2 lần) với thứ tự tăng dần, nhưng với xác suất gần như lý tưởng khi trọng số được sắp xếp theo thứ tự giảm dần. –

+0

Tôi có phát điên không, hoặc không nên là người đầu tiên 'SELECT @row_count = COUNT (*) FROM @ table' có một' WHERE weight = @ next_weight' được thêm vào nó? Nếu không @weight_point sẽ luôn là 0 hoặc ít hơn đi vào kiểm tra vòng lặp và do đó giá trị hàng đầu sẽ luôn được chọn. – oflahero

7

Bạn chỉ cần cộng tổng trọng số của tất cả các hàng candiate, sau đó chọn một điểm ngẫu nhiên trong tổng đó, sau đó chọn bản ghi phối hợp với điểm đã chọn đó (mỗi bản ghi sẽ mang theo tổng trọng lượng tích lũy).

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

Tôi đã làm một nhỏ điểm chuẩn của giải pháp của bạn và MatBailie và có vẻ như bạn mất gấp đôi thời gian của Mat.Trên một bảng với 2 hàng và 1 milion lặp đi lặp lại, giải pháp của Mat mất khoảng 45 giây và giải pháp của bạn khoảng 85 giây. –

3

Các "từng bước mang theo một một accumlating [sic] trọng lượng tổng" phần là tốn kém nếu bạn có rất nhiều hồ sơ. Nếu bạn cũng đã có nhiều điểm số/trọng số (ví dụ: phạm vi đủ rộng để phần lớn trọng số bản ghi là duy nhất. 1-5 sao có thể sẽ không cắt nó), bạn có thể làm điều gì đó như thế này để chọn giá trị trọng số . Tôi đang sử dụng VB.Net đây để chứng minh, nhưng điều này có thể dễ dàng được thực hiện trong tinh khiết Sql cũng như:

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 

Run này, và chọn kỷ lục với sự lớn nhất điểm ít hơn so với trọng lượng trở lại. Nếu nhiều hơn một bản ghi chia sẻ điểm số đó, hãy chọn nó một cách ngẫu nhiên. Những lợi thế ở đây là bạn không phải duy trì bất kỳ khoản tiền nào, và bạn có thể tinh chỉnh phương trình xác suất được sử dụng để phù hợp với sở thích của bạn. Nhưng một lần nữa, nó hoạt động tốt nhất với phân phối điểm số lớn hơn.

2

Cách thực hiện điều này với trình tạo số ngẫu nhiên là tích hợp hàm mật độ xác suất. Với một tập hợp các giá trị rời rạc, bạn có thể tính tổng tiền tố (tổng của tất cả các giá trị lên đến giá trị này) và lưu trữ nó. Với điều này, bạn chọn giá trị tổng số tiền tố minumum (tổng hợp đến ngày) lớn hơn số ngẫu nhiên.

Trên cơ sở dữ liệu, các giá trị tiếp theo sau khi chèn phải được cập nhật. Nếu tần suất cập nhật và kích thước tương đối của tập dữ liệu không làm cho chi phí thực hiện điều này có nghĩa là giá trị thích hợp có thể thu được từ một truy vấn có thể được giải quyết bởi một truy vấn chỉ mục .

Các vấn đề liên quan