Immagine © Mark Boyle | Australia Day Council del NSW.
Immagini di proprietà dei rispettivi artisti. Tutti i diritti riservati.
AT TIME ZONE è una funzionalità così interessante e non l'avevo notato fino a poco tempo fa, anche se Microsoft ha una pagina al riguardo da dicembre.
Vivo ad Adelaide in Australia. E come oltre un miliardo di altre persone nel mondo, le persone di Adelaide devono far fronte a un fuso orario di mezz'ora. In inverno, siamo UTC+9:30 e in estate siamo UTC+10:30. Tranne che se stai leggendo questo nell'emisfero settentrionale, dovrai ricordarlo per "inverno", intendo da aprile a ottobre. L'ora legale va da ottobre ad aprile e Babbo Natale si siede sulla spiaggia con una bibita fresca, sudando attraverso il suo folto vestito rosso e la barba. A meno che non stia salvando vite, ovviamente.
All'interno dell'Australia, abbiamo tre fusi orari principali (occidentale, centrale e orientale), ma questo si estende a cinque in estate, poiché i tre stati che si estendono all'estremità settentrionale dell'Australia (WA, Qld e NT) non lo fanno cerca di salvare la luce del giorno. Sono abbastanza vicini all'equatore da non preoccuparsene, o qualcosa del genere. È molto divertente per l'aeroporto di Gold Coast, la cui pista attraversa il confine NSW-QLD.
I server di database spesso vengono eseguiti in UTC, perché è semplicemente più facile non dover gestire la conversione tra UTC e locale (e viceversa) in SQL Server. Molti anni fa ricordo di aver dovuto correggere un rapporto che elencava gli incidenti che si sono verificati insieme ai tempi di risposta (da allora ho bloggato su questo). La misurazione dello SLA è stata piuttosto semplice:ho potuto vedere che si è verificato un incidente durante l'orario di lavoro del cliente e che hanno risposto entro un'ora. Ho potuto vedere che un altro incidente si è verificato al di fuori dell'orario di lavoro e la risposta è stata entro due ore. Il problema si è verificato quando è stato prodotto un rapporto al termine di un periodo in cui il fuso orario è cambiato, causando l'elenco di un incidente accaduto effettivamente alle 17:30 (ore esterne) come se si fosse verificato alle 16:30 (ore interne) . La risposta era durata circa 90 minuti, il che andava bene, ma il rapporto mostrava il contrario.
Tutto questo è stato risolto in SQL Server 2016.
Come utilizzare AT TIME ZONE in SQL Server 2016
Ora, con AT TIME ZONE, invece di dire:'20160101 00:00 +10:30', posso iniziare con un valore datetime che non ha un offset di fuso orario e usare AT TIME ZONE per spiegare che si trova ad Adelaide.
SELECT CONVERT(datetime,'20160101 00:00') AT TIME ZONE 'Cen. Australia Standard Time'; -- 2016-01-01 00:00:00.000 +10:30
E questo può essere convertito nell'ora americana aggiungendo nuovamente AT TIME ZONE.
SELECT CONVERT(datetime,'20160101 00:00') AT TIME ZONE 'Cen. Australia Standard Time' AT TIME ZONE 'US Eastern Standard Time'; -- 2015-12-31 08:30:00.000 -05:00
Ora, so che questo è molto più prolisso. E ho bisogno di convertire esplicitamente la stringa in datetime, per evitare un errore che dice:
Il tipo di dati dell'argomento varchar non è valido per l'argomento 1 della funzione AT TIME ZONE.Ma nonostante la prolissità, lo adoro, perché in nessun momento ho dovuto capire che Adelaide era in +10:30, o che orientale era -5:00 – avevo semplicemente bisogno di conoscere il fuso orario per nome. Capire se l'ora legale dovesse applicarsi o meno è stato gestito per me e non ho dovuto eseguire alcuna conversione da locale a UTC per stabilire una linea di base.
Funziona utilizzando il registro di Windows, che contiene tutte queste informazioni, ma purtroppo non è perfetto quando si guarda indietro nel tempo. L'Australia ha cambiato le date nel 2008 e gli Stati Uniti hanno cambiato le date nel 2005:entrambi i paesi risparmiano la luce del giorno per gran parte dell'anno. AT TIME ZONE lo capisce. Ma non sembra apprezzare il fatto che in Australia nel 2000, grazie alle Olimpiadi di Sydney, l'Australia abbia iniziato l'ora legale circa due mesi prima. Questo è un po' frustrante, ma non è colpa di SQL:dobbiamo incolpare Windows per questo. Immagino che il registro di Windows non ricordi l'aggiornamento rapido che è andato in giro quell'anno. (Nota per me stesso:potrei dover chiedere a qualcuno del team di Windows di risolvere il problema...)
L'utilità continua comunque!
Il nome del fuso orario non deve nemmeno essere una costante. Posso passare variabili e persino utilizzare colonne:
WITH PeopleAndTZs AS ( SELECT * FROM (VALUES ('Rob', 'Cen. Australia Standard Time'), ('Paul', 'New Zealand Standard Time'), ('Aaron', 'US Eastern Standard Time') ) t (person, tz) ) SELECT tz.person, SYSDATETIMEOFFSET() AT TIME ZONE tz.tz FROM PeopleAndTZs tz; /* Rob 2016-07-18 18:29:11.9749952 +09:30 Paul 2016-07-18 20:59:11.9749952 +12:00 Aaron 2016-07-18 04:59:11.9749952 -04:00 */
(Perché l'ho eseguito poco prima delle 18:30 qui ad Adelaide, che sembra essere quasi le 21 in Nuova Zelanda, dove si trova Paul, e quasi le 5 di questa mattina nella parte orientale dell'America, dove si trova Aaron.)
Questo mi permetterebbe di vedere facilmente che ora è per le persone ovunque si trovino nel mondo e di vedere chi sarebbe il migliore per rispondere a qualche problema, senza dover eseguire conversioni manuali di data e ora. E ancora di più, mi avrebbe permesso di farlo per le persone del passato. Potrei avere un rapporto che analizzi quali fusi orari consentirebbero il verificarsi del maggior numero di eventi durante l'orario lavorativo.
Questi fusi orari sono elencati in sys.time_zone_info
, insieme a qual è l'offset corrente e se è attualmente applicata l'ora legale.
nome | current_utc_offset | è_attualmente_dst |
---|---|---|
Singapore Standard Time | +08:00 | 0 |
W. Ora solare dell'Australia | +08:00 | 0 |
Ora solare di Taipei | +08:00 | 0 |
Ora solare di Ulan Bator | +09:00 | 1 |
Ora solare della Corea del Nord | +08:30 | 0 |
Aus Central W. Standard Time | +08:45 | 0 |
Ora solare del Transbaikal | +09:00 | 0 |
Ora solare di Tokyo | +09:00 | 0 |
Campionamento di righe da sys.time_zone_info
Mi interessa davvero solo qual è il nome, ma comunque. Ed è interessante vedere che esiste un fuso orario chiamato "Aus Central W. Standard Time" che è sul quarto d'ora. Vai a capire. Vale anche la pena notare che i luoghi sono indicati usando il loro nome di ora solare, anche se stanno attualmente osservando l'ora legale. Come Ulaanbaatar nell'elenco sopra, che non è elencato come Ulaanbaatar Daylight Time. Questo potrebbe far perdere la testa alle persone quando iniziano a utilizzare AT TIME ZONE.
AT TIME ZONE può causare problemi di prestazioni?
Ora, sono sicuro che ti starai chiedendo quale potrebbe essere l'impatto dell'utilizzo di AT TIME ZONE sull'indicizzazione.
In termini di forma del piano, non è diverso dall'affrontare datetimeoffset in generale. Se ho valori datetime, come nella colonna AdventureWorks Sales.SalesOrderHeader.OrderDate (su cui ho creato un indice chiamato rf_IXOD), eseguendo entrambe queste query:
select OrderDate, SalesOrderID from Sales.SalesOrderHeader where OrderDate >= convert(datetime,'20110601 00:00') at time zone 'US Eastern Standard Time' and OrderDate < convert(datetime,'20110701 00:00') at time zone 'US Eastern Standard Time' ;
E questa domanda:
select OrderDate, SalesOrderID from Sales.SalesOrderHeader where OrderDate >= convert(datetimeoffset,'20110601 00:00 -04:00') and OrderDate < convert(datetimeoffset,'20110701 00:00 -04:00');
In entrambi i casi, ottieni piani simili a questo:
Ma se esploriamo un po' più da vicino, c'è un problema.
Quello che usa AT TIME ZONE non usa molto bene le statistiche. Pensa che vedrà uscire 5.170 righe da quella ricerca dell'indice, quando in realtà sono solo 217. Perché 5.170? Bene, il recente post di Aaron, "Prestare attenzione alle stime", lo spiega, riferendosi al post "Stima della cardinalità per predicati multipli" di Paul. 5.170 è 31.465 (righe nella tabella) * 0,3 * sqrt(0,3).
La seconda query ha ragione, stimando 217. Nessuna funzione coinvolta, vedi.
Questo è probabilmente abbastanza giusto. Voglio dire, nel momento in cui sta producendo il piano, non avrà chiesto al registro le informazioni di cui ha bisogno, quindi non sa davvero quante stimare. Ma è possibile che si tratti di un problema.
Se aggiungo predicati aggiuntivi che so non possono essere un problema, le mie stime in realtà scendono ulteriormente, fino a 89,9 righe.
select OrderDate, SalesOrderID from Sales.SalesOrderHeader where OrderDate >= convert(datetime,'20110601 00:00') at time zone 'US Eastern Standard Time' and OrderDate < convert(datetime,'20110701 00:00') at time zone 'US Eastern Standard Time' and OrderDate >= convert(datetimeoffset,'20110601 00:00 +14:00') and OrderDate < convert(datetimeoffset,'20110701 00:00 -12:00');
La stima di troppe righe significa che viene allocata troppa memoria, ma la stima di una quantità insufficiente può causare una quantità insufficiente di memoria, con la potenziale necessità di uno spill per correggere il problema (che spesso può essere disastroso dal punto di vista delle prestazioni). Vai a leggere il post di Aaron per ulteriori informazioni su come stime scadenti possono essere negative.
Quando considero come gestire la visualizzazione dei valori per quelle persone di prima, posso usare query come questa:
WITH PeopleAndTZs AS ( SELECT * FROM (VALUES ('Rob', 'Cen. Australia Standard Time'), ('Paul', 'New Zealand Standard Time'), ('Aaron', 'US Eastern Standard Time') ) t (person, tz) ) SELECT tz.person, o.SalesOrderID, o.OrderDate AT TIME ZONE 'UTC' AT TIME ZONE tz.tz FROM PeopleAndTZs tz CROSS JOIN Sales.SalesOrderHeader o WHERE o.SalesOrderID BETWEEN 44001 AND 44010;
E ottieni questo piano:
... che non ha tali preoccupazioni:lo scalare di calcolo più a destra sta convertendo la data e l'ora OrderDate in datetimeoffset per UTC e lo scalare di calcolo più a sinistra lo sta convertendo nel fuso orario appropriato per la persona. L'avvertimento è perché sto facendo un CROSS JOIN, ed è stato del tutto intenzionale.
Vantaggi e svantaggi di altri metodi di conversione del tempo
Prima di AT TIME ZONE, una delle mie funzionalità preferite, ma spesso non apprezzata, di SQL 2008 era il tipo di dati datetimeoffset. Ciò consente di archiviare i dati di data e ora anche con il fuso orario, come "20160101 00:00 +10:30", che è quando quest'anno abbiamo festeggiato il nuovo anno ad Adelaide. Per vedere quando era negli Stati Uniti orientali, posso usare la funzione SWITCHOFFSET.
SELECT SWITCHOFFSET('20160101 00:00 +10:30', '-05:00'); -- 2015-12-31 08:30:00.0000000 -05:00
Questo è lo stesso momento, ma in una parte diversa del mondo. Se fossi stato al telefono con qualcuno nella Carolina del Nord oa New York, augurandogli un felice anno nuovo perché ad Adelaide era appena passata la mezzanotte, mi avrebbero detto “Cosa intendi? È ancora ora di colazione qui a Capodanno!”
Il problema è che per fare questo, devo sapere che a gennaio Adelaide è +10:30 e gli Stati Uniti orientali sono -5:00. E questo è spesso un dolore. Soprattutto se sto chiedendo di fine marzo, inizio aprile, ottobre, inizio novembre, quei periodi dell'anno in cui le persone non possono essere sicure in quale fuso orario si trovassero le persone in altri paesi perché cambiano di un'ora per l'ora legale, e tutti lo fanno secondo regole diverse. Il mio computer mi dice in che fuso orario si trovano le persone ora, ma è molto più difficile dire in quale fuso orario si troveranno in altri periodi dell'anno.
Per altre informazioni sulla conversione tra i fusi orari e su come persone come Aaron hanno affrontato l'ora legale, vedere i seguenti collegamenti:
- Utilizzo di AT TIME ZONE per correggere un vecchio rapporto (sono io!)
- Gestire la conversione tra i fusi orari in SQL Server – parte 1
- Gestire la conversione tra i fusi orari in SQL Server – parte 2
- Gestire la conversione tra i fusi orari in SQL Server – parte 3
E un po' di documentazione ufficiale Microsoft:
- AT TIME ZONE (Transact-SQL)
- sys.time_zone_info (Transact-SQL)
- Aiuto e supporto per l'ora legale
Dovresti usare AT TIME ZONE?
AT TIME ZONE non è perfetto. Ma è davvero utile, incredibilmente. È abbastanza flessibile da accettare colonne e variabili come input e posso vedere un enorme potenziale per questo. Ma se farà uscire le mie stime, allora dovrò stare attento. Ai fini della visualizzazione, questo non dovrebbe avere alcuna importanza, ed è qui che posso vederlo essere più utile. Semplificare la conversione di un valore visualizzato da o verso UTC (o verso o da un'ora locale), senza che nessuno debba scervellarsi su offset e ora legale, è una grande vittoria.
Questa è davvero una delle mie funzionalità preferite di SQL Server 2016. È da molto tempo che desidero qualcosa del genere.
Oh, e la maggior parte di quei miliardi di persone nel fuso orario di mezz'ora sono in India. Ma probabilmente lo sapevi già...