sql >> データベース >  >> RDS >> Database

ATTIMEZONEを使用して古いレポートを修正する

    SQL2016の機能ATTIMEZONEを見るとすぐに、ここsqlperformance.comで説明しました。数か月前、この機能が必要なレポートを思い出しました。この投稿は、それがどのように機能するかについてのケーススタディを形成します。これは、Matt Gordon(@sqlatspeed)がホストする今月のT-SQL火曜日に適合します。 (これは87回目のT-SQL火曜日です。特に、T-SQL火曜日によって促されないものについては、もっと多くのブログ投稿を書く必要があります。)

    状況はこれでした、そしてあなたが私の以前の投稿を読んだならば、これはよく知られているように聞こえるかもしれません。

    LobsterPot Solutionsが存在するずっと前に、発生したインシデントに関するレポートを作成する必要がありました。特に、SLA内で応答が行われた回数とSLAが欠落した回数を示す必要がありました。たとえば、平日の午後4時30分に発生したSev2インシデントは、1時間以内に応答する必要がありますが、平日の午後5時30分に発生したSev2インシデントは、3時間以内に応答する必要があります。または、そのようなもの–関係する数字を忘れましたが、ヘルプデスクの従業員は、午後5時がくると、物事にそれほど迅速に対応する必要がないため、安堵のため息をついたことを覚えています。 15分のSev1アラートは突然1時間に延長され、緊急性はなくなります。

    しかし、夏時間が開始または終了するたびに問題が発生します。

    データベースを扱ったことがあるなら、夏時間が苦痛であることをご存知でしょう。おそらくベン・フランクリンがそのアイデアを思いついたのでしょう。そのためには、彼は稲妻か何かに打たれるべきです。西オーストラリア州は最近数年間それを試し、賢明にそれを放棄しました。そして、一般的なコンセンサスは、日付/時刻データをUTCで保存することです。

    UTCでデータを保存しない場合、イベントが午前2時45分に開始され、時計が戻った後の午前2​​時15分に終了するリスクがあります。または、時計が進む直前の午前1時59分に開始するSLAインシデントが発生している。現在、これらの時間は、それらが存在するタイムゾーンを保存する場合は問題ありませんが、UTCでは期待どおりに機能します。

    …報告を除いて。

    特定の日付が夏時間の開始前か後かをどのように知る必要があるのでしょうか。 UTCの午前6時30分にインシデントが発生したことは知っているかもしれませんが、それはメルボルンの午後4時30分ですか、それとも午後5時30分ですか。メルボルンは10月の第1日曜日から4月の第1日曜日まで夏時間を採用していることを知っているので、明らかにどの月にあるかを考えることができますが、ブリスベン、オークランド、ロサンゼルス、フェニックスに顧客がいる場合は、そしてインディアナ内のさまざまな場所では、物事ははるかに複雑になります。

    これを回避するために、その会社にSLAを定義できるタイムゾーンはほとんどありませんでした。それ以上に対応するのは難しすぎると考えられていました。次に、レポートをカスタマイズして、「特定の日付にタイムゾーンがXからYに変更されたことを考慮してください」と言うことができます。乱雑に感じましたが、うまくいきました。 Windowsレジストリを検索する必要はなく、基本的には機能しました。

    しかし、最近は別のやり方でやっていたでしょう。

    今、私はATTIMEZONEを使用していました。

    ご覧のとおり、これで顧客のタイムゾーン情報を顧客のプロパティとして保存できるようになりました。次に、各インシデント時間をUTCで保存できるため、顧客の現地時間を使用してレポートを作成しながら、応答や解決などに必要な分数を計算できます。私のIncidentTimeが実際にdatetimeoffsetではなくdatetimeを使用して保存されていたとすると、次のようなコードを使用するだけで済みます。

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

    …これは、最初にタイムゾーンのないi.IncidentTimeをUTCに変換してから、顧客のタイムゾーンに変換します。また、このタイムゾーンは、「オーストラリア東部標準時」、「モーリシャス標準時」など、何でもかまいません。そして、SQLエンジンは、そのために使用するオフセットを把握する必要があります。

    この時点で、ある期間の各インシデントを一覧表示するレポートを非常に簡単に作成し、それを顧客のローカルタイムゾーンに表示することができます。値を時間データ型に変換してから、営業時間内に発生したインシデントの数を報告できます。

    そして、これはすべて非常に便利ですが、これをうまく処理するためのインデックス作成についてはどうでしょうか。結局のところ、ATTIMEZONEは関数です。ただし、タイムゾーンを変更しても、インシデントが実際に発生した順序は変更されないため、問題はありません。

    これをテストするために、dbo.Incidentsというテーブルを作成し、IncidentTime列にインデックスを付けました。次に、このクエリを実行し、インデックスシークが使用されていることを確認しました。

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

    しかし、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';

    運がない。インデックスが気に入らなかった。

    警告は、私が興味を持っているデータよりもはるかに多くのことを調べなければならないためです。

    datetimeoffsetフィールドを持つテーブルを使用してみました。結局のところ、AT TIME ZONEは、datetimeoffsetから別のdatetimeoffsetに移動するときに順序が変更されていなくても、datetimeからdatetimeoffsetに移動するときに順序を変更できます。比較しているものがタイムゾーンにあることを確認することも試みました。

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

    まだ運がない!

    だから今私は2つの選択肢がありました。 1つは、変換されたバージョンをUTCバージョンと一緒に保存し、インデックスを作成することでした。それは苦痛だと思います。確かに、私が望んでいるよりもはるかに多くのデータベースの変更です。

    もう1つのオプションは、私がヘルパー述語と呼んでいるものを使用することでした。これらは、LIKEを使用するときに表示される種類のものです。これらは、シーク述語として使用できる述語ですが、正確にはあなたが求めているものではありません。

    関心のあるタイムゾーンに関係なく、関心のあるIncidentTimesは非常に特定の範囲内にあると思います。その範囲は、どちらの側でも、私の好みの範囲より1日以内です。

    したがって、2つの追加の述語を含めます。

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

    これで、私のインデックスを使用できます。気になる28行にフィルタリングする前に、30行を調べる必要がありますが、全体をスキャンするよりもはるかに優れています。

    そして、ご存知のとおり、これは、CAST(myDateTimeColumns AS DATE)=@SomeDateを実行したり、LIKEを使用したりする場合など、通常のクエリから常に見られる種類の動作です。

    これで大丈夫です。 AT TIME ZONEは、タイムゾーンの変換を処理できるようにするのに最適です。クエリで何が起こっているかを考慮することで、パフォーマンスを犠牲にする必要もありません。

    @rob_farley


    1. 圧縮とそのパフォーマンスへの影響

    2. SQLは、列の最大値を持つ行のみを選択します

    3. SQL Server 2016:ストアドプロシージャを作成する

    4. EctoFragmentsを使用してタイムスタンプに間隔を追加します