Unity Catalogにおけるビューの作成

[Create views \| Databricks on AWS](https://docs.databricks.com/data-governance/unity-catalog/create-views.html) [2022/8/25時点]の翻訳です。

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

本書では、Unity Catalogでどのようにビューを作成するのかを説明します。

ビューはメタストアにある1つい上のテーブルやビューから構成される読み取り専用オブジェクトです。Unity Catalogの[3レベル名前空間](https://docs.databricks.com/data-governance/unity-catalog/queries.html#three-level-namespace-notation)の3層目に存在します。ビューは複数のスキーマ、カタログに存在するテーブルやビューから作成することができます。

[ダイナミックビュー](#ダイナミックビューの作成)は、行列のアクセス制御、データマスキングを提供するために使用されます。

ビュー作成構文のサンプルは以下のようになります。

```sql:SQL
CREATE VIEW main.default.experienced_employee
  (id COMMENT 'Unique identification number', Name)
  COMMENT 'View for experienced employees'
AS SELECT id, name
   FROM all_employee
   WHERE working_years > 5;
```

# 要件

- 親カタログに対する`USAGE`権限、親スキーマに対する`USAGE`、`CREATE`権限が必要です。メタストア管理者、カタログオーナーはこれらの権限全てを付与することができます。スキーマオーナーは、スキーマに対する`USAGE`、`CREATE`権限を付与することができます。
- ビューで参照されるテーブル、ビューを読み込む権限が必要です。(テーブル、ビューに対する`SELECT`、カタログ、スキーマに対する`USAGE`)

:::note
**注意**
**Single User**[アクセスモード](https://qiita.com/taka_yayoi/items/15aede468bdca58ec6a3#unity-catalog%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%A2%E3%83%87%E3%83%AB)のクラスターからビューを読み込むためには、参照されるすべてのテーブルとビューに対する`SELECT`権限が必要となります。
:::

ビューがワークスペースローカルのHiveメタストアのテーブルを参照している場合、このビューはワークスペースローカルのテーブルを保持しているワークスペースからしか参照できません。このため、Unity Catalogに存在するテーブルやビューからビューを作成することをお勧めします。

# ビューの作成

ビューを作成するには、以下のSQLコマンドを実行します。括弧内のアイテムはオプションです。プレースホルダーの値を置き換えてください。

- `<catalog_name>`: カタログの名称。
- `<schema_name>`: スキーマの名称。
- `<view_name>`: ビューの名称。
- `<query>`: ビューを構成するために使用されるクエリー、カラム、テーブル、ビュー。

```sql:SQL
CREATE VIEW <catalog_name>.<schema_name>.<view_name> AS
SELECT <query>;
```

```py:Python
spark.sql("CREATE VIEW <catalog_name>.<schema_name>.<view_name> AS "
  "SELECT <query>")
```

```r:R
library(SparkR)

sql(paste("CREATE VIEW <catalog_name>.<schema_name>.<view_name> AS ",
  "SELECT <query>",
  sep = ""))
```

```scala:Scala
spark.sql("CREATE VIEW <catalog_name>.<schema_name>.<view_name> AS " +
  "SELECT <query>")
```

例えば、`sales_raw`テーブルのカラムから`sales_redacted`というビューを作成するには以下を実行します。

```sql:SQL
CREATE VIEW sales_metastore.sales.sales_redacted AS
SELECT
  user_id,
  email,
  country,
  product,
  total
FROM sales_metastore.sales.sales_raw;
```

```py:Python
spark.sql("CREATE VIEW sales_metastore.sales.sales_redacted AS "
  "SELECT "
  "  user_id, "
  "  email, "
  "  country, "
  "  product, "
  "  total "
  "FROM sales_metastore.sales.sales_raw")
```

```r:R
library(SparkR)

sql(paste("CREATE VIEW sales_metastore.sales.sales_redacted AS ",
  "SELECT ",
  "  user_id, ",
  "  email, ",
  "  country, ",
  "  product, ",
  "  total ",
  "FROM sales_metastore.sales.sales_raw",
  sep = ""))
```

```scala:Scala
spark.sql("CREATE VIEW sales_metastore.sales.sales_redacted AS " +
  "SELECT " +
  "  user_id, " +
  "  email, " +
  "  country, " +
  "  product, " +
  "  total " +
  "FROM sales_metastore.sales.sales_raw")
```

また、[Databricks Terraform provider](https://docs.databricks.com/dev-tools/terraform/index.html)と[databricks\_table](https://registry.terraform.io/providers/databricks/databricks/latest/docs/resources/table)を用いることでビューを作成することができます。[databricks\_views](https://registry.terraform.io/providers/databricks/databricks/latest/docs/data-sources/views)を用いることで、ビュー名称のリストを取得することができます。

# ダイナミックビューの作成

Unity Catalogでは、以下を含むきめ細かいアクセスコントロールを設定するためにダイナミックビューを活用することができます。

- 行列レベルでのセキュリティ。
- データマスキング。

:::note
**注意**
**シングルユーザー**[アクセスモード](https://qiita.com/taka_yayoi/items/15aede468bdca58ec6a3#unity-catalog%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%A2%E3%83%87%E3%83%AB)では、ダイナミックビューを用いたきめ細かいアクセスコントロールはサポートされていません。
:::

Unity Catalogでは、どのユーザーが行、列、ビュー内のレコードにアクセスできるのかを動的に制限するための以下の関数を導入しています。

- `current_user()`: 現在のユーザーのメールアドレスを返却します。
- `is_account_group_member()`: 現在のユーザーが特定のアカウントレベルグループのメンバーである場合には`TRUE`を返却します。Unity Catalogのデータに対するダイナミックビューでの使用を推奨します。
- `is_member()`: 現在のユーザーが特定のワークスペースレベルのグループのメンバーの場合には`TRUE`を返却します。この関数は、既存のHiveメタストアとの互換性のために提供されています。アカウントレベルグループのメンバーシップを評価しないので、Unity Catalogデータに対するビューでは使用しないでください。

以下のサンプルでは、Unity Catalogのダイナミックビューをどのように作成するのかを説明します。

## 列レベルのアクセス権

ダイナミックビューを用いることで、特定のユーザーやグループがアクセスできるカラムを限定することができます。以下の例では、`auditors`グループのメンバーのみが`sales_raw`テーブルのメールアドレスにアクセスすることができます。クエリーの解析中にApache Sparkは`CASE`文を文字列リテラル`REDACTED`か、メールアドレスカラムの実際の値で置き換えます。他のカラムは通常通り返却されます。この戦略はクエリー性能にネガティブな影響を及ぼしません。

```sql:SQL
-- Alias the field 'email' to itself (as 'email') to prevent the
-- permission logic from showing up directly in the column name results.
CREATE VIEW sales_redacted AS
SELECT
  user_id,
  CASE WHEN
    is_account_group_member('auditors') THEN email
    ELSE 'REDACTED'
  END AS email,
  country,
  product,
  total
FROM sales_raw
```

```py:Python
# Alias the field 'email' to itself (as 'email') to prevent the
# permission logic from showing up directly in the column name results.
spark.sql("CREATE VIEW sales_redacted AS "
  "SELECT "
  "  user_id, "
  "  CASE WHEN "
  "    is_account_group_member('auditors') THEN email "
  "  ELSE 'REDACTED' "
  "  END AS email, "
  "  country, "
  "  product, "
  "  total "
  "FROM sales_raw")
```

```r:R
library(SparkR)

# Alias the field 'email' to itself (as 'email') to prevent the
# permission logic from showing up directly in the column name results.
sql(paste("CREATE VIEW sales_redacted AS ",
  "SELECT ",
  "  user_id, ",
  "  CASE WHEN ",
  "    is_account_group_member('auditors') THEN email ",
  "  ELSE 'REDACTED' ",
  "  END AS email, ",
  "  country, ",
  "  product, ",
  "  total ",
  "FROM sales_raw",
  sep = ""))
```

```scala:Scala
// Alias the field 'email' to itself (as 'email') to prevent the
// permission logic from showing up directly in the column name results.
spark.sql("CREATE VIEW sales_redacted AS " +
  "SELECT " +
  "  user_id, " +
  "  CASE WHEN " +
  "    is_account_group_member('auditors') THEN email " +
  "  ELSE 'REDACTED' " +
  "  END AS email, " +
  "  country, " +
  "  product, " +
  "  total " +
  "FROM sales_raw")
```

## 行レベルのアクセス権

ダイナミックビューを用いることで、行やフィールドレベルまで権限を設定することができます。以下の例では、`managers`グループのメンバーのみが$1,000,000を超えるトランザクションを参照することができます。他のユーザーに対してはフィルタリングされた結果が返却されます。

```sql:SQL
 CREATE VIEW sales_redacted AS
 SELECT
   user_id,
   country,
   product,
   total
 FROM sales_raw
 WHERE
   CASE
     WHEN is_account_group_member('managers') THEN TRUE
     ELSE total <= 1000000
   END;
```

```py:Python
 spark.sql("CREATE VIEW sales_redacted AS "
   "SELECT "
   "  user_id, "
   "  country, "
   "  product, "
   "  total "
   "FROM sales_raw "
   "WHERE "
   "CASE "
   "  WHEN is_account_group_member('managers') THEN TRUE "
   "  ELSE total <= 1000000 "
   "END")
```

```r:R
 library(SparkR)

 sql(paste("CREATE VIEW sales_redacted AS ",
   "SELECT ",
   "  user_id, ",
   "  country, ",
   "  product, ",
   "  total ",
   "FROM sales_raw ",
   "WHERE ",
   "CASE ",
   "  WHEN is_account_group_member('managers') THEN TRUE ",
   "  ELSE total <= 1000000 ",
   "END",
   sep = ""))
```

```scala:Scala
 spark.sql("CREATE VIEW sales_redacted AS " +
   "SELECT " +
   "  user_id, " +
   "  country, " +
   "  product, " +
   "  total " +
   "FROM sales_raw " +
   "WHERE " +
   "CASE " +
   "  WHEN is_account_group_member('managers') THEN TRUE " +
   "  ELSE total <= 1000000 " +
   "END")
```

## データマスキング

Unity CatalogのビューではSpark SQLを使用するので、より複雑なSQLエクスプレッションや正規表現を用いて高度なデータマスキングを実装することができます。以下の例では、すべてのユーザーはメールのドメインを解析できますが、`auditors`グループのメンバーはメールアドレス全体を参照できます。

```sql:SQL
-- The regexp_extract function takes an email address such as
-- user.x.lastname@example.com and extracts 'example', allowing
-- analysts to query the domain name.

CREATE VIEW sales_redacted AS
SELECT
  user_id,
  region,
  CASE
    WHEN is_account_group_member('auditors') THEN email
    ELSE regexp_extract(email, '^.*@(.*)$', 1)
  END
  FROM sales_raw
```

```py:Python
# The regexp_extract function takes an email address such as
# user.x.lastname@example.com and extracts 'example', allowing
# analysts to query the domain name.

spark.sql("CREATE VIEW sales_redacted AS "
  "SELECT "
  "  user_id, "
  "  region, "
  "  CASE "
  "    WHEN is_account_group_member('auditors') THEN email "
  "    ELSE regexp_extract(email, '^.*@(.*)$', 1) "
  "  END "
  "  FROM sales_raw")
```

```r:R
library(SparkR)

# The regexp_extract function takes an email address such as
# user.x.lastname@example.com and extracts 'example', allowing
# analysts to query the domain name.

sql(paste("CREATE VIEW sales_redacted AS ",
  "SELECT ",
  "  user_id, ",
  "  region, ",
  "  CASE ",
  "    WHEN is_account_group_member('auditors') THEN email ",
  "    ELSE regexp_extract(email, '^.*@(.*)$', 1) ",
  "  END ",
  "  FROM sales_raw",
  sep = ""))
```

```scala:Scala
// The regexp_extract function takes an email address such as
// user.x.lastname@example.com and extracts 'example', allowing
// analysts to query the domain name.

spark.sql("CREATE VIEW sales_redacted AS " +
  "SELECT " +
  "  user_id, " +
  "  region, " +
  "  CASE " +
  "    WHEN is_account_group_member('auditors') THEN email " +
  "    ELSE regexp_extract(email, '^.*@(.*)$', 1) " +
  "  END " +
  "  FROM sales_raw")
```

# 次のステップ

- [Manage privileges in Unity Catalog](https://docs.databricks.com/data-governance/unity-catalog/manage-privileges/index.html)

### Databricks 無料トライアル

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