sql >> Databasteknik >  >> RDS >> PostgreSQL

Representerar datum, tider och intervall i PostgreSQL

PostgreSQL kommer med ett gäng inbyggda datum- och tidsrelaterade datatyper. Varför ska du använda dem över strängar eller heltal? Vad ska du se upp med när du använder dem? Läs för att lära dig mer om hur du arbetar effektivt med dessa datatyper i Postgres.

En hel del typer

SQL-standarden, ISO 8601-standarden, PostgreSQL:s inbyggda katalog och bakåtkompatibilitet definierar tillsammans en uppsjö av överlappande, anpassningsbara datum-/tidsrelaterade datatyper och konventioner som i bästa fall är förvirrande. Denna förvirring sprider sig vanligtvis till databasdrivrutinskod, applikationskod, SQL-rutiner och resulterar i subtila buggar som är svåra att felsöka.

Å andra sidan förenklar användningen av inbyggda inbyggda typer SQL-satser och gör dem mycket lättare att läsa och skriva, och följaktligen mindre felbenägna. Att använda, säg heltal (antal sekunder sedan epok) för att representera tid, resulterar i svårhanterliga SQL-uttryck och mer programkod.

Fördelarna med inbyggda typer gör det värt besväret att definiera en uppsättning inte så smärtsamma regler och genomdriva dem i hela applikationen och operationskodbasen. Här är en sådan uppsättning, som bör ge sunda standardinställningar och en rimlig utgångspunkt för ytterligare anpassning om det behövs.

Typer

Använd endast följande tre typer (även om många är tillgängliga):

  • datum - ett specifikt datum, utan tid
  • timestamptz - ett specifikt datum och tid med mikrosekundsupplösning
  • intervall - ett tidsintervall med mikrosekundsupplösning

Dessa tre typer tillsammans bör stödja de flesta tillämpningsfall. Om du inte har specifika behov (som att spara förvaring) rekommenderas det starkt att du håller dig till just dessa typer.

datumet representerar ett datum utan tid och är ganska användbart i praktiken (se exempel nedan). Tidsstämpeltypen är varianten som inkluderar tidszonsinformationen – utan tidszonsinformationen finns det helt enkelt för många variabler som kan påverka tolkningen och extraheringen av värdet. Slutligen, intervallet representerar tidsintervall från så låga som en mikrosekund till miljoner år.

Literal Strings

Använd endast följande bokstavliga representationer, och använd rollbesättningsoperatorn för att minska detaljeringen utan att offra läsbarheten:

  • '2012-12-25'::date - ISO 8601
  • '2012-12-25 13:04:05.123-08:00'::timestamptz - ISO 8601
  • '1 month 3 days'::interval - Postgres traditionella format för intervallinmatning

Om du utelämnar tidszonen lämnas du överlämnad till Postgres-serverns tidszoninställning, TimeZone-konfigurationen som kan ställas in på databasnivå, sessionsnivå, rollnivå eller i anslutningssträngen, klientmaskinens tidszonsinställning och fler sådana faktorer.

När du frågar från applikationskoden, konvertera intervalltyper till en lämplig enhet (som dagar eller sekunder) med extract funktion och läs in värdet som ett heltal eller reellt värde.

Konfiguration och andra inställningar

  • Ändra inte standardinställningarna för GUC-konfigurationen DateStyle ,TimeZone och lc_time .
  • Ställ in eller använd inte miljövariablerna PGDATESTYLE och PGTZ .
  • Använd inte SET [SESSION|LOCAL] TIME ZONE ... .
  • Om du kan, ställ in systemets tidszon till UTC på maskinen som kör Postgres-servern, samt alla maskiner som kör programkod som ansluter till den.
  • Verifiera att din databasdrivrutin (som en JDBC-anslutare eller en Godatabase/sql-drivrutin) beter sig förnuftigt medan klienten körs på onetimezone och servern på en annan. Se till att det fungerar korrekt när en giltig icke-UTC TimeZone parametern ingår i anslutningssträngen.

Slutligen, notera att allt detta bara är riktlinjer och kan anpassas efter dina behov – men se till att du undersöker konsekvenserna av att göra det först.

Inbyggda typer och operatörer

Så exakt hur hjälper det att använda inbyggda typer för att förenkla SQL-kod? Här finns några exempel.

Datumtyp

Värden för datumet typ kan subtraheras för att ge intervallet mellan dem. Du kan också lägga till ett heltal av dagar till ett partikeldatum, eller lägga till ett intervall till ett datum för att ge en tidsstämpel :

-- 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ärdena för dessa typer är jämförbara, varför du kan beställa den sista frågan efter end_date - start_date , som har en typ av intervall . Här är ett annat exempel:

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

Tidsstämpeltyp

Värden av typen timestamptz kan också subtraheras (för att ge ett intervall ),läggs till (till ett intervall för att ge ytterligare en tidsstämpel ) och jämförs.

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

När du är inne på ämnet, notera att det finns 3 olika inbyggda funktioner som returnerar olika "aktuella tidsstämpel"-värden. De returnerar faktiskt olika saker:

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

Det finns även alias för dessa 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();

Intervalltyper

Intervalltypade värden kan användas som kolumndatatyper, kan jämföras med varandra och kan läggas till (och subtraheras från) tidsstämplar och datum. Här är några exempel:

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

Andra funktioner och konstruktioner

PostgreSQL kommer också med några användbara funktioner och konstruktioner som kan användas för att manipulera värden av dessa typer.

Extrahera

Extraherafunktionen kan användas för att hämta en specificerad del från det givna värdet, som månaden från ett datum. Den fullständiga listan över delar som kan extraheras finns dokumenterad här. Här är några användbara och icke-uppenbara exempel:

-- 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 sista exemplet är särskilt användbart i frågor som körs av applikationer, eftersom det kan vara lättare för applikationer att hantera ett intervall som ett flyttal för antalet sekunder/minuter/dagar/etc.

Tidszonsomvandling

Det finns också en praktisk funktion för att uttrycka en tidsstämpel i en annan tidszon. Vanligtvis skulle detta göras i applikationskoden – det är lättare att testa på det sättet och minskar beroendet av tidszonsdatabasen som Postgresserver kommer att referera till. Ändå kan det vara användbart ibland:

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

Konvertera till och från text

Funktionen to_char (docs)kan konvertera datum, tidsstämplar och intervall till text baserat på en formatsträng – Postgres-motsvarigheten till den klassiska C-funktionen 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');

För att konvertera från text till datum använd to_date , och för att konvertera text till tidsstämplar använd to_timestamp . Observera att om du använder formulären som listades i början av det här inlägget kan du bara använda cast-operatorerna istället.

-- 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 dokumentet för hela listan över formatsträngsmönster.

Det är bäst att använda dessa funktioner för enkla fall. För mer komplicerad analys eller formatering är det bättre att förlita sig på applikationskod, som (förmodligen) kan enhetstestades bättre.

Gränssnitt med applikationskod

Det är ibland inte bekvämt att skicka datum/tidsstämpeltz/intervallvärden till och från applikationskoden, speciellt när bundna parametrar används. Till exempel är det vanligtvis bekvämare att skicka ett intervall som ett heltal av dagar (eller timmar eller minuter) snarare än i ett strängformat. Det är också lättare att läsa i ett intervall som ett heltal/flyttal antal dagar (eller timmar, eller minuter etc.).

make_interval funktionen kan användas för att skapa ett intervallvärde från ett helt antal komponentvärden (se dokument här). to_timestamp funktion vi såg tidigare har en annan form som kan skapa atimestamptz-värde från Unix-epoktid.

-- 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. Grunderna för främmande nycklar i MySQL?

  2. Hur man läser versionsnummer från en databasfil i Android som placeras i tillgångsmappen

  3. Hur bestämmer du vilka SQL-tabeller som har en identitetskolumn programmatiskt

  4. Oracle SQL:Uppdatera en tabell med data från en annan tabell