これはあなたの仕様を満たすでしょう。実行日は気にせず、正確な境界上にない場合は、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) まで指定しても害はありませんが、それはあなたが求めたものではありません。
クエリで何が起こっているかについての質問に答える。
派生テーブルは行セットを返すクエリで、通常は で始まりSELECT
、かっこで囲まれ、エイリアスが与えられます。例えば:
SELECT X.FullName
FROM
(
SELECT FullName = FirstName + ' ' + LastName
FROM dbo.User
WHERE ID = 123
) AS X
ここではX
、内部のクエリの結果で構成されるテーブルを派生させました。AS
私にとっては混乱を招くだけなので、その部分をスキップするのが好きです。これは単純な例であることに注意してください。派生テーブルは一般にこれよりも複雑で、さまざまな状況で役立ちます。dbo.User
テーブルでエイリアスを使用しませんでしたが、使用することをお勧めします。ただし、派生テーブル内のすべての列参照は、派生テーブル内から解決されることに注意することが重要です。外部からのテーブルの使用はありません。
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
派生テーブルの列名は通常、クエリから検出されます。#1 で、X.FullName
実際には table の複数の列を含む式を参照していることを確認してくださいdbo.User
。式には、クエリで明示的に新しいエイリアスが与えられました。エイリアスを指定しないと、式に固有の名前がないため、エラーが発生します。実際、派生テーブルX
には列名がないため、前のポイントからのクエリはそのままでは実行されません! ただし、派生テーブルの外部で明示的な列エイリアスを提供するための構文があります。
SELECT X.ID, X.Name
FROM (VALUES (1, 'a'), (2, 'b')) X (ID, Name)
私がこの 2 番目の構文を好むのは、この 2 番目の構文が意図することを明確にするのに本当に役立ち、式を名前から分離するのに役立つためです。派生テーブルの内容を別の場所からコピー アンド ペーストすることがよくありますが、毎回列名を入力する必要がないのは便利です。エイリアスF
、T
、およびS
はFrom
、 、To
、およびStart
です。もっと長い名前にすることもできましたが、そうしないことにしました。
を使用するSELECT Alias = <Expression>
のは、 の単なるショートカットですSELECT <Expression> AS Alias
。私がこの方法を好むのは=
、エイリアスが可変長式の末尾ではなく、スキャンしやすい 1 つの列の左側に確実に存在するためです。UPDATE
この構文には、1 文字追加して変数名に変更したり、クエリを簡単にステートメントに変換したりできるという利点もあります。
@Variable =
各列式の前に追加するだけで、行セットを返す代わりに変数に値を割り当てるように任意のクエリを変更できます。注意すべき点の 1 つは、クエリが複数の行を返す場合、@Variable =
構文を引き続き使用できますが、サーバーはすべての行を具体化するすべての作業を実行し、変数は単に値を持つことです。 )いくつか、1、行から。最初の行または最後の行である可能性がありますが、一貫した行が返されると思われる場合でも、常にランダムな行であると想定する必要があります。特定の行が必要な場合は、特定の行の値を強制的に使用するために WHERE 句またはTOP
ステートメントを指定します。ORDER BY
CROSS APPLY
「外部参照」を持つことができるという特別なプロパティを持つ単純な派生テーブルです。つまり、その前に導入されたテーブルの列値を括弧内で使用できます。行が返されない場合は行を制限するINNER JOIN
か (ed 派生テーブルと同様)、OUTER APPLY
行を制限しません ( OUTER JOIN
ed 派生テーブルと同様)。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 へのこの接続は、いくつかの問題を引き起こします。
上記の表現は意図を適切に伝えておらず、レビュアーに、なぜ何かが 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 つの条件が同一であり、それらの内容を組み合わせることが最も理にかなっていることを認識して、次のコードを記述したとしたらどうでしょうか。
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)すべてを二重にインデントする手間が原因で発生する闘争を軽減するために、ブロックインデントの練習を次のように再定式化します。END
IF
これは次のようになります。
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
で始まる行に追加の意味が追加されないため、 を探すことさえ気にしないことに気付きます。最後に、クレイジーで労力のかかるインデント パターンを実行する必要はありません (後に複数のスペースを入れたり、すべてを整列させるなど)。たくさんの。そして、コードがIF
ELSE
IF
ELSE
IF
上記のように予期せずブロックします。
100% 明確にするために: このスタイルでは、BEGIN
/END
ブロックにステートメントが 1 つしかない場合、2 番目を追加するために何も再配置する必要がなく、コードは壊れません。
最後に、私自身の例にセミコロンを追加したことに注意してください。これは、SQL Server でセミコロンが必要になる日が来るという単純な理由によるものであり、巨大で面倒なセミコロン プロジェクトを必要とせずに、すべての運用コードが機能し続けるようにしたいからです。END
これにより、ブロックがいつ停止したかを明示的に示すという利点も得られます (ただし、率直に言って、前の直前の後にセミコロンが必要かどうかはわかりません。必要な場合は、ELSE
少なくとも、巨大なもの)。