Delta Lakeのgenerated columnの活用

[Use Delta Lake generated columns \| Databricks on AWS](https://docs.databricks.com/delta/generated-columns.html) [2022/10/28時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

:::note info
**プレビュー**
本機能は[パブリックプレビュー](https://docs.databricks.com/release-notes/release-types.html)です。
:::

本機能はDatabricksランタイム8.3以降で使用できます。

Delta Lakeでは、Deltaテーブルの他のカラムに対するユーザー定義関数に基づいて自動的に生成される値を持つ特殊なタイプのカラムをサポートしています。generated columnを有するテーブルに書き込みを行う際、それらのカラムに対して明示的に値を指定する必要はなく、Delta Lakeは自動で値を計算します。例えば、(日付でテーブルをパーティショニングするために)タイムスタンプのカラムから自動で日付のカラムを生成することができます。テーブルに書き込みを行う際に必要なのはタイムスタンプのカラムのデータを指定することだけです。しかし、明示的に値を指定する際には、値は[制約](https://docs.databricks.com/tables/constraints.html)`(<value> <=> <generation expression>) IS TRUE`を満足する必要があり、そうでない場合には書き込みはエラーとなり失敗します。

:::note warn
**重要！**
generated columnを持つテーブルでは、デフォルトよりも新しいテーブルライタープロトコルが必要となります。テーブルプロトコルのバージョンが何で、新しいテーブルプロトコルのバージョンを設定することが何を意味するのかを理解するためには、[Delta Lakeのテーブルプロトコルのバージョン管理](https://qiita.com/taka_yayoi/items/0a109082037246163218)をご覧ください。
:::

以下のサンプルでgenerated columnを持つテーブルの作成方法を説明しています。

```sql:SQL
CREATE TABLE default.people10m (
  id INT,
  firstName STRING,
  middleName STRING,
  lastName STRING,
  gender STRING,
  birthDate TIMESTAMP,
  dateOfBirth DATE GENERATED ALWAYS AS (CAST(birthDate AS DATE)),
  ssn STRING,
  salary INT
)
```

```py:Python
DeltaTable.create(spark) \
  .tableName("default.people10m") \
  .addColumn("id", "INT") \
  .addColumn("firstName", "STRING") \
  .addColumn("middleName", "STRING") \
  .addColumn("lastName", "STRING", comment = "surname") \
  .addColumn("gender", "STRING") \
  .addColumn("birthDate", "TIMESTAMP") \
  .addColumn("dateOfBirth", DateType(), generatedAlwaysAs="CAST(birthDate AS DATE)") \
  .addColumn("ssn", "STRING") \
  .addColumn("salary", "INT") \
  .execute()
```

```scala:Scala
DeltaTable.create(spark)
  .tableName("default.people10m")
  .addColumn("id", "INT")
  .addColumn("firstName", "STRING")
  .addColumn("middleName", "STRING")
  .addColumn(
    DeltaTable.columnBuilder("lastName")
      .dataType("STRING")
      .comment("surname")
      .build())
  .addColumn("lastName", "STRING", comment = "surname")
  .addColumn("gender", "STRING")
  .addColumn("birthDate", "TIMESTAMP")
  .addColumn(
    DeltaTable.columnBuilder("dateOfBirth")
     .dataType(DateType)
     .generatedAlwaysAs("CAST(dateOfBirth AS DATE)")
     .build())
  .addColumn("ssn", "STRING")
  .addColumn("salary", "INT")
  .execute()
```

generated columnは通常のカラムと同じように格納されます。すなわち、ストレージを消費します。

generated columnには以下の制約があります。

- 生成のエクスプレッションには、以下のタイプの関数を除いて同じ引数を与えた際に同じ結果を常に返却するSparkにおけるSQL関数を使用することができます。
    - ユーザー定義関数
    - 集計関数
    - ウィンドウ関数
    - 複数の行を返す関数
- Databricksランタイム8.4以降でのPythonサポートでは、Delta Lakeはパーティションカラムが以下のエクスプレッションのいずれかで定義された際には常に、クエリーに対するパーティションフィルターを生成することができます。
- `CAST(col AS DATE)`および`col`の型が`TIMESTAMP`
- `YEAR(col)`および`col`の型が`TIMESTAMP`
- `YEAR(col)`, `MONTH(col)`によって2つのパーティションカラムが定義され、`col`の型が`TIMESTAMP`
- `YEAR(col)`, `MONTH(col)`, `DAY(col)`によって3つのパーティションカラムが定義され、`col`の型が`TIMESTAMP`
- `YEAR(col)`, `MONTH(col)`, `DAY(col)`, `HOUR(col)`によって4つのパーティションカラムが定義され、`col`の型が`TIMESTAMP`
- `SUBSTRING(col, pos, len)`であり、`col`の型が`STRING`
- `DATE_FORMAT(col, format)`であり、`col`の型が`TIMESTAMP`

上述のエクスプレッションのいずれかでパーティションカラムが定義され、生成エクスプレッションのベースとなるカラムを用いてクエリーがデータをフィルタリングする際、Delta Lakeはベースのカラムと生成されたカラムの関係性を参照し、可能であれば生成されたパーティションカラムに基づいてパーティションフィルターを生成します。例えば、以下のテーブルを考えます。

```sql:SQL
CREATE TABLE events(
eventId BIGINT,
data STRING,
eventType STRING,
eventTime TIMESTAMP,
eventDate date GENERATED ALWAYS AS (CAST(eventTime AS DATE))
)
PARTITIONED BY (eventType, eventDate)
```

そして、以下のクエリーを実行します。

```sql:SQL
SELECT * FROM events
WHERE eventTime >= "2020-10-01 00:00:00" <= "2020-10-01 12:00:00"
```

Delta Lakeはパーティションフィルターが指定されていない場合でも、上述のクエリーがパーティション`date=2020-10-01`のデータのみを読み込むように、パーティションフィルターを自動で生成します。

別の例として以下のテーブルを考えます。

```sql:SQL
CREATE TABLE events(
eventId BIGINT,
data STRING,
eventType STRING,
eventTime TIMESTAMP,
year INT GENERATED ALWAYS AS (YEAR(eventTime)),
month INT GENERATED ALWAYS AS (MONTH(eventTime)),
day INT GENERATED ALWAYS AS (DAY(eventTime))
)
PARTITIONED BY (eventType, year, month, day)
```

そして、以下のクエリーを実行します。

```sql:SQL
SELECT * FROM events
WHERE eventTime >= "2020-10-01 00:00:00" <= "2020-10-01 12:00:00"
```

Delta Lakeはパーティションフィルターが指定されていない場合でも、上述のクエリーがパーティション`year=2020/month=10/day=01`のデータのみを読み込むように、パーティションフィルターを自動で生成します。

Delta Lakeが自動でパーティションフィルターを生成しているのかどうかを確認するために、[EXPLAIN](https://docs.databricks.com/sql/language-manual/sql-ref-syntax-qry-explain.html)句を用いて実行計画を確認することができます。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)