Snowflake のタスクで毎朝の取り込みを自動にする|0件の日とファイルを飛ばした日に気づく記録テーブルとアラート

毎朝の CSV の COPY INTO をタスクで自動にするときに、一緒に作る物を SQL つきで書きます。記録テーブル、取り込みに使うストアドプロシージャ、タスク、アラートの4つです。ストアドプロシージャは1回ごとに結果を1行残します。アラートはその日の行を見て、問題がある日だけメールで知らせます。試用の Snowflake で、新しいファイルが無いとき、形式の違うファイルが混ざったとき、SQL が失敗したときの記録の残り方を確かめました。本番に入れる SQL は、後半に1つにまとめています。

この記事をシェア:
目次 クリックで開く

毎朝届く注文の CSV を、手で COPY INTO して Snowflake に取り込んでいるとします。この作業をタスクで自動にすると、困るのは取り込めなかった日です。タスクの実行履歴が成功のままでも、新しいデータが1件も入っていない日があり、それに気づかないまま朝の集計が進むおそれがあります。

この記事では、取り込みを自動にするときに作る4つを、SQL つきで書きます。記録テーブル、取り込みに使うストアドプロシージャ(SQL で書いた処理の手順を保存したもの)、タスク、アラートです。1回ごとの結果が記録テーブルに1行ずつ残り、アラートがそれを毎朝見て、問題がある日だけメールで知らせます。

この記事の SQL と画面は、どれも試用の Snowflake で当社が動かして確かめたもので、注文の CSV は架空のデータです。

タスクのほかに、記録テーブル・ストアドプロシージャ・アラートを作る

タスクは、決めた時刻に SQL を実行する Snowflake の仕組みです。Snowflake の導入を支援している当社は、この一式を組む前に、タスクに COPY INTO を1文だけ書いて試しました。そのとき、困ったことが2つありました。

1つ目は、新しいファイルが無い日も、タスクの実行履歴が SUCCEEDED になったことです。どのファイルを取り込んだかは、ロードのメタデータとしてテーブルごとに記録され、取り込み済みのファイルは二度取り込まれません。何も入らなくても、タスクは成功になります。

2つ目は、形式の違うファイルが1つ混ざると、正しいファイルの120行も入らなかったことです。ON_ERROR を指定しないと、既定の ABORT_STATEMENT になり、COPY INTO の文全体が失敗します。この回は、取り込みの履歴にも行が残りませんでした。

どちらの場合も、タスクの実行履歴を見ただけでは、何行入ったかは分かりません。そこで、タスクのほかに3つを一緒に作ります。

毎朝の取り込みの一式。タスク LOAD_ORDERS は毎朝6時にストアドプロシージャを呼ぶ。ストアドプロシージャ LOAD_ORDERS_DAILY() は COPY INTO を ON_ERROR = SKIP_FILE で実行し、取り込みの履歴を数えて記録テーブルに1行書く。記録テーブル LOAD_LOG は1回の実行ごとに1行で、結果・取り込んだファイル数と行数・飛ばしたファイル数・エラー文を持つ。アラート CHECK_DAILY_LOAD_MAIL は毎朝6時30分に記録テーブルのその日の行を見て、OK でなければ LOAD_ALERTS に書き、メールで知らせる。担当は、メールが届いた日だけ LOAD_LOG のその日の行と LOAD_ALERTS を見る毎朝の取り込みの一式。タスク LOAD_ORDERS は毎朝6時にストアドプロシージャを呼ぶ。ストアドプロシージャ LOAD_ORDERS_DAILY() は COPY INTO を ON_ERROR = SKIP_FILE で実行し、取り込みの履歴を数えて記録テーブルに1行書く。記録テーブル LOAD_LOG は1回の実行ごとに1行で、結果・取り込んだファイル数と行数・飛ばしたファイル数・エラー文を持つ。アラート CHECK_DAILY_LOAD_MAIL は毎朝6時30分に記録テーブルのその日の行を見て、OK でなければ LOAD_ALERTS に書き、メールで知らせる。担当は、メールが届いた日だけ LOAD_LOG のその日の行と LOAD_ALERTS を見る
一式の全体。それぞれの箱の下に、これが要る理由を書いた

ファイルを置く場所は、LANDING という内部ステージです。内部ステージは、Snowflake の中に用意するファイルの置き場所です。S3 などのクラウドのストレージを、外部ステージとして使う形もあります。毎朝ステージにファイルを置く仕組みは、この一式とは別に用意してください。

本文に載せる SQL は、どれも試すときの文です。試すときは、検証用のデータベース test_ingest のまま使います。本番に入れるときは、後半の「本番に入れる SQL」の節にまとめた一式を使い、その中の <…> を自社の名前にします。

試す土台は、次の SQL で作ります。検証用のデータベースとスキーマ、ステージ、取り込み先の ORDERS テーブルを作り、19日分の注文のファイルを書き出します。ファイルの中身は、行を作る関数 GENERATOR の結果です。

USE ROLE ACCOUNTADMIN;
USE WAREHOUSE COMPUTE_WH;
CREATE DATABASE IF NOT EXISTS test_ingest;
CREATE SCHEMA IF NOT EXISTS test_ingest.raw;
USE SCHEMA test_ingest.raw;
CREATE OR REPLACE STAGE LANDING;
COPY INTO @LANDING/orders/orders_2026-10-19.csv
  FROM (SELECT SEQ4() + 1 AS ORDER_ID, '2026-10-19'::DATE AS ORDER_DATE,
               DECODE(MOD(SEQ4(), 2), 0, '渋谷店', '新宿店') AS STORE,
               1000 + MOD((SEQ4() + 1) * 7919, 9000) AS AMOUNT
        FROM TABLE(GENERATOR(ROWCOUNT => 100)))
  FILE_FORMAT = (TYPE = CSV COMPRESSION = NONE) HEADER = TRUE SINGLE = TRUE OVERWRITE = TRUE;
CREATE OR REPLACE TABLE ORDERS (ORDER_ID NUMBER, ORDER_DATE DATE, STORE STRING, AMOUNT NUMBER);
LIST @LANDING;

20日分と21日分のファイルも、日付・行数・注文番号の始まりを変えた同じ形の文で書き出しました。形式の違うファイルの書き出し方は、試す節で載せます。ORDERS の列は、自社の取り込み先のテーブルに合わせます。

記録テーブルには、1回の実行ごとに必ず1行を残す

記録テーブルは、ストアドプロシージャを1回呼ぶごとに1行を書くテーブルです。新しいファイルが無い回も、SQL が失敗した回も、1行を残します。

USE SCHEMA test_ingest.raw;
CREATE OR REPLACE TABLE LOAD_LOG (
  RUN_AT TIMESTAMP_LTZ,
  RESULT STRING,
  FILES_LOADED NUMBER,
  ROWS_LOADED NUMBER,
  FILES_SKIPPED NUMBER,
  MESSAGE STRING
);

RUN_AT は、ストアドプロシージャを始めた時刻です。RESULT は、その回の結果です。FILES_LOADED と ROWS_LOADED は、取り込んだファイルの数とデータの行数です。FILES_SKIPPED は、誤りがあって飛ばしたファイルの数です。

MESSAGE には、飛ばしたファイルのエラー文か、失敗した SQL のエラー文が入ります。飛ばしたファイルが2つ以上ある回は、そのうち1つのエラー文です。

RESULT に入る値は、OK、NO_FILE、PARTIAL、ERROR の4つです。値の名前は、当社が決めたものです。OK は、ファイルを1つ以上取り込み、飛ばしたファイルが無い回です。NO_FILE は、取り込んだファイルが0の回です。

PARTIAL は、飛ばしたファイルが1つ以上ある回です。ほかに新しいファイルがあれば、そちらは取り込みます。ERROR は、ストアドプロシージャの中の SQL が失敗した回です。

その日の行が1つも無いのは、ストアドプロシージャが呼ばれなかった日か、アラートが動く6時30分までに終わっていない日です。このとき、アラートは結果を NO_RUN と書きます。

タスクの実行履歴は過去7日分、取り込みの履歴は過去14日分までしか、INFORMATION_SCHEMA の関数では見られません。SNOWFLAKE.ACCOUNT_USAGE のビューなら365日分ありますが、反映が遅れます。記録テーブルは自分で作ったテーブルなので、削除しないかぎり残ります。

RESULT の値ごとに、担当が何を見て、どう直すかを先に決めておきます。次の表は、OK から ERROR までの結果を実際に出したうえで決めたものです。

結果ごとの対処表。OK は ROWS_LOADED がふだんの件数に近いかを見る。NO_FILE は、ステージの一覧にその日のファイルがあるかを見て、送り手に置いてもらう。21日分のファイルを置いた回は OK だった。PARTIAL は、取り込みの履歴の Load failed の行のファイル名とエラー文を見て、送り手にファイル全体を確かめてもらい、形式の違うファイルを REMOVE で外す。直したファイルを置いて呼ぶ手順は試していない。ERROR は、記録テーブルの MESSAGE とタスクの実行履歴の ERROR_MESSAGE を見て、原因を直す。その日の行が無いときはアラートが NO_RUN と書き、SHOW TASKS の state を見て、suspended なら RESUME する。この行は試していない結果ごとの対処表。OK は ROWS_LOADED がふだんの件数に近いかを見る。NO_FILE は、ステージの一覧にその日のファイルがあるかを見て、送り手に置いてもらう。21日分のファイルを置いた回は OK だった。PARTIAL は、取り込みの履歴の Load failed の行のファイル名とエラー文を見て、送り手にファイル全体を確かめてもらい、形式の違うファイルを REMOVE で外す。直したファイルを置いて呼ぶ手順は試していない。ERROR は、記録テーブルの MESSAGE とタスクの実行履歴の ERROR_MESSAGE を見て、原因を直す。その日の行が無いときはアラートが NO_RUN と書き、SHOW TASKS の state を見て、suspended なら RESUME する。この行は試していない
結果ごとの対処表。左の3列で何が起きたかを見て、4列目で直し、5列目で直したあとの取り込みを見る

ストアドプロシージャで COPY INTO を実行し、結果を記録テーブルに書く

ストアドプロシージャには、LOAD_ORDERS_DAILY という名前を付けました。中では、まず始めた時刻を控えて、COPY INTO を実行します。次に、取り込みの履歴を始めた時刻から見て、取り込んだファイルと飛ばしたファイルを数えます。最後に結果を決め、記録テーブルに1行書いて、結果を文字列で返します。

USE SCHEMA test_ingest.raw;
CREATE OR REPLACE PROCEDURE LOAD_ORDERS_DAILY()
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
  started_at TIMESTAMP_LTZ;
  files_loaded NUMBER DEFAULT 0;
  rows_loaded NUMBER DEFAULT 0;
  files_skipped NUMBER DEFAULT 0;
  first_error STRING;
  result STRING;
BEGIN
  started_at := CURRENT_TIMESTAMP();
  COPY INTO ORDERS FROM @LANDING/orders/
    FILE_FORMAT = (TYPE = CSV SKIP_HEADER = 1)
    ON_ERROR = SKIP_FILE;
  SELECT COALESCE(COUNT_IF(STATUS = 'Loaded'), 0),
         COALESCE(SUM(IFF(STATUS = 'Loaded', ROW_COUNT, 0)), 0),
         COALESCE(COUNT_IF(STATUS <> 'Loaded'), 0),
         MAX(FIRST_ERROR_MESSAGE)
    INTO :files_loaded, :rows_loaded, :files_skipped, :first_error
    FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(TABLE_NAME => 'ORDERS', START_TIME => :started_at));
  result := CASE WHEN files_skipped > 0 THEN 'PARTIAL'
                 WHEN files_loaded = 0 THEN 'NO_FILE'
                 ELSE 'OK' END;
  INSERT INTO LOAD_LOG VALUES (:started_at, :result, :files_loaded, :rows_loaded, :files_skipped, :first_error);
  RETURN result || ' files=' || files_loaded || ' rows=' || rows_loaded || ' skipped=' || files_skipped;
EXCEPTION
  WHEN OTHER THEN
    INSERT INTO LOAD_LOG VALUES (CURRENT_TIMESTAMP(), 'ERROR', 0, 0, 0, :SQLERRM);
    RAISE;
END;
$$;
CALL LOAD_ORDERS_DAILY();

取り込みの履歴は、COPY_HISTORY というテーブル関数で見ます。テーブル関数は、FROM 句に書くと結果をテーブルのように扱える関数です。テーブルの名前と数え始める時刻を指定すると、その時刻からあとに読んだファイルが1行ずつ出ます。

STATUS が Loaded のファイルは FILES_LOADED に、それ以外は FILES_SKIPPED に数えます。新しいファイルが無い回は、取り込みの履歴に何も出ないので、どちらも0になります。取り込みの履歴は COPY INTO の直後に見ていて、今回はどの回も数が合っていました。

見出しの行しか無いファイルが届いた日は、データが0行でも OK になるはずです。これも NO_FILE として扱うなら、CASE 式の2つ目の条件を書き換えます。書き換えたあとの条件は WHEN files_loaded = 0 OR rows_loaded = 0 THEN 'NO_FILE' です。見出しの行だけのファイルも、この書き換えも、今回は試していません。

SQL が失敗したときは、EXCEPTION の中で ERROR の行を書きます。そのあと、RAISE で同じ例外をもう一度発生させます。そうすると、ストアドプロシージャを呼んだタスクも失敗になり、タスクの実行履歴に FAILED が残ります。

COPY INTO には、ON_ERROR = SKIP_FILE を指定しました。誤りのあるファイルを、ファイルごと飛ばす指定です。ほかのファイルは取り込みます。CONTINUE は、誤りのある行だけを飛ばし、同じファイルの正しい行は取り込みます。

SKIP_FILE にしたのは、当社の判断です。CONTINUE にすると、1つのファイルのうち一部の行だけが入り、どの行が抜けたかをあとで探すことになります。ファイルごと飛ばしておけば、直したファイルを置き直すだけで済みます。なお、SKIP_FILE は誤りが無くてもファイル全体をいったん読み込むため、ほかの値より遅いとドキュメントにあります。

CONTINUE に変えるなら、数え方も見直します。この SQL は STATUS が Loaded 以外を飛ばした数に入れるので、一部だけ入ったファイルも飛ばしたファイルに数えます。

上の SQL は、一度直したあとのものです。最初に書いた版では、新しいファイルが無い回が OK と記録されました。次の画面の1行目です。

Snowsight の画面。直す前のストアドプロシージャでの記録テーブルの2行。1行目は RUN_AT_JST が 2026-10-21 00:15:23、RESULT が OK、FILES_LOADED が null、ROWS_LOADED が 0、FILES_SKIPPED が null、ORDERS_ROWS が 100。2行目は 00:14:05、OK、1、100、0、100。1行目が枠で囲んである
直す前のストアドプロシージャ。新しいファイルが無い回なのに、RESULT が OK になった

この回は、取り込みの履歴に行が1つもありませんでした。そのとき COUNT_IF の結果が、0ではなく NULL になりました。NULL は0と比べても真にならないので、CASE 式は最後の ELSE に進み、OK を返しました。

直したのは、COUNT_IF を COALESCE で包み、NULL のときに0にすることです。ストアドプロシージャを作ったら、新しいファイルが無い状態で一度呼び、記録テーブルに NO_FILE と書かれるかを確かめてください。

新しいファイルが無い回と、形式の違うファイルが混ざった回を試す

直したストアドプロシージャで、土台を作り直して最初から試しました。2026年10月21日の0時台に、19日分、20日分、21日分の注文のファイルを続けて置いています。

19日分のファイルの100行を取り込んだ回は、OK です。次に、ファイルを足さずにもう一度呼ぶと、NO_FILE と記録されました。

続けて、20日分のファイルを2つ置きました。1つは、いつもの形式の orders_2026-10-20.csv で、120行あります。もう1つは orders_2026-10-20_shopB.csv で、1行だけです。2つ目は、別の店から届いたことにして作ったファイルで、注文番号に英字を、金額に桁区切りのカンマを入れてあります。次の文で書き出しました。

USE SCHEMA test_ingest.raw;
COPY INTO @LANDING/orders/orders_2026-10-20_shopB.csv
  FROM (SELECT 'A-0001' AS ORDER_ID, '2026-10-20' AS ORDER_DATE, '池袋店' AS STORE, '3,200' AS AMOUNT)
  FILE_FORMAT = (TYPE = CSV COMPRESSION = NONE) HEADER = TRUE SINGLE = TRUE OVERWRITE = TRUE;

ストアドプロシージャを呼ぶと、結果は PARTIAL でした。取り込んだのは1つのファイルの120行で、飛ばしたファイルは1つです。取り込み先のテーブルの件数は220になりました。

形式の違うファイルを置いたまま、もう一度呼ぶと、また PARTIAL になりました。外すか直すまで、呼ぶたびに PARTIAL が続きます。

Snowsight の画面。記録テーブルの4行を新しい順に並べたもの。00:21:48 PARTIAL・0・0・1、00:20:26 PARTIAL・1・120・1、00:19:04 NO_FILE・0・0・0、00:17:46 OK・1・100・0。ORDERS_ROWS はどの行も 220。上の2行が枠で囲んである
記録テーブル。上の2行が PARTIAL。置いたまま呼んだ回は、取り込んだファイルが0

写真の ORDERS_ROWS は、記録テーブルを見る SELECT の中で、取り込み先のテーブルを数えた値です。SELECT を実行した時点の件数なので、どの行にも同じ値が出ます。

取り込みの履歴も、数え始める時刻を2時間前にして見ました。

USE SCHEMA test_ingest.raw;
SELECT FILE_NAME, STATUS, ROW_COUNT, ERROR_COUNT, FIRST_ERROR_MESSAGE
FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(TABLE_NAME => 'ORDERS', START_TIME => DATEADD(HOUR, -2, CURRENT_TIMESTAMP())))
ORDER BY LAST_LOAD_TIME DESC;
Snowsight の画面。取り込みの履歴の4行を新しい順に並べたもの。orders/orders_2026-10-20_shopB.csv が Load failed・ROW_COUNT 0・ERROR_COUNT 1、orders/orders_2026-10-20.csv が Loaded・120・0、orders/orders_2026-10-20_shopB.csv が Load failed・0・1、orders/orders_2026-10-19.csv が Loaded・100・0。Load failed の2行の FIRST_ERROR_MESSAGE は Numeric value 'A-0001' is not recognized。Load failed の2行が枠で囲んである
取り込みの履歴。形式の違うファイルは、呼んだ回ごとに Load failed の行が残った

飛ばしたファイルは、STATUS が Load failed の行として、呼んだ回ごとに残りました。NO_FILE の回は、行が出ていません。メールが届いた日にこの SQL を使うときは、START_TIME を2時間前ではなく、その日のタスクの時刻からにします。

FIRST_ERROR_MESSAGE には、Numeric value 'A-0001' is not recognized と出ました。金額のカンマの誤りは、エラー文に出ていません。エラー文に出るのは、最初の誤り1つだけでした。ERROR_COUNT は1で、誤りのあるデータの行数です。

注文番号だけを直しても、金額の誤りが残っていれば、そのファイルはまた飛ばされるはずです。送り手には、ファイル全体を確かめてもらいます。

タスクにして、予定の時刻に動くかを確かめる

タスクには、ストアドプロシージャを呼ぶ CALL を1文だけ書きます。

USE SCHEMA test_ingest.raw;
CREATE OR REPLACE TASK LOAD_ORDERS
  WAREHOUSE = COMPUTE_WH
  SCHEDULE = 'USING CRON 0 6 * * * Asia/Tokyo'
  AS CALL LOAD_ORDERS_DAILY();

SCHEDULE には、cron の式とタイムゾーンを続けて書きます。cron の式は、分・時・日・月・曜日の順の5つの欄です。USING CRON 0 6 * * * Asia/Tokyo は、日本時間の毎朝6時です。

WAREHOUSE を書くと、そのウェアハウスで動き、起動するたびに最低1分ぶんの料金がかかります。WAREHOUSE を省くとサーバーレスタスクになり、大きさは Snowflake が自動で決めます。初めの大きさを指定しないと、MEDIUM のウェアハウスにあたる大きさで始まります。

初めの大きさは、USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE で指定します。料金のかかり方は、料金とコストの記事にまとめています。

予定の時刻で動かす前に、SQL が失敗したときの動きを確かめました。形式の違うファイルを REMOVE で外し、取り込み先のテーブルの名前を変えてから、EXECUTE TASK でタスクを手動で1回動かしました。

記録テーブルには、ERROR の行が入りました。MESSAGE は SQL compilation error: Table 'ORDERS' does not exist です。タスクの実行履歴は FAILED で、ERROR_CODE は1757でした。

Snowsight の画面。タスクの実行履歴の5行。1行目は STATE が FAILED、ERROR_CODE が 1757、ERROR_MESSAGE が Uncaught exception of type 'STATEMENT_ERROR' on line 11 で始まる文、SCHEDULED_JST が 2026-10-21 00:24。2行目から下は 2026-10-20 23 時台の SUCCEEDED と FAILED(100038)。1行目が枠で囲んである
タスクの実行履歴。1行目が SQL の失敗。2行目から下は、同じ名前で前に作って削除したタスクの実行

タスクの実行履歴の ERROR_MESSAGE は、次の文で始まっていました。

Uncaught exception of type 'STATEMENT_ERROR' on line 11 …

記録テーブルとタスクの実行履歴の、どちらにも失敗が残りました。写真のように、タスクの名前で実行履歴を見ると前の実行も出るので、その日の分は SCHEDULED_TIME_RANGE_START で絞ります。

テーブルの名前を元に戻し、21日分のファイル orders_2026-10-21.csv を置きました。80行のファイルです。作ったばかりのタスクは、一時停止の状態から始まります。予定の時刻に動かすには、ALTER TASK … RESUME を実行します。

今回は、試すために時刻を数分先の0:31 に変えてから RESUME しました。

USE SCHEMA test_ingest.raw;
ALTER TASK LOAD_ORDERS SET SCHEDULE = 'USING CRON 31 0 * * * Asia/Tokyo';
ALTER TASK LOAD_ORDERS RESUME;
SHOW TASKS LIKE 'LOAD_ORDERS' IN SCHEMA test_ingest.raw;

0:31 を過ぎてから、タスクの実行履歴を直近1時間に絞って見ました。そのあと、タスクを一時停止に戻しました。

USE SCHEMA test_ingest.raw;
SELECT NAME, STATE, ERROR_CODE,
       TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', SCHEDULED_TIME), 'YYYY-MM-DD HH24:MI:SS') AS SCHEDULED_JST,
       TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', COMPLETED_TIME), 'YYYY-MM-DD HH24:MI:SS') AS COMPLETED_JST
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(TASK_NAME => 'LOAD_ORDERS',
       SCHEDULED_TIME_RANGE_START => DATEADD(HOUR, -1, CURRENT_TIMESTAMP())))
ORDER BY SCHEDULED_TIME DESC;
ALTER TASK LOAD_ORDERS SUSPEND;
Snowsight の画面。タスクの実行履歴の5行。1行目は SCHEDULED で、SCHEDULED_JST が 2026-10-22 00:31:00、COMPLETED_JST が null。2行目は SUCCEEDED で、2026-10-21 00:31:00 に予定し 00:31:03 に完了。3行目は FAILED・1757。4行目と5行目は 2026-10-20 23 時台の実行。上の2行が枠で囲んである
0:31 の回が SUCCEEDED で、翌日の回が SCHEDULED で入っている。下の2行は、前に削除したタスクの実行

タスクは0:31 に予定どおり動き、3秒で終わりました。記録テーブルには OK の行が入り、取り込んだのは1つのファイルの80行です。取り込み先のテーブルの件数は300で、100+120+80 と合います。ここまでの記録テーブルの行を並べると、次のようになります。

記録テーブルの推移。00:17:46 は19日分のファイル(100行)を置いて呼び、OK・1・100・0、件数 100。00:19:04 はファイルを足さずに呼び、NO_FILE・0・0・0、件数 100。00:20:26 は20日分の正しいファイル(120行)と形式の違うファイル(1行)を置いて呼び、PARTIAL・1・120・1、件数 220。00:21:48 は形式の違うファイルを置いたまま呼び、PARTIAL・0・0・1、件数 220。00:24:29 は形式の違うファイルを外し、取り込み先のテーブルの名前を変えてタスクを手動で実行し、ERROR・0・0・0。タスクの実行履歴は FAILED(1757)。00:31:01 はテーブルの名前を戻し、21日分のファイル(80行)を置いて予定の時刻にタスクが実行し、OK・1・80・0、件数 300記録テーブルの推移。00:17:46 は19日分のファイル(100行)を置いて呼び、OK・1・100・0、件数 100。00:19:04 はファイルを足さずに呼び、NO_FILE・0・0・0、件数 100。00:20:26 は20日分の正しいファイル(120行)と形式の違うファイル(1行)を置いて呼び、PARTIAL・1・120・1、件数 220。00:21:48 は形式の違うファイルを置いたまま呼び、PARTIAL・0・0・1、件数 220。00:24:29 は形式の違うファイルを外し、取り込み先のテーブルの名前を変えてタスクを手動で実行し、ERROR・0・0・0。タスクの実行履歴は FAILED(1757)。00:31:01 はテーブルの名前を戻し、21日分のファイル(80行)を置いて予定の時刻にタスクが実行し、OK・1・80・0、件数 300
時刻は日本時間。6回の実行が、どれも記録テーブルに1行ずつ残った

タスクは、続けて10回失敗すると、自動で一時停止します。SUSPEND_TASK_AFTER_NUM_FAILURES の既定が10だからです。

アラートが記録テーブルを毎朝見て、問題がある日だけメールで知らせる

アラートは、決めた時刻に条件の SQL を実行し、結果が1行以上あれば、決めておいた処理を実行する仕組みです。今回は、タスクの30分後の毎朝6時30分に動かします。条件は、その日の記録テーブルに行が無いか、最後の行の RESULT が OK でないことです。

その日かどうかは、日本時間の日付で判定しています。記録テーブルを人が見るだけなら、担当は毎朝 SELECT を実行し、結果を読みに行く必要があります。アラートがメールを送る形にすれば、問題がある日だけ知らせが届くので、毎朝見に行かなくてもよくなります。

先に、メールを送らない形でも試しました。処理を、LOAD_ALERTS というテーブルに1行書くだけにしたアラートです。この SQL は載せていません。その日の最後の行が0:31 の OK の間は、アラートの実行履歴の STATE は CONDITION_FALSE でした。ファイルを足さずにストアドプロシージャを呼んで NO_FILE の行を足し、もう一度アラートを実行すると、TRIGGERED になりました。

メールの宛先にできるのは、同じアカウントの Snowflake ユーザーで、メールアドレスを確認済みの人だけです。試用の環境のユーザーは1人だけで、そのアドレスは宛先にしたいものではありませんでした。そこで、受け取り用のユーザーをパスワード無しで作りました。

USE ROLE ACCOUNTADMIN;
USE SCHEMA test_ingest.raw;
CREATE USER IF NOT EXISTS DATA_TEAM_NOTIFY
  TYPE = PERSON
  EMAIL = 'data-team@example.com'
  COMMENT = 'email recipient only, no password';
CALL SYSTEM$START_USER_EMAIL_VERIFICATION('DATA_TEAM_NOTIFY');

SYSTEM$START_USER_EMAIL_VERIFICATION を呼ぶと、そのアドレスに確認メールが送られます。受け取った人が確認メールのリンクを開くと、アドレスは確認済みになります。今回も、受け取る人にリンクを開いてもらってから先へ進みました。

この節の SQL では、宛先のアドレスとユーザー名を、説明のために作った名前に置き換えています。通知インテグレーションの名前と、ユーザーのコメントも同じです。data-team@example.com は、自社の確認済みのアドレスにして使います。

通知インテグレーションは、Snowflake からメールなどで外へ知らせるための設定です。ALLOWED_RECIPIENTS には、送ってよい宛先を書きます。知らせる処理は、ストアドプロシージャ NOTIFY_LOAD_RESULT にまとめました。その日の最後の結果とエラー文を LOAD_ALERTS に書き、SYSTEM$SEND_EMAIL でメールを送ります。

当社は、0時台の試しを片付けたあと、1時台に試す土台を作り直してメールを試しました。続けて試すときは、片付けずにそのまま次の塊を実行してかまいません。

次の塊は、記録テーブルと LOAD_ALERTS を作り直すので、それまでの記録テーブルの行は消えます。そのうえで、試すための PARTIAL の行を1つ INSERT で入れ、アラートを手動で動かします。この行に対応する取り込みは、していません。

USE ROLE ACCOUNTADMIN;
USE SCHEMA test_ingest.raw;
CREATE NOTIFICATION INTEGRATION IF NOT EXISTS LOAD_EMAIL_INT
  TYPE = EMAIL
  ENABLED = TRUE
  ALLOWED_RECIPIENTS = ('data-team@example.com');
-- 記録テーブルと LOAD_ALERTS を作り直す(それまでの行は消える)
CREATE OR REPLACE TABLE LOAD_LOG (RUN_AT TIMESTAMP_LTZ, RESULT STRING, FILES_LOADED NUMBER, ROWS_LOADED NUMBER, FILES_SKIPPED NUMBER, MESSAGE STRING);
CREATE OR REPLACE TABLE LOAD_ALERTS (DETECTED_AT TIMESTAMP_LTZ, LAST_RESULT STRING, MESSAGE STRING);
-- 試すための行(取り込みはしていない)。本番では入れない
INSERT INTO LOAD_LOG VALUES (CURRENT_TIMESTAMP(), 'PARTIAL', 1, 120, 1, 'Numeric value ''A-0001'' is not recognized');
CREATE OR REPLACE PROCEDURE NOTIFY_LOAD_RESULT()
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
  last_result STRING;
  last_message STRING;
  body STRING;
BEGIN
  SELECT COALESCE(MAX_BY(RESULT, RUN_AT), 'NO_RUN'), MAX_BY(MESSAGE, RUN_AT)
    INTO :last_result, :last_message
    FROM LOAD_LOG
    WHERE CONVERT_TIMEZONE('Asia/Tokyo', RUN_AT)::DATE = CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::DATE;
  INSERT INTO LOAD_ALERTS VALUES (CURRENT_TIMESTAMP(), :last_result, :last_message);
  body := 'これはブログ記事の検証用のテスト送信です。' || CHR(10) ||
          '本日の取り込みの結果: ' || last_result || CHR(10) ||
          'メッセージ: ' || COALESCE(last_message, '(なし)') || CHR(10) ||
          '記録テーブル LOAD_LOG の本日の行を確認してください。';
  CALL SYSTEM$SEND_EMAIL(
    'LOAD_EMAIL_INT',
    'data-team@example.com',
    '[テスト送信] Snowflake 取り込みの確認(ブログ検証)',
    :body);
  RETURN last_result;
END;
$$;
CREATE OR REPLACE ALERT CHECK_DAILY_LOAD_MAIL
  WAREHOUSE = COMPUTE_WH
  SCHEDULE = 'USING CRON 30 6 * * * Asia/Tokyo'
  IF (EXISTS (
    SELECT 1
    FROM (SELECT COUNT(*) AS RUNS, MAX_BY(RESULT, RUN_AT) AS LAST_RESULT
          FROM LOAD_LOG
          WHERE CONVERT_TIMEZONE('Asia/Tokyo', RUN_AT)::DATE = CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::DATE)
    WHERE RUNS = 0 OR LAST_RESULT <> 'OK'
  ))
  THEN
    CALL NOTIFY_LOAD_RESULT();
EXECUTE ALERT CHECK_DAILY_LOAD_MAIL;

件名と本文は、試したときのままです。アラートも作った直後は一時停止の状態なので、EXECUTE ALERT で手動で1回動かしました。

動かしたあとは、アラートの実行履歴と LOAD_ALERTS を、次の SELECT で1つの結果に並べて見ました。

USE SCHEMA test_ingest.raw;
SELECT 'ALERT' AS SRC, STATE AS VALUE1, COALESCE(SQL_ERROR_MESSAGE, 'null') AS VALUE2,
       TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', SCHEDULED_TIME), 'YYYY-MM-DD HH24:MI:SS') AS AT_JST
FROM TABLE(INFORMATION_SCHEMA.ALERT_HISTORY(SCHEDULED_TIME_RANGE_START => DATEADD(HOUR, -1, CURRENT_TIMESTAMP())))
WHERE NAME = 'CHECK_DAILY_LOAD_MAIL'
UNION ALL
SELECT 'LOAD_ALERTS', LAST_RESULT, MESSAGE, TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', DETECTED_AT), 'YYYY-MM-DD HH24:MI:SS')
FROM LOAD_ALERTS
ORDER BY AT_JST DESC;
Snowsight の画面。2行の結果。1行目は SRC が LOAD_ALERTS、VALUE1 が PARTIAL、VALUE2 が Numeric value 'A-0001' is not recognized、AT_JST が 2026-10-21 01:23:37。2行目は SRC が ALERT、VALUE1 が TRIGGERED、VALUE2 が null、AT_JST が 01:23:35。2行が枠で囲んである
SRC が ALERT の行がアラートの実行履歴、LOAD_ALERTS の行は NOTIFY_LOAD_RESULT が書いた行

アラートの実行履歴は TRIGGERED で、LOAD_ALERTS には PARTIAL とエラー文の行が入りました。TRIGGERED は、条件が成り立ち、処理も成功したという意味です。メールを送ったかどうかは、NOTIFICATION_HISTORY というテーブル関数で見ます。

USE SCHEMA test_ingest.raw;
SELECT INTEGRATION_NAME, STATUS, COALESCE(ERROR_MESSAGE, 'null') AS ERROR_MESSAGE,
       TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', CREATED), 'YYYY-MM-DD HH24:MI:SS') AS CREATED_JST,
       TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', PROCESSED), 'YYYY-MM-DD HH24:MI:SS') AS PROCESSED_JST
FROM TABLE(INFORMATION_SCHEMA.NOTIFICATION_HISTORY(START_TIME => DATEADD(HOUR, -1, CURRENT_TIMESTAMP())))
ORDER BY CREATED DESC;
Snowsight の画面。NOTIFICATION_HISTORY の1行。INTEGRATION_NAME の値は灰色の箱で隠してある。STATUS が SUCCESS、ERROR_MESSAGE は空、CREATED_JST が 2026-10-21 01:23:37、PROCESSED_JST が 01:23:39。STATUS のマスが枠で囲んである
送信の記録。STATUS が SUCCESS。灰色の箱は、通知インテグレーションの名前

STATUS は SUCCESS で、ERROR_MESSAGE は空でした。SUCCESS は、Snowflake がメールを送り出したという記録で、受信箱に届いたことまでは示しません。今回は、宛先の受信箱にも届き、日本語の本文も読めることを、受け取った人に確かめてもらいました。本番で動かし始めたときも、最初の1通が届いたかを、受け取る人に受信箱で確かめてもらいます。

このアラートは、その日の最後の行だけで判定します。PARTIAL のあとに誰かが手で呼び直して OK になれば、メールは届きません。

アラートそのものが失敗した日も、メールは届きません。アラートの実行履歴に CONDITION_FAILED か ACTION_FAILED が出たら、アラートの SQL が失敗しています。アラートは、条件や処理の SQL に誤った名前があっても、作るときにはエラーになりません。作ったら一度 EXECUTE ALERT で動かして確かめます。

タスクには、失敗を外へ知らせる ERROR_INTEGRATION という設定もありますが、これではメールは送れません。Amazon SNS などに送るための設定です。アラートもウェアハウスで動かすと、起動のたびに最低1分ぶんの料金がかかります。WAREHOUSE を省けば、サーバーレスのアラートになります。

メールが届いた日は、次の2つの SELECT で、記録テーブルと LOAD_ALERTS を新しい順に見ます。記録テーブルの行が増えてきたら、WHERE で RUN_AT を直近の数日に絞ります。

USE SCHEMA test_ingest.raw;
SELECT TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', RUN_AT), 'YYYY-MM-DD HH24:MI:SS') AS RUN_AT_JST,
       RESULT, FILES_LOADED, ROWS_LOADED, FILES_SKIPPED,
       (SELECT COUNT(*) FROM ORDERS) AS ORDERS_ROWS, MESSAGE
FROM LOAD_LOG ORDER BY RUN_AT DESC;
SELECT TO_CHAR(CONVERT_TIMEZONE('Asia/Tokyo', DETECTED_AT), 'YYYY-MM-DD HH24:MI:SS') AS DETECTED_JST, LAST_RESULT, MESSAGE
FROM LOAD_ALERTS ORDER BY DETECTED_AT DESC;

PARTIAL と NO_FILE の日に使う SQL と、ファイルの置き直し方

PARTIAL の日は、まず取り込みの履歴の Load failed の行で、飛ばしたファイルの名前を見ます。

直すときは、形式の違うファイルを REMOVE でステージから外してから、直したファイルを置きます。置いたままだと、呼ぶたびに読まれて PARTIAL が続くからです。置いたら、ストアドプロシージャを呼び直します。

USE SCHEMA test_ingest.raw;
LIST @LANDING;
REMOVE @LANDING/orders/orders_2026-10-20_shopB.csv;
CALL LOAD_ORDERS_DAILY();

内部ステージにファイルを置く SQL は PUT です。PUT は、Snowsight で SQL を書く画面であるワークシートからは実行できません。Snowflake CLI やドライバから実行します。Snowsight の画面から、名前付きの内部ステージへアップロードすることもできます。

PUT は、既定でファイルを gzip に圧縮します。同じ名前のファイルがステージにあると、既定では上書きしません。直したファイルを置いて呼び直す手順は、今回は試していません。

中身を変えて置き直したファイルは、同じ名前でも新しいファイルとして取り込まれます。前に取り込んだファイルと同じ名前・同じ中身のファイルは、呼び直しても読み飛ばされます。ロードのメタデータは64日で消えます。64日を過ぎて取り込んだかどうか分からなくなったファイルも、既定では読み飛ばされます。

ただし FORCE = TRUE を指定すると、取り込み済みのファイルも読み直すので、同じデータが二重に入るおそれがあります。二重に入ったテーブルを、取り込む前の状態に戻す方法は、Time Travel と UNDROP の記事に書きました。

NO_FILE の日は、LIST でステージにその日のファイルがあるかを見ます。無ければ、送り手に置いてもらってから、ストアドプロシージャを呼びます。LIST に出ているのに NO_FILE なら、orders/ の外に置いたか、取り込み済みのファイルを置き直したことが考えられます。CASE 式を書き換えた場合は、見出しの行だけのファイルが届いた日も NO_FILE になります。

本番に入れる SQL

本番に入れる一式は、ここにまとめました。作るのは、記録テーブル、LOAD_ALERTS、ストアドプロシージャ2つ、タスク、通知インテグレーション、メールを送るアラートです。最後に RESUME します。試すための INSERT・CALL・EXECUTE ALERT・REMOVE、メールを送らないアラート、0:31 の時刻は入れていません。

<…> の所を、自社の名前・宛先・件名・本文・時刻に書き換えます。書き換える所は次のとおりです。

  • データベースとスキーマ、ウェアハウス:<データベース>.<スキーマ> と <ウェアハウス>。
  • ファイルの置き場所と取り込み先:<ステージ>/<パス>/ と <取り込み先のテーブル>。テーブルの名前は、COPY_HISTORY に渡す TABLE_NAME にも入れます。
  • ファイルの形式:FILE_FORMAT を自社の CSV に合わせます。今回は見出しの行が1行あるので、SKIP_HEADER = 1 です。
  • メール:<通知インテグレーション>、<宛先>、<件名>、<本文の書き出し>。宛先は、同じアカウントの確認済みのユーザーのアドレスです。
  • 時刻:タスクの <タスクの分> <タスクの時> はファイルが届いたあと、アラートの時刻はタスクが終わったあとにします。
  • タイムゾーン:<タイムゾーン> は、タスクとアラートの時刻と、その日を判定する日付の両方に使います。

記録テーブルと LOAD_ALERTS の CREATE OR REPLACE TABLE は、最初の1回だけ実行します。2回目以降に実行すると、それまでの記録が消えます。また、取り込み先のテーブルを作り直すと、そのテーブルの取り込みの履歴は消え、置いたままのファイルをまた取り込むはずです。ロードのメタデータもテーブルごとに持つからです。

-- 実行した文から試すための文を除き、名前を置き換えた形。この形のまとまりでは実行していない
-- <…> は、自社の名前・宛先・件名・本文・時刻に書き換える
USE ROLE ACCOUNTADMIN;
USE WAREHOUSE <ウェアハウス>;
USE SCHEMA <データベース>.<スキーマ>;
-- 記録テーブルと LOAD_ALERTS(最初の1回だけ実行する)
CREATE OR REPLACE TABLE LOAD_LOG (
  RUN_AT TIMESTAMP_LTZ,
  RESULT STRING,
  FILES_LOADED NUMBER,
  ROWS_LOADED NUMBER,
  FILES_SKIPPED NUMBER,
  MESSAGE STRING
);
CREATE OR REPLACE TABLE LOAD_ALERTS (DETECTED_AT TIMESTAMP_LTZ, LAST_RESULT STRING, MESSAGE STRING);
-- 取り込みのストアドプロシージャ
CREATE OR REPLACE PROCEDURE LOAD_ORDERS_DAILY()
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
  started_at TIMESTAMP_LTZ;
  files_loaded NUMBER DEFAULT 0;
  rows_loaded NUMBER DEFAULT 0;
  files_skipped NUMBER DEFAULT 0;
  first_error STRING;
  result STRING;
BEGIN
  started_at := CURRENT_TIMESTAMP();
  COPY INTO <取り込み先のテーブル> FROM @<ステージ>/<パス>/
    FILE_FORMAT = (TYPE = CSV SKIP_HEADER = 1)
    ON_ERROR = SKIP_FILE;
  SELECT COALESCE(COUNT_IF(STATUS = 'Loaded'), 0),
         COALESCE(SUM(IFF(STATUS = 'Loaded', ROW_COUNT, 0)), 0),
         COALESCE(COUNT_IF(STATUS <> 'Loaded'), 0),
         MAX(FIRST_ERROR_MESSAGE)
    INTO :files_loaded, :rows_loaded, :files_skipped, :first_error
    FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(TABLE_NAME => '<取り込み先のテーブル>', START_TIME => :started_at));
  result := CASE WHEN files_skipped > 0 THEN 'PARTIAL'
                 WHEN files_loaded = 0 THEN 'NO_FILE'
                 ELSE 'OK' END;
  INSERT INTO LOAD_LOG VALUES (:started_at, :result, :files_loaded, :rows_loaded, :files_skipped, :first_error);
  RETURN result || ' files=' || files_loaded || ' rows=' || rows_loaded || ' skipped=' || files_skipped;
EXCEPTION
  WHEN OTHER THEN
    INSERT INTO LOAD_LOG VALUES (CURRENT_TIMESTAMP(), 'ERROR', 0, 0, 0, :SQLERRM);
    RAISE;
END;
$$;
-- タスク
CREATE OR REPLACE TASK LOAD_ORDERS
  WAREHOUSE = <ウェアハウス>
  SCHEDULE = 'USING CRON <タスクの分> <タスクの時> * * * <タイムゾーン>'
  AS CALL LOAD_ORDERS_DAILY();
-- メールの通知インテグレーションと、知らせるストアドプロシージャ
CREATE NOTIFICATION INTEGRATION IF NOT EXISTS <通知インテグレーション>
  TYPE = EMAIL
  ENABLED = TRUE
  ALLOWED_RECIPIENTS = ('<宛先>');
CREATE OR REPLACE PROCEDURE NOTIFY_LOAD_RESULT()
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
  last_result STRING;
  last_message STRING;
  body STRING;
BEGIN
  SELECT COALESCE(MAX_BY(RESULT, RUN_AT), 'NO_RUN'), MAX_BY(MESSAGE, RUN_AT)
    INTO :last_result, :last_message
    FROM LOAD_LOG
    WHERE CONVERT_TIMEZONE('<タイムゾーン>', RUN_AT)::DATE = CONVERT_TIMEZONE('<タイムゾーン>', CURRENT_TIMESTAMP())::DATE;
  INSERT INTO LOAD_ALERTS VALUES (CURRENT_TIMESTAMP(), :last_result, :last_message);
  body := '<本文の書き出し>' || CHR(10) ||
          '本日の取り込みの結果: ' || last_result || CHR(10) ||
          'メッセージ: ' || COALESCE(last_message, '(なし)') || CHR(10) ||
          '記録テーブル LOAD_LOG の本日の行を確認してください。';
  CALL SYSTEM$SEND_EMAIL(
    '<通知インテグレーション>',
    '<宛先>',
    '<件名>',
    :body);
  RETURN last_result;
END;
$$;
-- メールを送るアラート
CREATE OR REPLACE ALERT CHECK_DAILY_LOAD_MAIL
  WAREHOUSE = <ウェアハウス>
  SCHEDULE = 'USING CRON <アラートの分> <アラートの時> * * * <タイムゾーン>'
  IF (EXISTS (
    SELECT 1
    FROM (SELECT COUNT(*) AS RUNS, MAX_BY(RESULT, RUN_AT) AS LAST_RESULT
          FROM LOAD_LOG
          WHERE CONVERT_TIMEZONE('<タイムゾーン>', RUN_AT)::DATE = CONVERT_TIMEZONE('<タイムゾーン>', CURRENT_TIMESTAMP())::DATE)
    WHERE RUNS = 0 OR LAST_RESULT <> 'OK'
  ))
  THEN
    CALL NOTIFY_LOAD_RESULT();
-- 動かし始める(ALTER ALERT … RESUME と SHOW ALERTS は、今回は実行していない)
ALTER TASK LOAD_ORDERS RESUME;
ALTER ALERT CHECK_DAILY_LOAD_MAIL RESUME;
SHOW TASKS LIKE 'LOAD_ORDERS' IN SCHEMA <データベース>.<スキーマ>;
SHOW ALERTS LIKE 'CHECK_DAILY_LOAD_MAIL' IN SCHEMA <データベース>.<スキーマ>;

通知インテグレーションは、試したときとは別の名前で作ります。同じ名前のものが残っていると、IF NOT EXISTS のため作り直されません。ALLOWED_RECIPIENTS も、試したときの宛先のままになります。別の名前にしておけば、片付けで試したときの通知インテグレーションを削除しても、本番のメールには影響しません。

あとでタスクの時刻を変えるときは、先に ALTER TASK … SUSPEND で一時停止します。単独のタスクは、一時停止していないと変更できないからです。変えたあとは、RESUME し直します。

ロールと権限は、今回は ACCOUNTADMIN で試しました。ほかのロールで作るなら、次の権限が要ります。

  • タスク:データベースの USAGE、スキーマの USAGE と CREATE TASK、ウェアハウスの USAGE。RESUME のときには、タスクを所有するロールがアカウントの EXECUTE TASK を持っているかが確かめられ、無ければエラーになります。サーバーレスなら EXECUTE MANAGED TASK も要ります。
  • ストアドプロシージャ:スキーマの CREATE PROCEDURE。ほかのロールに呼ばせるなら、そのストアドプロシージャの USAGE を付与します。
  • ストアドプロシージャの中の SQL:ストアドプロシージャは既定で所有するロールの権限で動きます。そのロールには、取り込み先のテーブルと記録テーブルに書く権限、ステージのファイルを読む権限などが要ります。
  • アラート:アカウントの EXECUTE ALERT。付与できるのは ACCOUNTADMIN のロールだけです。ほかに、スキーマの USAGE と CREATE ALERT、データベースとウェアハウスの USAGE が要ります。条件で LOAD_LOG を読むので、その権限も要ります。
  • メール:通知インテグレーションを作るには、アカウントの権限が要ります。CREATE INTEGRATION か CREATE NOTIFICATION INTEGRATION です。SYSTEM$SEND_EMAIL を呼ぶのは NOTIFY_LOAD_RESULT です。それを所有するロールに、通知インテグレーションの USAGE を付与します。

ロールに権限を付与して確かめる進め方は、行アクセスポリシーの記事に書きました。

試し終えたら、検証用の test_ingest と受け取り用のユーザーを片付ける

試し終えたら、タスクとアラートを一時停止してから、test_ingest を削除します。データ保持期間を0にしておくと、削除したあとに Time Travel で戻せる期間も残りません。受け取り用のユーザーと通知インテグレーションはアカウントの物なので、データベースを削除しても残ります。試すためだけに作ったなら、これも削除します。

次の塊の各行は、実行した文です。ただし、この形のまとまりでは実行していません。SUSPEND と DROP DATABASE は試しのたびに、DROP USER と DROP INTEGRATION は検証の最後に実行しました。

USE ROLE ACCOUNTADMIN;
USE SCHEMA test_ingest.raw;
ALTER TASK test_ingest.raw.LOAD_ORDERS SUSPEND;
ALTER ALERT test_ingest.raw.CHECK_DAILY_LOAD_MAIL SUSPEND;
ALTER DATABASE test_ingest SET DATA_RETENTION_TIME_IN_DAYS = 0;
DROP DATABASE test_ingest;
-- 受け取り用のユーザーと通知インテグレーション(検証の最後に実行した)
DROP USER IF EXISTS DATA_TEAM_NOTIFY;
DROP INTEGRATION IF EXISTS LOAD_EMAIL_INT;

動かし始めた最初の数日は、タスク・記録テーブル・アラートの3つを並べて見る

本番で動かし始めた最初の数日は、メールを待つだけにせず、次の3つを毎朝並べて見ます。1つ目は、予定の時刻の回がタスクの実行履歴にあることです。2つ目は、同じ時刻の行が記録テーブルにあることです。3つ目は、そのあとアラートの実行履歴の行が増えていることです。

この3つを見るのは、今回確かめていないことがあるからです。アラートを RESUME して6時30分に動くところと、タスクが毎日続けて動くところは見ていません。取り込みの履歴の反映が遅れて、数え漏れることがあるかも確かめていません。

サーバーレスのタスクとアラート、Snowpipe、外部ステージ、ON_ERROR = CONTINUE も試していません。取り込み先のテーブルを毎朝作り直す運用、権限を絞ったロールでの運用、今回の料金も見ていません。今回の結果は、試用の Snowflake の1つのアカウントで続けて試したものです。Enterprise Edition・AWS の東京で、ACCOUNTADMIN と X-Small のウェアハウスを使いました。

この一式を、自社のファイルの置き方や取り込む頻度に合わせて組むところは、当社でもお手伝いできます。Snowflake 導入支援のうち、データ取り込みの構築の工程にあたります。

データ分析・データ基盤支援

Snowflake・BigQuery・Treasure Dataといったデータ基盤の選定から、基幹・SaaS・広告・POSに散らばったデータの集約、dbtでの指標統一、Lookerなどでのダッシュボード化までを支援します。

AT
aurant technologies 編集

上場企業からスタートアップまで、数多くのデータ分析基盤構築・AI導入プロジェクトを主導。単なる技術提供にとどまらず、MA/CRM(Salesforce, Hubspot, kintone, LINE)導入によるマーケティング最適化やバックオフィス業務の自動化など、常に「事業数値(売上・利益)」に直結する改善実績多数。

この記事が役に立ったらシェア:

データ基盤・データ分析の資料

「BIツール選定チェックリスト&4社比較」

データ分析・データ基盤支援の内容を見る