DAX workout - DATEADD function

間違えて理解していることが多い関数のうちのひとつ。

https://learn.microsoft.com/en-us/dax/dateadd-function-dax

`DATEADD(<dates>, <number_of_intervals>, <interval>)`

> 現在のコンテキストの日付から、指定された間隔数だけ時間を**前後にシフトした日付の列を含むテーブル**を返します。

# ポイント
- `<dates>`パラメータ
- 日付(日付時刻)の値を計算しない
- 戻り値はテーブル

## `<dates>`パラメータ
**A reference to a date/time column**
日付(日付時刻)列を参照。ただし、タイム インテリジェンス関数なので日付テーブルの日付列。
```:DAX
DATEADD( 'Dates'[Date], -7, DAY )
```
**A table expression that returns a single column of date/time values**
テーブル式。ただし、日付(日付時刻)列のみのテーブル。
```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-28",
			dt"2024-02-29",
			dt"2024-03-01",
			dt"2024-03-02"
		}

EVALUATE
	DATEADD( T1, -1, DAY )
```
この例では、2024-02-27 から 2024-03-02 の 5 行であっても 2024-02-28 から 2024-03-02 の 4 行が得られる。
|Value|
|:-|
|2024-02-28|
|2024-02-29|
|2024-03-01|
|2024-03-02|

**A Boolean expression that defines a single-column table of date/time values**
Boolean 式。ただし日付(日付時刻)列のみのテーブルを定義する式
```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-28",
			dt"2024-02-29",
			dt"2024-03-01",
			dt"2024-03-02"
		}

EVALUATE
	DATEADD( T1[Value] > dt"2024-02-28", -1, DAY )
```
Boolean 式は、CALCULATE 関数の <filter>と同じ扱い。この例では、`FILTER(ALL(T1[Value]), TI[Value] > dt"2024-02-28")` として評価される。
|Value|
|:-|
|2024-02-28|
|2024-02-29|
|2024-03-01|

```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-28",
			dt"2024-02-29",
			dt"2024-03-01",
			dt"2024-03-02"
		}

EVALUATE
	DATEADD( T1[Value] <> dt"2024-02-28", -1, DAY )
```
Boolean 式の評価結果、日付(日付時刻)列の値が連続していないときにエラーとなる。
>Function 'DATEADD' expects a contiguous selection when the date column is not unique, has gaps or it contains time portion.

```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-27 13:00:00",
			dt"2024-02-28",
			dt"2024-02-29",
			dt"2024-03-01",
			dt"2024-03-02"
		}

EVALUATE
	DATEADD( T1[Value] > dt"2024-02-01", -1, DAY )
```
日付時刻であっても時刻は考慮されない。内部式 `ALL( T1[Value] )` で重複を除外できないのでエラーになる。
>A date column containing duplicate dates was specified in the call to function 'DATEADD'. This is not supported.

## 日付(日付時刻)の値を計算しない
得られる結果は、**前後にシフトした日付の列を含むテーブル**。なので、日付(日付時刻)値の演算(加減算)をしない。
```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-28",
			dt"2024-02-29",
			dt"2024-03-01",
			dt"2024-03-02"
		}

EVALUATE
	ADDCOLUMNS(
		T1,
		"DATEADD",
			DATEADD( T1[Value], -1, DAY )
	)
```
`T1[Value]`には 2024-02-27 より前の日付値がないので 2024-02-27 の行は BLANK になる。`T1[Value]`をシフトしただけだから。
|Value|DATEADD|
|:-|:-|
|2024-02-27|(BLANK)|
|2024-02-28|2024-02-27|
|2024-02-29|2024-02-28|
|2024-03-01|2024-02-29|
|2024-03-02|2024-03-01|

DATEADD 関数(DAX)は日付(日付時刻)値の増加減の計算をしない。
```:DAX
DEFINE
	TABLE T1 =
		{
			dt"2024-02-27",
			dt"2024-02-29",
			dt"2024-03-02",
			dt"2024-03-04",
			dt"2024-03-06"
		}

EVALUATE
	ADDCOLUMNS(
		T1,
		"DATEADD",
			DATEADD( T1[Value], -1, DAY ),
		"MinusOne",
			T1[Value] - 1
	)
```
|Value|DATEADD|MinusOne|
|:-|:-|:-|
|2024-02-27|(BLANK)|2024-02-26|
|2024-02-29|(BLANK)|2024-02-28|
|2024-03-02|(BLANK)|2024-03-01|
|2024-03-04|(BLANK)|2024-03-03|
|2024-03-06|(BLANK)|2024-03-05|

## 戻り値はテーブル
戻り値はテーブルだから `EVALUATE` で評価できる。
```:DAX
DEFINE
	TABLE T1 =
		CALENDAR(
			dt"2024-01-01",
			dt"2024-12-31"
		)

EVALUATE
	DATEADD( T1[Date], 0, DAY )
```
戻り値はテーブルだからスカラ値として扱うことができない。
```:DAX
DEFINE
	TABLE T1 =
		CALENDAR(
			dt"2024-01-01",
			dt"2024-12-31"
		)

EVALUATE
{
	DATEADD( T1[Date], 0, DAY ) = dt"2024-02-25"
}
```
> A table of multiple values was supplied where a single value was expected.

## よくあるうっかり
たとえば、行見出し/軸が日付ごとではエラーにならないが、月や年など複数に日付が対象となるときエラーになるという動作。
```:DAX
DEFINE
	TABLE T1 =
		CALENDAR(
			dt"2024-01-01",
			dt"2024-12-31"
		)

EVALUATE
SUMMARIZECOLUMNS(
	TREATAS({dt"2024-02-25"}, T1[Date]),
	"Result",
		DATEADD( T1[Date], 0, DAY ) = dt"2024-02-25"
)
```
|Result|
|--:|
|True|

1行1列のテーブルもスカラ値として評価する Engine 所定の動作により、たまたま期待する動作をしたというだけ。DATEADD 関数(DAX)を日付(日付時刻)値の計算をする関数だと勘違いしているとやらかすケースである。 
## 末日の扱い
`<interval>`が `DAY` ではないとき、末日の扱われ方について知っておくことがある。
```:DAX
DEFINE
	TABLE T1 =
		CALENDAR(
			dt"2024-01-01",
			dt"2024-12-31"
		)

EVALUATE
	CALCULATETABLE(		
		DATEADD( T1[Date], -1, MONTH ),
		DATESBETWEEN(
			T1[Date],
			dt"2024-02-28", dt"2024-02-29"
		)
	)
```
2024-01-28 と 2024-01-29 ではなく、2024-01-28 から 2024-01-31。
|Date|
|:--|
|2024-01-28|
|2024-01-29|
|2024-01-30|
|2024-01-31|
```:DAX
DEFINE
	TABLE T1 =
		CALENDAR(
			dt"2024-01-01",
			dt"2024-12-31"
		)

EVALUATE
	CALCULATETABLE(		
		DATEADD( T1[Date], -1, MONTH ),
		T1[Date] = dt"2024-02-29"
	)
```
2024-01-31 ではなく、2024-01-29
|Date|
|:--|
|2024-01-29|
# 思ったこと🙄
Excel ワークシート関数と同じく、DAXには日付(日付時刻)値を計算する関数はない。

# その他
https://powerbi.connpass.com/

- [Power BI 勉強会 - connpass](https://powerbi.connpass.com/)
- [Power BI Desktop を使い始めるには](http://qiita.com/PowerBIxyz/items/6c4735cb5b5712e70981)
- [Excel で Power Query を使い始めるには](http://qiita.com/PowerBIxyz/items/52e563037b855a099ca9)