はじめに
先月リリースされた MySQL HeatWave 9.4.1 で、自然言語でSQLを生成して実行する機能が実装されました。
MySQL HeatWave GenAI now lets you generate SQL queries from natural-language statements using the new NL_SQL routine, making it easier for you to interact with databases. This feature collects information on the schemas, tables, and columns that you have access to, and then uses a Large Language Model (LLM) to generate an SQL query for the question pertaining to your data. It also lets you run the generated query and view the result set.
今回は、この機能を触って確認した内容を公開しようと思います。
なお、過去にデータベースとAIを組み合わせた検証記事も公開してますので、興味がある方は、ご一読下さい。
- MCPサーバを使って Oracle Database と会話してみた(SQLcl)
- MCPサーバを使って Oracle Database と会話してみた(FastMCP & Select AI)
- Aurora MySQL で Bedrock を使ってみた
検証インスタンス
- Oracle Cloud / MySQL HeatWave Version 9.4.1(大阪リージョン)
要件確認
MySQL HeatWaveのGenAI機能にプラスして、この機能を使用するにあたり、デフォルトで使用されるLLMは、 meta.llama-3.3-70b-instruct
となりますが、このLLMを使用する場合には、Generative AI Service との連携が必要になる為、以下のリファレンスの「Before You Begin」をご確認下さい。
検証
以前、検証に使用したオープンソースのECサイト構築パッケージ EC-CUBE バージョン4.3のデータがあった為、そちらのデータを使用して検証していきます。
この機能を使用するには、 sys.NL_SQL() プロシージャをCALLします。
まずは、以下のように実行してみます。
1 2 |
mysql> SET @input="どんな商品があるの?商品名の一覧を5件表示して。"; mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); |
第3引数で、対象のスキーマと、使用するモデルIDを指定しています。
現時点で、指定可能なモデルIDは、以下のみとなります。
- meta.llama-3.3-70b-instruct
- llama3.2-3b-instruct-v1
- llama3.1-8b-instruct-v1
実行結果は以下のようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
+-------------------------------------------------------------+ | Executing generated SQL statement... | +-------------------------------------------------------------+ | SELECT name FROM eccube.dtb_product ORDER BY RAND() LIMIT 5 | +-------------------------------------------------------------+ 1 row in set (3.976 sec) +--------------------------------------------------------+ | name | +--------------------------------------------------------+ | ライムスイカバー | | トリコロール・アイス | | 太陽の恵みたっぷり!無添加マンゴーアイス | | 黄金色の誘惑。濃厚プレミアムパンプキンアイス | | お菓子デザート!チョコサンド | +--------------------------------------------------------+ 5 rows in set (2.848 sec) Query OK, 0 rows affected (2.849 sec) |
dtb_product
テーブルの name
列の値を正しく認識して、結果が返されています。
特にテーブルやカラムにコメントがない状態でも、判別できているので、かなり期待できそうです。
ちなみに、デフォルトでは実行されたSQLも返されますが、こちらは、第3引数のオプションに、 'verbose',0
を追加することで、生成されたSQLの表示を抑制し、結果の一覧だけを返すことができます。
続いて、第2引数の output
にどのようなデータが返されてるか見てみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
mysql> SELECT JSON_PRETTY(@output)\G *************************** 1. row *************************** JSON_PRETTY(@output): { "tables": [ "eccube.customers", "eccube.daily_product_class_sales_data", "eccube.dtb_authority_role", "eccube.dtb_base_info", "eccube.dtb_block", "eccube.dtb_block_position", "eccube.dtb_calendar", "eccube.dtb_cart", "eccube.dtb_cart_item", "eccube.dtb_category", ・・・(省略) ], "schemas": [ "eccube" ], "model_id": "meta.llama-3.3-70b-instruct", "sql_query": "SELECT <code>name</code> FROM <code>eccube</code>.<code>dtb_product</code> ORDER BY RAND() LIMIT 5", "is_sql_valid": 1 } |
マニュアル に書かれている通り、以下の内容が返されていることが分かります。
出力項目 | 内容 |
---|---|
tables,schemas | SQLの生成する為に考慮されたテーブルとデータベースのリスト |
model_id | 使用したモデルID |
sql_query | 生成されたSQL |
is_sql_valid | 生成されたSQLが有効化かどうか(0:無効 / 1:有効) |
商品名 = dtb_product.name
という判別が自動でできていた為、別途、products
テーブルが存在した場合にどうなるのかを見てみたいと思います。
以下のようなテーブルとデータを作って確認してみます。
1 2 3 4 5 6 7 8 9 10 11 |
mysql> SELECT * FROM products; +------------+--------------+-------------+------------+ | product_id | product_name | category | unit_price | +------------+--------------+-------------+------------+ | 1 | Laptop | Electronics | 1000.00 | | 2 | Book | Books | 20.00 | | 3 | Shirt | Clothing | 50.00 | | 4 | Laptop | Electronics | 1000.00 | | 5 | Book | Books | 20.00 | | 6 | Shirt | Clothing | 50.00 | +------------+--------------+-------------+------------+ |
それでは、同じ質問をしてみましょう。
1 2 |
mysql> SET @input="どんな商品があるの?商品名の一覧を5件表示して。"; mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); |
結果は、新しく追加した products
テーブルが参照されるようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
+------------------------------------------------------------------+ | Executing generated SQL statement... | +------------------------------------------------------------------+ | SELECT product_name FROM eccube.products ORDER BY RAND() LIMIT 5 | +------------------------------------------------------------------+ 1 row in set (10.791 sec) +--------------+ | product_name | +--------------+ | Book | | Book | | Laptop | | Laptop | | Shirt | +--------------+ 5 rows in set (10.791 sec) Query OK, 0 rows affected (10.793 sec) |
こういった似たようなリソース名が複数存在した場合にどれを参照すればいいかLLMで判断できないようなケースもあるかと思います。
その場合の対応の1つとして、引数で参照するテーブルを指定する方法があります。
先程の schemas
オプションを tables
オプションに変更して以下のように実行することで、dtb_product
を参照するようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('tables',JSON_ARRAY(JSON_OBJECT('schema_name', 'eccube','table_name', 'dtb_product')),'model_id','meta.llama-3.3-70b-instruct')); +---------------------------------------------+ | Executing generated SQL statement... | +---------------------------------------------+ | SELECT name FROM eccube.dtb_product LIMIT 5 | +---------------------------------------------+ 1 row in set (3.090 sec) +--------------------------------------------------------+ | name | +--------------------------------------------------------+ | 彩のジェラートCUBE | | 甘酸っぱい誘惑!チェリーアイスサンド | | みんな大好き!なめらかバニラアイス | | 新鮮な自家製ブルーベリーのジェラート | | とろける口どけ。濃厚チョコアイス | +--------------------------------------------------------+ 5 rows in set (3.090 sec) Query OK, 0 rows affected (3.091 sec) |
但し、この方法では、質問内容に沿った情報がどこにあるのかを実行者が把握しておく必要があります。
また、質問内容に応じて実行パラメータを変更するのは実用的ではありません。
この問題には、テーブルやカラムにコメントを付与して判別するというよくある機能が、MySQL HeatWave にも実装されているので、これを利用してみます。
まずは、products
テーブルに「商品情報」ではなく、「商品カテゴリ情報」というコメントを付与してみました。
1 2 3 4 5 6 7 8 9 10 11 |
mysql> ALTER TABLE products COMMENT = '商品カテゴリ情報'; mysql> SHOW CREATE TABLE products\G *************************** 1. row *************************** Table: products Create Table: CREATE TABLE products ( product_id int NOT NULL AUTO_INCREMENT, product_name varchar(255) DEFAULT NULL, category varchar(100) DEFAULT NULL, unit_price decimal(10,2) DEFAULT NULL, PRIMARY KEY (product_id) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品カテゴリ情報' |
しかし、これだけでは、products
テーブルに商品名の情報があると判断されているようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +------------------------------------------------------------------+ | Executing generated SQL statement... | +------------------------------------------------------------------+ | SELECT product_name FROM eccube.products ORDER BY RAND() LIMIT 5 | +------------------------------------------------------------------+ 1 row in set (10.567 sec) +--------------+ | product_name | +--------------+ | Book | | Laptop | | Shirt | | Laptop | | Book | +--------------+ 5 rows in set (1 min 3.468 sec) Query OK, 0 rows affected (1 min 3.470 sec) |
では、dtb_product
テーブルに「商品情報」のコメントを追加してみました。
1 2 3 4 5 6 7 8 9 10 11 |
mysql> ALTER TABLE dtb_product COMMENT = '商品情報'; mysql> SHOW CREATE TABLE dtb_product\G *************************** 1. row *************************** Table: dtb_product Create Table: CREATE TABLE dtb_product ( id int unsigned NOT NULL AUTO_INCREMENT, creator_id int unsigned DEFAULT NULL, product_status_id smallint unsigned DEFAULT NULL, name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, (省略) ) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='商品情報' |
テーブルにコメント追加後に、もう1度実行してみると、想定通り dtb_product
テーブルを参照してくれました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +---------------------------------------------+ | Executing generated SQL statement... | +---------------------------------------------+ | SELECT name FROM eccube.dtb_product LIMIT 5 | +---------------------------------------------+ 1 row in set (4.85 sec) +--------------------------------------------------------+ | name | +--------------------------------------------------------+ | 彩のジェラートCUBE | | 甘酸っぱい誘惑!チェリーアイスサンド | | みんな大好き!なめらかバニラアイス | | 新鮮な自家製ブルーベリーのジェラート | | とろける口どけ。濃厚チョコアイス | +--------------------------------------------------------+ 5 rows in set (4.85 sec) Query OK, 0 rows affected (4.85 sec) |
次は、単一テーブルでなく、複数テーブルを参照するような質問をしてみましょう。
商品名だけでなく、その商品の価格も返すようにしてみます。
EC-CUBE4.3では、商品の価格の情報は、dtb_product
ではなく、商品の規格ごとに dtb_product_class
テーブルに保存されています。
以下のように質問を変えて実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
mysql> SET @input="どんな商品があるの?商品名の一覧を5件取得し、値段も表示して。"; Query OK, 0 rows affected (0.00 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +--------------------------------------------------------------+ | Executing generated SQL statement... | +--------------------------------------------------------------+ | SELECT product_name, unit_price FROM eccube.products LIMIT 5 | +--------------------------------------------------------------+ 1 row in set (2.37 sec) +--------------+------------+ | product_name | unit_price | +--------------+------------+ | Laptop | 1000.00 | | Book | 20.00 | | Shirt | 50.00 | | Laptop | 1000.00 | | Book | 20.00 | +--------------+------------+ 5 rows in set (2.37 sec) Query OK, 0 rows affected (2.37 sec) |
結果は残念ながら、products
テーブルを参照してしまいました。
商品情報と価格情報と見受けられるカラム名が products
テーブルには存在するからでしょうか?
そこで、実行する引数で dtb_product
と dtb_product_class
テーブルを指定すると、 dtb_product_class.price01
の値を返してくれました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
mysql> CALL sys.NL_SQL( -> @input, -> @output, -> JSON_OBJECT( -> 'tables', JSON_ARRAY( -> JSON_OBJECT('schema_name', 'eccube', 'table_name', 'dtb_product'), -> JSON_OBJECT('schema_name', 'eccube', 'table_name', 'dtb_product_class') -> ), -> 'model_id', 'meta.llama-3.3-70b-instruct' -> ) -> ); +-------------------------------------------------------------------------------------------------------------------------------+ | Executing generated SQL statement... | +-------------------------------------------------------------------------------------------------------------------------------+ | SELECT T1.name, T2.price01 FROM eccube.dtb_product AS T1 JOIN eccube.dtb_product_class AS T2 ON T1.id = T2.product_id LIMIT 5 | +-------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (2.29 sec) +--------------------------------------------------------+-----------+ | name | price01 | +--------------------------------------------------------+-----------+ | 彩のジェラートCUBE | 115000.00 | | 甘酸っぱい誘惑!チェリーアイスサンド | 3000.00 | | みんな大好き!なめらかバニラアイス | NULL | | 新鮮な自家製ブルーベリーのジェラート | NULL | | とろける口どけ。濃厚チョコアイス | NULL | +--------------------------------------------------------+-----------+ 5 rows in set (2.29 sec) Query OK, 0 rows affected (2.29 sec) |
ですが、EC-CUBEの画面上に表示される価格は、dtb_product_class.price02
なので、この情報を返すように、実行時にテーブル名を指定するのではなく、コメント情報から判別させてみます。
テーブルとカラムにコメントを追加します。
1 2 |
mysql> ALTER TABLE dtb_product_class COMMENT = '商品価格情報'; mysql> ALTER TABLE dtb_product_class MODIFY COLUMN price02 DECIMAL(12,2) NOT NULL COMMENT '販売価格'; |
質問内容の「値段」を「販売価格」に変更して実行すると、想定通り dtb_product_class.price02
の値が返されました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
mysql> SET @input="どんな商品があるの?商品名の一覧を5件取得し、販売価格も表示して"; Query OK, 0 rows affected (0.00 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +-------------------------------------------------------------------------------------------------------------------------------+ | Executing generated SQL statement... | +-------------------------------------------------------------------------------------------------------------------------------+ | SELECT T1.name, T2.price02 FROM eccube.dtb_product AS T1 JOIN eccube.dtb_product_class AS T2 ON T1.id = T2.product_id LIMIT 5 | +-------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (6.61 sec) +--------------------------------------------------------+---------+ | name | price02 | +--------------------------------------------------------+---------+ | 彩のジェラートCUBE | 1200.00 | | 甘酸っぱい誘惑!チェリーアイスサンド | 380.00 | | みんな大好き!なめらかバニラアイス | 500.00 | | 新鮮な自家製ブルーベリーのジェラート | 450.00 | | とろける口どけ。濃厚チョコアイス | 620.00 | +--------------------------------------------------------+---------+ 5 rows in set (6.61 sec) Query OK, 0 rows affected (6.61 sec) |
今度は「一番売れている商品って何?」っていう質問には、注文商品 dtb_order_item
テーブルから注文数の最も多い商品を返してくれました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mysql> SET @input="一番売れている商品って何?"; Query OK, 0 rows affected (0.01 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Executing generated SQL statement... | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | SELECT T1.name FROM eccube.dtb_product AS T1 JOIN eccube.dtb_product_class AS T2 ON T1.id = T2.product_id JOIN eccube.dtb_order_item AS T3 ON T2.id = T3.product_class_id GROUP BY T1.name ORDER BY SUM(T3.quantity) DESC LIMIT 1 | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (8.89 sec) +--------------------------------------------------+ | name | +--------------------------------------------------+ | 大人な味わいのコーヒーアイスバー | +--------------------------------------------------+ 1 row in set (8.89 sec) Query OK, 0 rows affected (8.90 sec) |
商品名で GROUP BY されているのを ID でした方がいいかな?とかいうのはあるのですが、追加でコメント付与はしない中で、それなりに妥当な情報を抽出しているように思います。
さらに、テーブル一覧やVIEWも問題なく取得できました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
mysql> SET @input="テーブルの一覧を見せて。"; Query OK, 0 rows affected (0.00 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +--------------------------------------------------------------------------------+ | Executing generated SQL statement... | +--------------------------------------------------------------------------------+ | SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'eccube' | +--------------------------------------------------------------------------------+ 1 row in set (2.89 sec) +---------------------------------------+ | TABLE_NAME | +---------------------------------------+ | customers | | daily_product_class_sales_data | | dtb_authority_role | | dtb_base_info | | dtb_block | | dtb_block_position | | dtb_calendar | | dtb_cart | | dtb_cart_item | (省略) | v_daily_product_class_sales | +---------------------------------------+ 73 rows in set (2.89 sec) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mysql> SET @input="VIEWはありますか?"; Query OK, 0 rows affected (0.01 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +--------------------------------------------------------------------------------------------------------+ | Executing generated SQL statement... | +--------------------------------------------------------------------------------------------------------+ | SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_TYPE = 'VIEW' AND TABLE_SCHEMA = 'eccube' | +--------------------------------------------------------------------------------------------------------+ 1 row in set (2.65 sec) +-----------------------------+ | TABLE_NAME | +-----------------------------+ | v_daily_product_class_sales | +-----------------------------+ 1 row in set (2.65 sec) Query OK, 0 rows affected (2.65 sec) |
これまで、コメントを付与したり、対象のテーブルを指定することで臨むデータを返してくれましたが、テーブルデータに全く関係ない内容で実行するとどうなるのでしょうか?
「明日の東京の天気は?」として実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
mysql> SET @input="明日の東京の天気は?"; Query OK, 0 rows affected (0.00 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +---------------------------------------------------------------------------------------------------------+ | Executing generated SQL statement... | +---------------------------------------------------------------------------------------------------------+ | SELECT title FROM eccube.dtb_calendar WHERE holiday = CURDATE() + INTERVAL '1' DAY AND title = '東京' | +---------------------------------------------------------------------------------------------------------+ 1 row in set (5.51 sec) Empty set (5.51 sec) Query OK, 0 rows affected (5.52 sec) |
無理やり感はありますが、存在するテーブルとカラム名から想定してSQLを実行しています。
また、「500円持ってて、210円のジュースを買いました。残ってるお金は?」という小学生の算数のような内容であった場合には、テーブルを参照せずに、ちゃんと計算した結果を返してくれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mysql> SET @input="500円持ってて、210円のジュースを買いました。残ってるお金は?"; Query OK, 0 rows affected (0.00 sec) mysql> CALL sys.NL_SQL(@input, @output, JSON_OBJECT('schemas',JSON_ARRAY('eccube'),'model_id','meta.llama-3.3-70b-instruct')); +-------------------------------------+ | Executing generated SQL statement...| +-------------------------------------+ | SELECT 500 - 210 AS remaining_money | +-------------------------------------+ 1 row in set (2.74 sec) +-----------------+ | remaining_money | +-----------------+ | 290 | +-----------------+ 1 row in set (2.74 sec) Query OK, 0 rows affected (2.74 sec) |
まとめ
MySQL HeatWave 9.4.1で新たに追加された自然言語でSQLを実行する機能について、実際のEC-CUBEのデータを使用して検証を行いました。
実際に使ってみて、テーブルとカラムに適切なコメントを設定していれば、非常に使える機能だなと感じました。
また、本記事内では LLM に meta.llama-3.3-70b-instruct
を指定して実行していますが、llama3.2-3b-instruct-v1
と llama3.1-8b-instruct-v1
を指定した場合、うまくSQLが生成されなかったり、実行エラーになったりするケースが多く見られたので、現在指定できるLLMのうちでは、 meta.llama-3.3-70b-instruct
を使用するのがマストかもしれません。
この機能により、SQLを知らないユーザーでもデータベースから情報を抽出できる可能性が広がると思いますので、是非、MySQL HeatWaveを使用してみて下さい。