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

SQL Server 2008 の while ループで日付範囲を反復処理してから INSERT を実行する

    SQL はセットベースの言語であり、ループは最後の手段にする必要があります。したがって、セットベースのアプローチは、一度に 1 つずつループして挿入するのではなく、最初に必要なすべての日付を生成して一度に挿入することです。 Aaron Bertrand は、ループなしでセットまたはシーケンスを生成する素晴らしいシリーズを書いています:

    パート 3 は、日付を扱うため、特に関連性があります。

    Calendar テーブルがないと仮定すると、積み上げ CTE メソッドを使用して、開始日と終了日の間の日付のリストを生成できます。

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1) , (1), (1), (1), (1), (1), (1), (1)) n (N)),N2 (N) AS (N1 から 1 を選択 AS N1 CROSS JOIN N1 AS N2),N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2)SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1) Date =DATEADD(DAY, ROW_NUMBER() OVER(ORDER) BY N) - 1, @StartDate)FROM N3;  

    リンクされた記事で説明されているように、これがどのように機能するかについての詳細はスキップしました。本質的には、10行のハードコーディングされたテーブルから始まり、このテーブルをそれ自体と結合して100行(10 x 10)を取得してから、このテーブルに結合します10,000行を取得するために100行をそれ自体に変換します(この時点で停止しましたが、さらに行が必要な場合は、さらに結合を追加できます).

    各ステップで、出力は N という単一の列です 値は 1 です (単純にするため)。 10,000 行を生成する方法を定義すると同時に、実際に TOP を使用して必要な数だけ生成するように SQL Server に指示します。 開始日と終了日の差 - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1) .これにより、不要な作業が回避されます。両方の日付が確実に含まれるように、差に 1 を追加する必要がありました。

    ランキング機能 ROW_NUMBER() を使用する 生成された各行に増分番号を追加し、この増分番号を開始日に追加して、日付のリストを取得します。 ROW_NUMBER()以降 開始日が含まれていることを確認するには、ここから 1 を差し引く必要があります。

    次に、NOT EXISTS を使用して既に存在する日付を除外するだけのケースになります。 .上記のクエリの結果を dates と呼ばれる独自の CTE に含めました。 :

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1) , (1), (1), (1), (1), (1), (1), (1)) n (N)),N2 (N) AS (N1 から 1 を選択 AS N1 CROSS JOIN N1 AS N2),N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),Dates AS( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1) Date =DATEADD(DAY, ROW_NUMBER( ) OVER(ORDER BY N) - 1, @StartDate) FROM N3)INSERT INTO MyTable ([TimeStamp])SELECT DateFROM Dates AS dWHERE NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE d.Date =t.[TimeStamp])  

    SQL Fiddle の例

    (リンクされた記事で説明されているように) カレンダー テーブルを作成する場合、これらの余分な行を挿入する必要はないかもしれません。次のように、その場で結果セットを生成することができます:

    SELECT [Timestamp] =c.Date, t.[FruitType], t.[NumOffered], t.[NumTaken], t.[NumAbandoned], t.[NumSpoiled]FROM dbo.Calendar AS c LEFT JOIN dbo.MyTable AS t ON t.[タイムスタンプ] =c.[日付]WHERE c.Date>=@StartDateAND c.Date <@EndDate;  

    補遺

    実際の質問に答えるには、ループを次のように記述します:

    DECLARE @StartDate AS DATETIMEDECLARE @EndDate AS DATETIMEDECLARE @CurrentDate AS DATETIMESET @StartDate ='2015-01-01'SET @EndDate =GETDATE()SET @CurrentDate =@StartDateWHILE (@CurrentDate <@EndDate)BEGIN存在しない場合 (SELECT 1 FROM myTable WHERE myTable.Timestamp =@CurrentDate) BEGIN INSERT INTO MyTable ([タイムスタンプ]) VALUES (@CurrentDate); END SET @CurrentDate =DATEADD(DAY, 1, @CurrentDate); /*現在の日付をインクリメント*/END  

    SQL Fiddle の例

    私はこのアプローチを推奨していません。何かが 1 回しか行われていないからといって、それを行う正しい方法を示すべきではないということにはなりません.

    追加説明

    スタック CTE メソッドはセット ベースのアプローチを複雑にしすぎている可能性があるため、文書化されていないシステム テーブル master..spt_values を使用して単純化します。 .実行した場合:

    SELECT NumberFROM master..spt_valuesWHERE Type ='P';  

    0 から 2047 までのすべての数値が得られることがわかります。

    実行すると:

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();SELECT Date =DATEADD(DAY, number, @StartDate)FROM master..spt_valuesWHERE type ='P';   

    開始日から 2047 日先までのすべての日付を取得します。さらに where 句を追加すると、これを終了日より前の日付に制限できます:

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();SELECT Date =DATEADD(DAY, number, @StartDate)FROM master..spt_valuesWHERE type ='P'AND DATEADD(DAY, number, @StartDate) <=@EndDate;  

    これで、単一のセットベースのクエリで必要なすべての日付が得られました。NOT EXISTS を使用して、テーブルに既に存在する行を削除できます。

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();SELECT Date =DATEADD(DAY, number, @StartDate)FROM master..spt_valuesWHERE type ='P'AND DATEADD(DAY, number, @StartDate) <=@EndDateAND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[タイムスタンプ] =DATEADD(DAY, number, @StartDate));  

    最後に、INSERT を使用して、これらの日付をテーブルに挿入できます。

    DECLARE @StartDate DATE ='2015-01-01', @EndDate DATE =GETDATE();INSERT YourTable ([タイムスタンプ])SELECT Date =DATEADD(DAY, number, @StartDate)FROM master.. spt_valuesWHERE type ='P'AND DATEADD(DAY, number, @StartDate) <=@EndDateAND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[Timestamp] =DATEADD(DAY, number, @StartDate));  

    これが、セットベースのアプローチがはるかに効率的であるだけでなく、単純であることを示すのにある程度役立つことを願っています.



    1. MySQLORDERBY別のテーブルの最大行数

    2. ビューのMySQLインデックスが機能しない

    3. DATEとしてのMySQLCAST

    4. Oracleから日時に変換