SQL Test for Snowflake ! ～Insight SQL TestingがSnowflakeに対応しました～

こんにちは！インサイトテクノロジーの松尾です。

とうとうInsight SQL TestingがSnowflakeに対応しました！

https://www.insight-tec.com/news/press/202407_5273/

本投稿ではInsight SQL Testingを使ってSnowflake移行アセスメントをどんな感じに実施できるのか、ご紹介したいと思います。

## モチベーション

### 疑問

DBで動作しているワークロードをSnowflake へ移行したい！

既存のアプリケーションはそのまま動くの？

### Snowflakeでは当然SQLが使えます

https://docs.snowflake.com/ja/guides-overview-queries

> Snowflakeは、 ANSI SQL:1999および SQL:2003分析拡張機能のサブセットを含む、標準の SQL をサポートしています。Snowflakeは、多くのコマンドの一般的なバリエーションもサポートしていますが、それらのバリエーションは互いに競合しません。

とはいえ仕様差がつきものですよね？

OracleとPostgreSQLの仕様差があってアプリケーションの移行が大変なのと同様、元の環境で動いていたSQLがそのまま同様にSnowflakeで動くとは限りません。そのため、既存のワークロードをSnowflakeへもっていく場合には、そのアプリケーションやサービスで改修が必要なのか？必要な場合はどの程度の改修なのか？を調べる必要が出てきます。

### どんな差異があるのだろう？

一般的にはDBのエンジン変更を伴う移行の場合は以下のような違いがある可能性を疑います。

* SQLの仕様差
    * DBMS独自の構文
    * Oracleの外部結合(+)、ROWIDの利用など
    * 予約語の違い
    * 組み込み関数の有無や挙動の違い
* SQL実行結果が異なる
* 性能の違い

こういった変更のある可能性があると、どうしても移行工数は(勘と経験と度胸に基づいて！)大きめの見積もりとせざるを得ません。

### どんなステップで調査・対応を進めていきますか？

一般的には以下のような流れで調査や対応を行っていくと思います。

* ドキュメントを読み込みSnowflakeの仕様を確認
    * まずドキュメントは膨大
* アプリのソースコードを調査し、修正が必要な部分や難易度を確認
    * ソースコードから実際のクエリの動きを想像するのが大変
    * ドキュメントがきちんと整備されてないと期待されている動作が不明
* 全体を調査して移行工数見積もり
* そしてその後に実際の開発作業など、、
* さらにその後にテストを実施

ここで問題になりがちなのは、テストはどうしても後の方の工程になるため、特に性能問題が発生する場合に気付くのは後のフェーズになってしまうことです。

## SQLテストを使って省力化しよう♪

### SQLテストとは？

移行のための検証ステップをSQLテストを使って省力化しましょう。SQLテストはSQLがきちんと動くかを確認するテストです。

SQLテストの活用には以下のようなメリットがあります。

* SQLの一覧を用意できればそれをテストケースとして何度でも実施できる
* 環境さえ用意すればだれでも実行できる
* アプリケーションの改修を行わなくても新環境に対してSQLを発行できる
* そのため、早い段階で発生するかもしれない問題に気づける可能性がある

そして「Insight SQL Testing」はそのSQLテストを便利に実行するためのツールです。

### Insight SQL Testingの機能

Insight SQL Testingは以下の機能で構成されます。

* 現行環境でアプリケーションが発行するSQLを自動収集
* 収集したSQLをテスト
    * 実行可否
    * 実行結果比較
        * Select：結果セットの比較
        * 更新DML：Affected rowsの比較
    * 実行速度比較
        * SQL実行の実時間による比較
* テスト結果の確認・評価
    * サマリー画面
    * 生成AIによるエラー説明・修正案提示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/1d40f724-9aed-483b-b1de-8cae3de0be4a.png)

ちなみに、今回ご紹介するSnowflake以外では、以下のようなデータベースに対応しています。

* SQL取得元
    * Oracle(オンプレ、RDS)
    * SQL Server(オンプレ、RDS)
    * PostgreSQL(オンプレ、RDS、Aurora)
    * MySQL(オンプレ、RDS、Aurora)
    * CSVファイル
* SQL実行先(DBのクライアントから接続できるものであれば基本なんでも利用可能)
    * Oracle
    * SQL Server
    * PostgreSQL
    * MySQL
    * Snowflake(★)

さまざまな組み合わせで利用はできますが、今までは「Oracle→PostgreSQL」に加えて、PostgreSQL、MySQLなどのメジャーバージョンアップのための利用が定番化しており、他の組み合わせは少ないです。
今後、Snowflakeでの利用がどのように広がっていくかとても楽しみです。

## 典型的なOracleのSQLをSnowflakeで実行できるかテストしてみる

では早速、Insight SQL Testing を使用して典型的なOracleのSQLをSnowflakeで実行できるかテストしてみます。

次のSQL、どれがエラーになるか、2秒で答えてください。

| SQL | コメント |
|-----|---------|
|select * from emp;||
|select empno, ename from emp order by empno;||
|delete from emp where rowid not in (select min(rowid) from emp e1 group by e1.ename);|rowid使用|
|select 1/3 from dual;|dual表|
|select d.deptno,d.dname,e.empno,nvl(e.ename,'(no employee)') ename from dept d, emp e where d.deptno=e.deptno(+) order by deptno,empno;|(+)演算子、nvl関数|
|select d.deptno,d.dname,e.empno,nvl(e.ename,'(no employee)') ename from dept d, emp e where d.deptno=e.deptno(+);|(+)演算子、nvl関数|
|select to_char(empno,'00000'), ename from emp order by empno;|to_char|
|select empno, ename, hiredate, trunc(sysdate - hiredate) from emp order by empno;|trunc関数、sysdate|
|select empno, ename, to_char(hiredate) from emp where empno = '7369’;|書式なしto_char|
|select * from full;|fullテーブルからの取得|
|insert into bonus values ('test', 'test', power(10,2), 10);|values句でpower関数|
|select * from emp where empno = :val;|バインド変数を使ったクエリ|

はい、時間切れです。
Snowflakeマスターの方ならすぐわかるかもしれませんが、普通の人はわかりませんよね！？

Insight SQL Testing で上記SQLをテスト用のSQL(評価SQLセット)として用意し、テスト環境(OracleとSnowflake)を用意してテスト(アセスメント)を実行します。

アセスメント結果はこちら！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/d4d2fe7a-f2af-29b4-c396-400ba7b29caa.png)

アセスメント結果のサマリーにおいて、早速「ターゲットDBデノミ失敗」が4件と結果がすぐにわかってしまいました。

### エラーになったSQL

せっかくなので、どのSQLがエラーになったかを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/8bce54b4-7222-9d3b-f355-44a0fac523b1.png)

例えば一番上の`rowid`を使用しているクエリを見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/c52fbb81-4903-39a7-6209-348797894243.png)

Snowflakeにも`rowid`はないようですね。Insight SQL Testingのエラー説明機能でもその旨が指摘されています。これはあきらめて`rowid`的な役割をする何かで書き換えるしかありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/71fce60d-82bf-143a-9c23-f090d2d60a05.png)

では次！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/40b092ac-c1aa-d9d5-1454-113b849e0c00.png)

`sysdate`も存在しないようです。エラー説明機能で確認してみると、`current_timestamp`を使ってみるようアドバイスがありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/104b2b4c-28fa-f2d3-fcd2-992f855ec3d4.png)

試しに、指摘に従って修正して実行してみます。実はこれだけではまだ足りません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/a84ac592-f18a-8fae-f8b3-1909f364a340.png)

TIMESTAMP型とDATE型の式となってエラーになりました。そのためDATE型に変換してやればいいことがわかります。厳密にはTimezoneなどの調整も必要になりますが、こんな感じに修正すればいいことがわかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/a38e29c4-6920-2f07-1b23-b9e8cec3d0c1.png)

次の`full`のエラーは予約語のためです。ダブルクォーテーションで囲うと実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/2890c75b-88a1-67ab-8563-23a5f1a138c2.png)

最後の一つはvalue句での関数使用です。これは現在エラーになるそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/531ad336-dac2-b213-a51d-844baef223d2.png)

例えば以下の感じに修正すると実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/718eaf73-c50d-8253-e90d-110320509719.png)

### 結果が異なるSQL

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/b14ddc07-18f9-420c-5f6a-27f338ec0b0c.png)

それぞれ結果がどのように異なったか見てみると、書式指定のない`to_char`や1/3の演算の精度などSnowflakeでなくても起こりそうな問題でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/f7ae4ec1-b9b3-d5e9-f9f9-7ae00ace739c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/5b44f1c5-2dfd-a806-b689-8efa92559eda.png)

これは本来は書式を指定すべきものですね。

### パフォーマンスに問題があった時の確認として

今回はパフォーマンスをちゃんと見るほど大量のデータを入れていませんが、何か問題があったときなど実行計画を確認すると思います。Insight SQL Testingでは実行計画の取得機能もありますので参考情報として利用できるでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/3ef8b9ff-0a9e-a69d-21be-ff95d286bb5c.png)

### 結果まとめ

以下のように4つのエラーになるSQLがあることがわかりました。ちなみに、副次効果として、dual表があることや(+)の演算子も利用可能なことがわかりましたね！

| SQL | コメント |
|-----|---------|
|select * from emp;||
|select empno, ename from emp order by empno;||
|delete from emp where rowid not in (select min(rowid) from emp e1 group by e1.ename);|★rowid使用のためエラー|
|select 1/3 from dual;|☆dual表は問題ないが1/3の精度問題で結果が相違と判定|
|select d.deptno,d.dname,e.empno,nvl(e.ename,'(no employee)') ename from dept d, emp e where d.deptno=e.deptno(+) order by deptno,empno;||
|select d.deptno,d.dname,e.empno,nvl(e.ename,'(no employee)') ename from dept d, emp e where d.deptno=e.deptno(+);||
|select to_char(empno,'00000'), ename from emp order by empno;||
|select empno, ename, hiredate, trunc(sysdate - hiredate) from emp order by empno;|★Sysdateがないため修正が必要|
|select empno, ename, to_char(hiredate) from emp where empno = '7369’;|☆to_char関数の日付書式に注意が必要|
|select * from full;|★fullが予約語のため修正が必要|
|insert into bonus values ('test', 'test', power(10,2), 10);|★関数演算はvalues句で使用不可|
|select * from emp where empno = :val;||

## おわりに

Insight SQL Testing活用したSQL Testで簡単にSQLの一覧をテストして評価できることを確認できました。

* 基本的なSQLでも予期せぬエラーとなったり挙動が異なったりするSQLがあります
* Snowflakeの仕様を調査して、かつそれが自社のアプリやサービスにどれくらい影響があるのかの机上調査はとても大変
* SQLテストツールを活用するとこれらの確認を簡単に行うことができ省力化可能なので、ぜひ活用しましょう！！

本投稿でご紹介したInsight SQL Testingにご興味がある方はぜひお問合せください

https://www.insight-tec.com/products/sqltesting/

AWS Marketplace からもトライアル利用することが可能ですが、一度お打合せさせていただいた方が進めやすいとは思います。※トライアル利用する場合は意図せず課金されないようご注意ください。

https://aws.amazon.com/marketplace/pp/prodview-okilzstcrioto

それでは、次回もお楽しみに！