Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Choix pondéré aléatoire dans T-SQL

La réponse de Dane inclut une auto-jointure d'une manière qui introduit une loi carrée. (n*n/2) lignes après la jointure où il y a n lignes dans la table.

L'idéal serait de pouvoir analyser la table une seule fois.

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

Cela passera par le tableau, en définissant @id à l'id de chaque enregistrement value tout en décrémentant @weight indiquer. Finalement, le @weight_point deviendra négatif. Cela signifie que la SUM de tous les poids précédents est supérieur à la valeur cible choisie au hasard. C'est l'enregistrement que nous voulons, donc à partir de ce moment, nous définissons @id à lui-même (en ignorant tous les ID dans le tableau).

Cela parcourt la table une seule fois, mais doit parcourir toute la table même si la valeur choisie est le premier enregistrement. Parce que la position moyenne est à mi-chemin dans le tableau (et moins si ordonnée par poids croissant), l'écriture d'une boucle pourrait éventuellement être plus rapide... (surtout si les pondérations sont dans des groupes communs) :

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