PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Rappresentazione di date, orari e intervalli in PostgreSQL

PostgreSQL viene fornito con una serie di tipi di dati integrati relativi a data e ora. Perché dovresti usarli su stringhe o numeri interi? A cosa dovresti prestare attenzione mentre li usi? Leggi per saperne di più su come lavorare in modo efficace con questi tipi di dati in Postgres.

Un sacco di tipi

Lo standard SQL, lo standard ISO 8601, il catalogo integrato di PostgreSQL e la compatibilità con le versioni precedenti insieme definiscono una pletora di tipi di dati e convenzioni sovrapposti e personalizzabili relativi a data/ora che creano confusione nella migliore delle ipotesi. Questa confusione in genere si riversa nel codice del driver del database, nel codice dell'applicazione, nelle routine SQL e si traduce in bug sottili di cui è difficile eseguire il debug.

D'altra parte, l'uso di tipi incorporati nativi semplifica le istruzioni SQL e le rende molto più facili da leggere e da scrivere e, di conseguenza, meno soggette a errori. L'uso, ad esempio, di numeri interi (numero di secondi da epoch) per rappresentare il tempo, risulta in espressioni SQL ingombranti e altro codice dell'applicazione.

I vantaggi dei tipi nativi rendono utile definire un insieme di regole non troppo dolorose e applicarle in tutta l'applicazione e nella base di codice operativa. Eccone uno di questi set, che dovrebbe fornire impostazioni predefinite sane e un punto di partenza ragionevole per un'ulteriore personalizzazione, se necessario.

Tipi

Usa solo i seguenti 3 tipi (sebbene molti siano disponibili):

  • data - una data specifica, senza ora
  • timestamptz - una data e un'ora specifiche con risoluzione di microsecondi
  • intervallo - un intervallo di tempo con risoluzione di microsecondi

Questi tre tipi insieme dovrebbero supportare la maggior parte dei casi d'uso delle applicazioni. Se non hai esigenze specifiche (come la conservazione dello spazio di archiviazione), ti consigliamo vivamente di attenerti solo a questi tipi.

La data rappresenta una data senza ora, ed è abbastanza utile nella pratica (vedi esempi sotto). Il tipo di timestamp è la variante che include le informazioni sul fuso orario:senza le informazioni sul fuso orario ci sono semplicemente troppe variabili che possono influenzare l'interpretazione e l'estrazione del valore. Infine, l'intervallo rappresenta intervalli di tempo da un microsecondo fino a milioni di anni.

Stringhe letterali

Usa solo le seguenti rappresentazioni letterali e usa l'operatore cast per ridurre la verbosità senza sacrificare la leggibilità:

  • '2012-12-25'::date - ISO 8601
  • '2012-12-25 13:04:05.123-08:00'::timestamptz - ISO 8601
  • '1 month 3 days'::interval - Formato tradizionale Postgres per l'input a intervalli

L'omissione del fuso orario ti lascia alla mercé dell'impostazione del fuso orario del server Postgres, della configurazione del fuso orario che può essere impostata a livello di database, sessione, ruolo o nella stringa di connessione, dell'impostazione del fuso orario della macchina client e più tali fattori.

Durante la query dal codice dell'applicazione, converti i tipi di intervallo in un'unità adatta (come giorni o secondi) utilizzando extract funzione e leggere il valore come intero o valore reale.

Configurazione e altre impostazioni

  • Non modificare le impostazioni predefinite per la configurazione GUC DateStyle ,TimeZone e lc_time .
  • Non impostare o utilizzare le variabili di ambiente PGDATESTYLE e PGTZ .
  • Non utilizzare SET [SESSION|LOCAL] TIME ZONE ... .
  • Se puoi, imposta il fuso orario di sistema su UTC sulla macchina che esegue il server Postgres, così come su tutte le macchine che eseguono il codice dell'applicazione che si connettono ad esso.
  • Convalida che il driver del database (come un connettore JDBC o un driver Godatabase/sql) si comporti in modo ragionevole mentre il client è in esecuzione su un fuso orario e il server su un altro. Assicurati che funzioni correttamente quando un TimeZone valido non UTC il parametro è incluso nella stringa di connessione.

Infine, tieni presente che tutte queste sono solo linee guida e possono essere modificate in base alle tue esigenze, ma assicurati di indagare prima sulle implicazioni di farlo.

Tipi nativi e operatori

Quindi, in che modo esattamente l'utilizzo dei tipi nativi aiuta a semplificare il codice SQL? Ecco alcuni esempi.

Tipo data

Valori della data tipo può essere sottratto per dare l'intervallo tra loro. Puoi anche aggiungere un numero intero di giorni a una data particolare, o aggiungere un intervallo a una data per dare 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;

I valori di questi tipi sono comparabili, motivo per cui puoi ordinare l'ultima query per end_date - start_date , che ha un tipo di intervallo . Ecco un altro esempio:

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

Tipo di timestamp

Valori di tipo timestamptz può anche essere sottratto (per dare un intervallo ),aggiunto (a un intervallo per dare un altro timestamptz ) e confrontati.

-- 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;

Durante l'argomento, nota che ci sono 3 diverse funzioni integrate che restituiscono vari valori di "marcatura temporale corrente". In realtà restituiscono cose diverse:

-- 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();

Esistono anche alias per queste funzioni:

-- 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();

Tipi di intervallo

I valori tipizzati a intervallo possono essere utilizzati come tipi di dati di colonna, possono essere confrontati tra loro e possono essere aggiunti (e sottratti da) timestamp e date. Ecco alcuni esempi:

-- 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;

Altre funzioni e costrutti

PostgreSQL include anche alcune utili funzioni e costrutti che possono essere usati per manipolare valori di questo tipo.

Estrai

La funzione di estrazione può essere utilizzata per recuperare una parte specificata dal valore dato, come il mese da una data. L'elenco completo delle parti che possono essere estratte è documentato qui. Di seguito alcuni esempi utili e non ovvi:

-- 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);

L'ultimo esempio è particolarmente utile nelle query eseguite dalle applicazioni, poiché può essere più semplice per le applicazioni gestire un intervallo come valore in virgola mobile del numero di secondi/minuti/giorni/ecc.

Conversione del fuso orario

C'è anche una pratica funzione per esprimere un timestamptz in un altro fuso orario. In genere ciò verrebbe eseguito nel codice dell'applicazione:è più semplice eseguire il test in questo modo e riduce la dipendenza dal database del fuso orario a cui farà riferimento Postgresserver. Tuttavia, a volte può essere 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';

Conversione da e verso testo

La funzione to_char (docs)può convertire date, timestamp e intervalli in testo in base a una stringa di formato, l'equivalente Postgres della classica funzione C 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');

Per convertire da testo a date, usa to_date e per convertire il testo in timestamp usa to_timestamp . Tieni presente che se utilizzi i moduli elencati all'inizio di questo post, puoi semplicemente utilizzare gli operatori di cast.

-- 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');

Consulta i documenti per l'elenco completo dei modelli di stringa di formato.

È meglio usare queste funzioni per casi semplici. Per un'analisi o una formattazione più complicate, è meglio fare affidamento sul codice dell'applicazione, che può (probabilmente) essere sottoposto a test unitari migliori.

Interfaccia con il codice dell'applicazione

A volte non è conveniente passare valori di data/timestamptz/intervallo ae dal codice dell'applicazione, specialmente quando vengono utilizzati parametri associati. Ad esempio, di solito è più conveniente passare un intervallo come numero intero di giorni (o ore o minuti) piuttosto che in un formato stringa. È anche più facile leggere in un intervallo come numero intero/virgola mobile di giorni (o ore, minuti ecc.).

Il make_interval La funzione può essere utilizzata per creare un valore di intervallo da un numero intero di valori di componenti (consultare i documenti qui). Il to_timestamp la funzione che abbiamo visto in precedenza ha un'altra forma che può creare un valore atimestamptz dall'epoca di 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;