SnowflakeのSemantic Modelを使ってみた


## はじめに

Snowflakeでは自然言語を用いてSnowflakeデータに対する質問を行うことができるCortex Analystと呼ばれる機能を提供しています。ビジネスユーザーがデータに関する質問をする際に使用する語彙と、データベーススキーマで使用される語彙の間にはギャップがあります。このギャップを埋めるために、Snowflakeはセマンティックモデルを採用しています。本記事では、Snowflakeのセマンティックモデルの仕様について調べたことをまとめます。

## Cortex Analystとは？
ユーザーの問い合わせに対して構造化データの内容を元に回答を作成するアプリケーションの作成を手助けするものだそうです。構造化データを使ったRAGチャットアプリみたいなものを作るのに必要な機能を提供するような感じかと。
>Cortex Analyst is a fully-managed, LLM-powered Snowflake Cortex feature that helps you create applications capable of reliably answering business questions based on your structured data in Snowflake.With Cortex Analyst, business users can ask questions in natural language and receive direct answers without writing SQL. Available as a convenient REST API, Cortex Analyst can be seamlessly integrated into any application.

https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst

## セマンティックモデルとは？

セマンティックモデルは、データの意味を明確にし、ユーザーがデータをより直感的に理解できるようにするための手法です。この概念は、SnowflakeのCortex Analystだけでなく、他の多くのビジネスインテリジェンス（BI）ツールやデータ分析プラットフォームでも広く採用されています。セマンティックモデルを採用することでユーザがデータの構造や関係性を意識しなくて済みます。

## Snowflakeにおけるセマンティックモデルの基本概念

セマンティックモデルは、物理的なデータベーステーブルやビューの上に構築された論理テーブルの集合です。各論理テーブルは、次のような論理カラムの集合で構成されます。

- **Dimensions**: カテゴリカルな値（例：state, user_type）
- **Time Dimensions**: 時間に関する値（例：sale_date, created_at）
- **Measures**: 数値的な値（例：revenue, impressions）

論理カラムは、基礎となる物理カラムへの参照や、複数の物理カラムを含む式で構成されます。例えば、`SUM(売上個数)*単価`のような式を使用して売上額を計算することができます。

## Snowflakeにおけるセマンティックモデルの構造

セマンティックモデルはYAML形式で記述されます。ドキュメントにあった例を載せます。

```yaml
# セマンティックモデルの名前と説明
name: <name>
description: <string>

# セマンティックモデルは1つ以上の論理テーブルを含むことができます
tables:
  - name: <name>
    description: <string>
    base_table:
      database: <database>
      schema: <schema>
      table: <base table name>
    dimensions:
      - name: <name>
        synonyms: <array of strings>
        description: <string>
        expr: <SQL expression>
        data_type: <data type>
        unique: <boolean>
    time_dimensions:
      - name: <name>
        synonyms: <array of strings>
        description: <string>
        expr: <SQL expression>
        data_type: <data type>
        unique: <boolean>
    measures:
      - name: <name>
        synonyms: <array of strings>
        description: <string>
        expr: <SQL expression>
        data_type: <data type>
        default_aggregation: <aggregate function>
    filters:
      - name: <name>
        synonyms: <array of strings>
        description: <string>
        expr: <SQL expression>
```

## ジョインの定義

複数のテーブルをひとつのセマンティックモデルに記載してその関係性を定義できます。特にスター・スキーマ構造において、ファクトテーブルとディメンションテーブル間のデータクエリが容易になります。

```yaml
relationships:
  - name: <string>
    left_table: <string>
    right_table: <string>
    relationship_columns:
      - left_column: <string>
        right_column: <string>
    join_type: <join_type>
    relationship_type: <relationship_type>
```

## 実際に作ってみた

サンプルのデータセットを使って実際にセマンティックモデルを作成してみました。
セマンティックモデル作成の手助けをしてくれる[semantic model generator](https://github.com/Snowflake-Labs/semantic-model-generator)なるものがあったので使ってみました。

**データセットの作成**:
デモとして書店の売上情報のデータを作成。

```sql
create database demo;
create schema book_store;

CREATE TABLE demo.book_store.Books (
    BookID INT PRIMARY KEY,
    Title VARCHAR(255),
    AuthorID INT,
    Genre VARCHAR(100),
    Price DECIMAL(10, 2),
    PublishedDate DATE
);

INSERT INTO demo.book_store.Books (BookID, Title, AuthorID, Genre, Price, PublishedDate) VALUES
(1, '吾輩は猫である', 1, '小説', 1200.00, '1905-01-01'),
(2, 'こころ', 1, '小説', 1500.00, '1914-01-01'),
(3, '羅生門', 2, '短編小説', 800.00, '1915-01-01');

CREATE TABLE demo.book_store.Authors (
    AuthorID INT PRIMARY KEY,
    Name VARCHAR(255),
    BirthDate DATE,
    Nationality VARCHAR(100)
);

INSERT INTO demo.book_store.Authors (AuthorID, Name, BirthDate, Nationality) VALUES
(1, '夏目漱石', '1867-02-09', '日本'),
(2, '芥川龍之介', '1892-03-01', '日本');

CREATE TABLE demo.book_store.Sales (
    SaleID INT PRIMARY KEY,
    BookID INT,
    SaleDate DATE,
    Quantity INT,
    TotalAmount DECIMAL(10, 2)
);

INSERT INTO demo.book_store.Sales (SaleID, BookID, SaleDate, Quantity, TotalAmount) VALUES
(1, 1, '2024-01-15', 2, 2400.00),
(2, 2, '2024-01-16', 1, 1500.00),
(3, 3, '2024-01-17', 3, 2400.00),
(4, 1, '2024-01-18', 1, 1200.00),
(5, 2, '2024-01-19', 2, 3000.00),
(6, 3, '2024-01-20', 1, 800.00),
(7, 1, '2024-01-21', 3, 3600.00),
(8, 2, '2024-01-22', 1, 1500.00),
(9, 3, '2024-01-23', 2, 1600.00),
(10, 1, '2024-01-24', 2, 2400.00);

```
**semantic model generatorをデプロイ**
[レポジトリ](https://github.com/Snowflake-Labs/semantic-model-generator)のREADMEを元にデプロイ

**セマンティックモデルを作る**

create a new semantic modelを選ぶ
![semantic_generator001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947895/65c6c51f-c1e7-24c2-dbac-a798a3b33fab.png)

モデル名と対象のテーブルを選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947895/48a5f8df-3f38-2946-ec1d-bf9dd2b4396c.png)

モデルの枠ができるので、必要箇所を埋めてく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947895/8a392e8c-1084-e571-5599-233ef8e35587.png)

「Join Editor」ボタンからJoinの定義を設定することも可能
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947895/82205617-7857-1aa6-1a64-1a7303fa2b6b.png)

右側のウィンドウでセマンティックモデルを使ったチャットアプリがすぐ試せるので、Joinしないとわからないような質問を投げてみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3947895/3a0bff09-dd83-d6d0-6913-bde8d62f9eef.png)
いい感じに返ってきた:thumbsup:

<details><summary>最終的にできたセマンティックモデル</summary>

```yaml
name: my_model
tables:
  - name: AUTHORS
    description: Table to store information about authors, including their ID, name,
      birthdate, and nationality.__
    base_table:
      database: DEMO
      schema: BOOK_STORE
      table: AUTHORS
    primary_key:
      columns:
        - AUTHORID
    dimensions:
      - name: NAME
        synonyms:
          - '  '
        description: List of renowned Japanese authors.__
        expr: NAME
        data_type: TEXT
        sample_values:
          - 夏目漱石
          - 芥川龍之介
      - name: NATIONALITY
        synonyms:
          - '  '
        description: Nationality of the author.__
        expr: NATIONALITY
        data_type: TEXT
        sample_values:
          - 日本
      - name: AUTHORID
        synonyms:
          - '  '
        description: Unique identifier for each author.__
        expr: AUTHORID
        data_type: NUMBER
        sample_values:
          - '1'
          - '2'
    time_dimensions:
      - name: BIRTHDATE
        synonyms:
          - '  '
        description: Date of birth for authors.__
        expr: BIRTHDATE
        data_type: DATE
        sample_values:
          - '1867-02-09'
          - '1892-03-01'
  - name: BOOKS
    description: The BOOKS table stores information about published books, including
      their unique identifier, title, author, genre, price, and publication date.__
    base_table:
      database: DEMO
      schema: BOOK_STORE
      table: BOOKS
    primary_key:
      columns:
        - BOOKID
    dimensions:
      - name: TITLE
        synonyms:
          - '  '
        description: Japanese Book Titles__
        expr: TITLE
        data_type: TEXT
        sample_values:
          - 吾輩は猫である
          - こころ
          - 羅生門
      - name: GENRE
        synonyms:
          - '  '
        description: Genre of the book, such as novel or short story.__
        expr: GENRE
        data_type: TEXT
        sample_values:
          - 小説
          - 短編小説
      - name: BOOKID
        synonyms:
          - '  '
        description: Unique identifier for each book in the database.__
        expr: BOOKID
        data_type: NUMBER
        sample_values:
          - '1'
          - '2'
          - '3'
      - name: AUTHORID
        synonyms:
          - '  '
        description: Unique identifier for each author.__
        expr: AUTHORID
        data_type: NUMBER
        sample_values:
          - '1'
          - '2'
    time_dimensions:
      - name: PUBLISHEDDATE
        synonyms:
          - '  '
        description: Date of publication for books.__
        expr: PUBLISHEDDATE
        data_type: DATE
        sample_values:
          - '1905-01-01'
          - '1914-01-01'
          - '1915-01-01'
    measures:
      - name: PRICE
        synonyms:
          - '  '
        description: The price of the book.__
        expr: PRICE
        data_type: NUMBER
        sample_values:
          - '1200.00'
          - '1500.00'
          - '800.00'
  - name: SALES
    description: This table represents sales transactions of books, storing information
      about each sale, including the sale ID, book ID, sale date, quantity sold, and
      total amount.__
    base_table:
      database: DEMO
      schema: BOOK_STORE
      table: SALES
    primary_key:
      columns:
        - SALEID
    dimensions:
      - name: BOOKID
        synonyms:
          - '  '
        description: Unique identifier for each book in the sales database.__
        expr: BOOKID
        data_type: NUMBER
        sample_values:
          - '1'
          - '2'
          - '3'
    time_dimensions:
      - name: SALEDATE
        synonyms:
          - '  '
        description: Date of sale.__
        expr: SALEDATE
        data_type: DATE
        sample_values:
          - '2023-01-15'
          - '2023-01-16'
          - '2023-01-17'
    measures:
      - name: SALEID
        synonyms:
          - '  '
        description: Unique identifier for each sales record.__
        expr: SALEID
        data_type: NUMBER
        sample_values:
          - '1'
          - '2'
          - '3'
      - name: QUANTITY
        synonyms:
          - '  '
        description: Quantity of items sold.__
        expr: QUANTITY
        data_type: NUMBER
        sample_values:
          - '2'
          - '1'
          - '3'
      - name: TOTALAMOUNT
        synonyms:
          - '  '
        description: Total Sales Amount__
        expr: TOTALAMOUNT
        data_type: NUMBER
        sample_values:
          - '2400.00'
          - '1500.00'
          - '1200.00'
relationships:
  - name: 売上と書籍
    left_table: SALES
    right_table: BOOKS
    join_type: inner
    relationship_type: one_to_one
    relationship_columns:
      - left_column: BOOKID
        right_column: BOOKID
  - name: 書籍と著者
    left_table: BOOKS
    right_table: AUTHORS
    join_type: inner
    relationship_type: one_to_one
    relationship_columns:
      - left_column: AUTHORID
        right_column: AUTHORID
```

</details>

## セマンティックモデルの作成のヒント

ドキュメントの内容を転記。

1. **ビジネスドメインやトピックごとにYAMLファイルを整理する**: セマンティックモデルを特定のビジネスドメインやトピックに合わせて構造化し、スコープを絞ることで、より効果的なデータ取得が可能になります。

2. **エンドユーザーの視点から考える**: ユーザーが質問しそうな主要な質問を特定し、それに答えるために必要なテーブルやカラムのみを含めます。

3. **複雑な計算をキャプチャする**: ビジネス固有のクエリを式(expr)に組み込みます。

4. **広いテーブルを使用する**: メトリックごとにカラムを持つテーブルを使用することで、各メトリックに関するセマンティック情報を提供します。

5. **自動生成された説明をレビューする**: セマンティックモデルジェネレーターを使用する場合、自動生成された説明を必ず確認し、必要に応じて修正します。

6. **シンプルに始めて徐々に拡張する**: 小規模なテーブルとカラムから始め、徐々にセマンティックモデルYAMLを拡張していきます。

7. **検証済みクエリを含める**: 検証済みクエリリポジトリ（VQR）を含めることで、結果の精度と信頼性を向上させます。




## まとめ

AIを用いてデータを活用する際はデータソースの質が回答精度に大きく影響します。セマンティックモデルを適切に構築することで、データの理解とクエリの精度を大幅に向上させることが期待できます。この記事が何かの参考になれば幸いです。

## 参考URL
https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst/semantic-model-spec