はじめに
AIの利活用推進において、データベースへの自然言語検索を可能にするNatural language to SQL(NL2SQL)は、最初のステップとして有用です。
これまでは専門スキルが必要だったデータ取得も、NL2SQLの利用によって、ITやSQLの知識を問わず誰もが直接行えるようになります。これにより、社内のあらゆる層で「データの民主化」が加速し、データ取得の工数削減やより正確な情報による意思決定を可能にします。
さらに、AI Agent時代において、NL2SQLは「人間とDB」だけでなく、「AIエージェントとDB」をつなぐインターフェースとしても有用です。エージェントが定義されたテーブル構造に基づき、自らSQLを生成・実行することで、正確かつ高度なアクションを実現します。
Oracle DatabaseでのNL2SQL
Oracle Autonomous AI Database(ADB) をご存知でしょうか。ADBはOCIで提供されるフルマネージド型のOracle Databaseサービスです。
ADBにはLLMと連携しSQLではなく自然言語でDBを検索する「Select AI 」という機能があります。これを使えば簡単にADB上のオブジェクトに対して自然言語での問い合わせが可能です。
また、ADBはPostgreSQLやMySQL、Salesforceといった多種多様なデータソースと連携が容易に可能で、外部ソースのデータをあたかもADB上にあるかのように問い合わせることができます。
この「Select AI」と「外部データソース連携」を組み合わせ、外部データソースへの自然言語検索が可能です。この機能を「AI Proxy Database」と呼んでいます。(Sidecarと呼ばれるケースもあります)
今回はこのADBとPostgreSQLを使って「AI Proxy Database」の機能を試してみたいと思います。
検証内容
ADBとPostgreSQLを接続し、ADBを経由してPostgreSQLへ自然言語検索をしてみたいと思います。
さらに、ADB上のテーブルとPostgreSQL上のテーブル両方を参照する自然言語問い合わせも試してみたいと思います。
検証環境の構成は以下のイメージです。

検証はざっくり以下の3Stepで進めます。
Step1: ADB-PostgreSQL接続
1-1. Autonomous AI Database(ADB)を構築
1-2. PostgreSQLを構築
1-3. PostgreSQLにデータを投入
1-4. ADBとPostgreSQLを接続
Step2: ADBでのSelect AIの設定
2-1. LLMの設定
2-2. ADBの設定
Step3: Select AIの検証
3-1. ADB経由のSelect AIによるPostgreSQLへの問い合わせの検証
3-2. Select AI DeepDive
3-3. ADB上にもテーブルを作成し、PostgreSQLとADB双方を参照する問い合わせの検証
それでは環境構築から進めていきます。
Step1: ADB-PostgreSQL接続
やることは以下の通りです。
1-1. Autonomous AI Database(ADB)を構築
1-2. PostgreSQLを構築
1-3. PostgreSQLにデータを投入
1-4. ADBとPostgreSQLを接続
1-1: ADBの構築
ADBの構築はこちらの手順を参照し実施してください。
https://oracle-japan.github.io/ocitutorials/adb/adb101-provisioning/
1-2: PostgreSQLの構築
次にPostgreSQLの構築を進めます。OCIで提供されるPostgreSQLのマネージドサービスである、OCI Database with PostgreSQLを利用します。
OCIのコンソールにて新規PostgreSQLを立ち上げ
OCIコンソールにてGUIで構築します。求められる内容を入力するだけです。
配置するサブネットはADBと同じPrivate Subnetとしました。

後段の接続のため、作成したPostgreSQLの詳細情報から「プライマリ・エンドポイントのFQDN」をメモしておきます。

PostgreSQLにアクセス
作成したDBにアクセスしていきます。こちらの手順を参考に進めていきます。
クライアントのインストール
まずはアクセス元となるVMにClientをInstallしていきます。
下記のURLにアクセスし、ComputeのOSおよび構築したPostgreSQLのバージョンに従ってインストールを進めていきます。
https://www.postgresql.org/download/
表示されたセットアップスクリプトをコピぺして実行します。
# スクリプトの作成
[opc@ol8vm ~]$ vi setup_pg.sh
# スクリプトの中身
#!/bin/bash
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql
sudo dnf install -y postgresql16-server
sudo /usr/pgsql-16/bin/postgresql-16-setup initdb
sudo systemctl enable postgresql-16
sudo systemctl start postgresql-16
# 実行権限を付与
[opc@ol8vm ~]$ chmod +x setup_pg.sh
# 実行
[opc@ol8vm ~]$ ./setup_pg.sh
CA証明書の配置
データベース・システム>詳細よりCA証明書をダウンロードしComputeに配置します

今回はここに置いてみました
$ pwd
/home/opc/.postgresql
$ ls
CaCertificate-PostgreSQL.pub
接続パターン①: Computeから直接接続する場合
検証レベルだと直接接続するかと思いますので手順を記載します。
ネットワーク・セキュリティの設定
アクセス元のCompute等のPrivate IPと宛先のポート(5432)を入力します。

接続
psqlで接続します。クライアントをInstallしたComputeにて下記のコマンドを実行してください。
$ psql "sslmode=verify-full sslrootcert=CA証明書のパス host=PostgreSQLのプライベートエンドポイントFQDN dbname=postgres user=ユーザー名"
接続パターン②: Bastionを使う場合
マニュアルの手順ではBastionを経由して接続しているのでそちらも試しておきます。
アクセス元のComputeでの設定
Public Subnet上のComputeからアクセスしたいので、Bastionのプラグインを有効化します
コンピュート>管理>Oracle Cloudエージェント
※有効化してから実行中になるまで数分かかります

Bastionの作成
アイデンティティとセキュリティ>要塞を選択します
情報を入力し作成します。
Public SubnetのComputeからの接続の場合、CIDRブロック許可リストにはPublic IPを入力してください。

ネットワーク・セキュリティの設定
BastionからアクセスするVCNのIngressルールを追加しておきます
BastionのPrivate IPと宛先となるPostgreSQLのポート(5432)を入力します。

Bastionのセッションの作成
作成したBastionの中のセッションタブに遷移し、セッションを作成します

PostgreSQLへの接続
作成したセッションでSSHトンネルを作成します。
セッションの右側のメニューからSSHコマンドをコピーします

コピーしたコマンドは以下の様な形になっていると思います。
privateKeyとlocalportの編集が必要なため、登録した鍵と任意のLocalport(15432等)を入力してください。
ssh -i <privateKey> -N -L <localPort>:PostgreSQLのプライベートエンドポイントFQDN:5432 -p 22 xxx@yyy
セッションが切れてしまうため、必要に応じてServerAliveInterval=60等を設定しておくと良いです。
1-3: PostgreSQLへのデータ投入
PostgreSQLへの接続が可能になったので、データを投入していきます。
スキーマの作成
まずはテスト用のスキーマを作成します。
PostgreSQLを作成した際に作成した管理ユーザーでアクセスし、ユーザー(ord_mgr)およびスキーマ(order_system)を作成していきます。
製造業の受発注のシステムを意識したDBにしようと思います。
postgres=> CREATE SCHEMA order_system;
CREATE SCHEMA
postgres=> COMMENT ON SCHEMA order_system IS '受発注システム';
COMMENT
postgres=> CREATE USER ord_mgr WITH PASSWORD 'パスワード';
CREATE ROLE
postgres=> alter schema order_system owner to ord_mgr;
ALTER SCHEMA
postgres=> alter user ord_mgr set search_path to order_system, public;
ALTER ROLE
一度exitし、作成したord_mgrで接続可能か確認してみましょう
$ psql "sslmode=verify-full sslrootcert=CA証明書のパス host=PostgreSQLのプライベートエンドポイントFQDN dbname=postgres user=ord_mgr"
データの投入
データを投入していきます。
Geminiにお願いして適当なデータを作ってもらいました。
| 項目 | 内容 |
|---|---|
| スキーマ名 | order_system |
| inventory |
100件(製品マスタ) 製品ID( MTR/SNS/CB)、在庫数(0〜500)、倉庫名(横浜/名古屋/福岡) |
| orders |
1,000件(受注履歴)。過去30日間の注文、ステータス(Shipped/Pending/Cancelled)、顧客ID |
サンプルデータの投入用スクリプトを実行しデータを投入していきます。
-- データ投入用のスクリプトを実行
postgres=> \i insert_sample_data.sql
SET
CREATE TABLE
CREATE TABLE
TRUNCATE TABLE
INSERT 0 100
INSERT 0 1000
投入されたデータを確認してみましょう。
-- テーブルの一覧の確認
postgres=> \dt
List of relations
Schema | Name | Type | Owner
--------------+-----------+-------+---------
order_system | inventory | table | ord_mgr
order_system | orders | table | ord_mgr
(2 rows)
-- 各種データの確認
postgres=> SELECT * FROM orders LIMIT 5;
ord_id | prod_id | qty | ord_date | status | cust_id
--------+---------+-----+------------+-----------+----------
961 | MTR-033 | 14 | 2026-03-08 | Cancelled | CUST-048
962 | MTR-028 | 9 | 2026-02-12 | Pending | CUST-024
963 | MTR-010 | 20 | 2026-03-05 | Shipped | CUST-026
964 | MTR-002 | 6 | 2026-03-01 | Pending | CUST-029
965 | MTR-013 | 2 | 2026-02-20 | Shipped | CUST-008
postgres=> SELECT * FROM inventory LIMIT 5;
prod_id | stock_qty | wh_name | update_at
---------+-----------+--------------+----------------------------
MTR-001 | 475 | 福岡センター | 2026-02-26 00:47:30.601843
MTR-002 | 443 | 福岡センター | 2026-02-26 00:47:30.601843
MTR-003 | 291 | 名古屋ロジ | 2026-02-26 00:47:30.601843
MTR-004 | 257 | 名古屋ロジ | 2026-02-26 00:47:30.601843
MTR-005 | 125 | 名古屋ロジ | 2026-02-26 00:47:30.601843
(5 rows)
postgres=> SELECT o.ord_id, o.prod_id, i.wh_name, o.qty, o.status, o.ord_date FROM orders o JOIN inventory i ON o.prod_id = i.prod_id ORDER BY o.ord_date DESC LIMIT 5;
ord_id | prod_id | wh_name | qty | status | ord_date
--------+---------+------------+-----+-----------+------------
153 | MTR-015 | 名古屋ロジ | 12 | Shipped | 2026-02-26
170 | MTR-015 | 名古屋ロジ | 11 | Cancelled | 2026-02-26
24 | MTR-015 | 名古屋ロジ | 5 | Shipped | 2026-02-26
78 | MTR-015 | 名古屋ロジ | 5 | Cancelled | 2026-02-26
211 | MTR-015 | 名古屋ロジ | 2 | Cancelled | 2026-02-26
(5 rows)
postgres=> SELECT wh_name, count(*) as product_types, sum(stock_qty) as total_stock FROM inventory GROUP BY wh_name;
wh_name | product_types | total_stock
--------------+---------------+-------------
名古屋ロジ | 50 | 13620
横浜第一 | 24 | 6553
福岡センター | 26 | 7273
(3 rows)
postgres=> SELECT status, count(*) as order_count FROM orders GROUP BY status;
status | order_count
-----------+-------------
Shipped | 494
Cancelled | 253
Pending | 253
(3 rows)
無事にデータ入っていることが確認できました。
これにてPostgreSQLのDBの準備は完了です。
1-4: ADBとPostgreSQLを接続
こちらを参考に進めていきましょう。
ネットワーク・セキュリティ設定
ADBからPostgresへのアクセスのため、Private Subnet内での5432ポートへのアクセスを許可します。

ついでにComputeからADBへのアクセス用のルールも追加しておきます
- ソースCIDR: ComputeのPrivate IP
- 宛先ポート: 1522
ADBの設定
WalletをDLしてアクセス元となるCompute上に配置します。
OCIコンソールにて「データベース接続>ウォレット」のダウンロードでDLします。


DLしたウォレットはscpコマンド等でComputeに配置し、unzipしておきます。
$ unzip Wallet_idb.zip
Archive: Wallet_idb.zip
inflating: ewallet.pem
inflating: README
inflating: cwallet.sso
inflating: tnsnames.ora
inflating: truststore.jks
inflating: ojdbc.properties
inflating: sqlnet.ora
inflating: ewallet.p12
inflating: keystore.jks
Walletを環境変数に設定します。
# 環境変数の設定
$ export TNS_ADMIN=/home/opc/my_home/ADB_wallet
ComputeでのSQL実行環境の設定
ADB接続用のデータベースクライアントとしてSQLclを入れておきます。
以下のURLからSQLclをDLしてComputeに配置し、unzipしてください。
https://www.oracle.com/database/sqldeveloper/technologies/sqlcl/download/
$ unzip sqlcl-latest.zip
ComputeからADBへのアクセス
ComputeからADBへアクセスします。
OCIのコンソールにてADBの接続情報を確認します。
データベース接続の接続文字列を確認します。

idb_highを利用します。
コマンドコンソールに戻り、ADBへ接続していきます。
先ほど解凍したSQLclの中にsqlcl/binの中のsqlファイルを実行します。
[opc@ol8vm bin]$ ./sql admin@idb_high
SQLcl: Release 25.4 Production on Fri Feb 27 08:13:31 2026
Copyright (c) 1982, 2026, Oracle. All rights reserved.
Password? (**********?) パスワード入れる
Last Successful login time: Fri Feb 27 2026 08:13:39 +00:00
Connected to:
Oracle AI Database 26ai Enterprise Edition Release 23.26.1.1.0 - for Oracle Cloud and Engineered Systems
Version 23.26.1.1.0
SQL>
アクセスできました。
ADBとPostgreSQLの接続
PostgreSQLのテーブルにADBからアクセスするための設定を進めます。ADBとPostgreSQLはDatabase Link(DBLINK)と呼ばれるOracle Databaseの機能によって接続します。
ADBのユーザーの作成
検証用のユーザーを作成します。ai_userという名前にしました。
CREATE USER ai_user IDENTIFIED BY "パスワード";
権限の付与
ADBでのDBLINKの作成やSelect AIの利用に、各種パッケージの利用権限を付与する必要があります。ADBにログインして以下のコマンドを実行してください。
-- 基本的な接続権限とリソース権限
GRANT CONNECT, RESOURCE TO ai_user;
-- 表領域の割り当て
ALTER USER ai_user QUOTA UNLIMITED ON DATA;
-- DBLINK作成権限
GRANT CREATE DATABASE LINK TO ai_user;
GRANT READ, WRITE ON DIRECTORY DATA_PUMP_DIR TO ai_user;
-- Select AI用パッケージの実行権限
GRANT EXECUTE ON DBMS_CLOUD_AI TO ai_user;
GRANT EXECUTE ON DBMS_CLOUD TO ai_user;
GRANT EXECUTE ON DBMS_CLOUD_ADMIN TO ai_user;
PostgreSQL接続のためのクレデンシャル作成
ADB上にPostgreSQLへの接続用のクレデンシャルを作成します。ai_userでADBにログインしDBMS_CLOUD.CREATE_CREDENTIALパッケージを実行してください。
usernameとPasswordはPostgreSQL側のユーザーを指定します。
SQL> show user
USER is "AI_USER"
-- DBLINK用のクレデンシャルを作成
BEGIN
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name => 'POSTGRESQL_CRED',
username => 'PostgreSQLで作成したユーザー(今回はord_mgr)',
password => '<postgresql_password>'
);
END;
/
DBLINKの作成
DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINKパッケージを実行し、DBLINKを作成します。
PostgreSQLの各種情報を指定します。credential_nameには先ほど作成したDBLINK用のクレデンシャル名を指定してください。
BEGIN
DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK(
db_link_name => 'POSTGRESQL_LINK',
hostname => 'primary.***.us-ashburn-1.oci.oraclecloud.com',
port => '5432',
service_name => 'postgres',
credential_name => 'POSTGRESQL_CRED',
gateway_params => JSON_OBJECT('db_type' VALUE 'postgres', 'enable_ssl' VALUE true),
ssl_server_cert_dn => NULL,
private_target => true
);
END;
/
パッケージの実行が完了したら接続は完了です。確認してみましょう。
PostgreSQLのテーブルをADBから参照
PostgreSQL上で実行したのと同じSQLをADBから実行してみましょう。
-- ADBにログインして実行
SELECT "status", count(*) as order_count
FROM "orders"@POSTGRESQL_LINK
GROUP BY "status";
status ORDER_COUNT
____________ ______________
Pending 253
Cancelled 253
Shipped 494
PostgreSQL上で直接実行した時と同じ結果が得られ、ADBからPostgreSQLのテーブルを参照できていることが確認できました。
Tips
Oracleは指定しないと大文字、PostgreSQLは小文字で入力を扱います。今回PostgreSQL上のテーブルは小文字で作成されているため、Oracleではテーブル名やカラム名は小文字と明示する必要があり、PostgreSQLのオブジェクトは"で囲んでいます。
Step2: ADBでのSelect AIの設定
ADBでSelect AIを実行するための準備を進めていきます。
2-1. LLMの準備
2-2. ADBの準備
2-1: LLMの準備
Select AIはユーザーが投げた自然言語の問い合わせをADBからLLMに対してカスケードしSQL化してDBにて実行します。
そのため、ADBからLLMへのアクセス準備を進めていきます。
今回LLMはOCI Generative AI Serviceで提供されているモデルを利用します。
APIキーの取得
DBがLLMにアクセスできるようにAPIキーを取得します。
コンソール右上のユーザーアイコンをクリックし、ユーザー設定に飛びます。

「トークンおよびキー」のタブから「APIキーの追加」にてAPIキーを作成します。

「APIキー・ペアの生成」より秘密キーのダウンロードを実行します。
DLが完了したら右下の「作成」からAPIキーを作成します。

作成すると構成ファイルのプレビューが表示されます。
これもコピーをして控えておきます。

2-2: ADBの準備
ADB側のSelect AIを利用するための下準備を進めます。
ADBへのAPIキーの登録
DBMS_CLOUD.CREATE_CREDENTIALプロシージャを使ってOCI生成AIサービスに接続するためクレデンシャルを作成します。先ほど取得したAPIキーもここで登録します。
BEGIN
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name => '好きな名前',
user_ocid => 'ユーザーのOCID',
tenancy_ocid => 'テナンシのOCID',
private_key => '先ほど保存したAPIの秘密鍵',
fingerprint => '先ほど作成したAPIキーのフィンガープリント'
);
END;
/
PL/SQL procedure successfully completed.
-- 確認
SELECT credential_name, username
FROM user_credentials;
CREDENTIAL_NAME USERNAME
__________________ _____________________
POSTGRESQL_CRED ord_mgr
OCI_CRED ocid1.userxxxxxx
ADBへの外部データのマッピング(Viewの作成)
Select AIは、「Oracle内のオブジェクト」を見てSQLを生成します。そのため、外部のデータのアクセスにおいても「Oracle内にあるように見せかける」必要があります。
そこで、AIに外部(PostgreSQL)のデータをあたかもOracle内部にある普通のテーブルであるかのように錯覚させるためにViewを活用します。
リモートのPostgreSQLの表をADB上でViewにしマッピングします。
まずはViewを作るためのSQLを作成します。
Tips
先ほどのTipsでもあった通り、PostgreSQLのテーブルの列名をそのまま「CREATE OR REPLACE VIEW ~ AS SELECT * ~」でOracleのViewにすると列名は全て小文字で作成されてしまいます。すると、Oracle上でViewに問い合わせるときは毎回オブジェクトを""で囲み、小文字で明示する必要が発生します。これは人間としても面倒なのはもちろん、AIもそこまで考慮できないため、生成するSQLにもPostgreSQL由来のオブジェクトには""をつけて小文字で扱うように指定してやる必要が出てきます。
これを避けるためにViewは大文字の列名を指定して作成したいです。しかし、テーブルの列名を人力で指定して作成するのは大変です。
そこで、PostgreSQL側から列名を取得して、大文字小文字等の扱いの違いをOracle仕様に変更するSQLを生成するSQLを実行します。
-- PostgreSQLのメタデータから直接列名を取得し、
-- Oracle側で二重引用符(")なしで扱えるView定義文を生成していきます。
SELECT
-- 1. View作成文のヘッダー部分(Oracle側でのView名)
'CREATE OR REPLACE VIEW ORDERS (' ||
-- 2. PostgreSQLのカラム名を自動取得し、大文字に変換してカンマ区切りで連結
-- UPPER("column_name") : 小文字のカラム名をOracle標準の大文字へ変換
-- WITHIN GROUP (ORDER BY "ordinal_position") : PostgreSQL上の定義順を維持
LISTAGG(UPPER("column_name"), ', ') WITHIN GROUP (ORDER BY "ordinal_position") ||
-- 3. データ取得元(DBLINK経由)の指定
-- ここで "*" を使うことで、列の定義(大文字)と取得データ(小文字)を自動マッピング
') AS SELECT * FROM "order_system"."orders"@postgresql_link;'
-- 4. PostgreSQL側の「システムカタログ(定義情報)」を直接参照
FROM "information_schema"."columns"@postgresql_link
-- 5. 対象のスキーマとテーブルを特定(PostgreSQL側は小文字なので小文字で指定)
WHERE "table_name" = 'orders'
AND "table_schema" = 'order_system';
-----------------------出力結果--------------------------
'CREATEORREPLACEVIEWORDERS('||LISTAGG(UPPER("COLUMN_NAME"),',')WITHINGROUP(ORDERBY"ORDINAL_POSITION")||')ASSELECT*FROM"ORDER_SYSTEM"."ORDERS"@POSTGRESQL_LINK;'
__________________________________________________________________________________________________________________________________________________________________
CREATE OR REPLACE VIEW ORDERS (ORD_ID, PROD_ID, QTY, ORD_DATE, STATUS, CUST_ID) AS SELECT * FROM "order_system"."orders"@postgresql_link;
生成されたSQLを実行し、Viewを作成します。
ORDERSテーブルに加えてINVENTORYテーブルも同じように取得しView化します。
-- ORDERS Viewの作成
CREATE OR REPLACE VIEW ORDERS (ORD_ID, PROD_ID, QTY, ORD_DATE, STATUS, CUST_ID) AS SELECT * FROM "order_system"."orders"@postgresql_link;
-- INVENTORY Viewの作成
CREATE OR REPLACE VIEW INVENTORY (PROD_ID, STOCK_QTY, WH_NAME, UPDATE_AT) AS SELECT * FROM "order_system"."inventory"@postgresql_link;
作成したViewを確認してみましょう。
SELECT object_name, object_type, status, last_ddl_time
FROM all_objects
WHERE object_name IN ('ORDERS', 'INVENTORY')
AND object_type = 'VIEW'
ORDER BY object_name;
OBJECT_NAME OBJECT_TYPE STATUS LAST_DDL_TIME
______________ ______________ _________ ________________
INVENTORY VIEW VALID 11-MAR-26
ORDERS VIEW VALID 11-MAR-26
-- データの確認
SQL> SELECT status, count(*) as order_count
2 FROM orders
3* GROUP BY status;
STATUS ORDER_COUNT
____________ ______________
Pending 253
Cancelled 253
Shipped 494
PostgreSQLにて直接実行した際と同じ結果を得られました。
参考: データの実態
PostgreSQLのデータをADBから参照できましたが、ADB上にデータの実態はないことも証明しておきましょう。
Oracleにおいて、テーブルにデータが入ると「セグメント」という物理的な領域が割り当てられます。Viewはただの「定義」なので、セグメントを持ちません。
以下のSQLを実行して、結果が0件であれば「物理的なストレージは一切消費していない(=データ実体がない)」ことの証明になります。
SELECT segment_name, segment_type, bytes
FROM user_segments
WHERE segment_name IN ('ORDERS', 'INVENTORY');
no rows selected
試しに実態のあるテーブルを作成してみると、64Kのセグメントが割り当てられていることがわかります。
-- 適当なテーブルを作成
SQL> CREATE TABLE TEST_TABLE (ID NUMBER, NAME VARCHAR2(100));
Table TEST_TABLE created.
-- 1行だけデータを投入
SQL> INSERT INTO TEST_TABLE VALUES (1, '実体データ');
1 row inserted.
SQL> COMMIT;
Commit complete.
-- 確認
SQL> select * from TEST_TABLE;
ID NAME
_____ ________
1 実体データ
-- セグメントの確認
SQL> SELECT segment_name, segment_type, bytes
2 FROM user_segments
3* WHERE segment_name IN ('ORDERS', 'TEST_TABLE');
SEGMENT_NAME SEGMENT_TYPE BYTES
_______________ _______________ ________
TEST_TABLE TABLE 65536
-- ↑ ORDERSの方は実態がないので表示されない。
データの実態はなくとも、メタデータはADB内に保存されているので、それも見てみましょう
-- Viewの定義文(メタデータ)を確認
SQL> SELECT view_name, text_length, text
2 FROM user_views
3* WHERE view_name = 'ORDERS';
VIEW_NAME TEXT_LENGTH TEXT
____________ ______________ _____________________________________________________________________________________________________________
ORDERS 106 SELECT "ord_id","prod_id","qty","ord_date","status","cust_id" FROM "order_system"."orders"@postgresql_link
-- Viewが持っている列情報(メタデータ)を確認
SQL> SELECT column_name, data_type, data_length
2 FROM user_tab_cols
3 WHERE table_name = 'ORDERS'
4* ORDER BY column_id;
COLUMN_NAME DATA_TYPE DATA_LENGTH
______________ ____________ ______________
ORD_ID NUMBER 22
PROD_ID VARCHAR2 20
QTY NUMBER 22
ORD_DATE DATE 7
STATUS VARCHAR2 20
CUST_ID VARCHAR2 20
6 rows selected.
このようにViewであればADBにはデータの実態を持たせることなく参照が可能なため、Cloud上のADBにデータの実態を配置したくないといった要件がある場合にもSelect AIを利用することが可能です。
プロファイルの作成
SELECT AI を使うには、事前に DBMS_CLOUD_AI.CREATE_PROFILE でプロファイルを作成し、SET_PROFILE で有効化しておく必要があります。object_listの指定は任意ですが、余計なテーブルの検索等をしないように指定しておきます。これにより、Select AIで「どのモデルを使い、どのオブジェクトを扱うか」を定義することができます。
-- 古いものがある場合は削除
BEGIN
DBMS_CLOUD_AI.DROP_PROFILE(profile_name => 'GENAI_POSTGRES_PROFILE');
END;
/
-- 作成
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_POSTGRES_PROFILE',
attributes => '{"provider": "oci",
"credential_name": "OCI_CRED",
"region": "us-ashburn-1",
"model": "xai.grok-4",
"object_list": [
{"owner": "ai_user", "name": "ORDERS"},
{"owner": "ai_user", "name": "INVENTORY"}
]
}'
);
END;
/
-- 有効化
EXEC DBMS_CLOUD_AI.SET_PROFILE('GENAI_POSTGRES_PROFILE');
これにて準備完了です。
Step3: Select AIでの問い合わせ
ようやく準備が整いました。現時点ではデータの実態はPostgreSQL上のみにあり、ADBにはPostgreSQL上のテーブルのViewだけが存在する状態です。
まずはこの状態でSelect AIの動作を確認してみましょう。
3-1: ADB経由のSelect AIによるPostgreSQLへの問い合わせの検証
それでは、ADBに自然言語で問い合わせてみましょう。
SQL> SELECT AI オーダーは何件ある?;
ORDER_COUNT
______________
1000
SQL> SELECT AI オーダーを最新の3件表示して;
QUANTITY CUSTOMER_ID ORDER_DATE ORDER_ID PRODUCT_ID STATUS
___________ ______________ _____________ ___________ _____________ ____________
5 CUST-049 26-FEB-26 24 MTR-015 Shipped
12 CUST-045 26-FEB-26 153 MTR-015 Shipped
5 CUST-012 26-FEB-26 78 MTR-015 Cancelled
きちんと回答が返ってきました。
参考: AIプロファイルにObject Listを指定しない場合
プロファイルにてオブジェクトリストを指定しなくてもSelect AIは利用できます。
指定しなければあらゆるオブジェクトが検索対象となります。
ですので、V$View等も検索することができ、DBA業務等にも活用可能です。
SQL> select AI showsql このDB内に存在するユーザーを5つ表示して;
RESPONSE
_____________________________________
SELECT u."USERNAME" AS "USER_NAME"
FROM "SYS"."ALL_USERS" u
WHERE ROWNUM <= 5
SQL> select AI このDB内に存在するユーザーを5つ表示して;
User Name
____________
SYS
AUDSYS
SYSTEM
SYSBACKUP
SYSDG
SQL> select AI showsql 現在、データベースに接続しているアクティブなセッション数を教えて。SQL生成時はスキーマ名を省略して記述してください。;
RESPONSE
______________________________________________
SELECT COUNT(*) AS "ACTIVE_SESSION_COUNT"
FROM "V$SESSION" "S"
WHERE UPPER("S"."STATUS") = UPPER('ACTIVE')
SQL> select AI 現在、データベースに接続しているアクティブなセッション数を教えて。SQL生成時はスキーマ名を省略して記述してください。;
ACTIVE_SESSION_COUNT
_______________________
1
オブジェクト指定しない時の問題
オブジェクトを指定しないとユーザー作成のオブジェクトの列名等をうまく取得できない場合があります。ユーザーが作成したテーブルの検索を実施する際には、検索精度向上のためAIプロファイルにてオブジェクトを指定するのがほぼ必須だと思います。
-- 前述と同じ問い合わせでもうまく列名を解釈できなかった例
SQL> SELECT AI オーダーを最新の3件表示して;
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:
SELECT
o."OrderId" AS order_id,
o."CustomerId" AS customer_id,
o."OrderDate" AS order_date,
o."ShippedDate" AS shipped_date
FROM
"ORDERS" o
ORDER BY
o."OrderDate" DESC
FETCH FIRST 3 ROWS ONLY
Exception encountered: ORA-00904: "O"."OrderDate": invalid identifier
3-2: Select AI DeepDive
Select AIもAIを利用している以上、思うような動作をしてくれない場合があります。
人間の問い合わせを正しく解釈し、きちんと回答を返してくれる場合もありますが、失敗する場合もあります。
失敗した場合にチューニングにより改善したケースについてご紹介します。
失敗した後チューニングを実施し再実行しても、同じ問い合わせだと失敗した際のキャッシュにHITしてしまい、エラーを含んだ回答しか返ってこなくなる場合があります。その場合は、プロンプトを少し変える、もしくは可能な場合は少し面倒ですがインスタンスを再起動してキャッシュクリアしてしまうのも手です。
日本語でテーブル名を解釈してしまう
SQL> SELECT AI オーダーを3件だけ表示して;
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:
SELECT *
FROM "オーダー" t
WHERE ROWNUM <= 3
Exception encountered: ORA-00942: table or view "ADMIN"."オーダー" does not exist
3件表示してというとエラーになりました。
「オーダー」という言葉を「orderテーブル」ではなく日本語の「オーダーテーブル」と解釈してしまった様です。
コメントでテーブルは英語であることを明示する
AIが理解しやすいようにオブジェクトにコメントをつけます。
SQL> COMMENT ON TABLE ORDERS IS 'PostgreSQLから取得した注文履歴データ。注文状況を確認する際に使用します。表や列名、データは全て英語で命名されています。';
Comment created.
SQL> COMMENT ON TABLE INVENTORY IS 'PostgreSQLから取得した在庫管理データ。在庫数や単価はこの表を参照します。表や列名、データは全て英語で命名されています。';
Comment created.
もう一度同じ問い合わせを投げてみましょう
SQL> SELECT AI オーダーを3件だけ表示して;
ORDER_ID PRODUCT_ID STATUS QUANTITY CUSTOMER_ID ORDER_DATE
___________ _____________ ____________ ___________ ______________ _____________
961 MTR-033 Cancelled 14 CUST-048 08-MAR-26
962 MTR-028 Pending 9 CUST-024 12-FEB-26
963 MTR-010 Shipped 20 CUST-026 05-MAR-26
今度はきちんと回答が生成され、精度が上がったことが確認できました。
SQLが複雑で処理がタイムアウトしてしまう
DBLINK越しの問い合わせの影響で、複雑なSQLが生成された場合、ORA-28511でタイムアウトしてしまうケースが多々あります。
例1
SQL> SELECT AI 在庫数が10個を下回っている商品をリストアップして。商品名と現在の在庫数、価格を表示して。;
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:
SELECT i."PROD_ID" AS "商品名",
SUM(i."STOCK_QTY") AS "現在の在庫数"
FROM "AI_USER"."INVENTORY" i
GROUP BY i."PROD_ID"
HAVING SUM(i."STOCK_QTY") < 10
Exception encountered: ORA-28511: lost RPC connection to heterogeneous remote agent using SID=ORA-28511: lost RPC connection to heterogeneous remote agent using SID=%s
例2
SQL> SELECT AI showsql 最新の注文日の最もオーダーが多い商品を検索して、その在庫数を教えて;
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:
WITH max_order_date AS (
SELECT MAX("ORD_DATE") AS latest_date FROM "AI_USER"."ORDERS"
),
orders_on_date AS (
SELECT o."PROD_ID", COUNT(DISTINCT o."ORD_ID") AS order_count
FROM "AI_USER"."ORDERS" o
INNER JOIN max_order_date md ON o."ORD_DATE" = md.latest_date
GROUP BY o."PROD_ID"
),
top_products AS (
SELECT od."PROD_ID", od.order_count,
RANK() OVER (ORDER BY od.order_count DESC) AS rnk
FROM orders_on_date od
),
latest_updates AS (
SELECT "PROD_ID", "WH_NAME", MAX("UPDATE_AT") AS max_update
FROM "AI_USER"."INVENTORY"
GROUP BY "PROD_ID", "WH_NAME"
),
current_stock AS (
SELECT lu."PROD_ID", SUM(i."STOCK_QTY") AS total_stock
FROM "AI_USER"."INVENTORY" i
INNER JOIN latest_updates lu
ON i."PROD_ID" = lu."PROD_ID"
AND i."WH_NAME" = lu."WH_NAME"
AND i."UPDATE_AT" = lu.max_update
GROUP BY lu."PROD_ID"
)
SELECT cs.total_stock AS "INVENTORY_QUANTITY"
FROM current_stock cs
INNER JOIN top_products tp ON cs."PROD_ID" = tp."PROD_ID"
WHERE tp.rnk = 1
Exception encountered: ORA-28511: lost RPC connection to heterogeneous remote agent using SID=ORA-28511: lost RPC connection to heterogeneous remote agent using SID=%s
対応のアプローチとしてSelect AI Feedbackを利用してみましょう
Select AI Feedback
Select AI FeedbackはAIの出力に対してフィードバックを行い精度を改善する機能です。
利用のためには少し設定が必要です。
-- 権限の付与
ADMINで実行
SQL> GRANT READ ON SYS.V_$SESSION TO AI_USER;
SQL> GRANT READ ON SYS.V_$MAPPED_SQL TO AI_USER;
-- 余計なメッセージを拾ってAIの動作を邪魔しないようにするためサーバー出力をOFF
SET SERVEROUTPUT OFF;
AIプロファイルにEmbeddingモデルを指定します。
マニュアルからAshburnリージョンで利用可能なEmbeddingモデルを確認し、Cohere Embed 4を指定しました。
-- 一旦既存のプロファイルを削除
BEGIN
DBMS_CLOUD_AI.DROP_PROFILE(profile_name => 'GENAI_POSTGRES_PROFILE');
END;
/
--再作成
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_POSTGRES_PROFILE',
attributes => '{"provider": "oci",
"credential_name": "OCI_CRED",
"region": "us-ashburn-1",
"model": "xai.grok-4",
"embedding_model": "cohere.embed-v4.0",
"object_list": [
{"owner": "ai_user", "name": "ORDERS"},
{"owner": "ai_user", "name": "INVENTORY"}
]
}'
);
END;
/
参考: プロファイルにEmbeddingモデルを指定していない場合
Embeddingモデルを指定していないプロファイルを利用している場合、Feedbackに失敗する場合があります。
-- Feedbackしてみる
SQL> select ai feedback sqlが複雑すぎるのでシンプルに考えてください;
Error starting at line : 1 in command -
select ai feedback sqlが複雑すぎるのでシンプルに考えてください
Error at Command Line : 1 Column : 20
Error report -
SQL Error: ORA-20404: Object not found - https://inference.generativeai.us-ashburn-1.oci.my$cloud_domain/20231130/actions/embedText
ORA-06512: at "C##CLOUD$SERVICE.DBMS_CLOUD$PDBCS_260213_0", line 2253
ORA-06512: at "C##CLOUD$SERVICE.DBMS_CLOUD_AI", line 18783
ORA-06512: at line 1
Embeddingモデルを指定しない場合、デフォルト値が採用されます。その結果、利用リージョンに存在しないモデルが指定されエラーが出る場合があります。なので上述の通りマニュアルから利用可能なモデルを確認し、プロファイルに指定しておくのが良いです。
Feedbackの実行
Feedbackをしてみましょう。SQLが複雑すぎることが問題の原因と考えられるため、もっとシンプルなSQLを生成するようにFeedbackしてみます。
SQL> SELECT AI feedback SQLが複雑すぎます。次からはJOINとORDER BYを使って、もっと短くシンプルなSQLを生成してください。;
RESPONSE
____________________________________________________________________________________________________________________________________________________
Based on your feedback, the SQL query for prompt "最新の注文日の最もオーダーが多い商品を検索して、その在庫数を教えて" is successfully refined. The refined SQL query as following:
SELECT SUM(i."STOCK_QTY") AS "TOTAL_INVENTORY"
FROM "AI_USER"."INVENTORY" i
JOIN (
SELECT inv."PROD_ID", inv."WH_NAME", MAX(inv."UPDATE_AT") AS max_update
FROM "AI_USER"."INVENTORY" inv
GROUP BY inv."PROD_ID", inv."WH_NAME"
) l ON i."PROD_ID" = l."PROD_ID" AND i."WH_NAME" = l."WH_NAME" AND i."UPDATE_AT" = l.max_update
WHERE i."PROD_ID" = (
SELECT top_prod."PROD_ID"
FROM (
SELECT o."PROD_ID", SUM(o."QTY") AS total_qty
FROM "AI_USER"."ORDERS" o
WHERE o."ORD_DATE" = (SELECT MAX(ord."ORD_DATE") FROM "AI_USER"."ORDERS" ord)
GROUP BY o."PROD_ID"
ORDER BY total_qty DESC
FETCH FIRST 1 ROW ONLY
) top_prod
)
フィードバックの結果、改善案を提示してくれています。
複雑な問い合わせを再トライ
このフィードバック結果を利用してくれることを期待しつつ、再度少し複雑な問い合わせを投げてみましょう。
SQL> SELECT AI showsql 最新の注文日の最もオーダーが多い商品を検索して、その在庫数を教えて;
RESPONSE
__________________________________________________________________________________________________
SELECT SUM(i."STOCK_QTY") AS "TOTAL_INVENTORY"
FROM "AI_USER"."INVENTORY" i
JOIN (
SELECT inv."PROD_ID", inv."WH_NAME", MAX(inv."UPDATE_AT") AS max_update
FROM "AI_USER"."INVENTORY" inv
GROUP BY inv."PROD_ID", inv."WH_NAME"
) l ON i."PROD_ID" = l."PROD_ID" AND i."WH_NAME" = l."WH_NAME" AND i."UPDATE_AT" = l.max_update
WHERE i."PROD_ID" = (
SELECT top_prod."PROD_ID"
FROM (
SELECT o."PROD_ID", SUM(o."QTY") AS total_qty
FROM "AI_USER"."ORDERS" o
WHERE o."ORD_DATE" = (SELECT MAX(ord."ORD_DATE") FROM "AI_USER"."ORDERS" ord)
GROUP BY o."PROD_ID"
ORDER BY total_qty DESC
FETCH FIRST 1 ROW ONLY
) top_prod
)
SQL> SELECT AI 最新の注文日の最もオーダーが多い商品を検索して、その在庫数を教えて;
TOTAL_INVENTORY
__________________
443
比較的シンプルなSQLが発行され、今度はエラーを回避し動作しました。
念の為SQLでも回答が正しいか確認してみましょう。
-- 最新の注文日の確認
SQL> SELECT MAX("ORD_DATE") FROM "AI_USER"."ORDERS";
MAX("ORD_DATE")
__________________
26-FEB-26
-- その日の最多販売商品を特定
SQL> SELECT "PROD_ID", SUM("QTY")
2 FROM "AI_USER"."ORDERS"
3 WHERE "ORD_DATE" = DATE '2026-02-26'
4 GROUP BY "PROD_ID"
5 ORDER BY 2 DESC
6* FETCH FIRST 1 ROW ONLY;
PROD_ID SUM("QTY")
__________ _____________
MTR-015 172
-- その商品の在庫合計を算出
SQL> SELECT SUM("STOCK_QTY") AS FINAL_CHECK
2 FROM "AI_USER"."INVENTORY"
3* WHERE "PROD_ID" = 'MTR-015';
FINAL_CHECK
______________
443
443が出力されました。少し時間はかかりますがSelect AIは正しい回答を出力しています。
参考: RPC Connection Errorへのその他の対応
Connection Errorの対応としてSQLを簡単にするアプローチを試しました。
それでもやはりこのエラーはちょこちょこ出ます。
DBのパラメータ設定の方もいじってTimeoutしづらくできないか試みてみます。
PostgreSQL側のタイムアウト等の設定を見てみましょう。
-- 変更前
postgres=> SELECT name, setting, unit, short_desc FROM pg_settings WHERE name IN ('statement_timeout', 'idle_in_transaction_session_timeout', 'tcp_keepalives_idle', 'max_connections', 'work_mem');
name | setting | unit | short_desc
-------------------------------------+---------+------+----------------------------------------------------------------------------
idle_in_transaction_session_timeout | 0 | ms | Sets the maximum allowed idle time between queries, when in a transaction.
max_connections | 225 | | Sets the maximum number of concurrent connections.
statement_timeout | 0 | ms | Sets the maximum allowed duration of any statement.
tcp_keepalives_idle | 7200 | s | Time between issuing TCP keepalives.
work_mem | 2048 | kB | Sets the maximum memory to be used for query workspaces.
(5 rows)
statement_timeout は 0(無制限)なのに「計算中」に切れるということは、Postgres自体が問題ではなく、ネットワーク経路の遮断、あるいはPostgres側でのリソース不足による遅延が考えられます。
少しkeepaliveを短くし、メモリの割り当てを増加することで、処理の途中で切れてしまうことが防げるかもしれません。
-- キープアライブを60秒に、メモリの割り当てを増強
SET tcp_keepalives_idle = 60; SET work_mem = '64MB';
-- 変更後
postgres=> SELECT name, setting, unit, short_desc FROM pg_settings WHERE name IN ('statement_timeout', 'idle_in_transaction_session_timeout', 'tcp_keepalives_idle', 'max_connections', 'work_mem');
name | setting | unit | short_desc
-------------------------------------+---------+------+----------------------------------------------------------------------------
idle_in_transaction_session_timeout | 0 | ms | Sets the maximum allowed idle time between queries, when in a transaction.
max_connections | 225 | | Sets the maximum number of concurrent connections.
statement_timeout | 0 | ms | Sets the maximum allowed duration of any statement.
tcp_keepalives_idle | 60 | s | Time between issuing TCP keepalives.
work_mem | 65536 | kB | Sets the maximum memory to be used for query workspaces.
(5 rows)
設定後も完全な改善はできませんでしたが、若干エラーが出づらくなった気がします。
3-3: ADB上にもテーブルを作成し、PostgreSQLとADB双方を参照する問い合わせの検証
これまではADB上にはViewのみ存在し、データの実態は持っていませんでした。
次はADBにもテーブルを持たせ、同じ問合せの中でPostgreSQLの表とADBの表の両方を参照し回答できるかみてみましょう。
オブジェクトの配置イメージは以下の通りです。

ADBへのデータの投入
ADB上にはCUSTOMERSテーブルを作成します。
CREATE TABLE CUSTOMERS (
CUST_ID VARCHAR2(10) PRIMARY KEY,
CUST_NAME VARCHAR2(100),
REGION VARCHAR2(50),
RANK VARCHAR2(20)
);
CUST_IDはPostgreSQLのテーブルには50種類あります。ですので、50個の会社名をInsertしていきます。
INSERT INTO CUSTOMERS (CUST_ID, CUST_NAME, REGION, RANK)
SELECT
'CUST-' || LPAD(LEVEL, 3, '0'),
CASE LEVEL
WHEN 1 THEN 'ヨツビシ交易'
WHEN 2 THEN 'ミツカド決済銀行'
WHEN 3 THEN 'トヨノキ自走車'
WHEN 4 THEN 'ハードバンク'
WHEN 5 THEN 'トニー電子'
WHEN 6 THEN 'ギガテンドー'
WHEN 7 THEN 'ワンツーアパレル'
WHEN 8 THEN 'ロックエンス'
WHEN 9 THEN 'ハローワークス'
WHEN 10 THEN '大西洋商事'
WHEN 11 THEN 'オリジン貸付'
WHEN 12 THEN 'ノムラノ証券'
WHEN 13 THEN '江戸海上火災'
WHEN 14 THEN 'ジャパンポスト'
WHEN 15 THEN 'ダイキン冷熱'
WHEN 16 THEN 'クボタネ農業'
WHEN 17 THEN 'テルモト医療'
WHEN 18 THEN 'エイトアイ'
WHEN 19 THEN 'アオンモール'
WHEN 20 THEN 'ロードストーン'
WHEN 21 THEN 'タケノコ製薬'
WHEN 22 THEN 'ナカソト薬品'
WHEN 23 THEN 'テラス製薬'
WHEN 24 THEN 'ダイイチ二共'
WHEN 25 THEN 'シンエツ化成'
WHEN 26 THEN 'エヌティー電信'
WHEN 27 THEN 'ケイビービーアイ'
WHEN 28 THEN 'ヒガシ日本鉄道'
WHEN 29 THEN 'トウカイ鉄道'
WHEN 30 THEN 'スカイジャパン'
WHEN 31 THEN 'ブルーエアライン'
WHEN 32 THEN 'クロネコ配送'
WHEN 33 THEN 'ニトリル家具'
WHEN 34 THEN '味のタネ'
WHEN 35 THEN 'サンライズ麦酒'
WHEN 36 THEN 'キリンジ飲料'
WHEN 37 THEN 'サントリーニ水'
WHEN 38 THEN 'メイジロウ乳業'
WHEN 39 THEN '日本煙草'
WHEN 40 THEN 'ハナオウ石鹸'
WHEN 41 THEN 'シセイドウ化粧'
WHEN 42 THEN 'パナニック家電'
WHEN 43 THEN 'ヒタチノ製作'
WHEN 44 THEN 'フジノミ通報'
WHEN 45 THEN 'エヌイーシー電'
WHEN 46 THEN 'キャノンポッド'
WHEN 47 THEN 'デンソウ自動'
WHEN 48 THEN 'ムラタノ部品'
WHEN 49 THEN 'ファナックル'
ELSE 'セキスイホーム'
END,
CASE MOD(LEVEL, 4) WHEN 0 THEN '東京' WHEN 1 THEN '名古屋' WHEN 2 THEN '大阪' ELSE '福岡' END,
CASE MOD(LEVEL, 3) WHEN 0 THEN 'Gold' WHEN 1 THEN 'Silver' ELSE 'Platinum' END
FROM DUAL
CONNECT BY LEVEL <= 50;
COMMIT;
クレデンシャルの修正
オブジェクトが増えたのでクレデンシャルに登録しておきましょう。クレデンシャルを再作成します。
BEGIN
-- 既存のプロファイルを一度削除
DBMS_CLOUD_AI.DROP_PROFILE(profile_name => 'GENAI_POSTGRES_PROFILE');
-- 再作成
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_POSTGRES_PROFILE',
attributes => '{"provider": "oci",
"credential_name": "OCI_CRED",
"region": "us-ashburn-1",
"model": "xai.grok-4",
"embedding_model": "cohere.embed-v4.0",
"object_list": [
{"owner": "ai_user", "name": "ORDERS"},
{"owner": "ai_user", "name": "INVENTORY"},
{"owner": "AI_USER", "name": "CUSTOMERS"}
]
}'
);
END;
/
Select AIで検索
Select AIで作成したテーブルを含めた検索をしてみましょう。
3つのパターンで試していきます。
- 1つのテーブルへの問い合わせ(ADBのみ)
- 2つのテーブルへの問い合わせ(ADBとPostgreSQL)
- 3つのテーブルへの問い合わせ(ADBとPostgreSQL)
ADB上のテーブルのみへの問い合わせ
まずはADB上のCUSOTMERSテーブルのみを参照する問い合わせをしてみましょう。
SQL> SELECT AI showsql ギガテンドー の顧客ID教えて;
RESPONSE
_______________________________________________
SELECT c."CUST_ID" AS customer_id
FROM "AI_USER"."CUSTOMERS" c
WHERE UPPER(c."CUST_NAME") = UPPER('ギガテンドー')
SQL> SELECT AI ギガテンドー の顧客ID教えて;
CUSTOMER_ID
______________
CUST-006
きちんと結果が得られました。
ADBとPostgreSQLの両テーブルへの問い合わせ
次はPostgreSQLのテーブルも参照させましょう。
先ほど同様、ADB上のCUSTOMERSテーブルに加えて、PostgreSQL上のORDERSテーブルも参照する必要のある問い合わせを投げてみます。
SQL> SELECT AI showsql ギガテンドーの注文件数を集計して。;
RESPONSE
____________________________________________________________
SELECT COUNT(o."ORD_ID") AS ORDER_COUNT
FROM "AI_USER"."ORDERS" o
JOIN "AI_USER"."CUSTOMERS" c ON o."CUST_ID" = c."CUST_ID"
WHERE UPPER(c."CUST_NAME") = UPPER('ギガテンドー')
SQL> SELECT AI ギガテンドーの注文件数を集計して。;
ORDER_COUNT
______________
18
18件の注文を取得しているという結果が得られました。
素晴らしいですね。
3つのテーブルへの問い合わせ
次は先ほどの2つに加えて、3つ目のINVENTORYテーブルも含めた3つのテーブルで試してみましょう。複数のJOINでSQLが重たくなることが予想されるので、SQLはシンプルに書くよう指示しておきます。
SQL> SELECT AI showsql ギガテンドーが注文した各製品について、その注文数量と、その製品が保管されている倉庫名を一覧で出して。SQLはシンプルに。;
RESPONSE
___________________________________________________________________
SELECT p."PROD_ID" AS "PRODUCT_ID",
p."TOTAL_QUANTITY" AS "ORDER_QUANTITY",
i."WH_NAME" AS "WAREHOUSE_NAME"
FROM (SELECT o."PROD_ID",
SUM(o."QTY") AS "TOTAL_QUANTITY"
FROM "AI_USER"."ORDERS" o
JOIN "AI_USER"."CUSTOMERS" c ON o."CUST_ID" = c."CUST_ID"
WHERE UPPER(c."CUST_NAME") = UPPER('ギガテンドー')
GROUP BY o."PROD_ID") p
JOIN (SELECT DISTINCT "PROD_ID",
"WH_NAME"
FROM "AI_USER"."INVENTORY") i ON p."PROD_ID" = i."PROD_ID"
SQL> SELECT AI ギガテンドーが注文した各製品について、その注文数量と、その製品が保管されている倉庫名を一覧で出して。SQLはシンプルに。;
PRODUCT_ID ORDER_QUANTITY WAREHOUSE_NAME
_____________ _________________ _________________
CB-014 6 横浜第一
SNS-021 7 横浜第一
MTR-026 3 横浜第一
SNS-023 4 福岡センター
SNS-024 1 名古屋ロジ
CB-006 10 名古屋ロジ
MTR-015 6 名古屋ロジ
MTR-032 14 名古屋ロジ
SNS-007 7 横浜第一
SNS-003 5 名古屋ロジ
SNS-009 7 横浜第一
MTR-016 17 名古屋ロジ
CB-028 16 名古屋ロジ
CB-018 8 名古屋ロジ
CB-007 20 横浜第一
CB-028 5 名古屋ロジ
MTR-030 19 名古屋ロジ
MTR-025 15 横浜第一
18 rows selected.
ギガテンドーが注文したプロダクトのIDと数量、倉庫名をきちんと一覧表示してくれました。
18行表示されており、先ほどの注文数量の問い合わせ結果とも一致しています。
他のSelect AI Actionでの動作
Select AIにはさまざまなアクションが用意されています。
参照
| アクション | 実行内容 |
|---|---|
| runsql | SQLを生成し、実行結果を表形式で返します(デフォルト)。 |
| showsql | 生成されたSQL文を表示のみ行い、実行はしません。 |
| explainsql | 生成されたSQLの意図や構造を自然言語で解説します。 |
| narrate | 実行結果を読み取り、自然な文章や要約で返します。 |
| chat | データベースのデータを使わず、AIと一般的な対話を行います。 |
| showprompt | AIエンジンに送信されている生のプロンプトを表示します。 |
| agent | 定義されたAIエージェントに複雑なタスクを依頼します。 |
| summarize | 指定したテキストや非構造化データの要約を行います。 |
| translate | プロンプトを指定したターゲット言語に翻訳します。 |
| feedback | 生成された回答に対してユーザーが評価や修正をフィードバックします。 |
narrateを使ってより対話っぽい形式で回答を得たいと思います。
narrate Actionを指定するとSQLクエリの結果をAIがまとめて返してくれます。
SQL> SELECT AI narrate ギガテンドーの注文状況を、倉庫ごとの注文数にまとめて教えて。;
RESPONSE
_________________
- 横浜第一: 7件
- 福岡センター: 1件
- 名古屋ロジ: 10件
SQL> SELECT AI narrate 直近の注文傾向から見て、"ギガテンドー"が最も好んで注文している製品はなに?;
RESPONSE
____________________________________________
ギガテンドーが直近で最も多く注文している製品はCB-028で、合計数量は21です。
18件あったギガテンドーの注文の中にCD-028は2つあり、それらを足し合わせた個数をきちんと返してくれています。
まとめ
今回はADBのNL2SQLの機能であるSelect AIを利用して、PostgreSQL上にあるデータを自然言語問い合わせしました。生成されるSQLの複雑さによって少し動作が不安定な部分もありますが、コメントやFeedbackにてAIにコンテキストを渡してあげることにより、きちんと正しい回答が得られることを確認できました。
ADBが接続できるデータソースとしてはこちらに記載の通りで、たくさんの選択肢があります。ADBをハブにすることで、多様なデータソースに対するNL2SQLを1度に実装できるのは非常に大きなメリットになると思います。
ぜひいろんなデータソースをADBをハブにして繋ぎ自然言語問い合わせしてみてください!
おまけ: シノニムを使ったSelectAI
Select AIを使うにはADB上にPostgreSQL上のデータがある様に見せる必要があります。
先ほどはViewを使ってマッピングしたわけですが、別のアプローチとしてシノニムを利用してみたいと思います。
2026年3月現在、公式で推奨される手法ではないのでご注意ください。
-- シノニムの作成
CREATE OR REPLACE SYNONYM SYN_ORDERS FOR "order_system"."orders"@POSTGRESQL_LINK;
CREATE OR REPLACE SYNONYM SYN_INVENTORY FOR "order_system"."inventory"@POSTGRESQL_LINK;
PostgreSQL上のorder_systemスキーマ上のordersテーブルと、inventoryテーブルをADB上ではSYN_ORDERS、SYN_INVENTORYに置き換えているイメージです。
シノニムによりDBLINKのリンク先を定義できました。
確認してみましょう。
SQL> SELECT * FROM SYN_ORDERS FETCH FIRST 1 ROWS ONLY;
ord_id prod_id qty ord_date status cust_id
_________ __________ ______ ____________ __________ ___________
1 MTR-015 15 10-FEB-26 Pending CUST-044
きちんとデータを取得できています。
Select AIしてみる
DBLINK先のPostgreSQLに対してもSelect AIが可能か試してみましょう。
まずはプロファイルを有効化します
EXEC DBMS_CLOUD_AI.SET_PROFILE('GENAI_POSTGRES_PROFILE');
問い合わせを投げてみましょう。
SQL> SELECT AI SYN_ORDERSのデータを3件だけ見せて;
ord_id prod_id qty ord_date status cust_id
_________ __________ ______ ____________ ____________ ___________
1 MTR-015 15 10-FEB-26 Pending CUST-044
2 MTR-015 9 16-FEB-26 Cancelled CUST-019
3 MTR-015 6 15-FEB-26 Cancelled CUST-036
シノニム名を指定すればうまく行きました。
シノニム名を指定しない場合は、テーブルが見つけられずエラーとなりました。
SQL> SELECT AI 最新のorderを3件見せて;
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:
SELECT *
FROM (SELECT * FROM "order" o
ORDER BY o."order_date" DESC)
WHERE ROWNUM <= 3
Exception encountered: ORA-00942: table or view "ADMIN"."order" does not exist
シノニム名を察してOrderテーブルを見に行くのは少しむずかしそうです。そこでシノニム名を大元のテーブル名と同じにして試してみます。
シノニム名の変更
既存のシノニムを削除し、PostgreSQL上のテーブル名と同じ名前で新しく作成します。
-- 古いシノニムを削除
DROP SYNONYM SYN_ORDERS;
DROP SYNONYM SYN_INVENTORY;
-- 元のテーブル名と同じ名前でシノニムを作成
CREATE SYNONYM ORDERS FOR "order_system"."orders"@POSTGRESQL_LINK;
CREATE SYNONYM INVENTORY FOR "order_system"."inventory"@POSTGRESQL_LINK;
-- 確認
SELECT * FROM ORDERS FETCH FIRST 1 ROWS ONLY;
ord_id prod_id qty ord_date status cust_id
_________ __________ ______ ____________ __________ ___________
1 MTR-015 15 10-FEB-26 Pending CUST-044
プロファイルの修正
プロファイルも修正します
-- 既存のプロファイルの削除
BEGIN
DBMS_CLOUD_AI.DROP_PROFILE(profile_name => 'GENAI_POSTGRES_PROFILE');
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
-- 再作成
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_POSTGRES_PROFILE',
attributes => '{"provider": "oci",
"credential_name": "OCI_CRED",
"region": "us-ashburn-1",
"model": "xai.grok-4",
"object_list": [
{"owner": "ai_user", "name": "ORDERS"},
{"owner": "ai_user", "name": "INVENTORY"}
]
}'
);
END;
/
-- プロファイルの有効化
EXEC DBMS_CLOUD_AI.SET_PROFILE('GENAI_POSTGRES_PROFILE');
Select AI を再トライ
再度試してみましょう
SQL> SELECT AI 最新のオーダーを3件見せて;
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:
SELECT o."ORDER_ID" AS order_id,
o."ORDER_DATE" AS order_date
FROM "ORDERS" o
ORDER BY o."ORDER_DATE" DESC
FETCH FIRST 3 ROWS ONLY
Exception encountered: ORA-28511: lost RPC connection to heterogeneous remote agent using SID=ORA-28511: lost RPC connection to heterogeneous remote agent using SID=***
SQLは問題なさそうですが、少し機嫌がよろしくないようです。
単純なSQLになる様にプロンプトを変えてみるとどうでしょうか。
SQL> SELECT AI オーダーを単純に3件だけ表示して;
ord_id prod_id qty ord_date status cust_id
_________ __________ ______ ____________ ____________ ___________
1 MTR-015 15 10-FEB-26 Pending CUST-044
2 MTR-015 9 16-FEB-26 Cancelled CUST-019
3 MTR-015 6 15-FEB-26 Cancelled CUST-036
今度は結果が得られました。
ViewではなくシノニムでもSELECT AIは動作することが確認できました。
結論
Viewを作成せずにシノニムを使ってSelect AIにてPostgreSQLのデータを取得することはできました。
ただし、シノニムを使うにはシノニム名をAIがテーブル名と解釈しないように、もしくはしても問題ないように工夫する必要があります。
また、チューニングに関してですが、シノニムにはコメントを付与することができません。なので、プロンプトでの情報付加か、SELECT AI Feedbackでのチューニングが有効です。
やはり使い勝手としてはViewの方が優れていると感じました。
-- シノニム自体へのコメントはSQLの構文エラーになる
SQL> COMMENT ON SYNONYM SYN_ORDERS IS 'PostgreSQLのordersテーブルのシノニムです';
Error starting at line : 1 in command -
COMMENT ON SYNONYM SYN_ORDERS IS 'PostgreSQLのordersテーブルのシノニムです'
Error report -
ORA-32594: invalid object category for COMMENT command
https://docs.oracle.com/error-help/db/ora-32594/
32594. 00000 - "invalid object category for COMMENT command"
*Cause: The object category specified is not a valid object for which to
use the COMMENT command.
*Action: Retry the COMMENT command by specifying an accepted object
category. (ie, Table, Operator, Indextype, etc.)
-- テーブルと指定してもリモートテーブルにはコメントはつけられない
SQL> COMMENT ON TABLE SYN_ORDERS IS 'PostgreSQLのordersテーブルのシノニムです';
Error starting at line : 1 in command -
COMMENT ON TABLE SYN_ORDERS IS 'PostgreSQLのordersテーブルのシノニムです'
Error report -
ORA-02021: DDL operations are not allowed on a remote database
https://docs.oracle.com/error-help/db/ora-02021/
02021. 00000 - "DDL operations are not allowed on a remote database"
*Cause: An attempt was made to use a DDL operation on a remote database.
For example, "CREATE TABLE tablename@remotedbname ...".
*Action: To alter the remote database structure, you must connect to the
remote database with the appropriate privileges.
