sql >> Databáze >  >> RDS >> PostgreSQL

Jaký typ časového razítka bych si měl vybrat v databázi PostgreSQL?

Za prvé, zpracování času a aritmetika PostgreSQL je fantastické a možnost 3 je v obecném případě v pořádku. Je to však neúplný pohled na čas a časová pásma a lze jej doplnit:

  1. Uložte název časového pásma uživatele jako uživatelské preference (např. America/Los_Angeles , nikoli -0700 ).
  2. Nechat si odesílat údaje o událostech/čase uživatele lokálně podle jejich referenčního rámce (nejpravděpodobněji odchylka od UTC, například -0700 ).
  3. V aplikaci převeďte čas na UTC a uloženy pomocí TIMESTAMP WITH TIME ZONE sloupec.
  4. Vrátit požadavky na čas v místním časovém pásmu uživatele (tj. převést z UTC do America/Los_Angeles ).
  5. Nastavte timezone databáze do UTC .

Tato možnost nefunguje vždy, protože může být obtížné získat časové pásmo uživatele, a proto je doporučeno použít TIMESTAMP WITH TIME ZONE pro lehké aplikace. Dovolte mi proto vysvětlit některé základní aspekty této možnosti 4 podrobněji.

Stejně jako možnost 3, důvod pro WITH TIME ZONE protože čas, kdy se něco stalo, je absolutní chvíle v čase. WITHOUT TIME ZONE dává příbuzného časové pásmo. Nikdy, nikdy, nikdy nesměšujte absolutní a relativní TIMESTAMP.

Z programového a konzistentního hlediska zajistěte, aby byly všechny výpočty prováděny s použitím časového pásma UTC. Toto není požadavek PostgreSQL, ale pomáhá to při integraci s jinými programovacími jazyky nebo prostředími. Nastavení CHECK na sloupci, abyste se ujistili, že zápis do sloupce časového razítka má posun časového pásma 0 je obranná pozice, která zabraňuje několika třídám chyb (např. skript ukládá data do souboru a něco jiného třídí časová data pomocí lexikálního řazení). Opět platí, že PostgreSQL to nepotřebuje ke správnému výpočtu data nebo k převodu mezi časovými pásmy (tj. PostgreSQL je velmi zběhlý v převodu časů mezi libovolnými dvěma libovolnými časovými pásmy). Aby bylo zajištěno, že data vstupující do databáze budou uložena s offsetem nula:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

Není to 100% dokonalé, ale poskytuje dostatečně silné opatření proti střelbě, které zajišťuje, že data jsou již převedena na UTC. Existuje mnoho názorů na to, jak to udělat, ale toto se z mé zkušenosti zdá být v praxi nejlepší.

Kritika zpracování databázových časových pásem je z velké části oprávněná (existuje spousta databází, které to zvládají s velkou nekompetencí), nicméně PostgreSQL nakládání s časovými razítky a časovými pásmy je docela úžasné (i přes několik „funkcí“ tu a tam). Například jedna taková funkce:

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Všimněte si, že AT TIME ZONE 'UTC' odebere informace o časovém pásmu a vytvoří relativní TIMESTAMP WITHOUT TIME ZONE pomocí referenčního rámce vašeho cíle (UTC ).

Při převodu z neúplného TIMESTAMP WITHOUT TIME ZONE na TIMESTAMP WITH TIME ZONE , chybějící časové pásmo je zděděno z vašeho připojení:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

Závěr:

  • uložit časové pásmo uživatele jako pojmenovaný štítek (např. America/Los_Angeles ) a nikoli odchylka od UTC (např. -0700 )
  • použijte UTC pro vše, pokud neexistuje pádný důvod pro uložení nenulového offsetu
  • všechny nenulové časy UTC považovat za chybu vstupu
  • nikdy nekombinujte relativní a absolutní časová razítka
  • používejte také UTC jako timezone v databázi, pokud je to možné

Poznámka k náhodnému programovacímu jazyku:datetime Pythonu datový typ je velmi dobrý v udržování rozdílu mezi absolutními a relativními časy (i když zpočátku frustrující, dokud jej nedoplníte knihovnou, jako je PyTZ).

UPRAVIT

Dovolte mi vysvětlit rozdíl mezi relativním a absolutním trochu více.

Pro záznam události se používá absolutní čas. Příklady:„Uživatel 123 přihlášen“ nebo „slavnostní promoce začínají 28. 5. 2011 14:00 PST.“ Bez ohledu na vaše místní časové pásmo, pokud byste se mohli teleportovat do místa, kde k události došlo, mohli byste být svědky, jak se událost odehrává. Většina časových údajů v databázi je absolutní (a proto by měla být TIMESTAMP WITH TIME ZONE , ideálně s posunem +0 a textovým štítkem představujícím pravidla pro konkrétní časové pásmo – nikoli posun).

Relativní událostí by bylo zaznamenat nebo naplánovat čas něčeho z perspektivy dosud neurčeného časového pásma. Příklady:„dveře naší firmy se otevírají v 8:00 a zavírají ve 21:00“, „sejdeme se každé pondělí v 7:00 na týdenní snídani“ nebo „každý Halloween ve 20:00“. Obecně platí, že relativní čas se používá v šabloně nebo továrně pro události a absolutní čas se používá téměř pro všechno ostatní. Existuje jedna vzácná výjimka, která stojí za zmínku a která by měla ilustrovat hodnotu relativních časů. Pro budoucí události, které jsou dostatečně daleko v budoucnosti, kde by mohla existovat nejistota ohledně absolutního času, kdy by k něčemu mohlo dojít, použijte relativní časové razítko. Zde je příklad ze skutečného světa:

Předpokládejme, že je rok 2004 a potřebujete naplánovat dodávku na 31. října 2008 ve 13 hodin na západním pobřeží USA (tj. America/Los_Angeles /PST8PDT ). Pokud jste to uložili pomocí absolutního času pomocí ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE , dodávka by se objevila ve 14 hodin, protože vláda USA schválila zákon o energetické politice z roku 2005, který změnil pravidla upravující letní čas. V roce 2004, kdy byla dodávka naplánována, datum 10-31-2008 by byl tichomořský standardní čas (+8000 ), ale počínaje rokem 2005+ databáze časových pásem uznaly, že 10-31-2008 by byl tichomořský letní čas (+0700 ). Uložení relativního časového razítka s časovým pásmem by vedlo ke správnému harmonogramu doručení, protože relativní časové razítko je imunní vůči špatně informované manipulaci Kongresu. Tam, kde je hranice mezi používáním relativních a absolutních časů pro plánování věcí, je fuzzy čára, ale moje pravidlo je, že plánování pro cokoli v budoucnosti delší než 3–6 měsíců by mělo využívat relativní časová razítka (plánované =absolutní vs plánované =relativní ???).

Druhý/poslední typ relativního času je INTERVAL . Příklad:"relace vyprší 20 minut po přihlášení uživatele". INTERVAL lze správně použít buď s absolutními časovými razítky (TIMESTAMP WITH TIME ZONE ) nebo relativní časová razítka (TIMESTAMP WITHOUT TIME ZONE ). Stejně tak je správné říci:„uživatelská relace vyprší 20 minut po úspěšném přihlášení (login_utc + session_duration)“ nebo „naše ranní schůzka při snídani může trvat pouze 60 minut (recurring_start_time + meeting_length)“.

Poslední zmatky:DATE , TIME , TIME WITHOUT TIME ZONE a TIME WITH TIME ZONE jsou všechny relativní datové typy. Například:'2011-05-28'::DATE představuje relativní datum, protože nemáte žádné informace o časovém pásmu, které by bylo možné použít k identifikaci půlnoci. Podobně '23:23:59'::TIME je relativní, protože neznáte ani časové pásmo, ani DATE reprezentovaný dobou. I s '23:59:59-07'::TIME WITH TIME ZONE , nevíte, co je DATE bylo by. A nakonec DATE s časovou zónou není ve skutečnosti DATE , je to TIMESTAMP WITH TIME ZONE :

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Vkládání dat a časových pásem do databází je dobrá věc, ale je snadné získat mírně nesprávné výsledky. Správné a úplné uložení časových informací vyžaduje minimální dodatečné úsilí, ale to neznamená, že je vždy nutné vynaložit další úsilí.



  1. Postgres UNIQUE CONSTRAINT pro pole

  2. Jak abecedně řadit v SQL

  3. Spočítat počet jedinečných hodnot

  4. Vypouštění připojených uživatelů z databáze Oracle