はじめに
Oracle Databaseの Select AI と VPD(Virtual Private Database) を組み合わせることで、自然言語によるデータアクセスをセキュアに制御できます。
本記事では、開発者・DBAが知っておくと便利なユースケースを例示します。
Select AIとは❓️
自然言語をSQLに変換し、データベースの問い合わせを自動化できるAIエージェントです。
アプリケーション側でSQLを書かずに「日本語で質問」するだけでデータを取得できます。
SELECT AI 各ユーザの月ごとの残業時間を教えて。;
VPD(Virtual Private Database)とは❓️
アクセスユーザーやコンテキストに応じて、自動的にSQLに条件を付与する仕組みです。従来のアプリ側制御とは違い、DBエンジンが直接アクセス制御を行うため、高いセキュリティを実現できます。
実際の仕組みとしては、与えられた情報(DBユーザ、セッション変数等々)をもとにしてDBファンクション内に記述されたルールでアクセス処理をコントロールします。
🧩Select AI × VPDの相乗効果
Select AIは、自然言語での問い合わせを自動的にSQLに変換して実行できる“AIエージェント”であり、多くの処理をデータベース側で完結させられるのが特徴です。
これによりアプリケーション側の実装負担を大幅に減らし、効率的に情報を取得できます。
一方で「誰が」「どんな条件で」データを参照できるかというアクセス制御までは、Select AI単体では担えません。
そこでVPD(Virtual Private Database)を組み合わせることで、自然言語による問い合わせの背後でもきめ細かな権限制御を自動的に適用可能です。
結果として、検索処理や権限管理もDB側にオフロードでき、アプリケーションはよりシンプルかつセキュアに運用できます。
検証のための下準備
デモとして、社員の勤怠情報を取得するためのテーブル構成をサンプルとして作ります。
テーブル/ビューは以下の3つです:
- EMPLOYEE:社員の基本情報を管理するマスタテーブル
- ATTENDANCE_DAILY:社員ごとの日単位の勤怠実績を記録するテーブル
- ATTENDANCE_MONTHLY_SUMMARY:日次勤怠を集計して月ごとの勤務状況を確認するビュー
EMPLOYEE (社員マスタ)
│ 1 ──── n
│
▼
ATTENDANCE_DAILY (日次勤怠)
│ 集計
▼
ATTENDANCE_MONTHLY_SUMMARY (月次勤怠ビュー)
ATTENDANCE_MONTHLY_SUMMARY (月次勤怠ビュー)の中身です⬇️
また各テーブル/ビューにはselect aiがコンテキストとして判断できるようにコメントを入れておきます。
COMMENT ON COLUMN "SELECT_AI_USER"."EMPLOYEE"."EMP_ID" IS
'内部的な一意ID。システムで自動採番され、社員ごとに一意で不変。外部表示や書類番号としてではなく、他表との結合キーとして用いる。';
COMMENT ON COLUMN "SELECT_AI_USER"."EMPLOYEE"."EMP_CODE" IS
'人事・給与等で用いられる社内の社員コード。人間が扱いやすい識別子で、システム間連携や検索で使用される。例: E000123。';
COMMENT ON COLUMN "SELECT_AI_USER"."EMPLOYEE"."EMP_NAME" IS
'社員の氏名(フルネーム)。日本語での表記を想定。姓と名の間には全角または半角スペースが入ります(例:田中 明子)。検索・一覧・レポート表示で用いる。';
-- (中略)
COMMENT ON TABLE "SELECT_AI_USER"."EMPLOYEE" IS
'社員の基本情報を保持する表。各行は1名の社員を表す。勤怠明細や各種集計と結合する基準となる。';
AIプロファイルの作成
select aiで使うためのAIプロファイルを作成します。
大阪リージョン、comere commnad aを使用します。
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
'GENAI_COHERE_COMMAND_A',
'{
"provider": "oci",
"credential_name": "OCI_GENAI_CRED",
"comments":"true",
"model":"cohere.command-a-03-2025",
"region": "ap-osaka-1",
"object_list": [
{"owner": "select_ai_user", "name": "EMPLOYEE"},
{"owner": "select_ai_user", "name": "ATTENDANCE_DAILY"},
{"owner": "select_ai_user", "name": "ATTENDANCE_MONTHLY_SUMMARY"}
]
}'
);
END;
/
登録したプロファイルはこちらで確認できます⬇️
SELECT PROFILE_ID, PROFILE_NAME, STATUS, DESCRIPTION, CREATED, LAST_MODIFIED
FROM USER_CLOUD_AI_PROFILES
ORDER BY CREATED DESC;
EXEC DBMS_CLOUD_AI.SET_PROFILE('GENAI_COHERE_COMMAND_A');
SELECT AI 単体の検証
検証1️⃣
select ai 従業員一覧をください;
SELECT
e."EMP_ID" AS 従業員ID,
e."EMP_NAME" AS 従業員名,
e."EMP_CODE" AS 従業員コード,
e."DEPT_NAME" AS 所属部署,
e."EMPLOYMENT_TYPE" AS 雇用区分,
e."HIRE_DATE" AS 入社日,
e."TERMINATION_DATE" AS 退職日
FROM
"SELECT_AI_USER"."EMPLOYEE" e
ORDER BY
e."EMP_NAME"
| 従業員ID | 従業員名 | 従業員コード | 所属部署 | 雇用区分 | 入社日 | 退職日 |
|----------|------------|--------------|----------|------------|----------|--------|
| 1 | 佐藤花子 | E002 | 開発部 | 正社員 | 01/04/24 | (null) |
| 2 | 山田太郎 | E001 | 営業部 | 正社員 | 01/04/24 | (null) |
| 3 | 田中三郎 | E004 | 企画部 | 正社員 | 01/04/24 | (null) |
| 4 | 鈴木次郎 | E003 | 運用部 | 契約社員 | 01/04/24 | (null) |
| 5 | 高橋四季 | E005 | サポート | パート | 01/04/24 | (null) |
| 6 | 高橋大輔 | E000901 | 開発部 | 正社員 | 01/01/24 | (null) |
検証2️⃣
select ai 開発部の社員を教えて;
All rows fetched: 1 in 0.181 seconds
Press SPACE to select this row.
SELECT e."EMP_NAME"
FROM "SELECT_AI_USER"."EMPLOYEE" e
WHERE UPPER(e."DEPT_NAME") = UPPER('開発部')
AND e."TERMINATION_DATE" IS NULL
| ID | EMP_NAME |
|----|----------|
| 1 | 佐藤花子 |
| 2 | 高橋大輔 |
検証3️⃣
select ai 開発部社員の各月の残業時間(h)を教えて;
SELECT
e."EMP_NAME" AS 社員名,
TO_CHAR(ams."MONTH_START", 'YYYY-MM') AS 月,
(ams."OVERTIME_MINUTES" / 60) AS 残業時間_時間
FROM
"SELECT_AI_USER"."EMPLOYEE" e
JOIN
"SELECT_AI_USER"."ATTENDANCE_MONTHLY_SUMMARY" ams
ON e."EMP_ID" = ams."EMP_ID"
WHERE
UPPER(e."DEPT_NAME") = UPPER('開発部')
ORDER BY
e."EMP_NAME", ams."MONTH_START"
- 月次の勤怠計算のため、ATTENDANCE_MONTHLY_SUMMARY (月次勤怠ビュー)を上手く必要な情報を持つコンテキスト対象として認識できています。
- 元々、対象のテーブル/ビューは分単位で保持しているものをsql内で分→時間に変換しています
| 社員名 | 月 | 残業時間_時間 |
|----------|----------|---------------------|
| 佐藤花子 | 2025-06 | 13 |
| 佐藤花子 | 2025-07 | 34.5 |
| 佐藤花子 | 2025-08 | 13.25 |
| 高橋大輔 | 2025-06 | 13.3333333333333333 |
| 高橋大輔 | 2025-07 | 60 |
| 高橋大輔 | 2025-08 | 10 |
VPD で実現する部署ごとのデータアクセス制御
📝 背景
SaaSアプリケーションでは、ユーザーがどの部署に属しているかに応じて、参照可能なデータを制御したいケースが多くあります。
従来はアプリケーション側でフィルタ条件を組み立てて SQL に付与していましたが、この方式では実装が複雑になり、SQLインジェクション等のリスクも増えてしまいます。
そこで登場するのが VPD (Virtual Private Database) です。
VPD を使うことで、部署ごとのアクセス制御をアプリ側ではなくDB側に委ねられます。
一方、問い合わせ生成の部分では Oracle Database Select AI を利用すれば、ユーザーは自然言語で「営業部の社員を一覧して」といった問い合わせをするだけで、SQLをアプリ側で実装・調整することなく、あらゆる検索パターンに対応できます。
つまり、
- 問い合わせの生成は Select AI
- アクセス制御は VPD
と役割を分担することで、アプリ側は「認証済みユーザーの属性を渡す」だけで済み、SQL実装や権限制御の負担を大幅に削減できます。
🗝️ アプリケーションコンテキストの基本
Oracle のアプリケーションコンテキストは、セッションごとに保持される「キーとバリューのペア」を格納できる仕組みです。
今回の例では次のように使います:
キー: EMP_CTX(アプリケーションコンテキスト名)
属性: DEPT
値: ユーザーの所属部署名(例: 開発部, 営業部 など)
アプリケーションはログイン後に pkg_emp_ctx.set_dept('営業部') のように呼び出すだけでOKです。
以降のSQLでは自動的に「営業部のデータしか見えない」ように制御できます(実の細かい挙動はDBファンクションにて制御)
⚙️ 実装例
アプリケーションコンテキストを作成
-- 部門ごとに制御するためのアプリケーションコンテキストを定義
CREATE CONTEXT emp_ctx USING select_ai_user.pkg_emp_ctx;
部門を設定するパッケージ
CREATE OR REPLACE PACKAGE select_ai_user.pkg_emp_ctx AS
-- 部門名をコンテキストにセットするプロシージャ
PROCEDURE set_dept(p_dept VARCHAR2);
END;
/
CREATE OR REPLACE PACKAGE BODY select_ai_user.pkg_emp_ctx AS
PROCEDURE set_dept(p_dept VARCHAR2) IS
BEGIN
DBMS_SESSION.SET_CONTEXT('EMP_CTX','DEPT', p_dept);
END;
END;
/
🔑 SaaSにおける認証とコンテキスト設定
多くのSaaS環境では、すでにユーザー認証機構が存在しています。
その認証機構の中で、ユーザー属性(例: 部署)を取得し、アプリケーションがDB接続直後に以下を実行する形となります。
BEGIN
select_ai_user.pkg_emp_ctx.set_dept(:user_department);
END;
🛡️ VPDとの組み合わせ
アプリケーションコンテキストにセットした値は VPDポリシー で利用できます。
-- employeeテーブルの部門ごと制御をおこなうファンクション
CREATE OR REPLACE FUNCTION select_ai_user.f_rls_emp_by_ctx(
p_schema VARCHAR2, p_object VARCHAR2
) RETURN VARCHAR2 AS
v_dept VARCHAR2(100) := SYS_CONTEXT('EMP_CTX','DEPT');
BEGIN
IF v_dept IS NULL THEN
RETURN '1=0'; -- 部門未設定なら何も見せない
ELSIF v_dept = '人事部' THEN
RETURN '1=1'; -- 人事部の場合は制限なし(全行参照可)
ELSE
v_dept := REPLACE(v_dept, '''', '''''');
RETURN 'DEPT_NAME = ''' || v_dept || '''';
END IF;
END;
/
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'SELECT_AI_USER',
object_name => 'EMPLOYEE',
policy_name => 'POL_EMP_BY_CTX',
function_schema => 'SELECT_AI_USER',
policy_function => 'F_RLS_EMP_BY_CTX',
statement_types => 'SELECT'
);
END;
/
この例では「営業部のユーザーが検索すると、EMPLOYEE表から営業部の社員だけ見える」という制御がDB側で自動的に働きます。
また人事部の場合は制限なく見れ、該当しない部門に関してはデータを見せない仕様です。
🔍 Select AI × VPDの検証
部門指定による行レベル制御
まずは開発部の情報をアプリケーションコンテキストでセッション変数として渡します
-- 開発部のユーザ
BEGIN
select_ai_user.pkg_emp_ctx.set_dept('開発部');
END;
/
その後、↑で実施したVPDなしと同じ自然言語でselect aiを実施します。
select ai 従業員一覧をください;
| 従業員ID | 従業員名 | 従業員コード | 所属部署 | 雇用区分 | 入社日 | 退職日 |
|----------|----------|--------------|----------|----------|----------|--------|
| 1 | 佐藤花子 | E002 | 開発部 | 正社員 | 01/04/24 | (null) |
| 6 | 高橋大輔 | E000901 | 開発部 | 正社員 | 01/01/24 | (null) |
何もせずとも開発部だけの結果が帰ってきました。
ちなみにshowsqlを入れて実行sqlを見ると、where句で絞る記述は見当たらないので、内部的に処理しているようです。
select ai showsql 従業員一覧をください;
-- where句は入ってない
SELECT
e."EMP_ID" AS 従業員ID,
e."EMP_NAME" AS 従業員名,
e."EMP_CODE" AS 従業員コード,
e."DEPT_NAME" AS 所属部署,
e."EMPLOYMENT_TYPE" AS 雇用区分,
e."HIRE_DATE" AS 入社日,
e."TERMINATION_DATE" AS 退職日
FROM
"SELECT_AI_USER"."EMPLOYEE" e
ORDER BY
e."EMP_NAME"
日次/月次の勤怠情報も必要なテーブル/ビューにポリシーを設定(DBMS_RLS.ADD_POLICY)しておくことで同様に限定した取得が可能です。
おわりに
本記事では、Oracle Database の Select AI による自然言語検索と、VPD (Virtual Private Database) によるきめ細かなアクセス制御を組み合わせることで、アプリケーション開発をシンプルにしつつセキュリティを強化できることを紹介しました。
- Select AI によって、ユーザーは SQL を意識せず直感的にデータを取得できる
- VPD によって、背後で自動的に行レベルの制御を適用し、安全なデータアクセスを担保できる
この二つを統合することで、「利便性」と「セキュリティ」を両立した新しいデータ活用の形が実現します。
SaaS や社内システムにおいて、実装負担を減らしつつ信頼性の高いデータアクセスを提供したい開発者・DBAにとって、大きな選択肢となるでしょう。
記事のボリュームが思ったほど大きくなってしまったので、列レベルのコントロールとしてのData Redactionについては記事を分けて、また別途書きたいと思います🙏✨