No creo que pueda hacer esto a bajo costo con una consulta simple, CTE y funciones de ventana:su definición de marco es estática, pero necesita un marco dinámico (dependiendo de los valores de la columna).
En general, deberá definir los límites inferior y superior de su ventana con cuidado:las siguientes consultas excluir la fila actual e incluir el borde inferior.
Todavía hay una diferencia menor:la función incluye pares anteriores de la fila actual, mientras que la subconsulta correlacionada los excluye...
Caso de prueba
Usando ts
en lugar de la palabra reservada date
como nombre de columna.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - Consulta de Roman
Use CTE, agregue marcas de tiempo en una matriz, anide, cuente...
Si bien es correcto, el rendimiento deteriora drásticamente con más de un puñado de filas. Hay un par de asesinos de rendimiento aquí. Ver más abajo.
ARR - cuenta los elementos de la matriz
Tomé la consulta de Roman y traté de simplificarla un poco:
- Eliminar el segundo CTE que no es necesario.
- Transforme la primera CTE en una subconsulta, que es más rápida.
- Directo
count()
en lugar de volver a agregar en una matriz y contar conarray_length()
.
Pero el manejo de arreglos es costoso y el rendimiento todavía deteriora gravemente con más filas.
SELECT id, ts
, (SELECT count(*)::int - 1
FROM unnest(dates) x
WHERE x >= sub.ts - interval '1h') AS ct
FROM (
SELECT id, ts
, array_agg(ts) OVER(ORDER BY ts) AS dates
FROM test
) sub;
COR - subconsulta correlacionada
podrías resolverlo con una subconsulta correlacionada simple. Mucho más rápido, pero aun así...
SELECT id, ts
, (SELECT count(*)
FROM test t1
WHERE t1.ts >= t.ts - interval '1h'
AND t1.ts < t.ts) AS ct
FROM test t
ORDER BY ts;
FNC - Función
Recorra las filas en orden cronológico con un row_number()
en función plpgsql y combínalo con un cursor sobre la misma consulta, abarcando el marco de tiempo deseado. Entonces podemos simplemente restar números de fila:
CREATE OR REPLACE FUNCTION running_window_ct(_intv interval = '1 hour')
RETURNS TABLE (id bigint, ts timestamp, ct int)
LANGUAGE plpgsql AS
$func$
DECLARE
cur CURSOR FOR
SELECT t.ts + _intv AS ts1, row_number() OVER (ORDER BY t.ts) AS rn
FROM test t ORDER BY t.ts;
rec record;
rn int;
BEGIN
OPEN cur;
FETCH cur INTO rec;
ct := -1; -- init
FOR id, ts, rn IN
SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
FROM test t ORDER BY t.ts
LOOP
IF rec.ts1 >= ts THEN
ct := ct + 1;
ELSE
LOOP
FETCH cur INTO rec;
EXIT WHEN rec.ts1 >= ts;
END LOOP;
ct := rn - rec.rn;
END IF;
RETURN NEXT;
END LOOP;
END
$func$;
Llamada con intervalo predeterminado de una hora:
SELECT * FROM running_window_ct();
O con cualquier intervalo:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>violín aquí
Sqlfiddle antiguo
Valor de referencia
Con la tabla de arriba, ejecuté un punto de referencia rápido en mi antiguo servidor de prueba:(PostgreSQL 9.1.9 en Debian).
-- TRUNCATE test;
INSERT INTO test
SELECT g, '2013-08-08'::timestamp
+ g * interval '5 min'
+ random() * 300 * interval '1 min' -- halfway realistic values
FROM generate_series(1, 10000) g;
CREATE INDEX test_ts_idx ON test (ts);
ANALYZE test; -- temp table needs manual analyze
Varié las negritas parte para cada ejecución y tomó el mejor de 5 con EXPLAIN ANALYZE
.
100 filas
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms
1000 filas
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms
5000 filas
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 filas
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
La función es la clara vencedora. Es el más rápido por orden de magnitud y el que mejor escala.
El manejo de arreglos no puede competir.