Dadas sus especificaciones (más información adicional en los comentarios),
- Tiene una columna de identificación numérica (números enteros) con solo unos pocos (o moderadamente pocos) huecos.
- Obviamente ninguna o pocas operaciones de escritura.
- ¡Su columna de ID tiene que ser indexada! Una clave primaria sirve muy bien.
La consulta a continuación no necesita una exploración secuencial de la tabla grande, solo una exploración 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 la count(*)
(para tablas enormes). Dadas las especificaciones anteriores, no lo necesita. Una estimación estará 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 sea mucho más pequeño 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", por lo tanto, agregue un 10% (suficiente para cubrir fácilmente los espacios en blanco) al número de filas para recuperar.
Cada uno id
se puede elegir varias veces por casualidad (aunque es muy poco probable con un gran espacio de identificación), por lo tanto, agrupe los números generados (o use DISTINCT
).
Únete a la id
s a la mesa grande. Esto debería ser muy rápido con el índice en su lugar.
Finalmente, elimine los excedentes id
que no hayan sido comidos por engaños y huecos. Cada fila tiene una oportunidad completamente igual de ser elegido.
Version 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;
Refina con rCTE
Especialmente si no está tan seguro sobre las brechas 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
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
Podemos trabajar con un excedente más pequeño en la consulta base. Si hay demasiados espacios, por lo que no encontramos suficientes filas en la primera iteración, el rCTE continúa iterando con el término recursivo. Todavía necesitamos relativamente pocos espacios en el espacio de ID o la recursión puede agotarse antes de que se alcance el límite, o tenemos que comenzar con un búfer lo suficientemente grande que desafíe el propósito de optimizar el rendimiento.
Los duplicados son eliminados por el 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 cumplamos el límite (a menos que la recursión se seque). Hay una serie de dificultades aquí si va a reescribirlo.
Envolver en función
Para uso repetido con parámetros variables:
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;
Llamada:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Incluso podría hacer que este genérico funcione para 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:
Alternativa posible
SI sus requisitos permiten conjuntos idénticos para llamadas repetidas (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 cuasialeatoria a la velocidad del rayo. Actualice su selección aleatoria a intervalos o eventos de su elección.
Donde n
es un porcentaje. El manual:
Los métodos de muestreo BERNOULLI
y SYSTEM
aceptan cada uno un argumento único 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.
El énfasis en negrita es mío. Es muy rápido , pero el resultado no es exactamente al azar . El manual nuevamente:
El SYSTEM
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:
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);
Ver la respuesta de Evan para más detalles.
Pero eso todavía no es exactamente al azar.