Generación de series temporales entre dos fechas en PostgreSQL
Tengo una consulta como esta que genera muy bien una serie de fechas entre 2 fechas dadas:
select date '2004-03-07' + j - i as AllDate
from generate_series(0, extract(doy from date '2004-03-07')::int - 1) as i,
generate_series(0, extract(doy from date '2004-08-16')::int - 1) as j
Genera 162 fechas entre 2004-03-07
y 2004-08-16
y esto es lo que quiero. El problema con este código es que no daría la respuesta correcta cuando las dos fechas son de años diferentes, por ejemplo cuando intento 2007-02-01
y 2008-04-01
.
¿Hay una solución mejor?
3 answers
Se puede hacer sin conversión a / desde int (pero a/desde timestamp en su lugar)
SELECT date_trunc('day', dd):: date
FROM generate_series
( '2007-02-01'::timestamp
, '2008-04-01'::timestamp
, '1 day'::interval) dd
;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-01 19:40:39
Puede generar series directamente con fechas. No es necesario utilizar ints o marcas de tiempo:
select date::date
from generate_series(
'2004-03-07'::date,
'2004-08-16'::date,
'1 day'::interval
) date;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-24 16:09:02
Hay dos respuestas (hasta ahora). Ambos funcionan, pero ambos son subóptimos. He aquí un tercero:
SELECT day::date
FROM generate_series(timestamp '2004-03-07'
, timestamp '2004-08-16'
, interval '1 day') day;
No es necesario un
date_trunc()
adicional. El reparto adate
(day::date
) lo hace implícitamente.Pero tampoco tiene sentido lanzar literales de fecha a
date
como parámetro de entrada. Au contraire,timestamp
es la mejor opción aquí. La ventaja en el rendimiento es pequeña, pero no hay razón para no tomarla. Y usted no implica innecesariamente DST reglas acopladas con el tipo de datostimestamp with time zone
. Véase la explicación a continuación.
Equivalente más corto:
SELECT day::date
FROM generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;
O incluso con la función set-returning en la lista SELECT
:
SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;
La palabra clave AS
es requerida aquí, ya que el alias de la columna day
se malinterpretaría de otra manera.
Yo no aconsejaría usar la última antes de Postgres 10, al menos no con más de una función de retorno de conjuntos en la misma lista SELECT
. Véase:
¿Por qué?
Hay una serie de variantes sobrecargadas de generate_series()
. Actualmente (Postgres 10):
SELECT oid::regprocedure AS function_signature , prorettype::regtype AS return_type FROM pg_proc where proname = 'generate_series';
function_signature | return_type :-------------------------------------------------------------------------------- | :-------------------------- generate_series(integer,integer,integer) | integer generate_series(integer,integer) | integer generate_series(bigint,bigint,bigint) | bigint generate_series(bigint,bigint) | bigint generate_series(numeric,numeric,numeric) | numeric generate_series(numeric,numeric) | numeric generate_series(timestamp without time zone,timestamp without time zone,interval) | timestamp without time zone generate_series(timestamp with time zone,timestamp with time zone,interval) | timestamp with time zone
La variante tomar y devolver numeric
se añadió con Postgres 9.5. Pero los únicos relevantes aquí son los dos últimos en negrita tomando y regresando timestamp
/ timestamptz
.
As puedes ver, no hay ninguna variante que tome o devuelva date
. Es por eso que necesitamos un cast explícito si queremos devolver date
. Pasar timestamp
resuelve a la función derecha directamente sin tener que descender a las reglas de resolución de tipo de función y sin conversión adicional para la entrada.
Y timestamp '2004-03-07'
es perfectamente válido. La parte de tiempo por defecto es 00:00
si se omite.
Gracias a tipo de función resolución todavía podemos pasar date
. Pero eso requiere más trabajo de Postgres. Hay una implicit cast from date
to timestamp
as well as from date
to timestamptz
. Sería ambiguo, pero timestamptz
es "preferido" entre los "tipos de Fecha/hora". Así que la coincidencia se decide en el paso 4d.:
Revise todos los candidatos y mantenga aquellos que aceptan tipos preferidos (de la categoría de tipo del tipo de datos de entrada) en la mayoría de las posiciones donde se requerirá conversión de tipo. Mantener todos los candidatos si ninguno acepta tipos preferidos. Si solo queda un candidato, úselo; de lo contrario continúe al siguiente paso.
Además del trabajo extra en la resolución de tipo de función, esto agrega un molde adicional a timestamptz
. El reparto de timestamptz
no solo agrega más costo, sino que también puede introducir problemas con el horario de verano (DST) que conducen a resultados inesperados en casos raros. (DST es un concepto estúpido, por cierto, no puedo enfatizar esto lo suficiente.) Relacionado:
- ¿Cómo puedo generar una serie de fechas en PostgreSQL?
- ¿Cómo puedo generar una serie temporal en PostgreSQL?
He añadido demos al violín para mostrar el plan de consulta más caro:
dbfiddle aquí
Relacionado:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-13 00:56:32