Estoy tratando con una tabla de Postgres (llamada "vidas") que contiene registros con columnas para time_stamp, usr_id, transaction_id y lives_remaining. Necesito una consulta que me dé el total más reciente de lives_remaining para cada usr_id
- Hay varios usuarios (usr_id distintos)
- time_stamp no es un identificador único: a veces, los eventos de usuario (uno por fila en la tabla) ocurrirán con el mismo time_stamp.
- trans_id es único solo para rangos de tiempo muy pequeños: con el tiempo se repite
- left_lives (para un usuario determinado) puede aumentar y disminuir con el tiempo
ejemplo:
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 07:00 | 1 | 1 | 1 09:00 | 4 | 2 | 2 10:00 | 2 | 3 | 3 10:00 | 1 | 2 | 4 11:00 | 4 | 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
Como necesitaré acceder a otras columnas de la fila con los datos más recientes para cada usr_id dado, necesito una consulta que dé un resultado como este:
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
Como se mencionó, cada usr_id puede ganar o perder vidas y, a veces, estos eventos con marca de tiempo ocurren tan cerca que tienen la misma marca de tiempo. Por lo tanto, esta consulta no funcionará:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
En su lugar, necesito usar time_stamp (primero) y trans_id (segundo) para identificar la fila correcta. Luego, también necesito pasar esa información de la subconsulta a la consulta principal que proporcionará los datos para las otras columnas de las filas apropiadas. Esta es la consulta pirateada con la que me puse a trabajar:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
Bien, esto funciona, pero no me gusta. Requiere una consulta dentro de una consulta, una autounión, y me parece que podría ser mucho más simple tomando la fila que MAX encontró que tiene la marca de tiempo y trans_id más grandes. La tabla "vidas" tiene decenas de millones de filas para analizar, por lo que me gustaría que esta consulta fuera lo más rápida y eficiente posible. Soy nuevo en RDBM y Postgres en particular, por lo que sé que necesito hacer un uso efectivo de los índices adecuados. Estoy un poco perdido sobre cómo optimizar.
Encontré una discusión similar aquí . ¿Puedo realizar algún tipo de Postgres equivalente a una función analítica de Oracle?
Cualquier consejo sobre cómo acceder a la información de columna relacionada utilizada por una función agregada (como MAX), crear índices y crear mejores consultas sería muy apreciado.
PD: puedes usar lo siguiente para crear mi caso de ejemplo:
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
fuente
MAX
BY
2 columnas!Respuestas:
En una tabla con 158k filas pseudoaleatorias (usr_id uniformemente distribuido entre 0 y 10k,
trans_id
uniformemente distribuido entre 0 y 30),Por costo de consulta, a continuación, me refiero a la estimación de costos del optimizador basado en costos de Postgres (con los
xxx_cost
valores predeterminados de Postgres ), que es una estimación de función ponderada de los recursos de CPU y E / S requeridos; puede obtener esto activando PgAdminIII y ejecutando "Consulta / Explicación (F7)" en la consulta con "Opciones de consulta / explicación" configuradas en "Analizar"usr_id
,trans_id
,time_stamp
))usr_id
,trans_id
))usr_id
,trans_id
,time_stamp
))usr_id
,EXTRACT(EPOCH FROM time_stamp)
,trans_id
))usr_id
,time_stamp
,trans_id
)); tiene la ventaja de escanear lalives
tabla solo una vez y, si aumenta temporalmente (si es necesario) work_mem para acomodar la clasificación en la memoria, será la más rápida de todas las consultas.Todos los tiempos anteriores incluyen la recuperación del conjunto de resultados completo de 10k filas.
Su objetivo es una estimación de costo mínima y un tiempo mínimo de ejecución de consultas, con énfasis en el costo estimado. La ejecución de la consulta puede depender significativamente de las condiciones del tiempo de ejecución (por ejemplo, si las filas relevantes ya están completamente almacenadas en la memoria caché o no), mientras que la estimación de costos no. Por otro lado, tenga en cuenta que la estimación de costos es exactamente eso, una estimación.
El mejor tiempo de ejecución de la consulta se obtiene cuando se ejecuta en una base de datos dedicada sin carga (por ejemplo, jugando con pgAdminIII en una PC de desarrollo). El tiempo de consulta variará en la producción según la carga real de la máquina / la distribución del acceso a los datos. Cuando una consulta aparece un poco más rápido (<20%) que la otra, pero tiene un costo mucho más alto, generalmente será más prudente elegir la que tenga un tiempo de ejecución más alto pero un costo menor.
Cuando espere que no haya competencia por la memoria en su máquina de producción en el momento en que se ejecuta la consulta (por ejemplo, la caché RDBMS y la caché del sistema de archivos no serán destruidas por consultas concurrentes y / o actividad del sistema de archivos), entonces el tiempo de consulta que obtuvo en modo autónomo (por ejemplo, pgAdminIII en una PC de desarrollo) será representativo. Si hay contención en el sistema de producción, el tiempo de consulta se degradará proporcionalmente al índice de costo estimado, ya que la consulta con el costo más bajo no depende tanto de la caché mientras que la consulta con un costo más alto revisará los mismos datos una y otra vez (activando E / S adicionales en ausencia de una caché estable), por ejemplo:
No olvide ejecutar
ANALYZE lives
una vez después de crear los índices necesarios.Consulta nº 1
Consulta nº 2
Actualización 29/01/2013
Finalmente, a partir de la versión 8.4, Postgres admite la función de ventana, lo que significa que puede escribir algo tan simple y eficiente como:
Consulta n. ° 3
fuente
Propondría una versión limpia basada en
DISTINCT ON
(ver documentos ):fuente
Aquí hay otro método, que no usa subconsultas correlacionadas o GROUP BY. No soy un experto en el ajuste del rendimiento de PostgreSQL, por lo que le sugiero que pruebe tanto esto como las soluciones proporcionadas por otras personas para ver cuál funciona mejor para usted.
Supongo que
trans_id
es único al menos sobre cualquier valor dado detime_stamp
.fuente
Me gusta el estilo de la respuesta de Mike Woodhouse en la otra página que mencionaste. Es especialmente conciso cuando lo que se maximiza es solo una columna, en cuyo caso la subconsulta solo puede usar
MAX(some_col)
yGROUP BY
las otras columnas, pero en su caso tiene una cantidad de 2 partes para maximizar, aún puede hacerlo usandoORDER BY
más en suLIMIT 1
lugar (como lo hizo Quassnoi):Encuentro que usar la sintaxis del constructor de filas es
WHERE (a, b, c) IN (subquery)
bueno porque reduce la cantidad de verborrea necesaria.fuente
De hecho, hay una solución hacky para este problema. Supongamos que desea seleccionar el árbol más grande de cada bosque en una región.
Cuando agrupe árboles por bosques, habrá una lista sin clasificar de árboles y tendrá que encontrar el más grande. Lo primero que debe hacer es ordenar las filas por sus tamaños y seleccionar la primera de su lista. Puede parecer ineficaz, pero si tiene millones de filas, será bastante más rápido que las soluciones que incluyen
JOIN
lasWHERE
condiciones y .Por cierto, tenga en cuenta que
ORDER_BY
forarray_agg
se introduce en Postgresql 9.0fuente
SELECT usr_id, (array_agg(time_stamp ORDER BY time_stamp DESC))[1] AS timestamp, (array_agg(lives_remaining ORDER BY time_stamp DESC))[1] AS lives_remaining, (array_agg(trans_id ORDER BY time_stamp DESC))[1] AS trans_id FROM lives GROUP BY usr_id
Hay una nueva opción en Postgressql 9.5 llamada DISTINCT ON
Elimina filas duplicadas y deja solo la primera fila como se define en la cláusula ORDER BY.
ver la documentación oficial
fuente
La creación de un índice en
(usr_id, time_stamp, trans_id)
mejorará enormemente esta consulta.Siempre, siempre debes tener algún tipo de
PRIMARY KEY
en tus mesas.fuente
Creo que tienes un problema importante aquí: no hay un "contador" que aumente de forma monótona para garantizar que una fila determinada haya ocurrido más tarde que otra. Toma este ejemplo:
No puede determinar a partir de estos datos cuál es la entrada más reciente. ¿Es el segundo o el último? No hay ninguna función sort o max () que pueda aplicar a cualquiera de estos datos para darle la respuesta correcta.
Aumentar la resolución de la marca de tiempo sería de gran ayuda. Dado que el motor de la base de datos serializa las solicitudes, con una resolución suficiente puede garantizar que no haya dos marcas de tiempo iguales.
Alternativamente, use un trans_id que no se renueve durante mucho, mucho tiempo. Tener un trans_id que se transfiere significa que no puede decir (para la misma marca de tiempo) si trans_id 6 es más reciente que trans_id 1 a menos que haga algunos cálculos matemáticos complicados.
fuente
Otra solución que puede resultarle útil.
fuente