Frage

Wie wählen Sie zufällig eine Tabellenzeile in T-SQL basierend auf einem angelegten Gewicht für alle in Frage kommenden Zeilen?

Zum Beispiel habe ich eine Reihe von Zeilen in einer Tabelle mit 50 gewichtet, 25 und 25 (die zu 100 aufsummiert, aber muss nicht), und ich möchte einer von ihnen mit einem statistischen Ergebnis gleichwertig dem Zufallsprinzip auszuwählen auf das jeweilige Gewicht.

War es hilfreich?

Lösung

Dane Antwort enthält ein Selbst in einer Art und Weise verbindet, die ein Quadratgesetz einführt. (n*n/2) Reihen nach dem Join, wo es n Zeilen in der Tabelle.

Was wäre ideal ist in der Lage sein, nur die Tabelle zu analysieren einmal.

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

Dies wird den Tisch gehen, @id zu jeder id Wert Bilanz, während gleichzeitig die Einstellung @weight Punkt Erniedrigen. Schließlich wird die @weight_point negativ gehen. Dies bedeutet, dass die SUM alle vorhergehenden Gewichte größer ist als der zufällig ausgewählte Zielwert. Dies ist die Platte, die wir wollen, so von diesem Zeitpunkt an wir @id an sich (dabei bitte ignorieren IDs in der Tabelle) eingestellt.

Dies führt durch nur ein einziges Mal den Tisch, sondern durch die gesamte Tabelle muss laufen, auch wenn der gewählte Wert der erste Datensatz ist. Da die durchschnittliche Position auf halben Weg durch den Tisch (und weniger, wenn bestellt Gewicht aufsteigend) eine Schleife zu schreiben möglicherweise schneller sein könnte ... (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

Andere Tipps

Sie müssen einfach die Gewichte aller Kandidaten Zeilen zusammenzufassen, wählen Sie dann einen beliebigen Punkt innerhalb dieser Summe, wählen Sie dann den Eintrag, der mit diesem gewählten Punktkoordinaten (jeder Datensatz schrittweise ein Ansammeln Gewichtssumme mit ihm trägt).

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 = ROUND(((@weight_sum - 1) * RAND() + 1), 0)

SELECT TOP 1 @id = t1.id
FROM @table t1, @table t2
WHERE t1.id >= t2.id
GROUP BY t1.id
HAVING SUM(t2.weight) >= @weight_point
ORDER BY t1.id

SELECT @id

Die „schrittweise eine eine accumlating [sic] Gewichtssumme tragen“ Teil ist teuer, wenn Sie viele Datensätze haben. Wenn Sie bereits auch eine breite Palette von Partituren / Gewichte (dh: ist der Bereich breit genug, dass die meisten Datensätze Gewichte eindeutig sind 1-5 Sterne wahrscheinlich wäre es nicht schneiden.), Können Sie etwas tun einen Gewichtswert zu holen . Ich verwende hier VB.Net zu demonstrieren, aber dies leicht in reiner Sql als auch getan werden könnte:

Function PickScore()
    'Assume we have a database wrapper class instance called SQL and seeded a PRNG already
    'Get count of scores in database
    Dim ScoreCount As Double = SQL.ExecuteScalar("SELECT COUNT(score) FROM [MyTable]")
    ' You could also approximate this with just the number of records in the table, which might be faster.

    'Random number between 0 and 1 with ScoreCount possible values
    Dim rand As Double = Random.GetNext(ScoreCount) / ScoreCount

    'Use the equation y = 1 - x^3 to skew results in favor of higher scores
    ' For x between 0 and 1, y is also between 0 and 1 with a strong bias towards 1
    rand = 1 - (rand * rand * rand)

    'Now we need to map the (0,1] vector to [1,Maxscore].
    'Just find MaxScore and mutliply by rand
    Dim MaxScore As UInteger = SQL.ExecuteScalar("SELECT MAX(Score) FROM Songs")
    Return MaxScore * rand
End Function

Führen Sie diese und wählen Sie die Aufzeichnung mit der größten Punktzahl kleiner als das zurück Gewicht. Wenn mehr als ein Rekordanteil, der ein Tor, es zufällig auszuwählen. Die Vorteile hier sind, dass Sie alle Summen nicht halten müssen, und Sie können die Wahrscheinlichkeit Gleichung zwicken verwendet, um Ihren Geschmack. Aber noch einmal, es funktioniert am besten mit einer größeren Verbreitung von Partituren.

Die Art und Weise dies mit Zufallszahlen-Generatoren zu tun ist, um die probabiliity Dichtefunktion zu integrieren. Mit einer Reihe von diskreten Werten können Sie das Präfix Summe (Summe aller Werte bis zu diesem) berechnen und speichern. Damit wählen Sie die minumum Präfixsumme (Aggregat bis heute) Wert, der größer ist als die Zufallszahl.

Auf einer Datenbank die nachfolgenden Werte nach einer Einführungs aktualisiert werden müssen. Wenn die relative Häufigkeit der Updates und die Größe des Datensatzes nicht die Kosten machen diese unerschwinglich zu tun, bedeutet dies, dass der entsprechende Wert aus einem einzigen s-argable (Prädikat, das durch einen Index-Lookup aufgelöst werden kann) erhalten werden kann in Abfrage .

Wenn Sie tun müssen, um eine Gruppe von Proben erhalten (sagen wir, Sie wollen 50 Zeilen aus einer Sammlung von 5M Reihen abzutasten), wobei jede Zeile eine Spalte namens Weight hat, die eine int ist und wo größere Werte mehr Gewicht bedeutet, Sie können Sie diese Funktion:

SELECT * 
FROM 
(
    SELECT TOP 50 RowData, Weight 
    FROM MyTable 
    ORDER BY POWER(RAND(CAST(NEWID() AS VARBINARY)), (1.0/Weight)) DESC
) X 
ORDER BY Weight DESC

Der Schlüssel hier ist die POWER () Funktion, wie dargestellt hier mit

Ein Verweis auf die Wahl einer Zufallsfunktion ist hier und hier

Alternativ können Sie:

1.0 * ABS(CAST(CHECKSUM(NEWID()) AS bigint)) / CAST(0x7FFFFFFF AS INT) 

Sie werfen Prüfsumme als BIGINT statt INT wegen diese Ausgabe:

  

Da Prüfsumme gibt ein int, und der Bereich eines int -2 ^ 31   (-2.147.483.648) bis 2 ^ 31-1 (2,147,483,647), der abs () Funktion kann   einen Überlauffehler zurück, wenn das Ergebnis genau sein geschieht   -2.147.483.648! Die Chancen sind natürlich sehr gering, etwa 1 in 4 Milliarden, aber wir waren es über einen ~ 1.8b Reihe Tisch jeden Tag laufen,   so es geschieht etwa einmal pro Woche! Fix ist die Prüfsumme zu gieße   Bigint vor dem abs.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top