Danes Antwort beinhaltet ein Selbst-Joins in einer Weise, die ein quadratisches Gesetz einführt. (n*n/2)
Zeilen nach dem Join, wo sich n Zeilen in der Tabelle befinden.
Was wäre mehr ideal ist in der Lage, nur einmal den Tisch zu analysieren.
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
Die durch den Tisch gehen, @id
zu jeder id
Wert Bilanz, während gleichzeitig der Einstellung @weight
Punkt Erniedrigen. Schließlich wird die @weight_point
negativ werden. Dies bedeutet, dass der SUM
aller vorhergehenden Gewichte größer als der zufällig gewählte Zielwert ist. Dies ist der Datensatz, den wir wollen, und von diesem Punkt an setzen wir @id
auf sich selbst (ignorieren alle IDs in der Tabelle).
Dies läuft nur einmal durch die Tabelle, muss aber die gesamte Tabelle durchlaufen, auch wenn der gewählte Wert der erste Datensatz ist. Da die durchschnittliche Position ist halb durch die Tabelle (und weniger, wenn nach aufsteigendem Gewicht geordnet) könnte eine Schleife möglicherweise schneller sein ... (Vor allem, wenn die Gewichtungen in gemeinsamen Gruppen sind):
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
Ich habe einige empirische Tests durchgeführt und herausgefunden, dass Ihre Lösung sehr empfindlich auf Eingabedaten reagiert. Meine Testdaten - Gewichte: 2, 998, Iterationen: 1M. Gewicht 2 sollte etwa 2k mal aufgenommen werden. Wenn die Reihenfolge der Gewichte in der Tabelle aufsteigend ist (2, 998), nimmt sie das Gewicht 2 nur etwa 500 mal auf. Wenn Sie die Reihenfolge umkehren, sind es etwa 2500 Mal. Wenn Sie "ROUND" in "FLOOR" ändern, nimmt das Gewicht 2 in aufsteigender Reihenfolge etwa 1000 Mal, beim Absteigen etwa 2000 Mal auf. Und das ist die richtige Wahrscheinlichkeit. Ich habe deine Antwort aktualisiert. –
Ich bin mir einfach nicht sicher, warum der 'FLOOR' besser funktioniert als der' ROUND'. Mit der 'RUNDE' nimmt es das kleine Gewicht zu oft (1/4 mal) in aufsteigender Reihenfolge auf oder zu oft in absteigender Reihenfolge. Der "FLOOR" nimmt auch das kleine Gewicht zu oft (1/2 Mal) in aufsteigender Reihenfolge auf, aber mit nahezu idealer Wahrscheinlichkeit, wenn die Gewichte in absteigender Reihenfolge sortiert werden. –
Werde ich verrückt, oder sollte der erste 'SELECT @row_count = COUNT (*) FROM @ table' ein' WHERE Gewicht = @ next_weight' angehängt haben? Andernfalls wird @weight_point immer 0 oder weniger sein, um in den Loop-Check zu gehen. Daher wird immer der oberste Wert ausgewählt. – oflahero