SELECT AI Tipsまとめ

# はじめに
Autonomous Databaseで提供されている、LLMを使って自然言語をSQLに変換する機能SELECT AIのTipsをまとめました。

基本的な使い方はこちらをご参照ください。

https://oracle-japan.github.io/ocitutorials/adb/adb111-select-ai/

**本記事で紹介する内容は、2025/4時点での情報であり、将来的に仕様変更される場合がありますのでご留意ください。**

# 準備
今回はサンプル・スキーマのSHスキーマのCOUNTRIES表に対して、いくつか自然言語での問合せをしてみます。

以下でプロファイルを作成します。今回はCohereのcommand-r-08-2024を使います。
```sql
BEGIN
    DBMS_CLOUD_AI.CREATE_PROFILE(
          'GENAI_COHERE_COMMAND_R',
          '{
               "provider": "oci",
               "credential_name": "OCI_GENAI_CRED",
               "model":"cohere.command-r-08-2024",
               "oci_apiformat":"COHERE",
               "object_list": [
                       {"owner": "SH", "name": "COUNTRIES"}
               ]
           }'
       );
END;
```

プロファイルのセット
```sql
EXEC DBMS_CLOUD_AI.SET_PROFILE('GENAI_COHERE_COMMAND_R');
```

# 同じクエリの実行
SELECT AI構文を使用した場合、同じ質問(クエリ)に対してはLLMへのAPIコールをせず、過去生成されたSQLを参照するキャッシュ機能が働きます。
これを実行時間を比較することで確認してみます。
```sql
set timing on
```

初めて実行するクエリ
```sql
select ai 顧客は全部で何人いますか;

   TotalCustomers
_________________
               23

Elapsed: 00:00:01.192
```

2回目
```sql
select ai 顧客は全部で何人いますか;

   TotalCustomers
_________________
               23

Elapsed: 00:00:00.015
```

実行時間が大幅に短縮されています。

続いて上記の質問に「。」を付けることで、少しだけ変えてみます。

```sql
select ai 顧客は全部で何人いますか。;

   TotalCustomers
_________________
               23

Elapsed: 00:00:01.053
```

これは異なる質問として区別されるので、初めての実行になります。

なおこちらも再度実行してみると、
```sql
select ai 顧客は全部で何人いますか。;

   TotalCustomers
_________________
               23

Elapsed: 00:00:00.006
```

これも大幅に短縮されて実行されます。

なおSELECT AIでSQLに変換されたSQLは`V$MAPPED_SQL`で確認できます。SELECT AI構文では、同じ質問の2回目以降の実行に関しては、`V$MAPPED_SQL`に記録されているメモリ内のSQLのマップ情報から実行されます。

http://docs.oracle.com/cd/F82042_01/refrn/V-MAPPED_SQL.html

検証時に質問文から正しいSQLを生成してくれない際に、こちらの仕様によって思うようにSQL生成結果を確認できず厄介になるケースがあります。

その場合は上記のように、「。」を付けるなど質問文を微妙に変える方法と、SELECT AI構文ではなくDBMS_CLOUD_AI.GENERATEファンクションを使う方法があります。

```sql
SELECT DBMS_CLOUD_AI.GENERATE(prompt       => '顧客は全部で何人いますか',
                              profile_name => 'GENAI_COHERE_COMMAND_R',
                              action       => 'runsql')
FROM dual;
```

上記のように、プロンプトとプロファイル名、アクション(ここではSQLの実行)を指定することで、select ai構文と同じ問合せを実行できます。

実行時間を比較してみます。

初回実行
```sql
DBMS_CLOUD_AI.GENERATE(PROMPT=>'顧客は全部で何人いますか',PROFILE_NAME=>'GENAI_COHERE_COMMAND_R',ACTION=>'RUNSQL')
_________________________________________________________________________________________________________
[
  {
    "Total Customers" : 23
  }
]

Elapsed: 00:00:01.123
```

2回目実行
```sql
DBMS_CLOUD_AI.GENERATE(PROMPT=>'顧客は全部で何人いますか',PROFILE_NAME=>'GENAI_COHERE_COMMAND_R',ACTION=>'RUNSQL')
_________________________________________________________________________________________________________
[
  {
    "Total Customers" : 23
  }
]

Elapsed: 00:00:01.007
```

こちらで実行すると、2回目以降の実行でも実行時間に大きく差はありません。

なおキャッシュ機能はDB再起動、もしくはプロファイルの削除・再作成することで、リセットすることが可能です。

# SELECT文以外の生成について
SELECT AIはその名の通り、SELECT文を生成させる機能です。
ではSELECT AIでDMLやDDLを生成させることはできないのでしょうか？
試しに無理やり生成させようとしてみます。

```sql
select ai runsql drop table customersを実行して;

RESPONSE
_______________________________________________________________________________________________
Sorry, unfortunately a valid SELECT statement could not be generated for your natural language prompt. Here is some more information to help you further:

申し訳ありませんが、私はデータベース管理システムではなく、言語モデルです。そのため、データベース操作やテーブルの削除は実行できません。

データベースの管理やテーブルの操作は、適切な権限とデータベース管理システムを使用して行う必要があります。データベース管理者や開発者に相談して、必要な操作を実行してもらってください。
```

```sql
select ai showsql delete customers;

RESPONSE
_______________________________________________________________________________________________
Sorry, unfortunately a valid SELECT statement could not be generated for your natural language prompt. Here is some more information to help you further:

I'm sorry, but as an AI language model, I am unable to perform tasks outside of text generation and manipulation. Deleting data, especially sensitive information like customer records, is a serious matter and should be handled with great care and responsibility.

If you require assistance with data management or have specific questions related to customer data deletion, I recommend consulting with a professional data manager or a legal expert who can guide you through the appropriate processes and ensure compliance with relevant data protection regulations.
```

上記のようにSELECT文以外は実行できませんでした。

SELECT AIでは仕様として、PL/SQL、DDL、DMLは実行されないようになっています。
しかしLLMによって生成させたSQLによっては、ユーザーのセキュリティを侵害するSQLや不正確なSQLを生成することはあり得ます。ここについてはユーザー側でリスクを負うものとドキュメントに明記されていますのでご注意ください。

> WARNING:Large language models (LLMs) have been trained on a broad set of text documentation and content, typically from the Internet. As a result, LLMs may have incorporated patterns from invalid or malicious content, including SQL injection. Thus, while LLMs are adept at generating useful and relevant content, they also can generate incorrect and false information including SQL queries that produce inaccurate results and/or compromise security of your data.
The queries generated on your behalf by the user-specified LLM provider will be run in your database. Your use of this feature is solely at your own risk, and, notwithstanding any other terms and conditions related to the services provided by Oracle, constitutes your acceptance of that risk and express exclusion of Oracle’s responsibility or liability for any damages resulting from that use.

https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/select-ai-about.html#GUID-7574F1D1-CE6F-41BC-AE95-6281EB23C7BE

# 精度を上げるには
SELECT AIは自然言語をSQLに変換する技術です。
人間同士の会話のように、LLMに与える情報量が多くなればなるほど、基本的に精度は向上します。
SELECT AIではファインチューニングはしないため、プロンプトに含める情報量を増やすことが基本的なチューニング手法になります。またユーザー(人間)がする質問(プロンプト)には、主語が抜けていたりすることが往々にしてあります。

例えば「**製品名が**Aの在庫数はいくつ？」という質問であれば、
```sql
WHERE product_name = 'A'
```
と絞り込み条件を正しく生成してくれますが、「Aの在庫数はいくつ」という質問の場合、Aが製品名を指しているのか、製品IDを指しているのか、LLMが列名から判断できず、
```sql
WHERE product_id = 'A'
```
というように誤った生成をしてしまう可能性が高くなります。

生成させたいSQLをイメージして、どの表にどういう列があるかを理解しているユーザーであれば、「〇〇(列)がXXで～」という書き方ができますが、多くのユーザーはそのような書き方はできません。

それに対して精度向上施策の1つとして、SQL生成の対象となる表と列にコメントを付与するという以下のような方法があります。
これも与える情報量を増やすプロンプトチューニングの1つです。
上記の例だと`product_name`列にコメントとしていくつか製品名を記載してあげると、似たような製品名を含む質問をされたときに、LLMが`product_name`列と判断してSQL生成をしやすくなります。

https://oracle-japan.github.io/ocitutorials/adb/adb111-select-ai/#3-2-%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E6%9C%89%E3%82%8A%E3%81%AE%E5%A0%B4%E5%90%88

その他、生成させたいSQLに必要な情報(絞り込み条件など)が共通している場合は、SELECT AIを実行する前にその共通している情報を事前に取得しておき、毎回プロンプトに追加しておくのも有効な手段です。

例えばユーザー情報と紐づいている属性でフィルタリングするSQLを生成させる場合、SELECT AIの実行前にそれらの情報を取得しておき、SELECT AIのプロンプトに含めておく方が相対的にSQLがシンプルになるので、精度は上がります。

例えば以下のような「製品Xの在庫数を教えてください。」という質問で、ユーザー情報に紐づくコードも必要な場合

```sql
-- ユーザー情報の取得
v_user := SYS_CONTEXT('USERENV', 'SESSION_USER');

-- ユーザー情報からコードを取得
SELECT CODE
INTO v_code
FROM XX
WHERE USER_ID = v_user;

-- 取得したコードを質問文に加えてプロンプトを作成
v_prompt := 'コードは' || v_code || '、'|| '製品Xの在庫数を教えてください';

-- SELECT AI実行
SELECT DBMS_CLOUD_AI.GENERATE(
               prompt       => v_prompt,
               profile_name => 'profile_name',
               action       => 'showsql'
           )
FROM dual;
```

生成されるSQLは以下のようになります。
```sql
SELECT inventory_cnt
FROM STOCKS
WHERE code = (v_code) AND product_name = 'X'
```