sql >> Database >  >> RDS >> Database

AT TIME ZONE gebruiken om een ​​oud rapport te herstellen

Zodra ik de SQL 2016-functie AT TIME ZONE zag, waarover ik hier op sqlperformance.com een een paar maanden geleden herinnerde ik me een rapport dat deze functie nodig had. Dit bericht vormt een casestudy over hoe ik het zag werken, wat past in de T-SQL-dinsdag van deze maand, georganiseerd door Matt Gordon (@sqlatspeed). (Het is de 87e T-SQL-dinsdag en ik moet echt meer blogposts schrijven, vooral over dingen die niet door T-SQL-dinsdag worden gevraagd.)

De situatie was deze, en dit klinkt misschien bekend als je die eerdere post van mij leest.

Lang voordat LobsterPot Solutions bestond, moest ik een rapport maken over incidenten die zich hebben voorgedaan, en met name het aantal keren dat er binnen SLA werd gereageerd en het aantal keren dat de SLA werd gemist, laten zien. Een Sev2-incident dat zich op een doordeweekse dag om 16.30 uur heeft voorgedaan, moet bijvoorbeeld binnen 1 uur worden beantwoord, terwijl een Sev2-incident dat op een doordeweekse dag om 17.30 uur heeft plaatsgevonden, binnen 3 uur moet worden beantwoord. Of zoiets – ik ben de cijfers vergeten, maar ik herinner me wel dat de helpdeskmedewerkers opgelucht ademhaalden als 17.00 uur rondliep, omdat ze niet zo snel hoefden te reageren. De Sev1-waarschuwingen van 15 minuten zouden plotseling een uur duren en de urgentie zou verdwijnen.

Maar er ontstond een probleem wanneer de zomertijd begon of eindigde.

Ik weet zeker dat als je met databases te maken hebt gehad, je de pijn zult kennen die zomertijd is. Vermoedelijk kwam Ben Franklin op het idee - en daarvoor zou hij door de bliksem of zoiets moeten worden getroffen. West-Australië heeft het onlangs een paar jaar geprobeerd en heeft het verstandig verlaten. En de algemene consensus is om datum-/tijdgegevens op te slaan in UTC.

Als u geen gegevens opslaat in UTC, loopt u het risico dat een evenement begint om 2:45 uur en eindigt om 02:15 uur nadat de klokken zijn teruggezet. Of een SLA-incident dat begint om 01:59 uur net voordat de klokken vooruit gaan. Nu zijn deze tijden prima als je de tijdzone waarin ze zich bevinden opslaat, maar in UTC-tijd werkt het gewoon zoals verwacht.

... behalve voor rapportage.

Want hoe moet ik weten of een bepaalde datum voor of na de zomertijd was? Ik weet misschien dat er om 06:30 uur in UTC een incident heeft plaatsgevonden, maar is dat 16:30 uur in Melbourne of 17:30 uur? Natuurlijk kan ik bedenken in welke maand het is, omdat ik weet dat Melbourne zomertijd in acht neemt van de eerste zondag van oktober tot de eerste zondag in april, maar als er dan klanten zijn in Brisbane, en Auckland, en Los Angeles, en Phoenix, en op verschillende plaatsen in Indiana wordt het een stuk ingewikkelder.

Om dit te omzeilen waren er maar heel weinig tijdzones waarin SLA's voor dat bedrijf konden worden gedefinieerd. Het werd gewoon als te moeilijk beschouwd om voor meer dan dat te zorgen. Een rapport kan dan worden aangepast om te zeggen:"Bedenk dat op een bepaalde datum de tijdzone is gewijzigd van X in Y". Het voelde rommelig, maar het werkte. Er was niets nodig om het Windows-register op te zoeken en het werkte eigenlijk gewoon.

Maar tegenwoordig zou ik het anders hebben gedaan.

Nu zou ik AT TIME ZONE hebben gebruikt.

Zie je, nu zou ik de tijdzone-informatie van de klant kunnen opslaan als eigendom van de klant. Ik kon vervolgens elke incidenttijd opslaan in UTC, waardoor ik de nodige berekeningen kon maken rond het aantal minuten om te reageren, op te lossen, enzovoort, terwijl ik kon rapporteren met behulp van de lokale tijd van de klant. Ervan uitgaande dat mijn IncidentTime daadwerkelijk was opgeslagen met datetime, in plaats van datetimeoffset, zou het gewoon een kwestie zijn van het gebruik van code zoals:

i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE c.tz

…die eerst de tijdzoneloze i.IncidentTime in UTC zet, voordat deze wordt omgezet naar de tijdzone van de klant. En deze tijdzone kan 'AUS Eastern Standard Time' of 'Mauritius Standard Time' of wat dan ook zijn. En de SQL Engine wordt overgelaten om erachter te komen welke offset daarvoor moet worden gebruikt.

Op dit punt kan ik heel gemakkelijk een rapport maken waarin elk incident over een bepaalde periode wordt vermeld, en dit in de lokale tijdzone van de klant weergeven. Ik kan de waarde converteren naar het gegevenstype tijd en vervolgens rapporteren hoeveel incidenten al dan niet binnen kantooruren plaatsvonden.

En dit is allemaal erg handig, maar hoe zit het met de indexering om dit netjes af te handelen? AT TIME ZONE is immers een functie. Maar het veranderen van de tijdzone verandert niets aan de volgorde waarin de incidenten zich daadwerkelijk hebben voorgedaan, dus het zou goed moeten zijn.

Om dit te testen, heb ik een tabel gemaakt met de naam dbo.Incidents en de kolom IncidentTime geïndexeerd. Toen heb ik deze zoekopdracht uitgevoerd en bevestigd dat er een indexzoekopdracht werd gebruikt.

select i.IncidentTime, itz.LocalTime
from dbo.Incidents i
cross apply (select i.IncidentTime AT TIME ZONE 'UTC' 
  AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime)
where i.IncidentTime >= '20170201'
and i.IncidentTime < '20170301';

Maar ik wil filteren op itz.LocalTime…

select i.IncidentTime, itz.LocalTime
from dbo.Incidents i
cross apply (select i.IncidentTime AT TIME ZONE 'UTC' 
  AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime)
where itz.LocalTime >= '20170201'
and itz.LocalTime < '20170301';

Geen succes. Hij hield niet van de index.

De waarschuwingen zijn omdat het veel meer moet doorzoeken dan de gegevens waarin ik geïnteresseerd ben.

Ik heb zelfs geprobeerd een tabel te gebruiken met een datetimeoffset-veld. AT TIME ZONE kan immers de volgorde wijzigen bij het verplaatsen van datetime naar datetimeoffset, ook al verandert de volgorde niet bij het verplaatsen van datetimeoffset naar een andere datetimeoffset. Ik heb zelfs geprobeerd ervoor te zorgen dat het ding waarmee ik het vergeleek zich in de tijdzone bevond.

select i.IncidentTime, itz.LocalTime
from dbo.IncidentsOffset i
cross apply (select i.IncidentTime AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime)
where itz.LocalTime >= cast('20170201' as datetimeoffset) 
  AT TIME ZONE 'Cen. Australia Standard Time'
and itz.LocalTime < cast('20170301' as datetimeoffset) 
  AT TIME ZONE 'Cen. Australia Standard Time';

Nog steeds geen geluk!

Dus nu had ik twee opties. Een daarvan was om de geconverteerde versie naast de UTC-versie op te slaan en die te indexeren. Ik denk dat dat een pijn is. Het is zeker veel meer een databasewijziging dan ik zou willen.

De andere optie was om te gebruiken wat ik helperpredikaten noem. Dit zijn de dingen die je ziet als je LIKE gebruikt. Het zijn predikaten die kunnen worden gebruikt als zoekpredikaten, maar niet precies waar u om vraagt.

Ik denk dat het niet uitmaakt in welke tijdzone ik geïnteresseerd ben, de IncidentTimes waar ik om geef, binnen een zeer specifiek bereik liggen. Dat bereik is aan beide kanten niet meer dan een dag groter dan mijn voorkeursbereik.

Dus ik zal twee extra predikaten toevoegen.

select i.IncidentTime, itz.LocalTime
from dbo.IncidentsOffset i
cross apply (select i.IncidentTime 
    AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime)
where itz.LocalTime >= cast('20170201' as datetimeoffset) 
  AT TIME ZONE 'Cen. Australia Standard Time'
and itz.LocalTime < cast('20170301' as datetimeoffset) 
  AT TIME ZONE 'Cen. Australia Standard Time
and i.IncidentTime >= dateadd(day,-1,'20170201')
and i.IncidentTime < dateadd(day, 1,'20170301');

Nu kan mijn index worden gebruikt. Het moet door 30 rijen kijken voordat het wordt gefilterd naar de 28 waar het om geeft, maar dat is een stuk beter dan het hele ding te scannen.

En weet je - dit is het soort gedrag dat ik de hele tijd zie bij reguliere zoekopdrachten, zoals wanneer ik CAST(myDateTimeColumns AS DATE) =@SomeDate doe, of LIKE gebruik.

Ik ben hier oké mee. AT TIME ZONE is geweldig om me mijn tijdzoneconversies te laten afhandelen, en door na te gaan wat er met mijn vragen gebeurt, hoef ik ook de prestaties niet op te offeren.

@rob_farley


  1. In Oracle AS werkt de alias niet

  2. Hoe Python met SQL Server te verbinden om het backend-proces te automatiseren

  3. Primaire MySQL-sleutels

  4. SQLite TOON TABELLEN Equivalent