Dane's antwoord omvat een self joins op een manier die een kwadratische wet introduceert. (n*n/2)
rijen na de join waar er n rijen in de tabel zijn.
Wat ideaal zou zijn, is om de tabel maar één keer te kunnen ontleden.
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
Dit gaat door de tabel, instelling @id
naar de id
van elk record waarde terwijl tegelijkertijd @weight
wordt verlaagd punt. Uiteindelijk zal de @weight_point
negatief zal gaan. Dit betekent dat de SUM
van alle voorgaande gewichten groter is dan de willekeurig gekozen streefwaarde. Dit is het record dat we willen, dus vanaf dat moment stellen we @id
. in naar zichzelf (waarbij ID's in de tabel worden genegeerd).
Deze loopt maar één keer door de tabel, maar moet wel de hele tabel doorlopen, ook als de gekozen waarde het eerste record is. Omdat de gemiddelde positie halverwege de tabel is (en minder indien gerangschikt op oplopend gewicht), kan het schrijven van een lus mogelijk sneller zijn... (Vooral als de wegingen in gemeenschappelijke groepen zijn):
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