1

SQL 2008 R2 サーバーで実行される SQL クエリがあります。毎月1日と16日に開催されます。クエリには、StartDate および EndDate パラメーターを設定するための次のロジックが含まれています。

DECLARE     @RunDateTime datetime
DECLARE     @RunDate datetime
DECLARE     @StartDate datetime
DECLARE     @EndDate datetime
DECLARE     @Month int
DECLARE     @Year int
DECLARE     @strStartDate varchar(10)

SET     @RunDateTime = GetDate()
SET     @RunDate = CAST(@RunDateTime AS DATE)

IF                  DATEPART(d, @RunDate) = 16
    BEGIN       
                    SET @StartDate =  DATEADD(d, -15, @RunDate)
                    SET @EndDate = @RunDate
    END
ELSE
    BEGIN
                    IF      Month(@RunDate) = 1
                            SET @Month = 12
                    ELSE    
                            SET @Month = Month(@RunDate) - 1

                    IF      Month(@RunDate) = 1
                            SET @Year = Year(@RunDate) - 1
                    ELSE
                            SET @Year = Year(@RunDate)

                    SET @strStartDate = CAST(@Year AS VARCHAR) + CAST(@Month AS VARCHAR) + '16'
                    SET @StartDate = CONVERT(datetime, @strStartDate)
                    SET @EndDate = @RunDate
    END

クエリが 16 日に実行される場合、日付範囲は月の 1 日から 15 日にします。クエリが 1 日に実行される場合、日付範囲は 16 日から前月末までになります。

職場での要件の変化により、日付から文字列への変換を使用せずにこれを行う方法を見つける必要があると言われました。これは可能ですか?私はここで自分の深さからすぐに抜け出しています。

4

4 に答える 4

2

あなたの以前の質問を見たとき、私はこれにアプローチするより簡単な方法があることを知っていました。これはどう:

DECLARE     @StartDate date;
DECLARE     @EndDate date;
DECLARE     @RunDateTime datetime = getdate() - 2;

if DAY(@RunDateTime) = 1
begin
    set @StartDate = dateadd(day, 15, DATEADD(MONTH, -1, @RunDateTime));
    set @EndDate = DATEADD(day, -1, @RunDateTime);
end
else if day(@RunDateTime) = 16
begin
    set @StartDate = dateadd(day, -DAY(@RunDateTime) + 1, @RunDateTime);
    set @EndDate = dateadd(day, -DAY(@RunDateTime) + 15, @RunDateTime);
end
else -- do your error processing here
于 2013-01-03T19:00:52.403 に答える
2

これはあなたの仕様を満たすでしょう。実行日は気にせず、正確な境界上にない場合は、EndDate を現在の日付に調整します。一部の日付( など20120201)にエラーがあるため、指定されたコードと正確に比較できませんでした。

DECLARE
   @StartDate datetime,
   @EndDate datetime;

SELECT
   @StartDate = Convert(date, M.Anchor + X.S),
   @EndDate = Convert(date,
      M.Anchor + Day(GetDate() - 1)
      - CASE WHEN Day(GetDate()) IN (1, 16) THEN 1 ELSE 0 END
   )
FROM
   (VALUES (1, 15, 0), (16, 31, 15)) X (F, T, S)
   CROSS APPLY (
      SELECT DateAdd(month, DateDiff(month, 0, GetDate() - 1), 0)
   ) M (Anchor)
WHERE Day(GetDate() - 1) BETWEEN X.F AND X.T;

このコードを日付の年に対してテストしたところ、すべてが正しく機能していると思います。

この SqlFiddleで 1 年間の計算結果をチェックして、その正確性を自分で確認してください。

基本的には、昨日の日付から月初を計算します。この日付を使用して、月初からのオフセットを決定し、開始日を計算します (1 日から 15 日までの場合は 0 を使用し、16 日から 31 日までの場合は 15 を使用します)。最後に、月初 + 計算された日から終了日を計算し、前日の終了期間を使用するために 1 日と 16 日に特別な調整を行います。

実際には、実行日全体ではなく、常に期間の終わり (1 - 16 または 1 - 28/29/30/31) まで指定しても害はありませんが、それはあなたが求めたものではありません。

クエリで何が起こっているかについての質問に答える。

  1. 派生テーブルは行セットを返すクエリで、通常は で始まりSELECT、かっこで囲まれ、エイリアスが与えられます。例えば:

    SELECT X.FullName
    FROM
       (
          SELECT FullName = FirstName + ' ' + LastName
          FROM dbo.User
          WHERE ID = 123
       ) AS X
    

    ここではX、内部のクエリの結果で構成されるテーブルを派生させました。AS私にとっては混乱を招くだけなので、その部分をスキップするのが好きです。これは単純な例であることに注意してください。派生テーブルは一般にこれよりも複雑で、さまざまな状況で役立ちます。dbo.Userテーブルでエイリアスを使用しませんでしたが、使用することをお勧めします。ただし、派生テーブル内のすべての列参照は、派生テーブル内から解決されることに注意することが重要です。外部からのテーブルの使用はありません。

  2. VALUESクエリで提供された値から行セットを作成する方法です。よく知られている通常の構文はINSERT dbo.Table VALUES (1, 'a');. SQL Server 2008 では、これは のように同時に複数の行を許可するように拡張されましたINSERT dbo.Table VALUES (1, 'a'), (2, 'b');SELECTさらに、派生テーブル内のクエリの代わりに、この特別な複数行表記を使用できます。例えば:

    SELECT X.*
    FROM (VALUES (1, 'a'), (2, 'b')) X
    -- Alias X is for a derived table of 2 rows & 2 unnamed columns
    
  3. 派生テーブルの列名は通常、クエリから検出されます。#1 で、X.FullName実際には table の複数の列を含む式を参照していることを確認してくださいdbo.User。式には、クエリで明示的に新しいエイリアスが与えられました。エイリアスを指定しないと、式に固有の名前がないため、エラーが発生します。実際、派生テーブルXには列名がないため、前のポイントからのクエリはそのままでは実行されません! ただし、派生テーブルの外部で明示的な列エイリアスを提供するための構文があります。

    SELECT X.ID, X.Name
    FROM (VALUES (1, 'a'), (2, 'b')) X (ID, Name)
    

    私がこの 2 番目の構文を好むのは、この 2 番目の構文が意図することを明確にするのに本当に役立ち、式を名前から分離するのに役立つためです。派生テーブルの内容を別の場所からコピー アンド ペーストすることがよくありますが、毎回列名を入力する必要がないのは便利です。エイリアスFT、およびSFrom、 、To、およびStartです。もっと長い名前にすることもできましたが、そうしないことにしました。

  4. を使用するSELECT Alias = <Expression>のは、 の単なるショートカットですSELECT <Expression> AS Alias。私がこの方法を好むのは=、エイリアスが可変長式の末尾ではなく、スキャンしやすい 1 つの列の左側に確実に存在するためです。UPDATEこの構文には、1 文字追加して変数名に変更したり、クエリを簡単にステートメントに変換したりできるという利点もあります。

  5. @Variable =各列式の前に追加するだけで、行セットを返す代わりに変数に値を割り当てるように任意のクエリを変更できます。注意すべき点の 1 つは、クエリが複数の行を返す場合、@Variable =構文を引き続き使用できますが、サーバーはすべての行を具体化するすべての作業を実行し、変数は単に値を持つことです。 )いくつか、1、行から。最初の行または最後の行である可能性がありますが、一貫した行が返されると思われる場合でも、常にランダムな行であると想定する必要があります。特定の行が必要な場合は、特定の行の値を強制的に使用するために WHERE 句またはTOPステートメントを指定します。ORDER BY

  6. CROSS APPLY「外部参照」を持つことができるという特別なプロパティを持つ単純な派生テーブルです。つまり、その前に導入されたテーブルの列値を括弧内で使用できます。行が返されない場合は行を制限するINNER JOINか (ed 派生テーブルと同様)、OUTER APPLY行を制限しません ( OUTER JOINed 派生テーブルと同様)。ONすべてのフィルタリングは句を介して行われるため、句は必要ありませんWHERE。オプティマイザーは、クエリの意図をよく理解しており、APPLY外側の行ごとに 1 回ずつ実行することはありません。ほとんどの場合、通常の結合のようにインテリジェントにデータを取得できます。ここでは、複数回使用できるクエリで計算された値を取得する安価な方法として使用しました。私もできたはずですDECLARE @MonthDate = <same expression>代わりにそれを使用しましたが、必要のないときに変数を宣言しないのが好きな部分があります( @MonthDate 変数は単一のクエリにのみ必要であるため)。

PS私はあなたのサンプルコードに見られることを1つ指摘したいと思います(許可されている場合)。次のセクションを検討してください。

IF   Month(@RunDate) = 1
     SET @Month = 12
ELSE    
     SET @Month = Month(@RunDate) - 1

IF   Month(@RunDate) = 1
     SET @Year = Year(@RunDate) - 1
ELSE
     SET @Year = Year(@RunDate)

この並べ方はすべて、IF / ELSE ブロックの意図を伝えようとするのに最適ですが、私の専門的な意見では、 and を使用する必要がBEGINありENDます。これをコーディングした人が条件をこのようにレイアウトすることを選択した理由の 1 つは、インデント スタイルが (私にとって) 少し過剰であることです。それぞれBEGINが 2 つのインデントの深さになり、1 つはIF(1 つのインデント ブロックとして扱う) 用で、もう 1 つは 用BEGIN .. ENDです。しかし、ブロックのない単一ステートメント IF へのこの接続は、いくつかの問題を引き起こします。

  1. 上記の表現は意図を適切に伝えておらず、レビュアーに、なぜ何かが 2 回行われているのか、実際には 2 つの条件が同じであるのかを評価するように強制します。まず、ブロックを次のように書き換える必要があります。

    IF   Month(@RunDate) = 1
         BEGIN
            SET @Month = 12;
            SET @Year = Year(@RunDate) - 1;
         END
    ELSE
         BEGIN
            SET @Month = Month(@RunDate) - 1;
            SET @Year = Year(@RunDate);
         END;
    

    これは今では賢明に理解し、見直すことができます。

  2. 複数の条件またはネストされたレベルを追加し始めるとすぐに、深刻な問題が発生し、デバッグがほとんど不可能になることがあります。進取の気性に富んだ開発者が、2 つの条件が同一であり、それらの内容を組み合わせることが最も理にかなっていることを認識して、次のコードを記述したとしたらどうでしょうか。

    IF   Month(@RunDate) = 1
         SET @Month = 12;
         SET @Year = Year(@RunDate) - 1;
    ELSE
         SET @Month = Month(@RunDate) - 1;
         SET @Year = Year(@RunDate);
    

    これは良さそうに見えますSETが、2 番目のステートメントがその前のステートメントに属していないという事実が隠されてIFいます。ELSEが孤立しているため、適切にコンパイルされません。しかし、次のような変更があった場合はどうなるでしょうか。

    IF   Month(@RunDate) = 1
         SET @Month = 12;
         IF @SpecialFlag = 1
            SET @Year = Year(@RunDate) - 1;
    ELSE
         SET @Month = Month(@RunDate) - 1;
         IF @SpecialFlag = 1
            SET @Year = Year(@RunDate);
    

    今、私たちはひどい混乱を抱えています!これは正しく解析されますが、開発者が意図した結果とはかけ離れています:ELSEブロックは@SpecialFlag条件の一部です! インデントがあるため、コードではそのようには見えません。

したがって、コードの書式設定規則は、強い信念を持っている場合に好まれる可能性があり、組織や人々が変更に非常に抵抗する可能性があることは理解していますが、1)すべてのブロックで andを使用するBEGINと、いくつかの利点が得られることをお勧めします。 2)すべてを二重にインデントする手間が原因で発生する闘争を軽減するために、ブロックインデントの練習を次のように再定式化します。ENDIF

  • 終わりのないブロックを開始IFし、より深いレベルで一致させる代わりに。ELSEBEGINEND

  • で終わるブロックを作成して開始IFし、行末に配置します。ELSEENDBEGIN

これは次のようになります。

IF Month(@RunDate) = 1 BEGIN
   SET @Month = 12;
   SET @Year = Year(@RunDate) - 1;
END
ELSE BEGIN
   SET @Month = Month(@RunDate) - 1;
   SET @Year = Year(@RunDate);
END;

これは、異なるスタイルのインデントに慣れている人にとっては非常に奇妙に見えることを認識しています。すべての変更は、最初は難しいものです。しかし、少し練習すれば、それはあなたに成長し、痛みが少なくなると信じています. END他のものと合わせる目を養うだけBEGINです。最終的に、またはBEGINで始まる行に追加の意味が追加されないため、 を探すことさえ気にしないことに気付きます。最後に、クレイジーで労力のかかるインデント パターンを実行する必要はありません (後に複数のスペースを入れたり、すべてを整列させるなど)。たくさんの。そして、コードがIFELSEIFELSEIF上記のように予期せずブロックします。

100% 明確にするために: このスタイルでは、BEGIN/ENDブロックにステートメントが 1 つしかない場合、2 番目を追加するために何も再配置する必要がなく、コードは壊れません。

最後に、私自身の例にセミコロンを追加したことに注意してください。これは、SQL Server でセミコロンが必要になる日が来るという単純な理由によるものであり、巨大で面倒なセミコロン プロジェクトを必要とせずに、すべての運用コードが機能し続けるようにしたいからです。ENDこれにより、ブロックがいつ停止したかを明示的に示すという利点も得られます (ただし、率直に言って、前の直前の後にセミコロンが必要かどうかはわかりません。必要な場合はELSE少なくとも、巨大なもの)。

于 2013-01-03T19:58:12.470 に答える
1

これを試してください(SQL FIDDLE)。

declare @startdate date, @enddate date, @runDate date
select @runDate = '20130216'

select @startdate = case datepart(day,@runDate) when 16 
                         then dateadd(day,-15, @runDate)
                         else dateadd(day,14, dateadd(month,-1,@runDate))
                    end,
       @enddate =  dateadd(day,-1,@runDate)

select @startdate, @enddate 
--Results for when @runDate = '20130216'
2013-02-01  2013-02-15

--Results for when @runDate = '20130201'
2013-01-15  2013-01-31
于 2013-01-03T19:23:10.600 に答える
0

このようなものがうまくいくかもしれません:

where
    Run_Date between
    (
        case
            when datepart(dd, getdate()) = 1 then dateadd(mm, -1, getdate())
            else dateadd(dd, -15, getdate())
        end
    )
    and
    (
        case
            when datepart(dd, getdate()) = 1 then dateadd(dd, -1, getdate())
            else dateadd(dd, -1, getdate())
        end
    )

数値は異なる場合があります

于 2013-01-03T18:55:32.317 に答える