Wie kann ich in reinem SQL eine zufällige Zeile anfordern (oder so nah wie möglich an einer wirklich zufälligen Zeile)?
Wie kann ich in reinem SQL eine zufällige Zeile anfordern (oder so nah wie möglich an einer wirklich zufälligen Zeile)?
Antworten:
Siehe diesen Beitrag: SQL zum Auswählen einer zufälligen Zeile aus einer Datenbanktabelle . Hierzu werden Methoden in MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 und Oracle beschrieben (Folgendes wird von diesem Link kopiert):
Wählen Sie mit MySQL eine zufällige Zeile aus:
SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Wählen Sie mit PostgreSQL eine zufällige Zeile aus:
SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Wählen Sie mit Microsoft SQL Server eine zufällige Zeile aus:
SELECT TOP 1 column FROM table
ORDER BY NEWID()
Wählen Sie mit IBM DB2 eine zufällige Zeile aus
SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Wählen Sie mit Oracle einen zufälligen Datensatz aus:
SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
order by rand()
Verlassen auf oder Äquivalente in allen dbs: |. auch hier erwähnt .
ORDER BY RAND()
falsch ist ...
O(n)
mit n
der Anzahl der Datensätze in der Tabelle. Stellen Sie sich vor, Sie haben 1 Million Datensätze. Möchten Sie wirklich 1 Million Zufallszahlen oder eindeutige IDs generieren? Ich würde das lieber verwenden COUNT()
und in einen neuen LIMIT
Ausdruck mit einer einzelnen Zufallszahl einbeziehen .
Lösungen wie Jeremies:
SELECT * FROM table ORDER BY RAND() LIMIT 1
funktionieren, aber sie benötigen einen sequentiellen Scan der gesamten Tabelle (da der jeder Zeile zugeordnete Zufallswert berechnet werden muss, damit der kleinste ermittelt werden kann), was selbst für mittelgroße Tabellen recht langsam sein kann. Meine Empfehlung wäre, eine Art indizierte numerische Spalte zu verwenden (viele Tabellen haben diese als Primärschlüssel) und dann etwas zu schreiben wie:
SELECT * FROM table WHERE num_value >= RAND() *
( SELECT MAX (num_value ) FROM table )
ORDER BY num_value LIMIT 1
Dies funktioniert in logarithmischer Zeit, unabhängig von der Tabellengröße, wenn num_value
indiziert. Eine Einschränkung: Dies setzt voraus, dass die num_value
Verteilung im Bereich gleichmäßig ist 0..MAX(num_value)
. Wenn Ihr Datensatz stark von dieser Annahme abweicht, erhalten Sie verzerrte Ergebnisse (einige Zeilen werden häufiger angezeigt als andere).
Ich weiß nicht, wie effizient das ist, aber ich habe es schon einmal benutzt:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
Da GUIDs ziemlich zufällig sind, bedeutet die Reihenfolge, dass Sie eine zufällige Zeile erhalten.
ORDER BY RAND() LIMIT 1
TOP 1
und verwendet newid()
.
ORDER BY NEWID()
nimmt 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
nimmt 0.0065 milliseconds
!
Ich werde definitiv mit letzterer Methode gehen.
rand()
eine Gleitkommazahl zurück, n
wobei 0 < n < 1
. Angenommen, es num_value
handelt sich um eine Ganzzahl, wird der Rückgabewert von rand() * max(num_value)
auch zu einer Ganzzahl gezwungen, wodurch alles nach dem Dezimalpunkt abgeschnitten wird. Daher rand() * max(num_value)
wird immer kleiner als sein max(num_value)
, weshalb die letzte Zeile niemals ausgewählt wird.
Sie haben nicht angegeben, welchen Server Sie verwenden. In älteren Versionen von SQL Server können Sie Folgendes verwenden:
select top 1 * from mytable order by newid()
In SQL Server 2005 und höher können Sie TABLESAMPLE
eine zufällige Stichprobe abrufen, die wiederholbar ist:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
Für SQL Server
newid () / order by funktioniert, ist jedoch für große Ergebnismengen sehr teuer, da für jede Zeile eine ID generiert und anschließend sortiert werden muss.
TABLESAMPLE () ist vom Standpunkt der Leistung aus gut, aber Sie erhalten eine Zusammenfassung der Ergebnisse (alle Zeilen auf einer Seite werden zurückgegeben).
Für eine bessere echte Zufallsstichprobe ist es am besten, Zeilen zufällig herauszufiltern. Ich habe das folgende Codebeispiel im SQL Server Books Online-Artikel Einschränken von Ergebnismengen mithilfe von TABLESAMPLE gefunden :
Wenn Sie wirklich eine zufällige Stichprobe einzelner Zeilen wünschen, ändern Sie Ihre Abfrage so, dass Zeilen zufällig herausgefiltert werden, anstatt TABLESAMPLE zu verwenden. In der folgenden Abfrage wird beispielsweise die NEWID-Funktion verwendet, um ungefähr ein Prozent der Zeilen der Sales.SalesOrderDetail-Tabelle zurückzugeben:
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
Die SalesOrderID-Spalte ist im CHECKSUM-Ausdruck enthalten, sodass NEWID () einmal pro Zeile ausgewertet wird, um eine Stichprobenauswahl pro Zeile zu erzielen. Der Ausdruck CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) ergibt einen zufälligen Float-Wert zwischen 0 und 1.
Wenn ich gegen eine Tabelle mit 1.000.000 Zeilen laufe, sind hier meine Ergebnisse:
SET STATISTICS TIME ON
SET STATISTICS IO ON
/* newid()
rows returned: 10000
logical reads: 3359
CPU time: 3312 ms
elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()
/* TABLESAMPLE
rows returned: 9269 (varies)
logical reads: 32
CPU time: 0 ms
elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)
/* Filter
rows returned: 9994 (varies)
logical reads: 3359
CPU time: 641 ms
elapsed time: 627 ms
*/
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
Wenn Sie mit TABLESAMPLE durchkommen, erhalten Sie die beste Leistung. Verwenden Sie andernfalls die Methode newid () / filter. newid () / order by sollte der letzte Ausweg sein, wenn Sie eine große Ergebnismenge haben.
Verwenden Sie nach Möglichkeit gespeicherte Anweisungen, um die Ineffizienz beider Indizes für RND () zu vermeiden und ein Datensatznummernfeld zu erstellen.
PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?, 1"; SET @ n = FLOOR (RAND () * (SELECT COUNT (*) FROM Tabelle)); EXECUTE RandomRecord USING @n;
Der beste Weg ist, einen zufälligen Wert nur zu diesem Zweck in eine neue Spalte einzufügen und so etwas zu verwenden (Pseude-Code + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
Dies ist die Lösung, die vom MediaWiki-Code verwendet wird. Natürlich gibt es eine gewisse Tendenz gegenüber kleineren Werten, aber sie stellten fest, dass es ausreichend war, den Zufallswert auf Null zu setzen, wenn keine Zeilen abgerufen wurden.
Für die Lösung newid () ist möglicherweise ein vollständiger Tabellenscan erforderlich, damit jeder Zeile eine neue Guid zugewiesen werden kann, die viel weniger leistungsfähig ist.
Die Lösung von rand () funktioniert möglicherweise überhaupt nicht (dh mit MSSQL), da die Funktion nur einmal ausgewertet wird und jeder Zeile dieselbe "Zufallszahl" zugewiesen wird.
Wenn wir für SQL Server 2005 und 2008 eine zufällige Stichprobe einzelner Zeilen (aus Books Online ) wünschen :
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Wenn Sie RAND () verwenden, da dies nicht empfohlen wird , erhalten Sie möglicherweise einfach die maximale ID (= Max):
SELECT MAX(ID) FROM TABLE;
Holen Sie sich einen Zufall zwischen 1..Max (= My_Generated_Random)
My_Generated_Random = rand_in_your_programming_lang_function(1..Max);
und führen Sie dann diese SQL aus:
SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1
Beachten Sie, dass nach Zeilen gesucht wird, deren IDs gleich oder höher als der ausgewählte Wert sind. Es ist auch möglich, nach der Zeile in der Tabelle zu suchen und eine gleiche oder niedrigere ID als My_Generated_Random zu erhalten. Ändern Sie dann die Abfrage wie folgt:
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
Wie in @ BillKarwins Kommentar zu @ cnus Antwort ausgeführt ...
Beim Kombinieren mit einem LIMIT habe ich festgestellt, dass es (zumindest mit PostgreSQL 9.1) viel besser funktioniert, sich einer zufälligen Reihenfolge anzuschließen, als die tatsächlichen Zeilen direkt zu ordnen: z
SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
FROM tbl_post
WHERE create_time >= 1349928000
) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100
Stellen Sie einfach sicher, dass das 'r' für jeden möglichen Schlüsselwert in der komplexen Abfrage, die damit verbunden ist, einen 'rand'-Wert generiert, aber beschränken Sie die Anzahl der Zeilen von' r 'nach Möglichkeit.
Das CAST als Ganzzahl ist besonders hilfreich für PostgreSQL 9.2, das eine spezifische Sortieroptimierung für Floating-Typen mit Ganzzahl und einfacher Genauigkeit bietet.
Die meisten Lösungen hier zielen darauf ab, das Sortieren zu vermeiden, müssen jedoch noch einen sequentiellen Scan über eine Tabelle durchführen.
Es gibt auch eine Möglichkeit, den sequentiellen Scan zu vermeiden, indem Sie zum Index-Scan wechseln. Wenn Sie den Indexwert Ihrer zufälligen Zeile kennen, können Sie das Ergebnis fast augenblicklich erhalten. Das Problem ist - wie man einen Indexwert errät.
Die folgende Lösung funktioniert unter PostgreSQL 8.4:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
limit 1;
In der obigen Lösung erraten Sie 10 verschiedene zufällige Indexwerte aus dem Bereich 0 .. [letzter Wert von id].
Die Zahl 10 ist willkürlich - Sie können 100 oder 1000 verwenden, da dies (erstaunlicherweise) keinen großen Einfluss auf die Reaktionszeit hat.
Es gibt auch ein Problem: Wenn Sie spärliche IDs haben, werden Sie diese möglicherweise übersehen . Die Lösung besteht darin , einen Sicherungsplan zu haben :) In diesem Fall eine reine alte Bestellung per zufälliger () Abfrage. Wenn die kombinierte ID so aussieht:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
union all (select * from cms_refs order by random() limit 1)
limit 1;
Nicht die Union ALL- Klausel. In diesem Fall wird der zweite Teil NIEMALS ausgeführt, wenn der erste Teil Daten zurückgibt!
Spät, aber über Google hierher gekommen, werde ich der Nachwelt halber eine alternative Lösung hinzufügen.
Ein anderer Ansatz besteht darin, TOP zweimal mit abwechselnden Bestellungen zu verwenden. Ich weiß nicht, ob es sich um "reines SQL" handelt, da es eine Variable im TOP verwendet, aber es funktioniert in SQL Server 2008. Hier ist ein Beispiel, das ich für eine Tabelle mit Wörterbuchwörtern verwende, wenn ich ein zufälliges Wort möchte.
SELECT TOP 1
word
FROM (
SELECT TOP(@idx)
word
FROM
dbo.DictionaryAbridged WITH(NOLOCK)
ORDER BY
word DESC
) AS D
ORDER BY
word ASC
Natürlich ist @idx eine zufällig generierte Ganzzahl, die in der Zieltabelle einschließlich von 1 bis COUNT (*) reicht. Wenn Ihre Spalte indiziert ist, profitieren Sie auch davon. Ein weiterer Vorteil ist, dass Sie es in einer Funktion verwenden können, da NEWID () nicht zulässig ist.
Schließlich wird die obige Abfrage in etwa 1/10 der Ausführungszeit einer NEWID () - Abfrage in derselben Tabelle ausgeführt. YYMV.
Sie können auch versuchen, die new id()
Funktion zu verwenden.
Schreiben Sie einfach Ihre Anfrage und verwenden Sie die Reihenfolge nach new id()
Funktion. Es ist ziemlich zufällig.
Damit MySQL zufällige Aufzeichnungen erhält
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1
Weitere Details http://jan.kneschke.de/projects/mysql/order-by-rand/
Ich habe diese Variation in den Antworten noch nicht ganz gesehen. Ich hatte eine zusätzliche Einschränkung, bei der ich bei einem anfänglichen Startwert jedes Mal denselben Satz von Zeilen auswählen musste.
Für MS SQL:
Minimales Beispiel:
select top 10 percent *
from table_name
order by rand(checksum(*))
Normalisierte Ausführungszeit: 1,00
NewId () Beispiel:
select top 10 percent *
from table_name
order by newid()
Normalisierte Ausführungszeit: 1.02
NewId()
ist unwesentlich langsamer als rand(checksum(*))
, daher möchten Sie es möglicherweise nicht für große Datensatzgruppen verwenden.
Auswahl mit Initial Seed:
declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */
select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */
Wenn Sie denselben Satz für einen Startwert auswählen müssen, scheint dies zu funktionieren.
In MSSQL (getestet am 11.0.5569) mit
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
ist deutlich schneller als
SELECT TOP 100 * FROM employee ORDER BY NEWID()
In SQL Server können Sie TABLESAMPLE mit NEWID () kombinieren, um eine ziemlich gute Zufälligkeit zu erzielen und trotzdem Geschwindigkeit zu haben. Dies ist besonders nützlich, wenn Sie wirklich nur 1 oder eine kleine Anzahl von Zeilen möchten.
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
Mit SQL Server 2012+ können Sie die OFFSET FETCH-Abfrage verwenden , um dies für eine einzelne zufällige Zeile zu tun
select * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY
Dabei ist id eine Identitätsspalte und n die gewünschte Zeile - berechnet als Zufallszahl zwischen 0 und count () - 1 der Tabelle (Offset 0 ist schließlich die erste Zeile).
Dies funktioniert mit Löchern in den Tabellendaten, solange Sie einen Index für die ORDER BY-Klausel haben. Es ist auch sehr gut für die Zufälligkeit - wenn Sie das selbst herausarbeiten, um es weiterzugeben, aber die Probleme bei anderen Methoden sind nicht vorhanden. Außerdem ist die Leistung ziemlich gut, bei einem kleineren Datensatz hält sie gut, obwohl ich keine ernsthaften Leistungstests für mehrere Millionen Zeilen ausprobiert habe.
SELECT * FROM table ORDER BY RAND() LIMIT 1
Ich muss CD-MaN zustimmen: Die Verwendung von "ORDER BY RAND ()" funktioniert gut für kleine Tabellen oder wenn Sie SELECT nur einige Male ausführen.
Ich verwende auch die Technik "num_value> = RAND () * ...", und wenn ich wirklich zufällige Ergebnisse erzielen möchte, habe ich eine spezielle "zufällige" Spalte in der Tabelle, die ich etwa einmal am Tag aktualisiere. Dieser einzelne UPDATE-Lauf dauert einige Zeit (insbesondere, weil Sie einen Index für diese Spalte benötigen), ist jedoch viel schneller als das Erstellen von Zufallszahlen für jede Zeile bei jedem Ausführen der Auswahl.
Seien Sie vorsichtig, da TableSample keine zufällige Stichprobe von Zeilen zurückgibt. Es leitet Ihre Abfrage an, eine zufällige Stichprobe der 8-KB-Seiten zu betrachten, aus denen Ihre Zeile besteht. Anschließend wird Ihre Abfrage anhand der auf diesen Seiten enthaltenen Daten ausgeführt. Aufgrund der Gruppierung von Daten auf diesen Seiten (Einfügereihenfolge usw.) kann dies zu Daten führen, die eigentlich keine Zufallsstichprobe sind.
Siehe: http://www.mssqltips.com/tip.asp?tip=1308
Diese MSDN-Seite für TableSample enthält ein Beispiel für die Generierung einer tatsächlich zufälligen Stichprobe von Daten.
Es scheint, dass viele der aufgelisteten Ideen immer noch die Reihenfolge verwenden
Wenn Sie jedoch eine temporäre Tabelle verwenden, können Sie einen zufälligen Index zuweisen (wie viele der Lösungen vorgeschlagen haben) und dann den ersten Index abrufen, der größer als eine beliebige Zahl zwischen 0 und 1 ist.
Zum Beispiel (für DB2):
WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
Ein einfacher und effizienter Weg von http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
Es gibt eine bessere Lösung für Oracle, anstatt dbms_random.value zu verwenden, während ein vollständiger Scan erforderlich ist, um Zeilen nach dbms_random.value zu ordnen, und es ist für große Tabellen ziemlich langsam.
Verwenden Sie stattdessen Folgendes:
SELECT *
FROM employee sample(1)
WHERE rownum=1
Erweitern Sie für SQL Server 2005 und höher die Antwort von @ GreyPanther für Fälle, in denen num_value
keine kontinuierlichen Werte vorhanden sind. Dies funktioniert auch in Fällen, in denen wir Datensätze nicht gleichmäßig verteilt haben und in denen num_value
es sich nicht um eine Zahl, sondern um eine eindeutige Kennung handelt.
WITH CTE_Table (SelRow, num_value)
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
)
SELECT * FROM table Where num_value = (
SELECT TOP 1 num_value FROM CTE_Table WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Zufällige Funktionen aus dem SQL könnten helfen. Auch wenn Sie sich auf nur eine Zeile beschränken möchten, fügen Sie diese am Ende hinzu.
SELECT column FROM table
ORDER BY RAND()
LIMIT 1