Tengo una tabla (en PostgreSQL 9.4) que se ve así:
CREATE TABLE dates_ranges (kind int, start_date date, end_date date);
INSERT INTO dates_ranges VALUES
(1, '2018-01-01', '2018-01-31'),
(1, '2018-01-01', '2018-01-05'),
(1, '2018-01-03', '2018-01-06'),
(2, '2018-01-01', '2018-01-01'),
(2, '2018-01-01', '2018-01-02'),
(3, '2018-01-02', '2018-01-08'),
(3, '2018-01-05', '2018-01-10');
Ahora quiero calcular para las fechas dadas y para cada tipo, en cuántas filas de dates_rangescada fecha cae. Los ceros podrían posiblemente omitirse.
Resultado deseado:
+-------+------------+----+
| kind | as_of_date | n |
+-------+------------+----+
| 1 | 2018-01-01 | 2 |
| 1 | 2018-01-02 | 2 |
| 1 | 2018-01-03 | 3 |
| 2 | 2018-01-01 | 2 |
| 2 | 2018-01-02 | 1 |
| 3 | 2018-01-02 | 1 |
| 3 | 2018-01-03 | 1 |
+-------+------------+----+
Se me ocurrieron dos soluciones, una con LEFT JOINyGROUP BY
SELECT
kind, as_of_date, COUNT(*) n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates
LEFT JOIN
dates_ranges ON dates.as_of_date BETWEEN start_date AND end_date
GROUP BY 1,2 ORDER BY 1,2
y uno con LATERAL, que es un poco más rápido:
SELECT
kind, as_of_date, n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates,
LATERAL
(SELECT kind, COUNT(*) AS n FROM dates_ranges WHERE dates.as_of_date BETWEEN start_date AND end_date GROUP BY kind) ss
ORDER BY kind, as_of_date
Me pregunto si hay alguna forma mejor de escribir esta consulta. ¿Y cómo incluir pares date-kind con 0 count?
En realidad, hay algunos tipos distintos, un período de hasta cinco años (1800 fechas) y ~ 30k filas en la dates_rangestabla (pero podría crecer significativamente).
No hay índices Para ser precisos en mi caso, es el resultado de una subconsulta, pero he querido limitar la pregunta a un problema, por lo que es más general.
fuente

(1,2018-01-01,2018-01-15)y(1,2018-01-20,2018-01-25)desea tenerlo en cuenta al determinar cuántas fechas superpuestas tiene?2018-01-31o2018-01-30o2018-01-29en él cuando la primera gama cuenta con todos ellos?generate_seriesson parámetros externos; no necesariamente cubren todos los rangos de ladates_rangestabla. En cuanto a la primera pregunta, supongo que no la entiendo: las filasdates_rangesson independientes, no quiero determinar la superposición.Respuestas:
La siguiente consulta también funciona si los "ceros faltantes" están bien:
pero no es más rápido que la
lateralversión con el pequeño conjunto de datos. Sin embargo, podría escalar mejor, ya que no se requiere unión, pero la versión anterior se agrega a todas las filas, por lo que puede perder nuevamente.La siguiente consulta intenta evitar el trabajo innecesario eliminando cualquier serie que no se superponga de todos modos:
- ¡Y tengo que usar el
overlapsoperador! Tenga en cuenta que debe agregarinterval '1 day'a la derecha ya que el operador de superposición considera que los períodos de tiempo están abiertos a la derecha (lo cual es bastante lógico porque a menudo se considera que una fecha es una marca de tiempo con un componente de hora de medianoche).fuente
generate_seriesse puede usar así. Después de algunas pruebas tengo las siguientes observaciones. De hecho, su consulta se escala muy bien con la longitud del rango seleccionado; prácticamente no hay diferencia entre un período de 3 años y 10 años. Sin embargo, durante períodos más cortos (1 año), mis soluciones son más rápidas. Supongo que la razón es que hay algunos rangos realmente largosdates_ranges(como 2010-2100), que están ralentizando su consulta. Sin embargo, limitarstart_dateyend_datedentro de la consulta interna debería ayudar. Necesito hacer algunas pruebas más.Construya una cuadrícula de todas las combinaciones y luego
LATERALúnase a su mesa, así:También debe ser lo más rápido posible.
Al
LEFT JOIN LATERAL ... on trueprincipio tenía , pero hay un agregado en la subconsultac, por lo que siempre obtenemos una fila y también podemos usarlaCROSS JOIN. No hay diferencia en el rendimiento.Si tiene una tabla que contiene todos los tipos relevantes , úsela en lugar de generar la lista con subconsulta
k.El reparto a
integeres opcional. De lo contrario tienesbigint.Los índices ayudarían, especialmente un índice de varias columnas en
(kind, start_date, end_date). Como está construyendo sobre una subconsulta, esto puede o no ser posible de lograr.El uso de funciones de devolución de conjuntos como
generate_series()en laSELECTlista generalmente no es aconsejable en las versiones de Postgres anteriores a la 10 (a menos que sepa exactamente lo que está haciendo). Ver:Si tiene muchas combinaciones con pocas o ninguna fila, esta forma equivalente puede ser más rápida:
fuente
SELECTlista, he leído que no es recomendable, sin embargo, parece que funciona bien, si solo hay una de esas funciones. Si estoy seguro de que solo habrá uno, ¿podría salir algo mal?SELECTlista funciona como se esperaba. Tal vez agregue un comentario para advertir en contra de agregar otro. O muévalo a laFROMlista para comenzar en versiones anteriores de Postgres. ¿Por qué arriesgar complicaciones? (Eso también es SQL estándar y no confundirá a las personas que vienen de otros RDBMS).Usando el
daterangetipoPostgreSQL tiene un
daterange. Usarlo es bastante simple. Comenzando con sus datos de muestra, nos movemos para usar el tipo en la tabla.Ahora, para consultarlo, revertimos el procedimiento y generamos una serie de fechas, pero aquí está el problema de que la consulta en sí misma puede usar el
@>operador de contención ( ) para verificar que las fechas estén dentro del rango, usando un índice.Tenga en cuenta que usamos
timestamp without time zone(para detener los riesgos de DST)Cuál es la superposición de día detallada en el índice.
Como beneficio adicional, con el tipo de rango de fechas puede detener las inserciones de rangos que se superponen con otros utilizando un
EXCLUDE CONSTRAINTfuente
JOINdemasiado, supongo.count(DISTINCT kind)1fecha de tipo2018-01-01está dentro de las dos primeras filas desdedates_ranges, pero su consulta proporciona8.count(DISTINCT kind)¿agregó laDISTINCTpalabra clave allí?DISTINCTpalabra clave todavía no funciona como se esperaba. Cuenta distintos tipos para cada fecha, pero quiero contar todas las filas de cada tipo para cada fecha.