web-dev-qa-db-fra.com

Générer des séries temporelles entre deux dates dans PostgreSQL

J'ai une requête comme celle-ci qui génère 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 c'est 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'essaie 2007-02-01 et 2008-04-01.

Y a-t-il une meilleure solution?

48
f.ashouri

Peut être fait sans conversion vers/de int (mais vers/de timestamp à la place)

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;
99
wildplasser

Il y a deux réponses (jusqu'à présent). Les deux fonctionnent, mais les deux sont sous-optimaux. En voici un troisième:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') day;
  • Pas besoin d'une date_trunc() supplémentaire. Le transtypage en date (day::date) le fait implicitement.

  • Mais il est également inutile de transtyper les littéraux de date en date en tant que paramètre d'entrée. Au contraire, timestamp est le meilleur choix ici. L'avantage en termes de performances est faible, mais il n'y a aucune raison de ne pas le prendre. Et vous n'impliquez pas inutilement les règles DST associées au type de données timestamp with time zone. Voir l'explication ci-dessous.

Équivalent plus court:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;

Ou même avec la fonction retour-ensemble dans la liste SELECT:

SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;

Le mot clé AS est obligatoire ici, car la colonne alias day serait mal comprise sinon.

Je conseillerais pas d'utiliser le dernier avant Postgres 10, du moins pas avec plus d'une fonction de renvoi d'ensemble dans la même liste SELECT. Voir:

Pourquoi?

Il existe un certain nombre de variantes surchargées de generate_series() . Actuellement (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 (entier, entier, entier) | integer 
 generate_series (entier, entier) | integer 
 generate_series (bigint, bigint, bigint) | bigint 
 generate_series (bigint, bigint) | bigint 
 generate_series (numérique, numérique, numérique) | numeric 
 generate_series (numeric, numeric) | numérique 
generate_series (horodatage sans fuseau horaire, horodatage sans fuseau horaire, intervalle) | horodatage sans fuseau horaire 
 generate_series (horodatage avec fuseau horaire, horodatage avec fuseau horaire, intervalle) | horodatage avec fuseau horaire

La variante prenant et renvoyant numeric a été ajoutée à Postgres 9.5. Mais les seuls pertinents ici sont les deux derniers en gras qui prennent et retournent timestamptimestamptz.

COMME VOUS POUVEZ LE CONSTATER, IL N’EXISTE _/AUCUNE VARIANTE PRENANT OU RENVOYANT date. C'est pourquoi nous avons besoin d'une distribution explicite si nous voulons renvoyer date. Passer timestamp résout directement la fonction correcte sans avoir à descendre dans les règles de résolution du type de fonction et sans transtypage supplémentaire pour l'entrée.

Et timestamp '2004-03-07' est parfaitement valide. La valeur heure par défaut est 00:00 si elle est omise.

Merci à résolution du type de fonction nous pouvons toujours passer date. Mais cela nécessite plus de travail de Postgres. Il existe un implicite cast de date à timestamp ainsi que de date à timestamptz. Serait ambigu, mais timestamptz est "préféré" parmi "Types de date/heure". Donc, le match est décidé à l'étape 4d. :

Parcourez tous les candidats et conservez ceux qui acceptent les types préférés (de la catégorie de type du type de données en entrée) aux positions les plus fréquentes où la conversion de type sera nécessaire. Gardez tous les candidats si aucun n'accepte types préférés. S'il ne reste qu'un candidat, utilisez-le; sinon continue à l'étape suivante.

En plus du travail supplémentaire lié à la résolution du type de fonction, ceci ajoute une conversion supplémentaire à timestamptz. Le transtypage en timestamptz augmente non seulement le coût, mais peut également poser des problèmes en ce qui concerne l’heure d’été (DST) entraînant des résultats inattendus dans de rares cas. (L'heure d'été est un concept débile, d'ailleurs, je ne saurais trop insister là-dessus.)

J'ai ajouté des démos au violon pour montrer le plan de requête plus coûteux:

_ {dbfiddle here

En relation:

31
Erwin Brandstetter

Vous pouvez générer des séries directement avec des dates. Pas besoin d'utiliser ints ou timestamps:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;
30
fbonetti

vous pouvez utiliser comme

select generate_series ('2012-12-31' :: horodatage, '2018-10-31' :: horodatage, '1 jour' :: intervalle) :: date 

0
Meyyappan