Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Zufällige gewichtete Auswahl in T-SQL

Danes Antwort beinhaltet eine Selbstverknüpfung auf eine Weise, die ein quadratisches Gesetz einführt. (n*n/2) Zeilen nach dem Join, wo es n Zeilen in der Tabelle gibt.

Idealer wäre es, die Tabelle nur einmal parsen zu können.

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

Dies geht durch die Tabelle und setzt @id zur id jedes Datensatzes Wert, während gleichzeitig @weight dekrementiert wird Punkt. Schließlich der @weight_point wird negativ werden. Das bedeutet, dass die SUM aller vorangehenden Gewichte größer ist als der zufällig gewählte Zielwert. Das ist der gewünschte Datensatz, also setzen wir von diesem Punkt an @id zu sich selbst (ignoriert alle IDs in der Tabelle).

Dies durchläuft die Tabelle nur einmal, muss aber die gesamte Tabelle durchlaufen, auch wenn der ausgewählte Wert der erste Datensatz ist. Da die durchschnittliche Position auf halbem Weg durch die Tabelle liegt (und weniger, wenn nach aufsteigender Gewichtung geordnet), könnte das Schreiben einer Schleife möglicherweise schneller sein ... (insbesondere wenn die Gewichtungen in gemeinsamen Gruppen liegen):

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