Schnelle Wege
Unter Berücksichtigung Ihrer Angaben (und zusätzlicher Informationen in den Kommentaren),
- Sie haben eine numerische ID-Spalte (ganzzahlige Zahlen) mit nur wenigen (oder mäßig wenigen) Lücken.
- Offensichtlich keine oder nur wenige Schreibvorgänge.
- Ihre ID-Spalte muss indiziert sein! Ein Primärschlüssel ist gut geeignet.
Die folgende Abfrage erfordert keine sequentielle Suche in der großen Tabelle, sondern nur eine Indexsuche.
Ermitteln 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;
Das einzige möglicherweise teure Teil ist die count(*)
(für große Tische). Bei den oben genannten Spezifikationen brauchen Sie das nicht. Eine Schätzung, um die vollständige Zählung zu ersetzen reicht völlig aus und ist fast kostenlos erhältlich:
SELECT (reltuples / relpages * (pg_relation_size(oid) / 8192))::bigint AS ct
FROM pg_class
WHERE oid = 'big'::regclass; -- your table name
Ausführliche Erklärung:
Solange ct
ist nicht beaucoup 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 in der id
Raum. Sie haben "wenige Lücken", also addieren Sie 10 % (genug, um die Lücken leicht abzudecken) zu der Anzahl der abzurufenden Zeilen.
-
Jede id
mehrmals zufällig ausgewählt werden kann (obwohl dies bei einem großen id-Raum sehr unwahrscheinlich ist), gruppieren Sie die generierten Zahlen (oder verwenden Sie DISTINCT
).
-
Beitritt zum id
s an den großen Tisch. Dies sollte sehr schnell gehen, wenn der Index vorhanden ist.
-
Schließlich den Überschuss abschneiden id
s, die nicht von Duplikaten und Lücken aufgefressen wurden. Jede Zeile hat eine völlig gleiche Chance gepflückt werden.
Kurzfassung
Sie können vereinfachen diese Abfrage. Der CTE in der obigen Abfrage ist nur für Lehrzwecke:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Verfeinern mit rCTE
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
)
TABLE random_pick
LIMIT 1000; -- actual limit
Wir können mit einer geringerer Überschuss in der Basisabfrage. Wenn es zu viele Lücken gibt, so dass wir in der ersten Iteration nicht genügend Zeilen finden, fährt die rCTE mit dem rekursiven Term fort zu iterieren. Wir brauchen immer noch relativ wenige Lücken im ID-Raum oder die Rekursion kann versiegen, bevor die Grenze erreicht ist - oder wir müssen mit einem ausreichend großen Puffer beginnen, was dem Zweck der Leistungsoptimierung widerspricht.
Duplikate werden durch das UNION
im rCTE.
Die äußere LIMIT
sorgt dafür, dass der CTE stoppt, 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 erzeugen und nicht aufzuhören, bis wir das Limit erreicht haben (es sei denn, die Rekursion läuft aus). Es gibt hier eine Reihe von Fallstricken, wenn Sie die Abfrage neu schreiben wollen.
In Funktion umwandeln
Zur wiederholten Verwendung mit dem gleiche Tabelle mit unterschiedlichen Parametern:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big
LANGUAGE plpgsql VOLATILE ROWS 1000 AS
$func$
DECLARE
_surplus int := _limit * _gaps;
_estimate int := ( -- get current estimate from system
SELECT (reltuples / relpages * (pg_relation_size(oid) / 8192))::bigint
FROM pg_class
WHERE 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
)
TABLE random_pick
LIMIT _limit;
END
$func$;
Anrufen:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Allgemeine Funktion
Wir können dies generisch machen, damit es funktioniert für beliebige Tabelle mit einer eindeutigen Integer-Spalte (in der Regel die PK): Übergeben Sie die Tabelle als polymorphen Typ und (optional) den Namen der PK-Spalte und verwenden Sie EXECUTE
:
CREATE OR REPLACE FUNCTION f_random_sample(_tbl_type anyelement
, _id text = 'id'
, _limit int = 1000
, _gaps real = 1.03)
RETURNS SETOF anyelement
LANGUAGE plpgsql VOLATILE ROWS 1000 AS
$func$
DECLARE
-- safe syntax with schema & quotes where needed
_tbl text := pg_typeof(_tbl_type)::text;
_estimate int := (SELECT (reltuples / relpages
* (pg_relation_size(oid) / 8192))::bigint
FROM pg_class -- get current estimate from system
WHERE oid = _tbl::regclass);
BEGIN
RETURN QUERY EXECUTE format(
$$
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * $1)::int
FROM generate_series(1, $2) g
LIMIT $2 -- hint for query planner
) r(%2$I)
JOIN %1$s USING (%2$I) -- eliminate misses
UNION -- eliminate dupes
SELECT *
FROM (
SELECT 1 + trunc(random() * $1)::int
FROM random_pick -- just to make it recursive
LIMIT $3 -- hint for query planner
) r(%2$I)
JOIN %1$s USING (%2$I) -- eliminate misses
)
TABLE random_pick
LIMIT $3;
$$
, _tbl, _id
)
USING _estimate -- $1
, (_limit * _gaps)::int -- $2 ("surplus")
, _limit -- $3
;
END
$func$;
Aufruf mit Standardwerten (wichtig!):
SELECT * FROM f_random_sample(null::big); --!
Oder genauer gesagt:
SELECT * FROM f_random_sample(null::"my_TABLE", 'oDD ID', 666, 1.15);
Ungefähr die gleiche Leistung wie bei der statischen Version.
Verwandt:
Dies ist sicher gegen SQL-Injection. Siehe:
Mögliche Alternative
I Ihre Anforderungen erlauben identische Sätze für wiederholte Anrufe (und wir sprechen hier von wiederholten Anrufen) eine MATERIALIZED VIEW
. Führen Sie die obige Abfrage einmal aus und schreiben Sie das Ergebnis in eine Tabelle. Die Benutzer erhalten eine quasi zufällige Auswahl in Windeseile. Aktualisieren Sie Ihre Zufallsauswahl in Intervallen oder bei Ereignissen Ihrer Wahl.
Wo n
ist ein Prozentsatz. Das Handbuch:
En BERNOULLI
y SYSTEM
Stichprobenverfahren akzeptieren jeweils eine einzige Argument, das den Teil der Tabelle angibt, aus dem eine Stichprobe gezogen werden soll, ausgedrückt als Prozentsatz zwischen 0 und 100 . Dieses Argument kann ein beliebiges sein real
-bewerteten Ausdruck.
Fettgedruckte Hervorhebung von mir. Es ist sehr schnell aber das Ergebnis ist nicht ganz zufällig . Nochmals das Handbuch:
En SYSTEM
Methode ist deutlich schneller als die BERNOULLI
Methode wenn kleine Stichprobenprozentsätze angegeben werden, aber sie kann eine aufgrund von Clustereffekten eine weniger zufällige Stichprobe der Tabelle.
Die Anzahl der zurückgegebenen Zeilen kann stark variieren. Für unser Beispiel, um zu erhalten etwa 1000 Zeilen:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Verwandt:
Oder das Zusatzmodul installieren tsm_system_rows um die Anzahl der angeforderten Zeilen genau zu ermitteln (wenn genug vorhanden sind) und die bequemere Syntax zu verwenden:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Siehe Evans Antwort für Einzelheiten.
Aber auch das ist nicht gerade zufällig.