Dadas sus especificaciones (más información adicional en los comentarios),
- Tiene una columna de ID numérico (números enteros) con solo unos pocos (o moderadamente pocos) espacios.
- Obviamente ninguna o pocas operaciones de escritura.
- ¡Tu columna de ID tiene que estar indexada! Una clave principal sirve muy bien.
La consulta a continuación no necesita un escaneo secuencial de la tabla grande, solo un escaneo de índice.
Primero, obtenga estimaciones para la consulta principal:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
La única parte posiblemente costosa es el count(*)
(para mesas grandes). Dadas las especificaciones anteriores, no lo necesita. Una estimación funcionará bien, disponible casi sin costo (explicación detallada aquí):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Mientras ct
no es mucho menor que id_span
, la consulta superará a otros enfoques.
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
-
Genera números aleatorios en el
id
espacio. Tiene "pocos espacios en blanco", así que agregue un 10 % (suficiente para cubrir fácilmente los espacios en blanco) al número de filas para recuperar. -
Cada
id
se puede elegir varias veces al azar (aunque es muy poco probable con un gran espacio de identificación), así que agrupe los números generados (o useDISTINCT
). -
Únete al
id
s a la mesa grande. Esto debería ser muy rápido con el índice en su lugar. -
Finalmente recorte el excedente
id
s que no han sido comidos por tontos y lagunas. Cada fila tiene una oportunidad completamente igual para ser elegido.
Versión corta
Puedes simplificar esta consulta El CTE en la consulta anterior es solo para fines educativos:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Refinar con rCTE
Especialmente si no está tan seguro acerca de las lagunas y las estimaciones.
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
Podemos trabajar con un superávit más pequeño en la consulta base. Si hay demasiados espacios en blanco y no encontramos suficientes filas en la primera iteración, el rCTE continúa iterando con el término recursivo. Todavía necesitamos relativamente pocos las brechas en el espacio de ID o la recursividad pueden agotarse antes de alcanzar el límite, o tenemos que comenzar con un búfer lo suficientemente grande que desafía el propósito de optimizar el rendimiento.
Los duplicados son eliminados por UNION
en el rCTE.
El exterior LIMIT
hace que el CTE se detenga tan pronto como tengamos suficientes filas.
Esta consulta está cuidadosamente redactada para usar el índice disponible, generar filas realmente aleatorias y no detenerse hasta que alcancemos el límite (a menos que la recursividad se agote). Hay una serie de trampas aquí si va a reescribirlo.
Envolver en función
Para uso repetido con diferentes parámetros:
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 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
)
TABLE random_pick
LIMIT _limit;
END
$func$;
Llamar:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Incluso podría hacer que esto sea genérico para que funcione con cualquier tabla:tome el nombre de la columna PK y la tabla como tipo polimórfico y use EXECUTE
... Pero eso está más allá del alcance de esta pregunta. Ver:
- Refactorice una función PL/pgSQL para devolver el resultado de varias consultas SELECT
Posible alternativa
SI sus requisitos permiten conjuntos idénticos para repetir llamadas (y estamos hablando de llamadas repetidas) consideraría una vista materializada . Ejecute la consulta anterior una vez y escriba el resultado en una tabla. Los usuarios obtienen una selección casi aleatoria a la velocidad de la luz. Actualice su selección aleatoria en intervalos o eventos de su elección.
Postgres 9.5 presenta TABLESAMPLE SYSTEM (n)
Donde n
es un porcentaje El manual:
El BERNOULLI
y SYSTEM
cada uno de los métodos de muestreo acepta un solo argumento que es la fracción de la tabla a muestrear, expresada como un porcentaje entre 0 y 100 . Este argumento puede ser cualquier real
-expresión valorada.
Énfasis en negrita mío. Es muy rápido , pero el resultado no es exactamente aleatorio . El manual de nuevo:
El SYSTEM
El método es significativamente más rápido que el BERNOULLI
método cuando se especifican pequeños porcentajes de muestreo, pero puede devolver una muestra menos aleatoria de la tabla como resultado de los efectos de agrupamiento.
El número de filas devueltas puede variar enormemente. Para nuestro ejemplo, para obtener aproximadamente 1000 filas:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Relacionado:
- Manera rápida de descubrir el recuento de filas de una tabla en PostgreSQL
O instale el módulo adicional tsm_system_rows para obtener exactamente el número de filas solicitadas (si hay suficientes) y permitir la sintaxis más conveniente:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Vea la respuesta de Evan para más detalles.
Pero eso todavía no es exactamente aleatorio.