La respuesta de Dane incluye una auto unión de una manera que introduce una ley cuadrada. (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), 0)
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á a través de la mesa, el establecimiento de @id
al valor de cada registro id
mientras que al mismo tiempo disminuyendo @weight
punto. Eventualmente, el @weight_point
será 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
en sí mismo (ignorando cualquier ID en la tabla).
Esto se ejecuta en la tabla solo una vez, pero tiene que ejecutarse en toda la tabla incluso si el valor elegido es el primer registro. Debido a que la posición promedio está a la mitad de la tabla (y menos si se ordena por peso ascendente) escribir un bucle podría ser más rápido ... (Especialmente si las ponderaciones 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
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), 0)
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
Hice algunas pruebas empíricas y descubrí que su solución es muy sensible en los datos de entrada. Mis datos de prueba - pesos: 2, 998, iteraciones: 1M. El peso 2 debe tomarse unas 2 veces. Si el orden de los pesos en la tabla es ascendente (2, 998), está recogiendo el peso 2 casi 500 veces. Si invierte la orden, es alrededor de 2500 veces. Si cambia 'ROUND' por' FLOOR', por orden ascendente, levanta el peso 2 aproximadamente 1000 veces, para descender unas 2000 veces. Y esa es la probabilidad adecuada. He actualizado tu respuesta. –
No estoy seguro, por qué el 'FLOOR' funciona mejor que el' ROUND'. Con el 'ROUND', está recogiendo el pequeño peso muy pocas veces (1/4 veces) con el orden ascendente o demasiadas veces con el orden descendente. El 'PISO 'también está recogiendo el pequeño peso muy pocas veces (1/2 veces) con el orden ascendente, pero con probabilidad casi ideal cuando los pesos se ordenan en orden descendente. –
¿Me estoy volviendo loco, o no debería el primer 'SELECT @row_count = COUNT (*) FROM @ table' tener un' WHERE weight = @ next_weight' anexado a él? De lo contrario, @weight_point siempre tendrá 0 o menos en la verificación de bucle, por lo que siempre se seleccionará el valor superior. – oflahero