sql >> Base de Datos >  >> RDS >> Sqlserver

Elección aleatoria ponderada en T-SQL

La respuesta de Dane incluye un auto se une de una manera que introduce una ley cuadrática. (n*n/2) filas después de la unión donde hay n filas en la tabla.

Lo que sería más ideal es poder analizar la tabla una sola vez.

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

Esto pasará por la tabla, configurando @id al id de cada registro valor mientras que al mismo tiempo decrementa @weight punto. Eventualmente, el @weight_point saldrá negativo. Esto significa que el SUM de todos los pesos anteriores es mayor que el valor objetivo elegido al azar. Este es el registro que queremos, así que a partir de ese momento establecemos @id a sí mismo (ignorando cualquier ID en la tabla).

Esto se ejecuta a través de la tabla solo una vez, pero tiene que ejecutarse a través de toda la tabla, incluso si el valor elegido es el primer registro. Debido a que la posición promedio está en la mitad de la tabla (y menos si se ordena por peso ascendente), escribir un bucle posiblemente podría ser más rápido... (Especialmente si los pesos están en grupos comunes):

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