4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Oracle DBを踏み台にしてPostgreSQLを自然言語検索する(Oracle AI Proxy Database)

4
Last updated at Posted at 2026-03-26

はじめに

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上のテーブル両方を参照する自然言語問い合わせも試してみたいと思います。
検証環境の構成は以下のイメージです。
image.png

検証はざっくり以下の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としました。
image.png

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

PostgreSQLにアクセス

作成したDBにアクセスしていきます。こちらの手順を参考に進めていきます。

クライアントのインストール

まずはアクセス元となるVMにClientをInstallしていきます。
下記のURLにアクセスし、ComputeのOSおよび構築したPostgreSQLのバージョンに従ってインストールを進めていきます。
https://www.postgresql.org/download/

image.png

表示されたセットアップスクリプトをコピぺして実行します。

# スクリプトの作成
[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に配置します
image.png

今回はここに置いてみました

$ pwd
/home/opc/.postgresql
$ ls
CaCertificate-PostgreSQL.pub

接続パターン①: Computeから直接接続する場合

検証レベルだと直接接続するかと思いますので手順を記載します。

ネットワーク・セキュリティの設定

アクセス元のCompute等のPrivate IPと宛先のポート(5432)を入力します。
image.png

接続

psqlで接続します。クライアントをInstallしたComputeにて下記のコマンドを実行してください。

$ psql "sslmode=verify-full sslrootcert=CA証明書のパス host=PostgreSQLのプライベートエンドポイントFQDN dbname=postgres user=ユーザー名"

接続パターン②: Bastionを使う場合

マニュアルの手順ではBastionを経由して接続しているのでそちらも試しておきます。

アクセス元のComputeでの設定

Public Subnet上のComputeからアクセスしたいので、Bastionのプラグインを有効化します
コンピュート>管理>Oracle Cloudエージェント
※有効化してから実行中になるまで数分かかります
image.png

Bastionの作成

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

ネットワーク・セキュリティの設定

BastionからアクセスするVCNのIngressルールを追加しておきます
BastionのPrivate IPと宛先となるPostgreSQLのポート(5432)を入力します。
image.png

Bastionのセッションの作成

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

PostgreSQLへの接続

作成したセッションでSSHトンネルを作成します。
セッションの右側のメニューからSSHコマンドをコピーします
image.png
コピーしたコマンドは以下の様な形になっていると思います。
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ポートへのアクセスを許可します。
image.png

ついでにComputeからADBへのアクセス用のルールも追加しておきます

  • ソースCIDR: ComputeのPrivate IP
  • 宛先ポート: 1522

ADBの設定

WalletをDLしてアクセス元となるCompute上に配置します。
OCIコンソールにて「データベース接続>ウォレット」のダウンロードでDLします。
image.png
image.png

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の接続情報を確認します。
データベース接続の接続文字列を確認します。
image.png

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キーを取得します。
コンソール右上のユーザーアイコンをクリックし、ユーザー設定に飛びます。
image.png

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

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

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

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が複雑すぎます。次からはJOINORDER 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の表の両方を参照し回答できるかみてみましょう。
オブジェクトの配置イメージは以下の通りです。
image.png

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. 1つのテーブルへの問い合わせ(ADBのみ)
  2. 2つのテーブルへの問い合わせ(ADBとPostgreSQL)
  3. 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 最新のorder3件見せて;

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.
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?