PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Représentation des dates, des heures et des intervalles dans PostgreSQL

PostgreSQL est livré avec un tas de types de données intégrés liés à la date et à l'heure. Pourquoi devriez-vous les utiliser sur des chaînes ou des entiers ? À quoi devez-vous faire attention lorsque vous les utilisez ? Lisez pour en savoir plus sur la façon de travailler efficacement avec ces types de données dans Postgres.

Tout un tas de types

La norme SQL, la norme ISO 8601, le catalogue intégré de PostgreSQL et la rétrocompatibilité définissent ensemble une pléthore de types de données et de conventions liés à la date/heure qui se chevauchent et sont personnalisables, ce qui est au mieux déroutant. Cette confusion déborde généralement sur le code du pilote de base de données, le code d'application, les routines SQL et entraîne des bogues subtils difficiles à déboguer.

D'autre part, l'utilisation de types intégrés natifs simplifie les instructions SQL et les rend beaucoup plus faciles à lire et à écrire, et par conséquent, moins sujettes aux erreurs. L'utilisation, par exemple, d'entiers (nombre de secondes depuis l'époque) pour représenter le temps, entraîne des expressions SQL peu maniables. et plus de code d'application.

Les avantages des types natifs font qu'il est intéressant de définir un ensemble de règles pas si pénibles et de les appliquer dans l'ensemble de l'application et de la base de code ops. Voici un tel ensemble, qui devrait fournir des valeurs par défaut saines et un point de départ raisonnable pour une personnalisation plus poussée si nécessaire.

Types

Utilisez uniquement les 3 types suivants (bien que plusieurs soient disponibles) :

  • date - une date précise, sans heure
  • horodatagetz - une date et une heure précises avec une résolution en microsecondes
  • intervalle - un intervalle de temps avec une résolution à la microseconde

Ensemble, ces trois types devraient prendre en charge la plupart des cas d'utilisation d'applications. Si vous n'avez pas de besoins spécifiques (comme la conservation de l'espace de stockage), il est fortement recommandé de vous en tenir à ces types.

La date représente une date sans heure, et est très utile dans la pratique (voir exemples ci-dessous). Le type d'horodatage est la variante qui inclut les informations de fuseau horaire - sans les informations de fuseau horaire, il y a tout simplement trop de variables qui peuvent affecter l'interprétation et l'extraction de la valeur. Enfin, l'intervalle représente des intervalles de temps allant d'une microseconde à des millions d'années.

Chaînes littérales

Utilisez uniquement les représentations littérales suivantes et utilisez l'opérateur cast pour réduire la verbosité sans sacrifier la lisibilité :

  • '2012-12-25'::date -ISO 8601
  • '2012-12-25 13:04:05.123-08:00'::timestamptz -ISO 8601
  • '1 month 3 days'::interval - Format Postgres traditionnel pour la saisie d'intervalles

L'omission du fuseau horaire vous laisse à la merci du paramètre de fuseau horaire du serveur Postgres, de la configuration du fuseau horaire qui peut être définie au niveau de la base de données, de la session, du rôle ou dans la chaîne de connexion, du paramètre de fuseau horaire de la machine cliente et plus de tels facteurs.

Lors de l'interrogation à partir du code d'application, convertissez les types d'intervalles en une unité appropriée (comme des jours ou des secondes) à l'aide de l'extract fonction et lire la valeur sous forme d'entier ou de valeur réelle.

Configuration et autres paramètres

  • Ne modifiez pas les paramètres par défaut pour la configuration GUC DateStyle ,TimeZone et lc_time .
  • Ne définissez pas ou n'utilisez pas les variables d'environnement PGDATESTYLE et PGTZ .
  • N'utilisez pas SET [SESSION|LOCAL] TIME ZONE ... .
  • Si vous le pouvez, définissez le fuseau horaire du système sur UTC sur la machine qui exécute le serveur Postgres, ainsi que sur toutes les machines exécutant le code d'application qui s'y connectent.
  • Vérifiez que votre pilote de base de données (comme un connecteur JDBC ou un pilote Godatabase/sql) se comporte de manière raisonnable lorsque le client s'exécute sur un fuseau horaire et le serveur sur un autre. Assurez-vous qu'il fonctionne correctement lorsqu'un TimeZone valide non-UTC Le paramètre est inclus dans la chaîne de connexion.

Enfin, notez que tout cela n'est qu'un guide et peut être modifié en fonction de vos besoins, mais assurez-vous d'abord d'étudier les implications d'une telle action.

Types et opérateurs natifs

Alors, comment l'utilisation de types natifs aide-t-elle exactement à simplifier le code SQL ? Voici quelques exemples.

Type de date

Valeurs de la date le type peut être soustrait pour donner l'intervalle entre eux. Vous pouvez également ajouter un nombre entier de jours à une date particulaire, ou ajouter un intervalle à une date pour donner un timestamptz :

-- 10 days from now (outputs 2020-07-26)
SELECT now()::date + 10;
 
-- 10 days from now (outputs 2020-07-26 04:44:30.568847+00)
SELECT now() + '10 days'::interval;

-- days till christmas (outputs 161 days 14:06:26.759466)
SELECT '2020-12-25'::date - now();

-- the 10 longest courses
  SELECT name, end_date - start_date AS duration
    FROM courses
ORDER BY end_date - start_date DESC
   LIMIT 10;

Les valeurs de ces types sont comparables, c'est pourquoi vous pouvez ordonner la dernière requête par end_date - start_date , qui a un type d'intervalle . Voici un autre exemple :

-- certificates expiring within the next 7 days
SELECT name
  FROM certificates
 WHERE expiry_date BETWEEN now() AND now() + '7 days'::interval;

Type d'horodatage

Valeurs de type timestamptz peut également être soustrait (pour donner un intervalle ), ajouté (à un intervalle donner un autre timestamptz ) et comparé.

-- difference of timestamps gives an interval
SELECT password_last_modified - created_at AS password_age
  FROM users;

-- can also use the age() function
SELECT age(password_last_modified, created_at) AS password_age
  FROM users;

Pendant que vous êtes sur le sujet, notez qu'il existe 3 fonctions intégrées différentes qui renvoient diverses valeurs "d'horodatage actuel". En fait, ils renvoient différentes choses :

-- transaction_timestamp() returns the timestampsz of the start of current transaction
-- outputs 2020-07-16 05:09:32.677409+00
SELECT transaction_timestamp();

-- statement_timestamp() returns the timestamptz of the start of the current statement
SELECT statement_timestamp();

-- clock_timestamp() returns the timestamptz of the system clock
SELECT clock_timestamp();

Il existe également des alias pour ces fonctions :

-- now() actually returns the start of the current transaction, which means it
-- does not change during the transaction
SELECT now(), transaction_timestamp();

-- transaction timestamp is also returned by these keyword-style constructs
SELECT CURRENT_DATE, CURRENT_TIMESTAMP, transaction_timestamp();

Types d'intervalle

Les valeurs de type intervalle peuvent être utilisées comme types de données de colonne, peuvent être comparées entre elles et peuvent être ajoutées (et soustraites) aux horodatages et aux dates.Voici quelques exemples :

-- interval-typed values can be stored and compared 
  SELECT num
    FROM passports
   WHERE valid_for > '10 years'::interval
ORDER BY valid_for DESC;

-- you can multiply them by numbers (outputs 4 years)
SELECT 4 * '1 year'::interval;

-- you can divide them by numbers (outputs 3 mons)
SELECT '1 year'::interval / 4;

-- you can add and subtract them (outputs 1 year 1 mon 6 days)
SELECT '1 year'::interval + '1.2 months'::interval;

Autres fonctions et constructions

PostgreSQL est également livré avec quelques fonctions et constructions utiles qui peuvent être utilisées pour manipuler des valeurs de ces types.

Extraire

La fonction d'extraction peut être utilisée pour récupérer une partie spécifiée de la valeur donnée, comme le mois à partir d'une date. La liste complète des pièces pouvant être extraites est documentée ici. Voici quelques exemples utiles et non évidents :

-- years from an interval (outputs 2)
SELECT extract(YEARS FROM '1.5 years 6 months'::interval);

-- day of the week (0=Sun .. 6=Sat) from timestamp (outputs 4)
SELECT extract(DOW FROM now());

-- day of the week (1=Mon .. 7=Sun) from timestamp (outputs 4)
SELECT extract(ISODOW FROM now());

-- convert interval to seconds (outputs 86400)
SELECT extract(EPOCH FROM '1 day'::interval);

Le dernier exemple est particulièrement utile dans les requêtes exécutées par des applications, car il peut être plus facile pour les applications de gérer un intervalle comme une valeur à virgule flottante du nombre de secondes/minutes/jours/etc.

Conversion de fuseau horaire

Il existe également une fonction pratique pour exprimer un timestamptz dans un autre fuseau horaire. En règle générale, cela se ferait dans le code de l'application - il est plus facile de tester de cette façon et réduit la dépendance à la base de données de fuseaux horaires à laquelle le serveur Postgres se référera. Néanmoins, cela peut parfois être utile :

-- convert timestamps to a different time zone
SELECT timezone('Europe/Helsinki', now());

-- same as before, but this one is a SQL standard
SELECT now() AT TIME ZONE 'Europe/Helsinki';

Conversion vers et à partir de texte

La fonction to_char (docs) peut convertir des dates, des horodatages et des intervalles en texte en fonction d'une chaîne de format - l'équivalent Postgres de la fonction C classique strftime .

-- outputs Thu, 16th July
SELECT to_char(now(), 'Dy, DDth Month');

-- outputs 01 06 00 12 00 00
SELECT to_char('1.5 years'::interval, 'YY MM DD HH MI SS');

Pour convertir du texte en dates, utilisez to_date , et pour convertir du texte en horodatages, utilisez to_timestamp . Notez que si vous utilisez les formulaires répertoriés au début de cet article, vous pouvez simplement utiliser les opérateurs de distribution à la place.

-- outputs 2000-12-25 15:42:50+00
SELECT to_timestamp('2000.12.25.15.42.50', 'YYYY.MM.DD.HH24.MI.SS');

-- outputs 2000-12-25
SELECT to_date('2000.12.25.15.42.50', 'YYYY.MM.DD');

Consultez la documentation pour la liste complète des modèles de chaîne de format.

Il est préférable d'utiliser ces fonctions pour des cas simples. Pour une analyse ou un formatage plus compliqué, il est préférable de s'appuyer sur le code de l'application, qui peut (sans doute) être mieux testé unitaire.

Interfaçage avec le code d'application

Il n'est parfois pas pratique de transmettre des valeurs de date/horodatage/intervalle vers et depuis le code d'application, en particulier lorsque des paramètres liés sont utilisés. Par exemple, il est généralement plus pratique de transmettre un intervalle sous la forme d'un nombre entier de jours (ou d'heures ou de minutes) plutôt que sous la forme d'une chaîne. Il est également plus facile de lire un intervalle sous la forme d'un nombre entier/à virgule flottante de jours (ou d'heures, ou de minutes, etc.).

Le make_interval La fonction peut être utilisée pour créer une valeur d'intervalle à partir d'un nombre entier de valeurs de composants (voir la documentation ici). Le to_timestamp La fonction que nous avons vue précédemment a une autre forme qui peut créer une valeur atimestamptz à partir de l'heure d'époque Unix.

-- pass the interval as number of days from the application code
SELECT name FROM courses WHERE duration <= make_interval(days => $1);

-- pass timestamptz as unix epoch (number of seconds from 1-Jan-1970)
SELECT id FROM events WHERE logged_at >= to_timestamp($1);

-- return interval as number of days (with a fractional part)
SELECT extract(EPOCH FROM duration) / 60 / 60 / 24;