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

Repræsentation af datoer, tidspunkter og intervaller i PostgreSQL

PostgreSQL kommer med en masse indbyggede dato- og tidsrelaterede datatyper. Hvorfor skulle du bruge dem over strenge eller heltal? Hvad skal du være opmærksom på, mens du bruger dem? Læs for at lære mere om, hvordan du arbejder effektivt med disse datatyper i Postgres.

En hel masse typer

SQL-standarden, ISO 8601-standarden, PostgreSQL's indbyggede katalog og bagudkompatibilitet definerer tilsammen et væld af overlappende, tilpasselige dato/tidsrelaterede datatyper og konventioner, der i bedste fald er forvirrende. Denne forvirring smitter typisk af på databasedriverkode, applikationskode, SQL-rutiner og resulterer i subtile fejl, som er svære at fejlfinde.

På den anden side forenkler brugen af ​​native indbyggede typer SQL-sætninger og gør dem meget nemmere at læse og skrive, og dermed mindre fejltilbøjelige. Brug af f.eks. heltal (antal sekunder siden epoke) til at repræsentere tid, resulterer i uhåndterlige SQL-udtryk og mere applikationskode.

Fordelene ved native typer gør det umagen værd at definere et sæt ikke-så-smertefulde regler og håndhæve dem i hele applikationen og ops-kodebasen. Her er et sådant sæt, som skulle give fornuftige standardindstillinger og et rimeligt udgangspunkt for yderligere tilpasning, hvis det kræves.

Typer

Brug kun følgende 3 typer (selvom mange er tilgængelige):

  • dato - en bestemt dato uden tid
  • tidsstempel - en bestemt dato og tid med mikrosekundsopløsning
  • interval - et tidsinterval med mikrosekundsopløsning

Disse tre typer tilsammen burde understøtte de fleste anvendelsessager. Hvis du ikke har specifikke behov (såsom at spare opbevaring), anbefales det stærkt at holde sig til netop disse typer.

datoen repræsenterer en dato uden tid og er ret nyttig i praksis (se eksempler nedenfor). Tidsstemplingstypen er varianten, der inkluderer tidszoneinformationen – uden tidszoneinformationen er der simpelthen for mange variabler, der kan påvirke fortolkningen og udtrækket af værdien. Til sidst, intervallet repræsenterer tidsintervaller fra så lave som et mikrosekund op til millioner af år.

Literale strenge

Brug kun følgende bogstavelige repræsentationer, og brug rollebesætningsoperatøren for at reducere ordlyden uden at ofre læsbarheden:

  • '2012-12-25'::date - ISO 8601
  • '2012-12-25 13:04:05.123-08:00'::timestamptz - ISO 8601
  • '1 month 3 days'::interval - Postgres traditionelle format til intervalinput

Udeladelse af tidszonen efterlader dig prisgivet Postgres-serverens tidszoneindstilling, TimeZone-konfigurationen, der kan indstilles på databaseniveau, sessionsniveau, rolleniveau eller i forbindelsesstrengen, klientmaskinens tidszoneindstilling og flere sådanne faktorer.

Mens du forespørger fra applikationskode, skal du konvertere intervaltyper til en passende enhed (som dage eller sekunder) ved hjælp af extract funktion og læs værdien ind som et heltal eller en reel værdi.

Konfiguration og andre indstillinger

  • Rediger ikke standardindstillingerne for GUC-konfigurationen DateStyle ,TimeZone og lc_time .
  • Indstil eller brug ikke miljøvariablerne PGDATESTYLE og PGTZ .
  • Brug ikke SET [SESSION|LOCAL] TIME ZONE ... .
  • Hvis du kan, skal du indstille systemets tidszone til UTC på den maskine, der kører Postgres-serveren, samt alle de maskiner, der kører applikationskode, der forbinder til den.
  • Valider, at din databasedriver (som en JDBC-forbindelse eller en Godatabase/sql-driver) opfører sig fornuftigt, mens klienten kører på onetimezone og serveren på en anden. Sørg for, at det fungerer korrekt, når en gyldig ikke-UTC TimeZone parameter er inkluderet i forbindelsesstrengen.

Bemærk endelig, at alle disse kun er retningslinjer og kan tilpasses til dine behov – men sørg for at undersøge konsekvenserne af at gøre det først.

Native typer og operatører

Så hvordan hjælper brugen af ​​native typer med at forenkle SQL-kode? Her er nogle eksempler.

Datotype

Værdier for datoen type kan trækkes fra for at give intervallet mellem dem. Du kan også tilføje et helt antal dage til en partikeldato eller tilføje et interval til en dato for at give et tidsstempel :

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

Værdierne for disse typer er sammenlignelige, hvorfor du kan bestille den sidste forespørgsel efter end_date - start_date , som har en type interval . Her er et andet eksempel:

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

Tidsstempeltype

Værdier af typen timestamptz kan også trækkes fra (for at give et interval ),tilføjet (til et interval for at give endnu en tidsstempel ) og sammenlignet.

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

Mens du er på emnet, skal du bemærke, at der er 3 forskellige indbyggede funktioner, der returnerer forskellige "aktuelle tidsstempel"-værdier. De returnerer faktisk forskellige ting:

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

Der er også aliaser for disse funktioner:

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

Intervaltyper

Intervaltypeværdier kan bruges som kolonnedatatyper, kan sammenlignes med hinanden og kan lægges til (og trækkes fra) tidsstempler og datoer. Her er et par eksempler:

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

Andre funktioner og konstruktioner

PostgreSQL kommer også med et par nyttige funktioner og konstruktioner, der kan bruges til at manipulere værdier af disse typer.

Udtræk

Udtræksfunktionen kan bruges til at hente en specificeret del fra den givne værdi, f.eks. måneden fra en dato. Den fulde liste over dele, der kan udtrækkes, er dokumenteret her. Her er et par nyttige og ikke-indlysende eksempler:

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

Det sidste eksempel er især nyttigt i forespørgsler, der køres af applikationer, da det kan være nemmere for applikationer at håndtere et interval som en flydende kommaværdi for antallet af sekunder/minutter/dage/osv.

Tidszonekonvertering

Der er også en praktisk funktion til at udtrykke en tidsstempel i en anden tidszone. Dette vil typisk blive gjort i applikationskoden – det er nemmere at teste på den måde og reducerer afhængigheden af ​​tidszonedatabasen, som Postgresserveren vil referere til. Ikke desto mindre kan det til tider være nyttigt:

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

Konvertering til og fra tekst

Funktionen to_char (docs)kan konvertere datoer, tidsstempler og intervaller til tekst baseret på en formatstreng – Postgres-ækvivalenten til den klassiske C-funktion 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'); 

Brug to_date til at konvertere fra tekst til datoer , og for at konvertere tekst til tidsstempler, brug to_timestamp . Bemærk, at hvis du bruger de formularer, der var angivet i begyndelsen af ​​dette indlæg, kan du bare bruge cast-operatorerne i stedet.

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

Se dokumenterne for den fulde liste over formatstrengmønstre.

Det er bedst at bruge disse funktioner til simple tilfælde. For mere kompliceret parsing eller formatering er det bedre at stole på applikationskode, som (velsagtens) bedre kan enhedstestes.

Interfacing med applikationskode

Det er nogle gange ikke praktisk at sende dato/tidsstempel/intervalværdier til og fra applikationskode, især når bundne parametre bruges. For eksempel er det normalt mere praktisk at sende et interval som et heltal af dage (eller timer eller minutter) i stedet for i et strengformat. Det er også lettere at læse i et interval som et heltal/floating-point antal dage (eller timer eller minutter osv.).

make_interval funktionen kan bruges til at skabe en intervalværdi ud fra et helt antal komponentværdier (se dokumenter her). to_timestamp funktion, vi så tidligere, har en anden form, der kan skabe atimestamptz-værdi fra Unix-epoketid.

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

  1. Oracle:forskel mellem max(id)+1 og sequence.nextval

  2. Hvorfor bruge INCLUDE-sætningen, når du opretter et indeks?

  3. Sådan fungerer MICROSECOND() i MariaDB

  4. Sådan fungerer SQLite Min()