Snowflake の外部データ取り込み方法まとめ （AWS・GCP・Azure）

みなさん、こんにちは！

AWS/GCP/Azure上にあるデータを Snowflake に取り込む方法についてまとめました。
それぞれの環境からデータを取り込むための具体的な方法と手順について、画像付きで詳しくご紹介します。

※本記事の操作はすべて`ACCOUNTADMIN`ロールで行っています。

## 準備
テスト用に以下のサンプルCSVファイル`employees.csv`を使用します。
```
1001,John,Doe,john.doe@example.com,Engineering,75000
1002,Jane,Smith,jane.smith@example.com,Marketing,65000
1003,Emily,Jones,emily.jones@example.com,Sales,70000
1004,Michael,Brown,michael.brown@example.com,HR,60000
1005,Jessica,Johnson,jessica.johnson@example.com,Finance,80000
```

データロードの前にテーブルを用意しておきます。
ワークシートで以下のSQLを実行し、テーブル`employees`を作成します。
```ruby:qiita.rb
CREATE OR REPLACE TABLE employees (
  employee_id STRING,
  first_name STRING,
  last_name STRING,
  email STRING,
  department STRING,
  salary INTEGER
);
```

また、データロードの前にデータ構造に合うファイル形式を作成しておく必要があります。
以下のSQLを実行し、ファイル形式`csv_format`を作成します。
```ruby:qiita.rb
CREATE OR REPLACE FILE FORMAT csv_format
  TYPE = 'CSV'
  FIELD_DELIMITER = ','
  SKIP_HEADER = 0;
```

## AWS
### S3バケット作成＆アップロード
データ取り込み元となるS3バケットを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/563497b9-3bc9-4301-803d-99235a5fa5aa.png)

バケット名は`snowflake-load-test-20250313`とし、その他の設定はデフォルトのままとしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/87041dd1-50cb-44e5-9557-4452f6312cb3.png)

`samples`フォルダを作成し、そこに`employees.csv`をアップロードします。

### 認証設定
S3アクセス用の認証設定を行います。

IAMコンソールを開き、「ポリシー」から以下のJSON形式ポリシーをもつポリシーを作成します。
`<bucket>`は対象のバケット、`<prefix>`は対象のフォルダで置き換えてください。
```ruby:qiita.rb
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
              "s3:PutObject",
              "s3:GetObject",
              "s3:GetObjectVersion",
              "s3:DeleteObject",
              "s3:DeleteObjectVersion"
            ],
            "Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
        },
        {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::<bucket>",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "<prefix>/*"
                    ]
                }
            }
        }
    ]
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/65afa0ba-8028-4d0a-ab71-19711ea2a684.png)

適当なポリシー名を指定し、ポリシーを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/b0b705b0-948c-47ac-9dfb-5716ab9f354f.png)

次にロールを作成します。

ロールの作成画面で「信頼されたエンティティタイプ」として「AWSアカウント」を選択します。「AWSアカウント」は「このアカウント」のままで、「オプション」は「外部IDを要求する」にチェックを入れておき、外部IDには「0000」と入力しておきます（AWSアカウント、外部IDは後で修正するため仮置きとして設定しています）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/6b5f69ce-736c-455e-b4e8-0d99b782eeaa.png)

「次へ」をクリックし、許可ポリシーで先ほど作成したポリシーを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/d6d1d6d5-4701-413b-ba88-bb9841c07ed5.png)

「次へ」をクリックし、適当なロール名を指定してロールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/16983c0a-c867-4866-be90-e193d60e4730.png)

次に、Snowflake 側で認証に必要となるクラウドストレージ統合を作成します。

Snowflake にログインし、以下のSQLを実行します。
`STORAGE_AWS_ROLE_ARN`には先ほど作成したロールのARNを指定し、`STORAGE_ALLOWED_LOCATIONS`にはロード対象ファイルが存在するディレクトリのパスを指定します。
```ruby:qiita.rb
CREATE STORAGE INTEGRATION s3_int
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'S3'
  STORAGE_AWS_ROLE_ARN = '<iam_role>'
  ENABLED = TRUE
  STORAGE_ALLOWED_LOCATIONS = ('s3://<bucket>/<path>/');
```
以下のSQLを実行し、作成したクラウドストレージ統合の設定値を確認します。
```ruby:qiita.rb
DESC INTEGRATION s3_int;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/70e6669b-0ef6-459b-a0f2-32bdfe5d5153.png)

実行結果から`STORAGE_AWS_IAM_USER_ARN`と`STORAGE_AWS_EXTERNAL_ID`の値を控えておきます。

AWSのIAMコンソールに戻り、先ほど作成したロールの「信頼関係」→「信頼ポリシーを編集」からJSON形式のポリシーを修正します。

`<snowflake_user_arn>`に`STORAGE_AWS_IAM_USER_ARN`の値を、`<snowflake_external_id>`に`STORAGE_AWS_EXTERNAL_ID`の値を設定します。

```ruby:qiita.rb
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<snowflake_user_arn>:"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "<snowflake_external_id>"
                }
            }
        }
    ]
}
```
設定したら「ポリシーを更新」をクリックします。以上でAWS側の設定は完了です。

### 外部ステージ作成
Snowflake 外部からデータをロードするにあたり、ロード対象データを示す外部ステージを作成する必要があります。

「データ」→「データベース」からステージ作成対象のスキーマに移動し、「作成」ボタンから「ステージ」→「Amazon S3」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/e3316ed6-9c46-40ef-8942-99f9526f345f.png)

ステージ作成画面が開くため、以下の項目を指定します。

* ステージ名：適当なステージ名
* URL：ロード対象データがあるS3ディレクトリのURL
* 認証：ストレージ統合（上記で作成したストレージ統合を選択）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/6b7a2ffa-aaf8-43df-8ab1-f808fed6b128.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/7235fb43-8942-4b38-945a-b6ae12d5f3f9.png)

設定が完了したら「作成」ボタンをクリックします。

作成に成功すると、以下のように`employees.csv`が認識され表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/c89f7147-126a-4521-9a92-1a9765c348b2.png)

### データロード
以下のSQLを実行し、データをテーブルにロードします。
```ruby:qiita.rb
COPY INTO employees FROM @ext_stage_s3 FILE_FORMAT=csv_format;
```
上記の実行後、`employees`テーブルにデータが入っていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/33737faf-c398-40d8-ab3b-32ba7c03ef1f.png)

## GCP
### GCSバケット作成＆アップロード
GCSバケットとディレクトリを作成し、ファイルをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/0aafffd8-f381-4040-96f7-0e666cfb7d17.png)

### 認証設定
Snowflake で以下のSQLを実行します。
```ruby:qiita.rb
CREATE STORAGE INTEGRATION gcs_int
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'GCS'
  ENABLED = TRUE
  STORAGE_ALLOWED_LOCATIONS = ('gcs://<bucket>/<path>/');
```

上記実行後、以下のSQLで設定値を確認します。
```ruby:qiita.rb
DESC INTEGRATION gcs_int;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/b355d76d-70cc-4507-90af-e31a4e685b06.png)

実行結果から`STORAGE_GCP_SERVICE_ACCOUNT`の値を控えておきます。

GCPのIAMコンソールに移り、「アクセスを許可」をクリックします。設定画面で以下の項目を設定します。

* 新しいプリンシパル：`STORAGE_GCP_SERVICE_ACCOUNT`の値
* ロール：Storage オブジェクト管理者

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/a6d66ccf-dab9-40e4-86eb-ab53237e0ff7.png)

設定が完了したら「保存」をクリックします。

### 外部ステージ作成
Snowflake に戻り、ステージ作成を行います。
ステージ作成画面で以下の項目を指定します。

* ステージ名：適当なステージ名
* URL：ロード対象データがあるGCSディレクトリのURL
* 認証：ストレージ統合（上記で作成したストレージ統合を選択）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/b05582f8-72f8-469c-99f4-97d01d55699d.png)
設定が完了したら「作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/4f379019-180c-4b20-9c06-fc4f74ee505c.png)
外部ステージが作成されました。

### データロード
以下のSQLを実行し、データをテーブルにロードします。
```ruby:qiita.rb
COPY INTO employees FROM @ext_stage_gcs FILE_FORMAT=csv_format;
```
上記の実行後、`employees`テーブルにデータが入っていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/2391b856-6d54-45ff-af87-1ecb79751fa5.png)

## Azure
### ストレージ作成＆アップロード
ストレージアカウントのコンソールを開き、ストレージアカウントを作成します。基本設定以外はデフォルトのままとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/33416b18-b843-46f1-b81c-55058b1712cb.png)
ストレージアカウントの作成が完了したら「コンテナ」でファイルアップロード対象となるコンテナ（`samples`コンテナ）を新規作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/be87211d-0e21-441a-b303-6eecbdec3a7e.png)
コンテナにファイルをアップロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/e7083507-3d48-455d-8bf5-8e0786d73fa2.png)
アップロードしたファイルのメニューから「プロパティ」をクリックして開くと、URLを取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/60fd92ca-0bfd-4057-a46d-54621a281ae8.png)
URLは以下のような形式となっています。
```
https://<storage_account>.blob.core.windows.net/samples/employees.csv
```

### 認証設定
Snowflake で以下のSQLを実行します。

```ruby:qiita.rb
CREATE STORAGE INTEGRATION azure_int
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'AZURE'
  ENABLED = TRUE
  AZURE_TENANT_ID = '<tenant_id>'
  STORAGE_ALLOWED_LOCATIONS = ('azure://<storage_account>.blob.core.windows.net/<path>/');
```

※`<tenant_id>`は Azure ポータルから「Microsoft Entra ID」を開き、概要の「テナントID」で確認可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/4fe34310-4656-4bce-b385-13b04c40f786.png)

以下のSQLで設定値を確認します。
```ruby:qiita.rb
DESC INTEGRATION azure_int;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/b9451b08-fcf9-49ca-9067-ca66c3888fa2.png)
上記の`AZURE_CONSENT_URL`のリンクをクリックします。

アクセス許可画面にリダイレクトされるので、「承諾」をクリックします。
これにより、Snowflake がストレージアカウントにアクセスするためのアプリケーションが作成されます。

次に、アプリケーションにストレージアカウントに対するロールを付与します。

対象ストレージアカウントの「アクセス制御（IAM）」を開き、「追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/201d3c57-b143-474d-a4c1-c704b3df6690.png)

ロールの割り当ての追加で、「ストレージ BLOB データ共同作成者」をクリックして次へ進みます。
※ロードのみの場合は「ストレージ BLOB データ閲覧者」でも問題ありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/f8bd3c4f-bc2e-4552-a072-5b6f717f5dcb.png)

「メンバーを選択する」をクリックし、`AZURE_MULTI_TENANT_APP_NAME`に記載のアプリケーションを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/876b47df-b0ed-46cd-a6c4-49df1f2b8ca9.png)
「レビューと割り当て」をクリックして完了です。

### 外部ステージ作成
Snowflake に戻り、ステージ作成を行います。
ステージ作成画面で以下の項目を指定します。

* ステージ名：適当なステージ名
* URL：ロード対象データがあるストレージコンテナのURL
* 認証：ストレージ統合（上記で作成したストレージ統合を選択）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/609fde8c-448f-4c3e-b6db-02172e4d8061.png)
設定が完了したら「作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/1ca1b650-d686-48b5-bb5e-f59ef07ecb2d.png)

外部ステージが作成されました。

### データロード
以下のSQLを実行し、データをテーブルにロードします。
```ruby:qiita.rb
COPY INTO employees FROM @ext_stage_azure FILE_FORMAT=csv_format;
```

上記の実行後、`employees`テーブルにデータが入っていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956386/e1c4ff99-e530-4aac-90a6-ff01f2e5208a.png)

## 補足
COPY コマンドによるデータロードではロードの状況が記録されており、同じファイルが立て続けにロードされてもデータが重複して挿入されることはありません。

## さいごに
Snowflake の外部データ取り込み方法についてご紹介しました。

ストレージ統合作成 → 認証設定 → 外部ステージ作成 →データロード という流れは共通しているのですが、クラウドプロバイダごとに設定方法や認証方法に若干違いがあり、少しややこしく感じた部分もありました。本記事が外部データ取り込みを行う際の参考になれば幸いです。

## 参考
* [データのロードの概要 | Snowflake Documentation](https://docs.snowflake.com/ja/user-guide/data-load-overview)
* [Amazon S3からSnowflakeへのバルクロードを設定からやってみた | DevelopersIO](https://dev.classmethod.jp/articles/snowflake-bulk-loading-from-amazon-s3/)