SQL Server DATEADD 関数の使い方:期間計算の基本と応用
はじめに
データベース、特にリレーショナルデータベースの世界では、日付と時刻のデータは非常に重要です。顧客の注文日、商品の製造日、イベントの発生日時、契約の開始日・終了日など、様々なビジネスデータに日付や時刻が関連しています。これらの日付/時刻データを扱う際、特定の日付から一定期間前後の日付を計算したり、期間の開始日や終了日を求めたりといった操作は頻繁に発生します。
SQL Server には、このような日付/時刻操作を効率的に行うための組み込み関数が多数用意されています。その中でも特に利用頻度が高く、期間計算の基本となるのが DATEADD
関数です。
本記事では、SQL Server の DATEADD
関数に焦点を当て、その基本的な使い方から、様々な datepart
(日付の部分)を指定した場合の挙動、さらには日付関連の他の関数と組み合わせた実践的な期間計算の方法までを詳細に解説します。約5000語というボリュームで、初心者の方でも理解できるよう丁寧に説明し、具体的なコード例を豊富に示します。
日付/時刻データの扱いに自信がない方、DATEADD
関数の機能を十分に活用できていないと感じている方にとって、本記事が強力な一助となることを願っています。
DATEADD 関数とは?
DATEADD
関数は、指定された日付または時刻に、指定された datepart
(年、月、日、時間など)単位で、指定された数値を加算または減算する関数です。簡単に言えば、「ある日付から○年○ヶ月○日後の(または前の)日付」を計算するために使用します。
この関数を使うことで、例えば以下のような計算が簡単に行えます。
- 今日から30日後の日付
- 来月の最初の日
- 従業員の入社日から3年後の日付
- 注文日から90日間の保証期間が終了する日付
- 特定のイベント発生時刻の1時間前の時刻
期間計算において、基準となる日付から相対的な日付を求める際に DATEADD
は不可欠な関数です。
DATEADD 関数の構文
DATEADD
関数の基本的な構文は以下の通りです。
sql
DATEADD ( datepart , number , date )
各引数について詳しく見ていきましょう。
-
datepart
:- 加算または減算を行う日付/時刻の部分を指定します。例えば、年、月、日、時間、分などです。
- 文字列リテラルとして指定しますが、シングルクォーテーションで囲む必要があります (
'year'
,'month'
)。省略形 ('yy'
,'mm'
) も使用可能です。 - 指定できる
datepart
の種類については後述します。
-
number
:datepart
の単位で加算または減算する整数を指定します。- 正の数を指定すると未来の日付/時刻が、負の数を指定すると過去の日付/時刻が計算されます。
- ゼロを指定すると、元の
date
と同じ日付/時刻が返されます。 - データ型は
int
が推奨されますが、暗黙的にint
に変換可能なデータ型も使用できます。ただし、結果がint
の範囲を超えるとエラーになる可能性があります。
-
date
:- 基準となる日付または時刻を指定します。
- データ型は、
date
,datetime
,smalldatetime
,datetime2
,datetimeoffset
のいずれか、またはこれらのデータ型に解決可能な式(列名、ユーザー定義変数など)である必要があります。 - 日付形式の文字列を指定することも可能ですが、SQL Server が認識できる形式である必要があります。通常は
'YYYY-MM-DD'
,'YYYY-MM-DD HH:MI:SS'
などの標準的な形式を使用するのが安全です。 NULL
を指定した場合、結果もNULL
になります。
datepart
の種類と挙動
DATEADD
関数で指定できる datepart
には様々な種類があります。それぞれの指定によって計算結果の挙動が異なります。主な datepart
とその省略形、挙動について解説します。
datepart | 省略形 | 説明 | 備考 |
---|---|---|---|
年 | |||
year |
yy , yyyy |
年を加減します。 | 結果の月日時は保持されますが、存在しない日付(例: 閏年以外の2月29日)になった場合はエラーとなります。 |
quarter |
qq , q |
四半期を加減します。 | 3ヶ月単位での加減算になります。例えば、1四半期加算すると3ヶ月後の日付になります。 |
月 | |||
month |
mm , m |
月を加減します。 | 結果として生成される月の日数が、元の月の末日より少ない場合、結果はその月の最終日になります。(例: 1月31日に1ヶ月加算すると2月28日または29日になる) |
日 | |||
dayofyear |
dy , y |
年の日数(1月1日からの日数)を加減します。 | day と同じ結果を返します。通常は day を使用します。 |
day |
dd , d |
日を加減します。 | 最も基本的な単位です。 |
week |
wk , ww |
週を加減します。 | 7日単位での加減算になります。例えば、1週加算すると7日後の日付になります。 |
weekday |
dw , w |
週の中の日(曜日)を加減します。 | 注意が必要な datepart です。 SET DATEFIRST の設定によって挙動が変わります。指定した曜日の数だけ進みますが、結果として特定の日数ではなく曜日が変わります。後述。 |
時間 | |||
hour |
hh |
時間を加減します。 | |
minute |
mi , n |
分を加減します。 | |
second |
ss , s |
秒を加減します。 | |
millisecond |
ms |
ミリ秒を加減します。 | datetime 型の精度は1/300秒なので、ミリ秒を指定しても最も近い .000, .003, .007 秒に丸められることがあります。datetime2 型ではより正確に扱えます。 |
microsecond |
mcs |
マイクロ秒を加減します。 | datetime2 型でのみ有効です。datetime 型などではオーバーフローエラーになる可能性があります。 |
nanosecond |
ns |
ナノ秒を加減します。 | datetime2 型でのみ有効です。他の型ではオーバーフローエラーになる可能性があります。 |
注意点:
microsecond
とnanosecond
は、基準となるdate
のデータ型がdatetime2
である場合に最も正確に扱えます。datetime
は精度が低いため、これらの細かい単位を指定しても意図した結果にならない、あるいはエラーになる可能性があります。weekday
は他のdatepart
と挙動が大きく異なります。単に日数を加算するのではなく、SET DATEFIRST
の設定に基づいた曜日単位の移動になります。この詳細は後述します。
具体的な使用例
様々な datepart
を使った具体的な例を見ていきましょう。基準日として、今日のシステム日付/時刻を取得する GETDATE()
関数を使用します。
sql
-- 現在の日付と時刻を取得
SELECT GETDATE() AS CurrentDateTime;
出力例:
“`
CurrentDateTime
2023-10-27 10:30:00.123
“`
※ 以降の例では、この日時を基準として計算を行います。
年の加減算 (year
, yy
, yyyy
)
“`sql
— 1年後の日付
SELECT DATEADD(year, 1, GETDATE()) AS OneYearLater;
— 5年前の日付
SELECT DATEADD(yyyy, -5, GETDATE()) AS FiveYearsAgo;
“`
出力例:
“`
OneYearLater
2024-10-27 10:30:00.123
FiveYearsAgo
2018-10-27 10:30:00.123
``
year` を指定すると、年のみが変化し、月、日、時刻の部分はそのまま保持されます。ただし、元の月日が存在しない年の場合はエラーになります(例: 2020年2月29日に1年加算しようとする)。
四半期の加減算 (quarter
, qq
, q
)
“`sql
— 2四半期後の日付 (6ヶ月後)
SELECT DATEADD(quarter, 2, GETDATE()) AS TwoQuartersLater;
— 1四半期前の日付 (3ヶ月前)
SELECT DATEADD(qq, -1, GETDATE()) AS OneQuarterAgo;
“`
出力例:
“`
TwoQuartersLater
2024-04-27 10:30:00.123
OneQuarterAgo
2023-07-27 10:30:00.123
``
quarter` は3ヶ月単位で加減算を行います。2四半期後は6ヶ月後、1四半期前は3ヶ月前と同じ結果になります。
月の加減算 (month
, mm
, m
)
month
の加減算は、結果として生成される月の日数が元の月の末日より少ない場合に特別な挙動を示します。
“`sql
— 3ヶ月後の日付
SELECT DATEADD(month, 3, GETDATE()) AS ThreeMonthsLater;
— 12ヶ月前の日付 (1年前)
SELECT DATEADD(m, -12, GETDATE()) AS TwelveMonthsAgo;
— 1月31日に1ヶ月加算
SELECT DATEADD(month, 1, ‘2023-01-31’) AS Jan31AddOneMonth;
— 3月31日に1ヶ月加算
SELECT DATEADD(month, 1, ‘2023-03-31’) AS Mar31AddOneMonth;
— 1月31日に2ヶ月加算 (閏年ではない)
SELECT DATEADD(month, 2, ‘2023-01-31’) AS Jan31AddTwoMonthsNonLeap;
— 1月31日に2ヶ月加算 (閏年)
SELECT DATEADD(month, 2, ‘2024-01-31’) AS Jan31AddTwoMonthsLeap;
“`
出力例:
“`
ThreeMonthsLater
2024-01-27 10:30:00.123
TwelveMonthsAgo
2022-10-27 10:30:00.123
Jan31AddOneMonth
2023-02-28 00:00:00.000 — 2月31日は存在しないため、2月の末日 (28日) になる
Mar31AddOneMonth
2023-04-30 00:00:00.000 — 4月31日は存在しないため、4月の末日 (30日) になる
Jan31AddTwoMonthsNonLeap
2023-03-31 00:00:00.000 — 3月31日は存在する
Jan31AddTwoMonthsLeap
2024-03-31 00:00:00.000 — 3月31日は存在する
“`
この挙動は重要です。例えば、支払いサイクルを計算する際に「月末日から1ヶ月後」を求める場合などに、意図した月末日が正確に得られるようになっています。
日の加減算 (day
, dd
, d
, dayofyear
, dy
, y
)
dayofyear
, dy
, y
は day
, dd
, d
と同じ挙動を示します。通常は day
を使用します。
“`sql
— 7日後の日付
SELECT DATEADD(day, 7, GETDATE()) AS SevenDaysLater;
— 30日前の日付
SELECT DATEADD(d, -30, GETDATE()) AS ThirtyDaysAgo;
— dayofyear は day と同じ
SELECT DATEADD(dayofyear, 7, GETDATE()) AS SevenDaysLaterDY;
“`
出力例:
“`
SevenDaysLater
2023-11-03 10:30:00.123
ThirtyDaysAgo
2023-09-27 10:30:00.123
SevenDaysLaterDY
2023-11-03 10:30:00.123 — day と同じ結果
“`
日単位の加減算は最も直感的で、指定した日数だけ日付が進む(または戻る)シンプルな計算になります。
週の加減算 (week
, wk
, ww
)
“`sql
— 2週間後の日付 (14日後)
SELECT DATEADD(week, 2, GETDATE()) AS TwoWeeksLater;
— 4週間前の日付 (28日後)
SELECT DATEADD(ww, -4, GETDATE()) AS FourWeeksAgo;
“`
出力例:
“`
TwoWeeksLater
2023-11-10 10:30:00.123
FourWeeksAgo
2023-09-29 10:30:00.123
``
weekは7日単位で加減算を行います。
DATEADD(week, N, date)は
DATEADD(day, N * 7, date)` と同じ結果になります。
曜日の加減算 (weekday
, dw
, w
) – 要注意!
weekday
は他の datepart
とは異なり、SET DATEFIRST
の設定に依存して挙動が変わります。SET DATEFIRST
は週の最初の曜日を定義する設定です(1=月曜日, 2=火曜日, …, 7=日曜日)。デフォルトは米国英語環境では7(日曜日)、日本語環境では1(月曜日)であることが多いですが、サーバーやセッションの設定によって異なります。
DATEADD(weekday, number, date)
は、指定された date
の曜日から、number
で指定された数だけ「曜日」を進めます。しかし、結果として返される日付は、元の date
の曜日に関係なく、date
から number
日後の日付になります。
つまり、DATEADD(weekday, number, date)
は DATEADD(day, number, date)
と常に同じ結果を返します。
これは直感的ではないかもしれません。「次の月曜日の日付」を求めるために DATEADD(weekday, 1, '2023-10-27' -- 金曜日)
としても、次の月曜日になるわけではなく、単純に1日後の土曜日が返されます。
混乱を避けるため、通常は weekday
を使わず、day
を使うことを強く推奨します。 特定の曜日を基準にした計算が必要な場合は、DATEPART(weekday, date)
や DATENAME(weekday, date)
と組み合わせるなど、より複雑なロジックが必要になります。
例(SET DATEFIRST
が7=日曜日の場合):
“`sql
SET DATEFIRST 7; — 日曜日が週の最初 (USA 形式)
SELECT DATEPART(weekday, GETDATE()) AS CurrentWeekday; — 金曜日 (2023-10-27 は金曜日。日曜日を1とする週で5番目)
— 金曜日から1 weekday 加算
SELECT DATEADD(weekday, 1, GETDATE()) AS AddOneWeekday;
— 金曜日から5 weekday 加算 (次の水曜日になる「はず」だが…)
SELECT DATEADD(weekday, 5, GETDATE()) AS AddFiveWeekday;
SET DATEFIRST 1; — 月曜日が週の最初 (ISO 形式, 日本語環境のデフォルトなど)
SELECT DATEPART(weekday, GETDATE()) AS CurrentWeekdayISO; — 金曜日 (2023-10-27 は金曜日。月曜日を1とする週で5番目)
— 金曜日から1 weekday 加算 (SET DATEFIRST 7 と同じ結果になる)
SELECT DATEADD(weekday, 1, GETDATE()) AS AddOneWeekdayISO;
“`
出力例(GETDATE()
が 2023-10-27 10:30:00.123
の場合):
“`
CurrentWeekday
5 — SET DATEFIRST 7 (日曜=1) の場合、金曜日は5番目
AddOneWeekday
2023-10-28 10:30:00.123 — 単純に1日後
AddFiveWeekday
2023-11-01 10:30:00.123 — 単純に5日後
CurrentWeekdayISO
5 — SET DATEFIRST 1 (月曜=1) の場合、金曜日は5番目
AddOneWeekdayISO
2023-10-28 10:30:00.123 — SET DATEFIRST 7 と同じ結果 (単に1日後)
``
DATEADD(weekday, number, date)
この結果からもわかるように、は
SET DATEFIRSTの設定に関わらず、単純に
DATEADD(day, number, date)と同じ計算結果を返します。したがって、
weekday` は実際には「曜日単位で進む」のではなく、「指定日数だけ進む」という点で誤解を招きやすいため、使用は避けるのが無難です。
時間の加減算 (hour
, hh
)
“`sql
— 3時間後の時刻
SELECT DATEADD(hour, 3, GETDATE()) AS ThreeHoursLater;
— 8時間前の時刻
SELECT DATEADD(hh, -8, GETDATE()) AS EightHoursAgo;
“`
出力例:
“`
ThreeHoursLater
2023-10-27 13:30:00.123
EightHoursAgo
2023-10-27 02:30:00.123
“`
分の加減算 (minute
, mi
, n
)
“`sql
— 45分後の時刻
SELECT DATEADD(minute, 45, GETDATE()) AS FortyFiveMinutesLater;
— 30分前の時刻
SELECT DATEADD(n, -30, GETDATE()) AS ThirtyMinutesAgo;
“`
出力例:
“`
FortyFiveMinutesLater
2023-10-27 11:15:00.123
ThirtyMinutesAgo
2023-10-27 10:00:00.123
“`
秒の加減算 (second
, ss
, s
)
“`sql
— 90秒後の時刻 (1分30秒後)
SELECT DATEADD(second, 90, GETDATE()) AS NinetySecondsLater;
— 5秒前の時刻
SELECT DATEADD(s, -5, GETDATE()) AS FiveSecondsAgo;
“`
出力例:
“`
NinetySecondsLater
2023-10-27 10:31:30.123
FiveSecondsAgo
2023-10-27 10:29:55.123
“`
ミリ秒以下の加減算 (millisecond
, ms
, microsecond
, mcs
, nanosecond
, ns
)
これらの単位は、使用する日付/時刻のデータ型の精度に依存します。
datetime
およびsmalldatetime
: 精度が低く (約3.33ミリ秒)、ミリ秒単位の正確な計算には向きません。最も近い .000, .003, .007 秒に丸められます。datetime2
: 精度が高く、最大ナノ秒まで正確に扱えます。datetimeoffset
:datetime2
と同様の精度を持ちますが、タイムゾーン情報を含みます。DATEADD
はタイムゾーンオフセットを変更せず、指定された単位をローカル時間に適用します。
“`sql
— datetime 型でミリ秒加算 (丸められる可能性あり)
SELECT DATEADD(millisecond, 500, CAST(‘2023-10-27 10:30:00.123’ AS datetime)) AS Add500msDatetime;
SELECT DATEADD(millisecond, 1, CAST(‘2023-10-27 10:30:00.123’ AS datetime)) AS Add1msDatetime; — 3.33ms 単位で丸められるため、結果は 2023-10-27 10:30:00.127 になるか、123のままになるかなど、正確な結果は保証されない
— datetime2 型でミリ秒加算 (正確)
SELECT DATEADD(millisecond, 500, CAST(‘2023-10-27 10:30:00.1234567’ AS datetime2)) AS Add500msDatetime2;
— datetime2 型でマイクロ秒加算
SELECT DATEADD(microsecond, 10, CAST(‘2023-10-27 10:30:00.1234567’ AS datetime2)) AS Add10mcsDatetime2;
— datetime2 型でナノ秒加算
SELECT DATEADD(nanosecond, 100, CAST(‘2023-10-27 10:30:00.1234567’ AS datetime2)) AS Add100nsDatetime2;
— datetime 型で microsecond/nanosecond を加算しようとするとエラーになる可能性が高い
— SELECT DATEADD(microsecond, 10, GETDATE()) AS Add10mcsDatetimeError; — GETDATE() は datetime を返すことが多い
“`
出力例:
“`
Add500msDatetime
2023-10-27 10:30:00.623 — datetime でも多くの場合は小数点以下3桁まで表示できる
Add1msDatetime
2023-10-27 10:30:00.127 — 環境やSQL Serverのバージョンによるが、このように丸められることがある
Add500msDatetime2
2023-10-27 10:30:00.6234567 — datetime2 では正確
Add10mcsDatetime2
2023-10-27 10:30:00.1234667 — datetime2 では正確
Add100nsDatetime2
2023-10-27 10:30:00.1234568 — datetime2 では正確
``
datetime2` 型を使用するようにしてください。
マイクロ秒やナノ秒単位の正確な計算が必要な場合は、必ず
DATEADD と他の関数を組み合わせた期間計算の応用
DATEADD
関数は単独で使用することも多いですが、他の日付関連関数(DATEDIFF
, GETDATE
, CAST
, CONVERT
, DATEPART
など)と組み合わせることで、より高度で柔軟な期間計算が可能になります。特に重要な応用例として、「期間の開始日・終了日を求める」方法があります。
期間の開始日を求める
ある日付(例えば今日)が含まれる期間(月、四半期、年など)の最初の日を求める計算は非常に一般的です。この計算には、DATEADD
と DATEDIFF
を組み合わせた以下のパターンがよく使われます。
sql
DATEADD(datepart, DATEDIFF(datepart, 0, date), 0)
または、基準日として '1900-01-01'
を明示的に使うパターン:
sql
DATEADD(datepart, DATEDIFF(datepart, '1900-01-01', date), '1900-01-01')
('1900-01-01'
は SQL Server の日付型におけるゼロ日目(0に対応する日付)としてよく利用されます。日付型のリテラルとしては '1900-01-01'
と明示的に指定するのが分かりやすいでしょう。ただし、日付型が datetimeoffset の場合は SWITCHOFFSET
などでタイムゾーンを考慮する必要がありますが、ここでは基本的な日付型を前提とします。)
このパターンがなぜ機能するのかを分解して説明します。
-
DATEDIFF(datepart, '1900-01-01', date)
:- これは、基準日
'1900-01-01'
から指定されたdate
までの間に、指定されたdatepart
がいくつあるかを計算します。 - 例えば
DATEDIFF(month, '1900-01-01', GETDATE())
とすると、1900年1月1日から今日まで何ヶ月経過したかが計算されます。今日が2023年10月27日なら、約1233ヶ月といった整数値が返されます。
- これは、基準日
-
DATEADD(datepart, DATEDIFF(...), '1900-01-01')
:- ステップ1で計算された「
datepart
の数」を、基準日'1900-01-01'
に加算します。 - 例えば、
DATEADD(month, 1233, '1900-01-01')
とすると、1900年1月1日から1233ヶ月後の日付が計算されます。1900年1月1日の属する月の期間から1233ヶ月分進むため、計算結果は 今日(2023年10月27日)が属する月の最初の日、つまり2023-10-01
になります。
- ステップ1で計算された「
このパターンは、月、四半期、年、週などの任意の datepart
に適用できます。
例:
“`sql
— 今日の日付を含む月の最初の日
SELECT DATEADD(month, DATEDIFF(month, ‘1900-01-01’, GETDATE()), ‘1900-01-01’) AS StartOfMonth;
— 今日の日付を含む四半期の最初の日
SELECT DATEADD(quarter, DATEDIFF(quarter, ‘1900-01-01’, GETDATE()), ‘1900-01-01’) AS StartOfQuarter;
— 今日の日付を含む年の最初の日
SELECT DATEADD(year, DATEDIFF(year, ‘1900-01-01’, GETDATE()), ‘1900-01-01’) AS StartOfYear;
— 今日の日付を含む週の最初の日 (SET DATEFIRST に依存)
— SET DATEFIRST 1; — 月曜日始まりの場合
— SELECT DATEADD(week, DATEDIFF(week, ‘1900-01-01’, GETDATE()), ‘1900-01-01’) AS StartOfWeek;
— SET DATEFIRST 7; — 日曜日始まりの場合
— SELECT DATEADD(week, DATEDIFF(week, ‘1900-01-01’, GETDATE()), ‘1900-01-01’) AS StartOfWeek;
— 週の計算は SET DATEFIRST に依存するため注意が必要です。より安全な方法としては、
— DATEADD(day, 1 – DATEPART(weekday, GETDATE()), GETDATE()) のように現在日から曜日差を計算する方法もあります。
— 例: 月曜日始まり (DATEFIRST 1)。今日が金曜日(DATEPART = 5)なら、1 – 5 = -4日を加算すると月曜日になる。
— 例: 日曜日始まり (DATEFIRST 7)。今日が金曜日(DATEPART = 5)なら、1 – 5 = -4日を加算すると日曜日になる。
— ただし、この方法も SET DATEFIRST 依存です。常に月曜日を週の始まりとする場合は、以下のパターンがよく使われます。
SELECT DATEADD(wk, DATEDIFF(wk, 0, GETDATE()), 0) AS StartOfWeekMonday; — 0 は ‘1900-01-01’ に相当
“`
出力例(SET DATEFIRST 7 と仮定, GETDATE() は 2023-10-27 金曜日 10:30:00.123):
“`
StartOfMonth
2023-10-01 00:00:00.000
StartOfQuarter
2023-10-01 00:00:00.000 — 10月は第4四半期の始まり
StartOfYear
2023-01-01 00:00:00.000
— SET DATEFIRST 7 の場合 (日曜=1)
— 2023-10-27 は金曜日で週の5日目 (DATEPART)。1 – 5 = -4 を加算。
— DATEADD(day, -4, ‘2023-10-27’) は ‘2023-10-23’ (月曜日) になる。
— この DATEADD(day, …) パターンは SET DATEFIRST 依存
— StartOfWeekSunday (DATEDIFF(week, ‘1900-01-01’, GETDATE()) を使った場合)
— 1900-01-01 は月曜日。月曜日始まりで計算されるため、結果は月曜になる。
— 2023-10-23 00:00:00.000
— 結局 DATEDIFF/DATEADD (week) パターンも内部的には基準日 ‘1900-01-01’ とターゲット日の week の差を計算し、それを ‘1900-01-01’ に加算する。
— ‘1900-01-01’ は月曜日なので、結果は常に月曜日になる。
StartOfWeekMonday
2023-10-23 00:00:00.000 — これは常に月曜日を週の開始とするパターン
``
SET DATEFIRST
週の計算に関しては、の設定と
DATEDIFF/DATEADDの組み合わせの挙動をしっかり理解しておく必要があります。一般的には
DATEADD(wk, DATEDIFF(wk, 0, GETDATE()), 0)のパターンが、
SET DATEFIRST` に影響されず常に月曜日を週の開始として計算できるため、広く使用されます。
期間の終了日を求める
期間の終了日を求めるには、「次の期間の最初の日」を求め、そこから1日(または1ミリ秒など、必要な最小単位)を引く、というテクニックがよく使われます。
sql
DATEADD(最小datepart, -1, DATEADD(datepart, DATEDIFF(datepart, '1900-01-01', date) + 1, '1900-01-01'))
最小datepart
は、対象の期間の1単位より小さい、日付の最小単位です。例えば、月の終了日を求めるなら day
(-1) や millisecond
(-3) などを使用します。最も安全なのは day, -1
で日付部分の末日を求める方法です。
このパターンも分解して説明します。
-
DATEDIFF(datepart, '1900-01-01', date) + 1
:- 基準日から
date
までのdatepart
の数を計算し、それに1を加えます。これはdate
が含まれる期間の次の期間までの数を意味します。 - 例えば、
datepart
がmonth
でdate
が今日(2023年10月27日)の場合、1900年1月1日から今日まで約1233ヶ月。それに1を加えて1234ヶ月。これは1900年1月1日から数えて、今日が含まれる10月の次の月(11月)までの月数です。
- 基準日から
-
DATEADD(datepart, DATEDIFF(...) + 1, '1900-01-01')
:- ステップ1で計算された数を
'1900-01-01'
に加算します。 - 例えば、
DATEADD(month, 1234, '1900-01-01')
とすると、1900年1月1日から1234ヶ月後の日付が計算されます。これは今日が含まれる月の次の月(11月)の最初の日、つまり2023-11-01
になります。
- ステップ1で計算された数を
-
DATEADD(最小datepart, -1, DATEADD(datepart, DATEDIFF(...) + 1, '1900-01-01'))
:- ステップ2で求められた「次の期間の最初の日」から、最小単位を1つ引きます。
- 例えば、
DATEADD(day, -1, '2023-11-01')
とすると、2023年11月1日の前日、つまり2023-10-31
が得られます。これは今日(2023年10月27日)が含まれる月(10月)の最終日です。
このパターンも、月、四半期、年などの任意の datepart
に適用できます。
例:
“`sql
— 今日の日付を含む月の最終日
SELECT DATEADD(day, -1, DATEADD(month, DATEDIFF(month, ‘1900-01-01’, GETDATE()) + 1, ‘1900-01-01’)) AS EndOfMonth;
— 今日の日付を含む四半期の最終日
SELECT DATEADD(day, -1, DATEADD(quarter, DATEDIFF(quarter, ‘1900-01-01’, GETDATE()) + 1, ‘1900-01-01’)) AS EndOfQuarter;
— 今日の日付を含む年の最終日
SELECT DATEADD(day, -1, DATEADD(year, DATEDIFF(year, ‘1900-01-01’, GETDATE()) + 1, ‘1900-01-01’)) AS EndOfYear;
— 今日の日付を含む週の最終日 (SET DATEFIRST に依存)
— DATEADD(day, 7 – DATEPART(weekday, GETDATE()), GETDATE()) のように計算する方法もあります。
— 例: 月曜日始まり (DATEFIRST 1)。今日が金曜日(DATEPART = 5)なら、7 – 5 = 2日を加算すると日曜日になる。
— 例: 日曜日始まり (DATEFIRST 7)。今日が金曜日(DATEPART = 5)なら、7 – 5 = 2日を加算すると土曜日になる。
— 常に日曜日を週の終了とする場合は、以下のパターンがよく使われます。
SELECT DATEADD(wk, DATEDIFF(wk, 0, GETDATE()), 0) AS StartOfWeekMonday; — 先ほど求めた週の開始日 (月曜日)
SELECT DATEADD(day, 6, DATEADD(wk, DATEDIFF(wk, 0, GETDATE()), 0)) AS EndOfWeekSunday; — 月曜日に6日加算して日曜日を求める
“`
出力例(SET DATEFIRST 7 と仮定, GETDATE() は 2023-10-27 金曜日 10:30:00.123):
“`
EndOfMonth
2023-10-31 00:00:00.000
EndOfQuarter
2023-12-31 00:00:00.000 — 10月は第4四半期なので、終了は12月末
EndOfYear
2023-12-31 00:00:00.000
StartOfWeekMonday
2023-10-23 00:00:00.000 — 月曜日始まりの週の開始日
EndOfWeekSunday
2023-10-29 00:00:00.000 — 月曜日始まりの週の終了日 (日曜日)
“`
これらのパターンは非常に強力で、レポート作成やデータ抽出において「今月に入ってからの売上」「先四半期のデータ」「今年度の日次集計」といった、期間を絞り込む条件を作成する際に頻繁に利用されます。
期間フィルタリングへの応用
期間の開始日や終了日を求める計算を応用すると、テーブルから特定の期間のデータを抽出するための WHERE
句を作成できます。
例:過去30日間の注文データを取得する
sql
SELECT *
FROM Orders
WHERE OrderDate >= DATEADD(day, -30, GETDATE());
例:今月の注文データを取得する
sql
SELECT *
FROM Orders
WHERE OrderDate >= DATEADD(month, DATEDIFF(month, '1900-01-01', GETDATE()), '1900-01-01')
AND OrderDate < DATEADD(month, DATEDIFF(month, '1900-01-01', GETDATE()) + 1, '1900-01-01');
OrderDate < DATEADD(month, DATEDIFF(month, '1900-01-01', GETDATE()) + 1, '1900-01-01')
としているのは、今月の最終日 を含む 期間を BETWEEN
で指定する場合、時間部分まで考慮する必要があり複雑になるのを避けるためです。月の最初の日から、次の月の最初の日より前、という条件にすることで、時間部分に関わらずその月の全ての日付を正確に含めることができます。
例:前四半期の注文データを取得する
“`sql
— 前四半期の開始日
DECLARE @PrevQuarterStart DATE = DATEADD(quarter, DATEDIFF(quarter, ‘1900-01-01’, GETDATE()) – 1, ‘1900-01-01’);
— 前四半期の終了日の「次の日」 (今四半期の開始日)
DECLARE @PrevQuarterEndPlusOne DATE = DATEADD(quarter, DATEDIFF(quarter, ‘1900-01-01’, GETDATE()), ‘1900-01-01’);
SELECT *
FROM Orders
WHERE OrderDate >= @PrevQuarterStart
AND OrderDate < @PrevQuarterEndPlusOne;
“`
このように変数を使用すると、クエリが読みやすくなります。
日付型のキャストと変換
DATEADD
関数は日付/時刻型のデータを扱いますが、引数 date
に日付形式の文字列リテラルを指定することも可能です。しかし、この文字列が SQL Server で認識可能な形式でない場合、エラーになる可能性があります。明示的に CAST
や CONVERT
関数を使用して、文字列を正確な日付/時刻型に変換してから DATEADD
の引数に渡すのが安全な方法です。
“`sql
— 文字列を DATEADD の引数として直接使用 (形式によってはエラーになる可能性)
SELECT DATEADD(day, 10, ‘2023/10/27’); — 日本語環境では通ることが多いが、安全ではない
— 明示的に CAST して使用 (推奨)
SELECT DATEADD(day, 10, CAST(‘2023-10-27’ AS DATE));
— 明示的に CONVERT してスタイル指定で使用 (推奨)
SELECT DATEADD(hour, 5, CONVERT(datetime, ‘20231027 10:30:00’, 120)); — スタイル120 (ODBC Canonical) を指定
``
CAST
特に異なるロケール設定の環境間でスクリプトを移動させる場合などは、日付リテラルを直接使うのではなく、や
CONVERT` を利用するようにしましょう。
DATEADD 使用時の注意点と考慮事項
DATEADD
関数は強力ですが、使用する際に注意すべき点や考慮すべき事項がいくつかあります。
-
データ型の精度と範囲:
- 前述の通り、
datetime
とsmalldatetime
は精度が低く、ミリ秒以下の計算には適していません。datetime2
またはdatetimeoffset
を使用してください。 - 日付/時刻型の範囲を超える結果になる場合、エラーが発生します。例えば、最も未来の日付
'9999-12-31'
に1日加算しようとしたり、最も過去の日付'1753-01-01'
(datetime の最小値) から1日減算しようとしたりする場合です。 number
引数はint
型ですが、計算結果がint
の範囲を超えた場合、オーバーフローエラーが発生することがあります。非常に大きな数を加算/減算する場合は注意が必要です。
“`sql
— datetime の最大値を超えようとする (エラーになる)
— SELECT DATEADD(day, 1, ‘9999-12-31’); — エラー: ‘DATEADD’ function resulted in an overflow.— datetime の最小値を超えようとする (エラーになる)
— SELECT DATEADD(day, -1, ‘1753-01-01’); — エラー: ‘DATEADD’ function resulted in an overflow.
“` - 前述の通り、
-
うるう年と月の末日:
DATEADD
は、年の加減算でうるう年の2月29日が存在しない年に移動した場合、または月の加減算で結果の月に元の月の末日が存在しない場合、自動的に調整を行います。この挙動を理解しておくことが重要です (month
の例を参照)。
-
SET DATEFIRST
の影響:weekday
datepart
はSET DATEFIRST
の設定に強く依存し、かつその挙動が直感的ではないため、可能な限りday
datepart
を使用することを推奨します。週の始まりを固定したい場合は、DATEDIFF/DATEADD
パターンをweek
やday
と組み合わせて使用してください。
-
NULL の扱い:
date
引数にNULL
を指定した場合、結果は常にNULL
になります。これは他の多くの SQL 関数と同様の挙動です。
sql
SELECT DATEADD(day, 7, NULL) AS ResultWithNull;
出力:
“`
ResultWithNull
NULL
“` -
パフォーマンス:
WHERE
句で日付列に関数を適用すると、多くの場合、その列のインデックスが効率的に使用されなくなります(SARGability の問題)。- 例えば
WHERE DATEADD(day, 30, OrderDate) >= GETDATE()
のようなクエリは、OrderDate >= DATEADD(day, -30, GETDATE())
のように書き換えることで、OrderDate
列のインデックスを利用しやすくなり、パフォーマンスが向上する可能性があります。 DATEADD
関数自体は通常非常に高速ですが、大量のデータに対してループ的に適用する場合などは、処理時間全体に影響を与える可能性も考慮してください。
-
タイムゾーン:
DATEADD
関数は、datetimeoffset
型を含む日付/時刻データに対して、タイムゾーンオフセットを変更せずに加減算を行います。計算は基本的に協定世界時 (UTC) またはサーバーのローカルタイムで行われます。タイムゾーンを跨いだ正確な計算が必要な場合は、事前にSWITCHOFFSET
やAT TIME ZONE
を使用して適切なタイムゾーンに変換しておく必要があります。
“`sql
— UTC 時刻に3時間加算 (オフセットは変わらない)
SELECT DATEADD(hour, 3, SYSDATETIMEOFFSET() AT TIME ZONE ‘UTC’) AS UTCAdd3Hours;— JST 時刻に3時間加算 (オフセットは変わらない)
SELECT DATEADD(hour, 3, SYSDATETIMEOFFSET() AT TIME ZONE ‘Tokyo Standard Time’) AS JSTAdd3Hours;
“`
まとめ:DATEADD をマスターするためのポイント
DATEADD
関数は、SQL Server における日付と時刻の期間計算の根幹をなす関数です。これをマスターすることで、様々なビジネス要件に対応した柔軟なクエリを作成できるようになります。
本記事で解説した内容をまとめると、DATEADD
を効果的に使うためには以下のポイントが重要です。
- 構文を理解する:
DATEADD ( datepart , number , date )
の3つの引数の役割を正確に把握します。 - 主要な
datepart
を覚える: 年、月、日、時間、分、秒などの主要なdatepart
とその省略形を理解し、それぞれの加減算の挙動(特にmonth
の末日処理)を把握します。 weekday
の特殊な挙動を理解し、可能な限りday
を使う:SET DATEFIRST
に依存し、直感的ではないweekday
は避けるのが無難です。- 日付/時刻型の精度を考慮する: ミリ秒以下の計算が必要な場合は
datetime2
を使用します。 - 期間の開始日・終了日を求めるパターンを理解・活用する:
DATEADD(datepart, DATEDIFF(datepart, 0, date), 0)
またはDATEADD(datepart, DATEDIFF(datepart, '1900-01-01', date), '1900-01-01')
およびその応用である終了日計算のパターンは非常に汎用的で強力です。 - 他の日付関数と組み合わせて使用する:
GETDATE
,DATEDIFF
,CAST
,CONVERT
などと組み合わせることで、複雑な日付計算や期間フィルタリングが可能になります。 - エラーケース(範囲オーバー、NULL)やパフォーマンス(SARGability)に注意する: 意図しない結果やエラーを防ぎ、効率的なクエリを作成するための考慮事項です。
- 具体的なコード例を試す: 実際にコードを書いて実行し、様々な
datepart
やnumber
、date
の組み合わせで結果を確認することが理解を深める上で最も効果的です。
終わりに
本記事では、SQL Server の DATEADD
関数について、その基本的な使い方から期間計算への応用までを詳細に解説しました。約5000語にわたる解説と豊富なコード例を通じて、DATEADD
関数が単に日付を足し引きするだけでなく、様々な期間計算の基盤となる強力なツールであることがお分かりいただけたかと思います。
データベース操作において日付や時刻を扱う機会は非常に多いです。DATEADD
関数を正しく理解し、他の日付関連関数と組み合わせて使いこなせるようになれば、より柔軟で正確なデータ処理が可能になり、日々の業務や開発における生産性が大きく向上するはずです。
ぜひ本記事を参考に、SQL Server での日付操作、特に期間計算のスキルを磨いてください。実践を通じて慣れていくことが、最も早く習得する道です。