Angesichts Ihrer Spezifikationen (plus zusätzliche Informationen in den Kommentaren),
- Sie haben eine numerische ID-Spalte (Ganzzahlen) mit nur wenigen (oder mäßig wenigen) Lücken.
- Offensichtlich keine oder wenige Schreibvorgänge.
- Ihre ID-Spalte muss indiziert werden! Ein Primärschlüssel dient gut.
Die folgende Abfrage erfordert keinen sequentiellen Scan der großen Tabelle, sondern nur einen Index-Scan.
Erhalten Sie zunächst Schätzungen für die Hauptabfrage:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Der einzig möglicherweise teure Teil ist der count(*)
(für große Tische). Angesichts der oben genannten Spezifikationen benötigen Sie es nicht. Ein Kostenvoranschlag reicht völlig aus und ist fast kostenlos erhältlich ( ausführliche Erklärung hier ):
SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
Solange ct
nicht viel kleiner als id_span
, wird die Abfrage andere Ansätze übertreffen.
WITH params AS (
SELECT 1 AS min_id -- minimum id <= current min id
, 5100000 AS id_span -- rounded up. (max_id - min_id + buffer)
)
SELECT *
FROM (
SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
FROM params p
,generate_series(1, 1100) g -- 1000 + buffer
GROUP BY 1 -- trim duplicates
) r
JOIN big USING (id)
LIMIT 1000; -- trim surplus
Generieren Sie Zufallszahlen im id
Raum. Sie haben "wenige Lücken", also addieren Sie 10% (genug, um die Leerzeichen leicht zu bedecken) zur Anzahl der abzurufenden Zeilen.
Jeder id
kann mehrmals zufällig ausgewählt werden (obwohl dies mit einem großen ID-Bereich sehr unwahrscheinlich ist). Gruppieren Sie daher die generierten Zahlen (oder verwenden Sie sie DISTINCT
).
Verbinden Sie die id
s mit dem großen Tisch. Dies sollte sehr schnell sein, wenn der Index vorhanden ist.
Zum Schluss Überschüsse id
abschneiden, die nicht von Betrügern und Lücken gefressen wurden. Jede Reihe hat die gleiche Chance, ausgewählt zu werden.
Kurzfassung
Sie können diese Abfrage vereinfachen . Der CTE in der obigen Abfrage dient nur zu Bildungszwecken:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Mit rCTE verfeinern
Vor allem, wenn Sie sich bei Lücken und Schätzungen nicht so sicher sind.
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM generate_series(1, 1030) -- 1000 + few percent - adapt to your needs
LIMIT 1030 -- hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
UNION -- eliminate dupe
SELECT b.*
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM random_pick r -- plus 3 percent - adapt to your needs
LIMIT 999 -- less than 1000, hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
Wir können mit einem kleineren Überschuss in der Basisabfrage arbeiten. Wenn zu viele Lücken vorhanden sind, sodass in der ersten Iteration nicht genügend Zeilen gefunden werden, iteriert der rCTE weiterhin mit dem rekursiven Term. Wir brauchen noch relativ wenige Lücken im ID-Bereich, oder die Rekursion läuft möglicherweise trocken, bevor das Limit erreicht ist - oder wir müssen mit einem ausreichend großen Puffer beginnen, der dem Zweck der Leistungsoptimierung widerspricht.
Duplikate werden UNION
im rCTE eliminiert.
Das Äußere LIMIT
lässt den CTE anhalten, sobald wir genügend Zeilen haben.
Diese Abfrage wurde sorgfältig entworfen, um den verfügbaren Index zu verwenden, tatsächlich zufällige Zeilen zu generieren und nicht anzuhalten, bis wir das Limit erreicht haben (es sei denn, die Rekursion läuft trocken). Hier gibt es eine Reihe von Fallstricken, wenn Sie es neu schreiben.
In Funktion einwickeln
Bei wiederholter Verwendung mit unterschiedlichen Parametern:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big AS
$func$
DECLARE
_surplus int := _limit * _gaps;
_estimate int := ( -- get current estimate from system
SELECT c.reltuples * _gaps
FROM pg_class c
WHERE c.oid = 'big'::regclass);
BEGIN
RETURN QUERY
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM generate_series(1, _surplus) g
LIMIT _surplus -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
UNION -- eliminate dupes
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM random_pick -- just to make it recursive
LIMIT _limit -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
)
SELECT *
FROM random_pick
LIMIT _limit;
END
$func$ LANGUAGE plpgsql VOLATILE ROWS 1000;
Anruf:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Sie können dieses Generikum sogar für jede Tabelle verwenden: Nehmen Sie den Namen der PK-Spalte und der Tabelle als polymorphen Typ und verwenden Sie EXECUTE
... Aber das geht über den Rahmen dieser Frage hinaus. Sehen:
Mögliche Alternative
WENN Ihre Anforderungen identische Sätze für wiederholte Anrufe zulassen (und wir sprechen über wiederholte Anrufe), würde ich eine materialisierte Ansicht in Betracht ziehen . Führen Sie die obige Abfrage einmal aus und schreiben Sie das Ergebnis in eine Tabelle. Benutzer erhalten eine blitzschnelle Auswahl quasi zufällig. Aktualisieren Sie Ihre zufällige Auswahl in Intervallen oder Ereignissen Ihrer Wahl.
Wo n
ist ein Prozentsatz. Das Handbuch:
Die BERNOULLI
und die SYSTEM
Stichprobenmethode akzeptieren jeweils ein einzelnes Argument, das den Bruchteil der zu untersuchenden Tabelle darstellt, ausgedrückt als
Prozentsatz zwischen 0 und 100 . Dieses Argument kann ein beliebiger real
Ausdruck sein.
Meine kühne Betonung. Es ist sehr schnell , aber das Ergebnis ist nicht gerade zufällig . Das Handbuch noch einmal:
Die SYSTEM
Methode ist erheblich schneller als die BERNOULLI
Methode, wenn kleine Stichprobenprozentsätze angegeben werden, kann jedoch aufgrund von Clustering-Effekten eine weniger zufällige Stichprobe der Tabelle zurückgeben.
Die Anzahl der zurückgegebenen Zeilen kann stark variieren. In unserem Beispiel erhalten Sie ungefähr 1000 Zeilen:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Verbunden:
Oder installieren Sie das zusätzliche Modul tsm_system_rows , um die Anzahl der angeforderten Zeilen genau zu ermitteln (sofern genügend vorhanden sind) und die bequemere Syntax zu berücksichtigen :
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Siehe Evans Antwort für Details.
Aber das ist immer noch nicht gerade zufällig.