Tengo una situación que creo que se puede resolver utilizando la función de ventana, pero no estoy seguro.
Imagina la siguiente tabla
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
Me gustaría tener un nuevo grupo en cada cambio en la columna id_type. Por ejemplo, primer grupo de 7:19:21 a 7:19:25, segundo inicio y finalización a las 7:19:26, y así sucesivamente.
Después de que funcione, quiero incluir más criterios para definir grupos.
En este momento, utilizando la consulta a continuación ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
Obtengo el siguiente resultado:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Si bien me gustaría:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
Después de resolver este primer paso, agregaré más columnas para usar como reglas para dividir grupos, y estos otros serán anulables.
Versión Postgres: 8.4 (Tenemos Postgres con Postgis, por lo que no es fácil de actualizar. Las funciones de Postgis cambian de nombre y hay otros problemas, pero espero que ya estemos reescribiendo todo y la nueva versión use una versión más nueva 9.X con postgis 2.x)
Respuestas:
Por algunos puntos
tmp
que se vuelve confusa..0
)date
. Si tiene fecha y hora, es una marca de tiempo (y la almacena como una)Es mejor usar una función de ventana.
Salidas
Explicación
Primero necesitamos resets. Los generamos con
lag()
Luego contamos para obtener grupos.
Luego envolvemos en una subselección
GROUP BY
yORDER
y seleccione el min max (rango)fuente
1. Funciones de ventana más subconsultas
Cuente los pasos para formar grupos, similares a la idea de Evan , con modificaciones y arreglos:
Esto supone que las columnas involucradas son
NOT NULL
. De lo contrario, necesitas hacer más.También suponiendo
date
que se definaUNIQUE
, de lo contrario, debe agregar un desempate a lasORDER BY
cláusulas para obtener resultados deterministas. Me gusta:ORDER BY date, id
.Explicación detallada (respuesta a una pregunta muy similar):
Nota en particular:
En casos relacionados,
lag()
con 3 parámetros puede ser esencial para cubrir el caso de la esquina de la primera (o última) fila con elegancia. (El tercer parámetro se usa por defecto si no hay una fila anterior (siguiente).Como solo estamos interesados en un cambio real de
id_type
(TRUE
), no importa en este caso particular.NULL
yFALSE
ambos no cuentan comostep
.count(step OR NULL) OVER (ORDER BY date)
es la sintaxis más corta que también funciona en Postgres 9.3 o anterior.count()
solo cuenta valores no nulos ...En Postgres modernos, la sintaxis equivalente más limpia sería:
Detalles:
2. Resta dos funciones de ventana, una subconsulta
Similar a la idea de Erik con modificaciones:
Si
date
se defineUNIQUE
, como mencioné anteriormente (nunca aclaraste), nodense_rank()
tendría sentido, ya que el resultado es el mismo que pararow_number()
y este último es sustancialmente más barato.Si no
date
está definido (y no sabemos que los únicos duplicados están activados ), todas estas consultas no tienen sentido, ya que el resultado es arbitrario.UNIQUE
(date, id_type)
Además, una subconsulta suele ser más barata que un CTE en Postgres. Solo use CTE cuando los necesite .
Respuestas relacionadas con más explicaciones:
En casos relacionados donde ya tenemos un número corriente en la tabla, podemos conformarnos con una sola función de ventana:
3. Máximo rendimiento con la función plpgsql
Como esta pregunta se ha vuelto inesperadamente popular, agregaré otra solución para demostrar el máximo rendimiento.
SQL tiene muchas herramientas sofisticadas para crear soluciones con sintaxis corta y elegante. Pero un lenguaje declarativo tiene sus límites para requisitos más complejos que involucran elementos de procedimiento.
Una función de procedimiento del lado del servidor es más rápida para esto que cualquier cosa publicada hasta ahora porque solo necesita un solo escaneo secuencial sobre la tabla y una sola operación de clasificación . Si hay disponible un índice de ajuste, incluso un solo escaneo de solo índice.
Llamada:
Prueba con:
Puede hacer que la función sea genérica con tipos polimórficos y pasar el tipo de tabla y los nombres de columna. Detalles:
Si no desea o no puede persistir una función para esto, incluso pagaría crear una función temporal sobre la marcha. Cuesta unos pocos ms.
dbfiddle para Postgres 9.6, comparando el rendimiento de los tres. Basado enel caso de prueba de Jack, modificado.
dbfiddle para Postgres 8.4, donde las diferencias de rendimiento son aún mayores.
fuente
count(x or null)
o incluso qué está haciendo allí. Tal vez podría mostrar algunos ejemplos en los que se requiere, ya que no se requiere aquí. Y, ¿cuál sería el requisito clave para cubrir esos casos de esquina? Por cierto, cambié mi voto a favor solo por el ejemplo pl / pgsql. Eso es realmente genial. (Pero, en general, estoy en contra de las respuestas que resumen otras respuestas o cubren casos de esquina, aunque odio decir que este es un caso de esquina porque no lo entiendo).count(x or null)
hace. Estaré encantado de hacerle ambas preguntas si lo prefiere.count(x or null)
necesario en Gaps and Islands?Puede hacer esto como una simple sustracción de
ROW_NUMBER()
operaciones (o si sus fechas no son únicas, aunque todavía son únicas porid_type
, entonces puede usarDENSE_RANK()
en su lugar, aunque será una consulta más costosa):Vea este trabajo en DB Fiddle (o vea la versión DENSE_RANK )
Resultado:
Lógicamente, puede pensar en esto como un simple
DENSE_RANK()
con unPREORDER BY
, es decir, desea laDENSE_RANK
totalidad de los elementos clasificados y desea ordenarlos por fechas, solo tiene que lidiar con el molesto problema del hecho de que en cada cambio en la fecha,DENSE_RANK
se incrementará. Lo haces usando la expresión que te mostré arriba. Imagínese si tuviera esta sintaxis:DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
dondePREORDER
se excluye del cálculo de clasificación y soloORDER BY
se cuenta.Tenga en cuenta que es importante
GROUP BY
tanto para laSeq
columna generada como para laid_type
columna.Seq
NO es único en sí mismo, puede haber superposiciones, también debe agruparloid_type
.Para leer más sobre este tema:
Ese primer enlace le proporciona un código que puede usar si desea que la fecha de inicio o finalización sea la misma que la fecha de finalización / inicio del período anterior o siguiente (por lo que no hay espacios). Además de otras versiones que podrían ayudarlo en su consulta. Aunque deben traducirse de la sintaxis de SQL Server ...
fuente
En Postgres 8.4 puedes usar una función RECURSIVA .
Cómo lo hicieron
La función recursiva agrega un nivel a cada id_type diferente, seleccionando las fechas una por una en orden descendente.
Luego use MAX (fecha), MIN (fecha) agrupando por nivel, id_type para obtener el resultado deseado.
Compruébalo: http://rextester.com/WCOYFP6623
fuente
Aquí hay otro método, que es similar al de Evan y Erwin, ya que utiliza el LAG para determinar las islas. Se diferencia de esas soluciones en que usa solo un nivel de anidamiento, sin agrupamiento y considerablemente más funciones de ventana:
La
is_start
columna calculada en SELECT anidado marca el comienzo de cada isla. Además, el SELECT anidado expone la fecha anterior de cada fila y la última fecha del conjunto de datos.Para las filas que son el comienzo de sus respectivas islas, la fecha anterior es efectivamente la fecha de finalización de la isla anterior. Así es como lo usa el SELECT principal. Selecciona solo las filas que coinciden con la
is_start = 1
condición, y para cada fila devuelta muestra las propias de la filadate
comobegin
y las siguientesprev_date
comoend
. Como la última fila no tiene una fila siguiente,LEAD(prev_date)
devuelve un valor nulo, para el cual la función COALESCE sustituye la última fecha del conjunto de datos.Puedes jugar con esta solución en dbfiddle .
Al introducir columnas adicionales que identifiquen las islas, es probable que desee introducir una subcláusula PARTITION BY en la cláusula OVER de cada función de ventana. Por ejemplo, si desea detectar las islas dentro de los grupos definidos por a
parent_id
, la consulta anterior probablemente tendrá que verse así:Y si decide optar por la solución de Erwin o Evan, creo que también será necesario agregar un cambio similar.
fuente
Más por interés académico que como una solución práctica, también puede lograr esto con un agregado definido por el usuario . Al igual que las otras soluciones, esto funcionará incluso en Postgres 8.4, pero como otros han comentado, actualice si puede.
El agregado se maneja
null
como si fuera diferentefoo_type
, por lo que las ejecuciones de valores nulos recibirían lo mismogrp
, eso puede o no ser lo que desea.dbfiddle aquí
fuente
Esto se puede hacer
RECURSIVE CTE
para pasar el "tiempo de inicio" de una fila a la siguiente, y algunos preparativos adicionales (conveniencia).Esta consulta devuelve el resultado que desea:
después de la preparación ... parte recursiva
Puede verificar esto en http://rextester.com/POYM83542
Este método no escala bien. Para una tabla de filas 8_641, toma 7 segundos, para una tabla dos veces ese tamaño, toma 28 segundos. Algunas muestras más muestran tiempos de ejecución parecidos a O (n ^ 2).
El método de Evan Carrol toma menos de 1s (es decir, ¡adelante!), Y se parece a O (n). Las consultas recursivas son absolutamente ineficientes y deben considerarse un último recurso.
fuente