J'ai une requête comme celle-ci qui génère bien une série de dates entre 2 dates données:
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
Il génère 162 dates entre 2004-03-07
et 2004-08-16
et ce que je veux. Le problème avec ce code est qu'il ne donnerait pas la bonne réponse lorsque les deux dates sont d'années différentes, par exemple lorsque j'essaye 2007-02-01
et 2008-04-01
.
Y a-t-il une meilleure solution?
Réponses:
Peut être fait sans conversion vers / depuis int (mais vers / depuis horodatage à la place)
SELECT date_trunc('day', dd):: date FROM generate_series ( '2007-02-01'::timestamp , '2008-04-01'::timestamp , '1 day'::interval) dd ;
la source
date_trunc
nécessaire?Pour générer une série de dates, c'est la manière optimale :
SELECT t.day::date FROM generate_series(timestamp '2004-03-07' , timestamp '2004-08-16' , interval '1 day') AS t(day);
Aucun supplément
date_trunc()
n'est nécessaire. Le cast todate
(day::date
) fait cela implicitement.Mais il est également inutile de convertir des littéraux de date
date
en paramètre d'entrée. Au contraire,timestamp
c'est le meilleur choix . L'avantage en termes de performances est faible, mais il n'y a aucune raison de ne pas en profiter. Et vous n'impliquez pas inutilement des règles DST (heure d'été) associées à la conversion dedate
verstimestamp with time zone
et inversement. Voir ci-dessous.Syntaxe courte équivalente, moins explicite:
SELECT day::date FROM generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;
Ou avec la fonction de retour de set dans la
SELECT
liste:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;
Le
AS
mot-clé est obligatoire dans la dernière variante, Postgres interpréterait mal l'alias de colonneday
sinon. Et je ne conseillerais pas cette variante avant Postgres 10 - du moins pas avec plus d'une fonction de retour d'ensemble dans la mêmeSELECT
liste:(Cela mis à part, la dernière variante est généralement la plus rapide par une petite marge.)
Pourquoi
timestamp [without time zone]
?Il existe un certain nombre de variantes surchargées de
generate_series()
. Actuellement (Postgres 11):(Des
numeric
variantes ont été ajoutées avec Postgres 9.5.) Les plus pertinentes sont les deux dernières en gras prenant et retournanttimestamp
/timestamptz
.Il n'y a pas de variante prise ou retour
date
. Un cast explicite est nécessaire pour revenirdate
. L'appel avec destimestamp
arguments résout directement la meilleure variante sans descendre dans les règles de résolution de type de fonction et sans conversion supplémentaire pour l'entrée.timestamp '2004-03-07'
est parfaitement valide, btw. La partie de temps omise est par défaut au00:00
format ISO.Grâce à la résolution du type de fonction, nous pouvons encore passer
date
. Mais cela nécessite plus de travail de la part de Postgres. Il y a un cast implicite dedate
àtimestamp
et un dedate
àtimestamptz
. Serait ambigu, maistimestamptz
est "préféré" parmi les "types de date / heure". Le match est donc décidé à l'étape 4d. :En plus du travail supplémentaire dans la résolution des types de fonction, cela ajoute une conversion supplémentaire
timestamptz
- ce qui non seulement ajoute plus de coûts, mais peut également introduire des problèmes avec DST conduisant à des résultats inattendus dans de rares cas. (L'heure d'été est un concept idiot, d'ailleurs, je ne saurais trop insister sur ce point.)J'ai ajouté des démos au violon montrant le plan de requête le plus cher:
db <> violon ici
En relation:
la source
SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
AS t(day)
dans l'SELECT * FROM func() AS t(day)
alias de table et de colonne. LeAS
mot-clé est bruit optionnel dans ce contexte. Voir: stackoverflow.com/a/20230716/939860Vous pouvez générer des séries directement avec des dates. Pas besoin d'utiliser des ints ou des horodatages:
select date::date from generate_series( '2004-03-07'::date, '2004-08-16'::date, '1 day'::interval ) date;
la source
Vous pouvez également l'utiliser.
select generate_series ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date
la source