Отговорът на Дейн включва самосъединяване по начин, който въвежда квадратен закон. (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))
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 WHERE weight = @next_weight
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))
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