はじめに
SELECT AIに自然言語で質問すると、SQL自体は生成されるものの、期待とは違うテーブルが使われることがあります。
たとえば「2026年の売上を顧客別に集計して」と質問したとき、データベース内に次のテーブルがあったらどうでしょうか。
- 受注を管理する
ORDER_HEADERS、ORDER_LINES - 請求を管理する
INVOICES、INVOICE_LINES - 入金を管理する
RECEIPTS - 過去システムから移行した
ARCHIVE_ORDERS
人間同士でも、「売上」が受注金額、請求金額、入金金額のどれを指すのか確認が必要です。LLMに全テーブルを見せたままでは、さらに選択肢が増えます。
このようなとき、すぐにモデルやプロンプトを変更したくなります。しかし、最初に見直したいのはSELECT AIへ見せるデータベースオブジェクトの範囲です。
本記事では、SELECT AIのobject_listとenforce_object_listを使い、業務単位で参照対象を絞り込む方法を説明します。さらに、複数のAIプロファイルをアプリケーションから安全に使い分ける設計まで踏み込みます。
本記事の想定環境
本記事の設定例は、2026年9月時点の次の環境を想定しています。
- Autonomous AI Database Serverless 26ai
- AI providerとしてOCI Generative AIを使用
-
DBMS_CLOUD_AIでAIプロファイルを作成 - 説明用の
SALESスキーマを使用
本文のSALESスキーマ、テーブル名、列名は説明用です。設定例を試す際は、実環境に存在する対象オブジェクトへ置き換えてください。
Select AIの対応機能は、データベースのバージョンやサービス形態によって異なります。特に、後述するobject_list_mode=automatedはすべての環境で利用できるわけではありません。Autonomous AI Database 19c、Oracle AI Database、Dedicated環境などで試す場合は、Oracle AI Database Select AI Capability Matrixで対応状況を確認してください。
この記事で伝えたいこと
先に結論をまとめます。
- SELECT AIのAIプロファイルは、データベース全体ではなく業務目的ごとに分ける
-
object_listには、その業務質問に必要なテーブル・ビューだけを指定する -
enforce_object_listを有効にして、指定外オブジェクトの利用を抑止する -
SHOWPROMPTとSHOWSQLを使い、SELECT AIへ何が渡り、何が生成されたか確認する - 固定した質問セットで、絞り込み前後の生成SQLを比較する
記事の後半では、アプリケーションへ組み込む場合の補足として、プロファイル選択、設定同期、生成SQLの検証も扱います。
object_listだけで「売上」の業務定義まで解決できるわけではありません。ただし、SQL生成前の探索範囲を正しく設計することで、誤ったテーブルやJOIN経路が選ばれる可能性を減らせます。
SELECT AIは何を材料にSQLを生成するのか
SELECT AIは、自然言語の質問だけをLLMへ送っているわけではありません。Oracle Databaseが質問をデータベースのメタデータで拡張し、そのプロンプトをLLMへ渡します。
Oracle公式ドキュメントでは、NL2SQLのプロンプトにスキーマ定義、テーブル・列コメント、データディクショナリの情報などが含まれ得ると説明されています。一方、通常のSQL生成では、テーブルやビューの実データそのものをプロンプト拡張に利用するわけではありません。
概念的には、次の流れです。
利用者の質問
+
AIプロファイルで対象として指定したオブジェクトのメタデータ
↓
LLMへ渡す拡張プロンプト
↓
Oracle SQLを生成
ここで重要なのは、候補となるテーブルが増えるほど、LLMが判断しなければならない対象も増えることです。
同じ意味に見える列、よく似た履歴テーブル、別業務のマスタ、廃止予定のビューなどが混ざると、次の問題が起こりやすくなります。
- 正しい業務テーブルではなく、名前が似ているテーブルを選ぶ
- 本来とは異なる外部キーや列名からJOINを組み立てる
- 現行テーブルではなく、履歴テーブルや移行テーブルを参照する
- 質問と無関係なメタデータがプロンプトに増える
- 同じ質問でも生成SQLが安定しにくくなる
したがって、精度改善の最初の一歩は「情報を追加すること」ではなく、不要な候補を減らすことです。
業務プロファイルという考え方
AIプロファイルをデータベースユーザーやスキーマと1対1で作ると、範囲が大きくなりがちです。そこで、質問の目的に合わせた「業務プロファイル」として分割します。
本記事では、業務ごとの対象オブジェクトや質問範囲をまとめた設計上の単位を「業務プロファイル」と呼び、OracleのAIプロファイルに対応づけます。「業務プロファイル」は本記事独自の設計用語であり、Oracleの機能名ではありません。
たとえば、同じSALESスキーマを使っていても、次のように分けられます。
| 業務プロファイル | 主な質問 | 対象オブジェクトの例 |
|---|---|---|
| 受注分析 | 受注額、受注件数、商品別受注数量 |
ORDER_HEADERS、ORDER_LINES、CUSTOMERS、PRODUCTS
|
| 請求分析 | 請求額、請求件数、顧客別請求額 |
INVOICES、INVOICE_LINES、CUSTOMERS
|
| 入金分析 | 入金額、未入金、入金遅延 |
RECEIPTS、INVOICES、CUSTOMERS
|
CUSTOMERSのように複数業務で利用するテーブルが重複しても問題ありません。大切なのは、「このプロファイルで答える質問には、どのテーブルが必要か」という基準で選ぶことです。
巨大な1プロファイルを避ける
次のようなAIプロファイルは、運用開始時には便利に見えます。
{
"object_list": [
{ "owner": "SALES" },
{ "owner": "BILLING" },
{ "owner": "FINANCE" }
]
}
nameを省略してownerだけを指定すると、そのownerの対象オブジェクトを広く候補にできます。探索用途には便利ですが、業務が異なる大量のオブジェクトを1つのプロファイルへ集めることにもなります。
精度を安定させたい場合は、まずはownerとobject nameを明示した最小構成から始め、必要性を確認しながら追加する方が原因を追いやすくなります。
絞り込みの目的は、質問に必要なテーブルと結合経路を残し、無関係な候補を除くことです。必要なマスタや中間テーブルまで外さないようにします。
object_listとenforce_object_listを設定する
以下は、OCI Generative AIをproviderとして、受注分析用のAIプロファイルを作る例です。
事前に資格証明やOCI Generative AIへのアクセス設定が必要です。環境構築については、Oracle公式のExample: Select AI with OCI Generative AIを参照してください。
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'SALES_ORDER_AI',
attributes => q'~{
"provider": "oci",
"credential_name": "GENAI_CRED",
"oci_compartment_id": "<your_compartment_ocid>",
"region": "<your_oci_genai_region>",
"model": "<your_model_id>",
"object_list": [
{"owner": "SALES", "name": "ORDER_HEADERS"},
{"owner": "SALES", "name": "ORDER_LINES"},
{"owner": "SALES", "name": "CUSTOMERS"},
{"owner": "SALES", "name": "PRODUCTS"}
],
"enforce_object_list": "true"
}~',
description => '受注分析用のSELECT AIプロファイル'
);
END;
/
<your_oci_genai_region>と<your_model_id>は、利用するOCI Generative AIのリージョンとモデルに置き換えてください。OCI providerのregionを省略した場合、デフォルトはus-chicago-1です。モデルの提供状況はリージョンごとに異なるため、実際にモデルを利用できるリージョンを明示します。
設定の役割は次のとおりです。
| 属性 | 役割 |
|---|---|
object_list |
自然言語からSQLへ変換するときに対象となるテーブル・ビューなどを指定する |
enforce_object_list |
trueの場合、object_listで指定したテーブルだけを使うようLLMへ強制する |
region |
OCI Generative AIを利用するリージョンを指定する |
model |
SQL生成に利用するモデルを指定する |
Oracle公式のProfile Attributesでは、enforce_object_list=falseの場合、LLMがユーザーからアクセス可能な別のテーブルを利用できると説明されています。デフォルトもfalseなので、対象を厳密に絞りたい場合は明示的に有効化します。
- DBMS_CLOUD_AI Package - Profile Attributes
- Generate and Improve SQL Queries - Restrict Table Access in AI Profile
テーブル名・ビュー名はowner付きで管理する
複数スキーマに同名オブジェクトが存在する環境では、nameだけでなくownerも指定します。
{"owner": "SALES", "name": "CUSTOMERS"}
引用識別子を使っている場合は、大文字・小文字を機械的に変換しないよう注意が必要です。たとえば"Mixed_Case"とMIXED_CASEは、Oracle SQLでは同じ識別子とは限りません。
アプリケーションからobject_listを生成する場合は、文字列を単純に.で分割するのではなく、Oracleの引用識別子規則を考慮してownerとobject nameを保持します。
補足:object_list_mode=automatedとの違い
Oracle AI Database 26aiでは、object_list_modeをautomatedにして、質問に関連するオブジェクトのメタデータを自動選択する構成も利用できます。Oracle公式の例では、この機能によりオブジェクト選択用のベクトル索引が作られることが説明されています。
ただし、object_list_mode=automatedと、業務範囲の設計は別の問題です。
-
object_list:そのAIプロファイルで候補にしてよい業務範囲 -
object_list_mode:候補の中から、どのメタデータをLLMへ渡すか -
enforce_object_list:生成SQLが候補外のテーブルを使わないようにするか
大規模スキーマで自動選択を使う場合でも、無関係な業務領域まで無制限に候補へ入れるのではなく、先に業務プロファイルの境界を決めておく方が管理しやすくなります。
利用できる属性はデータベースのバージョンやサービス形態で異なる可能性があります。導入前にOracle AI Database Select AI Capability Matrixも確認してください。
保存されたAIプロファイルを確認する
AIプロファイルを作ったら、作成処理が成功したことだけで終わらせず、Oracle側に保存された属性を確認します。
SELECT
profile_name,
attribute_name,
DBMS_LOB.SUBSTR(attribute_value, 4000, 1) AS attribute_value,
last_modified
FROM user_cloud_ai_profile_attributes
WHERE UPPER(profile_name) = 'SALES_ORDER_AI'
AND attribute_name IN (
'object_list',
'object_list_mode',
'enforce_object_list'
)
ORDER BY attribute_name;
このSQLは目視確認用です。
DBMS_LOB.SUBSTR(attribute_value, 4000, 1)で取得しているのは、CLOBである属性値の先頭4,000文字だけです。大きなobject_listの完全一致を判定する用途には使えません。
USER_CLOUD_AI_PROFILE_ATTRIBUTESでは、現在のAIプロファイルに保存されているobject_listなどの属性を確認できます。
ここでは、object_list、enforce_object_list、必要に応じてobject_list_modeが想定どおり保存されていることを確認します。アプリケーションとの厳密な同期確認は、後半の「アプリケーションへ組み込む場合」で説明します。
SHOWSQLで生成SQLを確認する
対象範囲を設定したら、まずはSQLを実行せずに生成結果を確認します。アプリケーションから呼び出す場合は、セッションのデフォルトプロファイルに依存しないDBMS_CLOUD_AI.GENERATEが扱いやすいです。
以下は、生成結果を評価するための業務上の基準です。掲載コードのプロンプトは「2026年の受注金額を顧客別に集計して」だけなので、数量×単価などの定義をLLMへ明示的に渡しているわけではありません。業務定義をモデルへ伝える方法は次回扱います。
- 受注金額は、
ORDER_LINESの数量×単価の合計 - 対象期間は2026年とし、
ORDER_HEADERSの受注日で絞り込む - 値引き、税、取消は考慮しない
-
ORDER_HEADERSとORDER_LINESは受注ID、CUSTOMERSとは顧客IDで結合する
実際の評価では、自社の業務定義に合わせて、値引き、税、取消、通貨、会計期間などの扱いも先に決めます。
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => '2026年の受注金額を顧客別に集計して',
profile_name => 'SALES_ORDER_AI',
action => 'showsql'
) AS generated_sql
FROM dual;
この例は固定の
promptを使っています。利用者入力を渡す場合は、action => 'showsql'だけに依存せず、入力によるアクション変更を防ぐ必要があります。詳しくは後半の「アプリケーションへ組み込む場合」で説明します。
次は、説明用の想定出力です。モデル、メタデータ、コメントなどによって実際のSQLは変わります。
SELECT
c.customer_id,
c.customer_name,
SUM(ol.quantity * ol.unit_price) AS order_amount
FROM sales.order_headers oh
JOIN sales.order_lines ol
ON ol.order_id = oh.order_id
JOIN sales.customers c
ON c.customer_id = oh.customer_id
WHERE oh.order_date >= DATE '2026-01-01'
AND oh.order_date < DATE '2027-01-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY order_amount DESC
この出力では、次を順に確認できます。
- 請求テーブルや入金テーブルではなく、受注テーブルを参照している
- 想定した受注IDと顧客IDでJOINしている
- 受注金額を「数量×単価」で計算している
- 受注日に対して2026年の範囲を指定している
- 顧客名ではなく、顧客IDを基準に集計している
SQLクライアントでAIプロファイルをセッションへ設定している場合は、次の形式でも確認できます。
EXEC DBMS_CLOUD_AI.SET_PROFILE('SALES_ORDER_AI');
SELECT AI SHOWSQL 2026年の受注金額を顧客別に集計して;
SELECT AIキーワードを利用できる環境と、DBMS_CLOUD_AI.GENERATEを使う環境には違いがあります。たとえばOracle公式ドキュメントでは、Database ActionsやAPEX ServiceではDBMS_CLOUD_AI.GENERATEを利用するよう案内されています。
SHOWPROMPTでLLMへ渡る範囲を確認する
SQLだけを見ても、なぜそのテーブルが選ばれたのか分からない場合があります。そのときはshowpromptを使い、SELECT AIが構築したプロンプトを確認します。
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => '2026年の受注金額を顧客別に集計して',
profile_name => 'SALES_ORDER_AI',
action => 'showprompt'
) AS augmented_prompt
FROM dual;
showpromptは、構築されたプロンプトを表示するためのアクションです。確認時には、意図しないスキーマのメタデータが含まれていないかを見ます。
出力形式は環境によって異なります。以下は実際の出力ではなく、確認対象を示す模式例です。
Database objects:
SALES.ORDER_HEADERS (...)
SALES.ORDER_LINES (...)
SALES.CUSTOMERS (...)
SALES.PRODUCTS (...)
User request:
2026年の受注金額を顧客別に集計して
ここにINVOICES、RECEIPTS、ARCHIVE_ORDERSなど、受注分析では使わないメタデータが含まれていないことを確認します。
ただし、出力にはスキーマ情報や追加指示が含まれる可能性があります。本番環境のshowprompt結果を、そのままログや公開記事へ貼り付けないようにしてください。
絞り込みの効果をどう評価するか
「良くなった気がする」で終わらせず、広いプロファイルと業務プロファイルを同じ質問セットで比較します。
1. 評価用の質問を固定する
たとえば、受注分析用に次の質問を用意します。
- 2026年の受注金額を顧客別に集計して
- 2026年9月に受注した未出荷受注を表示して
- 商品カテゴリ別の受注数量を多い順に表示して
- 顧客ごとの平均受注額を計算して
- 2026年8月から2026年9月までの受注件数を比較して
2. 期待するSQLの条件を定義する
SQL文字列が完全一致する必要はありません。少なくとも次を確認します。
- 期待するテーブルを参照している
- JOIN条件が正しい
- 集計式が正しい
- 日付条件が正しい列へ適用されている
- 粒度と
GROUP BYが一致している - 不要な履歴テーブルや別業務テーブルを参照していない
3. 同じ条件で複数回生成する
LLMの出力には揺らぎがあります。1回だけで判断せず、同じprovider、モデル、リージョン、質問で複数回生成します。比較するときは、変更する条件をプロファイルの対象範囲だけに絞ります。
また、試行回数、AIプロファイルの設定、生成SQLを記録し、改善しなかった質問も残します。これにより、たまたま成功した1回だけを根拠にすることを避けられます。
以下は評価表の記入例であり、実測結果ではありません。
| 質問 | プロファイル | 正しい対象テーブル | JOIN | 集計式 | 日付条件 | 集計粒度 | 総合判定 |
|---|---|---|---|---|---|---|---|
| 2026年の受注金額を顧客別に | 全社共通 | × | × | △ | × | × | 不正解 |
| 2026年の受注金額を顧客別に | 受注分析 | ○ | ○ | ○ | ○ | ○ | 正解 |
この形式で記録しておけば、テーブルを追加したときやモデルを変更したときの回帰テストにも利用できます。
アプリケーションへ組み込む場合
ここからは、SELECT AIを利用者向けアプリケーションへ組み込み、生成SQLを自動的に扱う場合の補足です。まず対象テーブルを絞ってSHOWPROMPTとSHOWSQLを確認する段階では、ここまでの手順だけでも検証を始められます。
利用者入力によるアクション変更を防ぐ
利用者が入力した文字列をそのままDBMS_CLOUD_AI.GENERATEのpromptへ渡す場合、action => 'showsql'だけで実行を常に防げるとは限りません。
Oracle公式ドキュメントでは、promptにSELECT AI <action>を含められ、プロンプト内で指定されたアクションが、GENERATEのaction引数より優先されると説明されています。
アプリケーションでは、SELECT AI <action>として解釈される入力を受け付けないなど、利用者入力によるアクションの変更を呼出し前に防ぎ、自然言語の質問本文だけをSQL生成専用の処理へ渡します。そのうえで、戻り値が生成SQLであることを確認し、後述する検証を通過するまで実行しません。
複数プロファイルをどう選ぶか
業務プロファイルを分けると、次に「利用者の質問をどのプロファイルへ渡すか」という問題が発生します。
小規模なシステムでは利用者に選んでもらう方法でも構いません。プロファイル数が増えた場合は、質問内容から候補を推薦し、必要なら利用者に確認してもらう方式が有効です。
質問を入力
↓
質問内の業務用語・テーブルの論理名・過去の質問例を照合
↓
候補プロファイルをスコアリング
↓
十分な確信がある → 推薦プロファイルを適用
確信が低い → 利用者に候補を表示して選択してもらう
たとえば「受注」「出荷」「商品別」という語が含まれていれば受注分析、「請求」「請求額」であれば請求分析を候補にできます。
ここで大切なのは、推薦を無条件に確定しないことです。上位候補の差が小さい場合や、一致する根拠が少ない場合は、利用者に確認した方が安全です。
なお、このプロファイル推薦はSELECT AIのネイティブ機能ではなく、SELECT AIを呼び出すアプリケーション側の設計です。
アプリケーション側とOracle側の範囲を同期する
本番アプリケーションでは、許可オブジェクトを次の2か所に持ちやすくなります。
- アプリケーションの業務プロファイル
- Oracleの
DBMS_CLOUD_AIAIプロファイル
この2つがずれると、画面上では対象外に見えるテーブルがLLMへ渡されたり、新しく追加したテーブルがSELECT AIから見えなかったりします。
そのため、アプリケーション側の業務プロファイルを正本とし、そこからOracleのAIプロファイル属性を生成する形にします。
業務プロファイルを更新
↓
owner + object nameを正規化
↓
DBMS_CLOUD_AIのAIプロファイルへ反映
↓
USER_CLOUD_AI_PROFILE_ATTRIBUTESから再取得
↓
期待するobject_listと一致することを確認
↓
一致した場合だけSQL生成を許可
同期判定では、ATTRIBUTE_VALUEのCLOB全体を取得してJSONとして解析します。その後、配列の文字列表現を比較するのではなく、ownerとnameの組を集合として比較します。要素の並び順や空白の違いではなく、許可オブジェクトの過不足を判定するためです。前半で示したDBMS_LOB.SUBSTR(..., 4000, 1)のSQLは表示用であり、この同期判定には使用しません。
同期に失敗したとき、古いプロファイルのまま処理を続行すると、精度問題を再現しにくくなります。同期状態が不明な場合はSQL生成を止め、プロファイルの再同期を促す方が原因を追いやすくなります。
object_listは認可機能の代わりではない
enforce_object_list=trueは、LLMが生成するSQLの対象を制限するために有効です。しかし、アプリケーションの認可やSQL実行時の安全確認を、AIプロファイルだけへ任せるべきではありません。
生成されたSQLは、実行前に別の境界で検証します。
生成SQL
↓
Oracle SQLとして構文解析
↓
SELECT / WITHの単一文か
↓
参照テーブルがアプリケーション側の許可集合に含まれるか
↓
必要なら参照列も許可集合に含まれるか
↓
許可した構文・関数だけを使用しているか
↓
問題がなければ実行
SELECTまたはWITHで始まる単一文という確認だけでは、実行時の振る舞いまで制限できません。たとえば、SELECT ... FOR UPDATEは行をロックでき、WITH句にはPL/SQLファンクションやプロシージャの宣言・定義を含められます。実行を許可する句や関数もポリシーとして決め、構文解析時に確認します。
以下は、検証処理のうち参照オブジェクトの確認部分だけを示した擬似コードです。
allowed_objects = {
"SALES.ORDER_HEADERS",
"SALES.ORDER_LINES",
"SALES.CUSTOMERS",
"SALES.PRODUCTS",
}
referenced_objects = parse_oracle_sql_and_extract_objects(generated_sql)
if not referenced_objects <= allowed_objects:
raise ValueError("許可されていないテーブルを参照しています")
生成SQLをアプリケーションから自動実行する場合は、単純な正規表現だけで判定せず、Oracle SQLを扱えるパーサーなどを使ってCTE、サブクエリ、引用識別子、複数文、許可しない句や関数も考慮します。ここは対象テーブルを絞る基本設定とは別の、本番アプリケーション向けの安全境界です。あわせて、実行に使うデータベースユーザーの権限も最小化します。
つまり、役割分担は次のようになります。
| 仕組み | 主な目的 |
|---|---|
object_list |
LLMへ見せる候補を整理し、SQL生成精度を高める |
enforce_object_list |
候補外のテーブルを使う生成を抑止する |
| DB権限 | 実際にアクセス可能なデータを制御する |
| アプリケーションのSQL検証 | 生成SQLが許可範囲と実行ポリシーを満たすか確認する |
まとめ
SELECT AIの精度が出ないとき、最初から長いプロンプトや大量のFew-shot例を追加する必要はありません。
まず確認したいのは、SELECT AIへ見せているデータベースオブジェクトの範囲です。
- AIプロファイルを業務目的ごとに分ける
-
object_listへ必要なテーブル・ビューだけを指定する -
enforce_object_list=trueを明示する - 保存されたプロファイル属性を読み戻して確認する
-
SHOWPROMPTとSHOWSQLで入力と出力を観察する - 生成後のSQLはアプリケーション側でも検証する
- 固定した質問セットで変更前後を比較する
対象テーブルの絞り込みだけで、すべての質問が正解になるわけではありません。それでも、LLMへ渡す前提を整え、誤ったテーブルやJOIN経路の候補を減らすための基本的な設計です。効果は、固定した質問セットと同じ実行条件で確認します。
次回は、対象テーブルを絞り込んだうえで、コメント・アノテーション・制約を使ってテーブルや列の業務上の意味をSELECT AIへ伝える方法を扱います。