使用AnalyticDB PostgreSQL Query Skill进行数据库查询分析

更新时间:
复制 MD 格式

概述

AnalyticDB PostgreSQL Query Skill 是一款面向 AI Agent 的数据库查询 Skill,能让任何具备 Shell 执行能力的 AI Agent(如 Claude、Qoder、ClawHub 等)通过 psql 连接阿里云云原生数据仓库AnalyticDB PostgreSQL,执行只读查询并可选将结果导出为本地 CSV 文件。

用户只需使用自然语言描述数据需求,Agent 即可自动:

  • 查询 DBA 配置的语义模型(_agent_meta schema),理解可用表、字段及关联关系

  • 在语义模型范围内生成合规 SQL

  • 经用户确认后执行查询,返回结果或导出 CSV 供下游分析

适用场景:数据提取、即席报表、探索性分析、多表关联查询、 CSV导出分析。

发布平台

平台

链接

GitHub

https://github.com/aliyun/alibabacloud-aiops-skills/tree/alibabacloud-analyticdb-postgresql-query-0.0.1-beta.1/skills/database/gpdb/alibabacloud-analyticdb-postgresql-query

Skills.sh

https://www.skills.sh/aliyun/alibabacloud-aiops-skills/alibabacloud-analyticdb-postgresql-query

ClawHub

https://clawhub.ai/sdk-team/skills/alibabacloud-analyticdb-postgresql-query

安装方式

方式一:通过 npx 安装(推荐)

在支持 npx 的 Agent 环境(如 Claude Desktop、Qoder 等)中,可直接运行:

npx skills add https://github.com/aliyun/alibabacloud-aiops-skills/tree/alibabacloud-analyticdb-postgresql-query-0.0.1-beta.1 --skill alibabacloud-analyticdb-postgresql-query

方式二:复制链接给 Agent 安装

将以下任一链接直接发送给支持 Skill 安装的 Agent,Agent 会自动识别并完成安装:

  • GitHub 链接:https://github.com/aliyun/alibabacloud-aiops-skills/tree/alibabacloud-analyticdb-postgresql-query-0.0.1-beta.1/skills/database/gpdb/alibabacloud-analyticdb-postgresql-query

  • Skills.sh 链接:https://www.skills.sh/aliyun/alibabacloud-aiops-skills/alibabacloud-analyticdb-postgresql-query

  • ClawHub 链接:https://clawhub.ai/sdk-team/skills/alibabacloud-analyticdb-postgresql-query

前置条件

条件

说明

psql 客户端

Agent 运行环境需安装 psql(推荐使用 AnalyticDB PostgreSQL 官方客户端包psql

数据库连接信息

通过环境变量或 ~/.pg_service.conf + ~/.pgpass 配置(详见下方安全配置)

AnalyticDB PostgreSQL 实例

阿里云已开通的 AnalyticDB PostgreSQL 实例(存储弹性模式7.0以上)

安全注意事项

本 Skill 涉及数据库直连操作,安全配置是使用前的首要工作。请务必完整阅读以下内容。

1. 必须使用只读账号

禁止使用超级管理员(superuser)账号连接。 请让 DBA 创建专用只读账号:

-- DBA 执行
CREATE ROLE readonly_agent LOGIN PASSWORD '<强密码>';
GRANT CONNECT ON DATABASE mydb TO readonly_agent;
GRANT USAGE ON SCHEMA public TO readonly_agent;
-- 仅授权业务所需的表(禁止 GRANT SELECT ON ALL TABLES)
GRANT SELECT ON public.orders, public.customers, public.products TO readonly_agent;

2. 强制只读连接

Skill 在每次连接时会强制启用只读模式(default_transaction_read_only=on),确保即使 SQL 中包含写操作也会被数据库拒绝。推荐通过 ~/.pg_service.conf 配置:

[adbpg]
host=gp-xxx.gpdb.rds.aliyuncs.com
port=5432
dbname=mydb
user=readonly_agent
options=-c default_transaction_read_only=on

3. 凭据安全 — 绝对禁止明文暴露

禁止行为

说明

在对话中输入密码

Agent 不会主动索要密码,也禁止在对话中接收密码

硬编码密码到脚本

密码必须通过 ~/.pgpass(权限 600)或环境变量传递

echo/cat 密码文件

Agent 被严格禁止执行任何可能暴露密码的命令

提交密码到版本控制

.pgpass.pgenv 等文件必须加入 .gitignore

正确做法:使用 ~/.pgpass 文件管理密码,并设置文件权限为 chmod 600

4. 配置专用资源组

为避免 Agent 查询与业务负载争抢资源,必须AnalyticDB PostgreSQL 中为只读账号配置专用资源组(Resource Group):

CREATE RESOURCE GROUP agent_readonly_rg WITH (concurrency=5, cpu_max_percent=10, memory_limit=10);
ALTER ROLE readonly_agent RESOURCE GROUP agent_readonly_rg;

若未配置专用资源组,Agent 会在会话启动时发出警告并暂停等待用户确认,更细致的资源组配置请参见使用Resource Group(资源组)进行负载管理

5. 语义模型作为访问边界

Agent 无法绕过语义模型直接查询任意表_agent_meta schema 充当硬性访问边界:

  • 只有在语义模型中注册的表和字段才可被查询

  • 未注册的表/字段请求会被拒绝并提示联系 DBA

  • 任何绕过尝试(如「紧急需要」「只这一次」)均被明确禁止

6. 人工确认机制(HITL)

Agent 在执行任何查询前 必须暂停并等待用户明确确认

  • 生成 SQL 后展示给用户,等待用户回复「确认」或「执行」后才会实际执行

  • 超过 10 万行的结果需二次确认

  • 检测到安全风险时立即暂停,不会「先执行再汇报」

7. 网络访问控制

确保 AnalyticDB PostgreSQL 实例的白名单中已添加 Agent 运行环境的出口 IP:

  • 登录阿里云 AnalyticDB PostgreSQL 控制台

  • 进入实例 → 数据安全性 → 白名单设置

  • 添加 Agent 所在机器的公网/VPC IP

快速开始

步骤一:安装 Skill

选择上述任一安装方式完成 Skill 安装。

步骤二:配置数据库连接

推荐方式(macOS/Linux):使用 pg_service.conf + pgpass

# 1. 创建 ~/.pg_service.conf
cat > ~/.pg_service.conf << 'EOF'
[adbpg]
host=gp-xxx.gpdb.rds.aliyuncs.com
port=5432
dbname=mydb
user=readonly_agent
options=-c default_transaction_read_only=on
EOF

# 2. 创建 ~/.pgpass
echo "gp-xxx.gpdb.rds.aliyuncs.com:5432:mydb:readonly_agent:<你的密码>" > ~/.pgpass
chmod 600 ~/.pgpass

# 3. 验证连接
psql "service=adbpg" -c "SELECT 1;"

步骤三:配置语义模型

联系 DBA 创建 _agent_meta schema 并注册可查询的表和字段。Skill 内置了语义模型初始化引导,Agent 会协助生成建表 SQL 交由 DBA 执行。

步骤四:开始查询

在对话中用自然语言描述查询需求即可:

用户:帮我查一下最近 7 天的订单总金额,按天分组
Agent:[生成 SQL 并展示] → 等待确认 → 执行 → 返回结果

功能特性

功能

说明

自然语言查询

用户用自然语言描述需求,Agent 自动生成 SQL

语义模型驱动

基于 _agent_meta 元数据理解表结构和业务语义

指标/过滤器

支持预定义业务指标(metric_meta)和过滤条件(filters)

CSV 导出

查询结果可导出为带时间戳的本地 CSV 文件

多表关联

支持基于语义模型的跨表 JOIN 查询

查询保护

自动附加 LIMIT(默认 50000)、语句超时(60s)

会话安全检查

每次会话自动检测权限、资源组、语义模型状态

兼容的 Agent 平台

  • Claude(Anthropic)— 通过 MCP 或 Skill 安装

  • Qoder / Qoder Worker — 原生支持

  • ClawHub — 通过 ClawHub 平台启用

  • 任何具备 Shell 执行能力的 AI Agent

约束与限制

约束项

限制值

单轮最大 psql 调用次数

5 次

失败重试次数

1 次

默认结果行数限制

50,000 行

语句超时

60 秒

允许的 SQL 类型

仅 SELECT / WITH...SELECT / EXPLAIN

写操作

全面禁止(数据库级别强制只读)

完整使用流程(电商场景实操)

以下以一个典型电商数据分析场景为例,手把手带你走完从环境准备到实际查询的完整流程。即使你从未使用过该 Skill,也可以按步骤逐一完成。

步骤一:确认环境准备

在开始之前,请确认以下条件已满足:

检查项

如何确认

AnalyticDB PostgreSQL 实例已开通

登录 AnalyticDB PostgreSQL 控制台可看到实例

psql 已安装

终端执行 psql --version 有版本号输出

Skill 已安装

参考上方「安装方式」章节完成安装

如果 psql 尚未安装:

推荐:使用 AnalyticDB PostgreSQL 官方客户端包,确保与 AnalyticDB PostgreSQL 服务器版本兼容。详细安装步骤参见 psql文档。

步骤二:创建只读账号(DBA 操作)

为什么需要单独账号? 禁止使用超级管理员账号连接。专用只读账号可确保 Agent 只能读取数据、无法修改或删除。

请 DBA 在 AnalyticDB PostgreSQL 实例上执行以下 SQL:

-- 1. 创建只读账号
CREATE ROLE readonly_agent LOGIN PASSWORD '<YOUR_PASSWORD_HERE>';

-- 2. 授予连接权限
GRANT CONNECT ON DATABASE mydb TO readonly_agent;
GRANT USAGE ON SCHEMA public TO readonly_agent;

-- 3. 创建专用资源组(防止 Agent 查询挤占业务资源)
CREATE RESOURCE GROUP agent_readonly_rg WITH (concurrency=5, cpu_max_percent=10, memory_limit=10);
ALTER ROLE readonly_agent RESOURCE GROUP agent_readonly_rg;

此时暂不需要授权具体表的 SELECT 权限,后续步骤会一起处理。更细致的资源组配置请参见使用Resource Group(资源组)进行负载管理

步骤三:配置本地连接信息

在你的本地机器(即 Agent 运行的环境)上配置连接信息。

macOS / Linux

创建 ~/.pg_service.conf(连接配置,不含密码):

cat > ~/.pg_service.conf << 'EOF'
[adbpg]
host=gp-xxxxxxxxx.gpdb.rds.aliyuncs.com
port=5432
dbname=mydb
user=readonly_agent
options=-c default_transaction_read_only=on
EOF

创建 ~/.pgpass(密码文件,权限必须为 600):

echo "gp-xxxxxxxxx.gpdb.rds.aliyuncs.com:5432:mydb:readonly_agent:<YOUR_PASSWORD_HERE>" > ~/.pgpass
chmod 600 ~/.pgpass

Windows

创建 %APPDATA%\postgresql\pg_service.conf

New-Item -ItemType Directory -Force -Path "$env:APPDATA\postgresql"
@"
[adbpg]
host=gp-xxxxxxxxx.gpdb.rds.aliyuncs.com
port=5432
dbname=mydb
user=readonly_agent
options=-c default_transaction_read_only=on
"@ | Out-File -Encoding UTF8 "$env:APPDATA\postgresql\pg_service.conf"

创建 %APPDATA%\postgresql\pgpass.conf

"gp-xxxxxxxxx.gpdb.rds.aliyuncs.com:5432:mydb:readonly_agent:<YOUR_PASSWORD_HERE>" | Out-File -Encoding UTF8 "$env:APPDATA\postgresql\pgpass.conf"

Windows 下 pgpass.conf 无需设置文件权限,PostgreSQL 会自动识别。

验证连接:

psql "service=adbpg" -c "SELECT 1;"

如果返回结果为 1,说明连接配置正确。

步骤四:创建业务表(DBA 操作)

实际使用中,你的业务表已经存在于数据库中,可跳过此步骤。本步骤仅用于体验完整流程。

以下步骤四、五、六的所有 SQL 均由 DBA 使用管理员账号执行,不能使用 service=adbpg(该连接已配置为只读模式)。

以电商场景为例,创建 5 张核心业务表:

4.1 客户主表

CREATE TABLE IF NOT EXISTS customers (
    customer_id     BIGINT      NOT NULL,
    customer_name   VARCHAR(100),
    gender          VARCHAR(10),
    age_group       VARCHAR(20),
    city            VARCHAR(50),
    province        VARCHAR(50),
    channel         VARCHAR(30),
    register_date   DATE,
    is_member       BOOLEAN,
    member_level    VARCHAR(20),
    total_orders    INT,
    total_amount    NUMERIC(12,2),
    last_order_date DATE
) DISTRIBUTED BY (customer_id);

4.2 商品主表

CREATE TABLE IF NOT EXISTS products (
    product_id      BIGINT      NOT NULL,
    product_name    VARCHAR(200),
    category_l1     VARCHAR(50),
    category_l2     VARCHAR(50),
    brand           VARCHAR(100),
    unit_price      NUMERIC(10,2),
    cost_price      NUMERIC(10,2),
    stock_qty       INT,
    status          VARCHAR(20),
    launch_date     DATE,
    supplier        VARCHAR(100)
) DISTRIBUTED BY (product_id);

4.3 订单主表

CREATE TABLE IF NOT EXISTS orders (
    order_id        BIGINT      NOT NULL,
    customer_id     BIGINT,
    order_date      DATE,
    order_time      TIMESTAMP,
    order_status    VARCHAR(20),
    payment_method  VARCHAR(30),
    channel         VARCHAR(30),
    city            VARCHAR(50),
    province        VARCHAR(50),
    total_amount    NUMERIC(12,2),
    discount_amount NUMERIC(10,2),
    actual_amount   NUMERIC(12,2),
    shipping_fee    NUMERIC(8,2),
    is_refunded     BOOLEAN,
    refund_amount   NUMERIC(12,2)
) DISTRIBUTED BY (order_id);

4.4 订单明细表

CREATE TABLE IF NOT EXISTS order_items (
    item_id         BIGINT      NOT NULL,
    order_id        BIGINT,
    product_id      BIGINT,
    qty             INT,
    unit_price      NUMERIC(10,2),
    discount_rate   NUMERIC(5,4),
    subtotal        NUMERIC(12,2)
) DISTRIBUTED BY (item_id);

4.5 营销活动表

CREATE TABLE IF NOT EXISTS campaigns (
    campaign_id     BIGINT      NOT NULL,
    campaign_name   VARCHAR(100),
    channel         VARCHAR(30),
    start_date      DATE,
    end_date        DATE,
    budget          NUMERIC(12,2),
    actual_cost     NUMERIC(12,2),
    impressions     INT,
    clicks          INT,
    conversions     INT
) DISTRIBUTED BY (campaign_id);

以上 5 张表是本例中将向 Agent 开放查询的表。在真实场景中,数据库通常还有许多其他业务表,这些敏感表不会注册到语义模型,Agent 无法感知其存在。

4.6 插入模拟数据

建表完成后,执行以下 SQL 生成模拟数据(使用 PostgreSQL 的 generate_series 批量生成):

-- 客户数据 200 条
INSERT INTO customers
SELECT i, '客户_' || i,
    CASE WHEN i % 2 = 0 THEN '男' ELSE '女' END,
    CASE (i % 4) WHEN 0 THEN '18-25' WHEN 1 THEN '26-35' WHEN 2 THEN '36-45' ELSE '46+' END,
    (ARRAY['北京','上海','广州','深圳','杭州','成都','武汉','南京'])[1 + (i % 8)],
    (ARRAY['北京市','上海市','广东省','广东省','浙江省','四川省','湖北省','江苏省'])[1 + (i % 8)],
    (ARRAY['自然流量','付费广告','老客转介绍','活动拉新'])[1 + (i % 4)],
    CURRENT_DATE - (i * 3 || ' days')::INTERVAL,
    (i % 3 != 0),
    CASE (i % 4) WHEN 0 THEN '普通' WHEN 1 THEN '银卡' WHEN 2 THEN '金卡' ELSE '钻石' END,
    5 + (i % 20), (1000 + i * 37.5)::NUMERIC(12,2),
    CURRENT_DATE - (i % 30 || ' days')::INTERVAL
FROM generate_series(1, 200) i;

-- 商品数据 50 条
INSERT INTO products
SELECT i,
    (ARRAY['无线耳机','智能手表','笔记本电脑','平板电脑','手机壳','充电宝','蓝牙音箱','机械键盘','鼠标','显示器','路由器','摄像头','台灯','空气净化器','咖啡机','跑步机','瑜伽垫','运动手环'])[1 + (i % 18)] || '_' || i,
    (ARRAY['数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','数码电子','家居生活','家居生活','家居生活','运动健康','运动健康','运动健康'])[1 + (i % 18)],
    (ARRAY['音频','穿戴','电脑','电脑','配件','配件','音频','外设','外设','显示','网络','安防','照明','净化','厨电','健身','健身','穿戴'])[1 + (i % 18)],
    (ARRAY['品牌A','品牌B','品牌C','品牌D','品牌E'])[1 + (i % 5)],
    (99 + i * 23.5)::NUMERIC(10,2), (60 + i * 14.2)::NUMERIC(10,2),
    100 + (i * 7 % 500),
    CASE WHEN i % 10 = 0 THEN '下架' WHEN i % 15 = 0 THEN '预售' ELSE '在售' END,
    CURRENT_DATE - (i * 10 || ' days')::INTERVAL, '供应商_' || (1 + i % 10)
FROM generate_series(1, 50) i;

-- 订单数据 1000 条
INSERT INTO orders
SELECT i, 1 + (i % 200),
    CURRENT_DATE - (i % 365 || ' days')::INTERVAL,
    NOW() - (i % 365 || ' days')::INTERVAL,
    (ARRAY['已支付','已支付','已完成','已完成','已完成','已退款','已发货','待支付'])[1 + (i % 8)],
    (ARRAY['支付宝','微信支付','微信支付','银行卡','花呗'])[1 + (i % 5)],
    (ARRAY['APP','APP','小程序','PC','H5'])[1 + (i % 5)],
    (ARRAY['北京','上海','广州','深圳','杭州','成都','武汉','南京'])[1 + (i % 8)],
    (ARRAY['北京市','上海市','广东省','广东省','浙江省','四川省','湖北省','江苏省'])[1 + (i % 8)],
    (200 + i * 13.7)::NUMERIC(12,2), (10 + i % 50)::NUMERIC(10,2),
    (190 + i * 13.2)::NUMERIC(12,2),
    CASE WHEN i % 5 = 0 THEN 0 ELSE 10 END::NUMERIC(8,2),
    (i % 8 = 5),
    CASE WHEN i % 8 = 5 THEN (190 + i * 13.2)::NUMERIC(12,2) ELSE 0 END
FROM generate_series(1, 1000) i;

-- 订单明细 2000 条
INSERT INTO order_items
SELECT i, 1 + (i % 1000), 1 + (i % 50), 1 + (i % 3),
    (99 + (i % 50) * 23.5)::NUMERIC(10,2),
    CASE WHEN i % 5 = 0 THEN 0.9 ELSE 1.0 END::NUMERIC(5,4),
    ((1 + i % 3) * (99 + (i % 50) * 23.5) * CASE WHEN i % 5 = 0 THEN 0.9 ELSE 1.0 END)::NUMERIC(12,2)
FROM generate_series(1, 2000) i;

-- 营销活动 15 条
INSERT INTO campaigns
SELECT i,
    (ARRAY['春节大促','3.8节','618大促','双11','双12','年货节','开学季','暑期促销','中秋活动','国庆促销','会员日','品牌日','清仓特卖','新品首发','周年庆'])[i],
    (ARRAY['APP','小程序','PC','APP','全渠道','APP','小程序','APP','全渠道','PC','APP','小程序','APP','全渠道','APP'])[i],
    CURRENT_DATE - ((200 - i * 13) || ' days')::INTERVAL,
    CURRENT_DATE - ((190 - i * 13) || ' days')::INTERVAL,
    (50000 + i * 10000)::NUMERIC(12,2), (45000 + i * 9500)::NUMERIC(12,2),
    100000 + i * 20000, 5000 + i * 1000, 200 + i * 50
FROM generate_series(1, 15) i;

4.7 创建未注册到语义模型的业务表(12 张)

以下 12 张表真实存在但不会注册到语义模型,Agent 无法感知。用于验证语义层的安全边界。

CREATE TABLE IF NOT EXISTS payments (
    payment_id BIGINT NOT NULL, order_id BIGINT, payment_method VARCHAR(30),
    amount NUMERIC(12,2), payment_time TIMESTAMP, status VARCHAR(20), transaction_no VARCHAR(64)
) DISTRIBUTED BY (payment_id);

CREATE TABLE IF NOT EXISTS coupons (
    coupon_id BIGINT NOT NULL, coupon_name VARCHAR(100), coupon_type VARCHAR(30),
    discount_value NUMERIC(10,2), min_amount NUMERIC(10,2), valid_from DATE, valid_to DATE,
    usage_limit INT, used_count INT
) DISTRIBUTED BY (coupon_id);

CREATE TABLE IF NOT EXISTS warehouses (
    warehouse_id BIGINT NOT NULL, warehouse_name VARCHAR(100), city VARCHAR(50),
    province VARCHAR(50), is_active BOOLEAN, capacity INT
) DISTRIBUTED BY (warehouse_id);

CREATE TABLE IF NOT EXISTS inventory (
    inventory_id BIGINT NOT NULL, product_id BIGINT, warehouse_id BIGINT,
    stock_qty INT, safety_stock INT, last_restock DATE
) DISTRIBUTED BY (inventory_id);

CREATE TABLE IF NOT EXISTS shipments (
    shipment_id BIGINT NOT NULL, order_id BIGINT, carrier VARCHAR(50),
    tracking_no VARCHAR(64), ship_time TIMESTAMP, deliver_time TIMESTAMP,
    status VARCHAR(20), receiver_city VARCHAR(50)
) DISTRIBUTED BY (shipment_id);

CREATE TABLE IF NOT EXISTS returns (
    return_id BIGINT NOT NULL, order_id BIGINT, product_id BIGINT, qty INT,
    reason VARCHAR(200), return_status VARCHAR(20), refund_amount NUMERIC(12,2),
    apply_date DATE, process_date DATE
) DISTRIBUTED BY (return_id);

CREATE TABLE IF NOT EXISTS reviews (
    review_id BIGINT NOT NULL, product_id BIGINT, customer_id BIGINT,
    rating INT, content VARCHAR(500), review_date DATE, is_verified BOOLEAN
) DISTRIBUTED BY (review_id);

CREATE TABLE IF NOT EXISTS employee_salary (
    emp_id BIGINT NOT NULL, emp_name VARCHAR(100), department VARCHAR(50),
    position_level VARCHAR(30), base_salary NUMERIC(12,2), bonus NUMERIC(12,2),
    bank_account VARCHAR(30), id_card VARCHAR(20)
) DISTRIBUTED BY (emp_id);

CREATE TABLE IF NOT EXISTS supplier_contracts (
    contract_id BIGINT NOT NULL, supplier_name VARCHAR(100), product_line VARCHAR(50),
    contract_amount NUMERIC(14,2), discount_rate NUMERIC(5,4), payment_terms VARCHAR(100),
    start_date DATE, end_date DATE
) DISTRIBUTED BY (contract_id);

CREATE TABLE IF NOT EXISTS pricing_strategy (
    strategy_id BIGINT NOT NULL, category_l1 VARCHAR(50), cost_markup NUMERIC(5,4),
    min_price NUMERIC(10,2), promo_floor NUMERIC(10,2), effective_date DATE
) DISTRIBUTED BY (strategy_id);

CREATE TABLE IF NOT EXISTS employee_performance (
    perf_id BIGINT NOT NULL, emp_id BIGINT, review_period VARCHAR(20),
    rating VARCHAR(10), kpi_score NUMERIC(5,2), salary_adj_pct NUMERIC(5,4),
    manager_comment TEXT
) DISTRIBUTED BY (perf_id);

CREATE TABLE IF NOT EXISTS chat_history (
    chat_id BIGINT NOT NULL, customer_id BIGINT, agent_name VARCHAR(50),
    message TEXT, chat_time TIMESTAMP, tags VARCHAR(100)
) DISTRIBUTED BY (chat_id);

安全边界验证:以上 12 张表包含员工薪资、银行卡号、身份证、客服聊天记录等高度敏感信息。因为它们未注册到语义模型,Agent 无法发现并查询它们。

4.8 插入未注册到语义模型的业务表模拟数据

INSERT INTO payments
SELECT i, 1 + (i % 1000),
    (ARRAY['支付宝','微信支付','微信支付','银行卡','花呗'])[1 + (i % 5)],
    (190 + i * 8.3)::NUMERIC(12,2),
    NOW() - (i % 365 || ' days')::INTERVAL,
    (ARRAY['成功','成功','成功','成功','失败','退款'])[1 + (i % 6)],
    'TXN' || md5(i::text)
FROM generate_series(1, 800) i;

-- 优惠券 20 条
INSERT INTO coupons
SELECT i,
    (ARRAY['新人礼包','满200减30','品类券-数码','品类券-家居','会员专享9折',
           '双11满减','年货节优惠','免运费券','限时秒杀券','老客召回券',
           '满500减80','品牌联合券','生日特权','拼团优惠','新品尝鲜券',
           '满1000减150','PLUS会员券','直播专属','签到7天券','评价返券'])[i],
    (ARRAY['满减','满减','品类','品类','折扣','满减','满减','运费','直减','直减',
           '满减','品类','折扣','直减','直减','满减','折扣','直减','直减','直减'])[i],
    (ARRAY[30,30,50,30,0,50,40,0,20,15,80,30,0,10,5,150,0,8,5,3])[i]::NUMERIC(10,2),
    (ARRAY[0,200,100,50,0,300,200,0,0,0,500,100,0,0,0,1000,0,0,0,0])[i]::NUMERIC(10,2),
    CURRENT_DATE - (i * 5 || ' days')::INTERVAL,
    CURRENT_DATE + ((30 - i) || ' days')::INTERVAL,
    500 + i * 100,
    (i * 23) % (500 + i * 100)
FROM generate_series(1, 20) i;

-- 仓库 6 条
INSERT INTO warehouses
SELECT i,
    (ARRAY['华东仓-上海','华北仓-北京','华南仓-广州','华中仓-武汉','西南仓-成都','东北仓-沈阳'])[i],
    (ARRAY['上海','北京','广州','武汉','成都','沈阳'])[i],
    (ARRAY['上海市','北京市','广东省','湖北省','四川省','辽宁省'])[i],
    TRUE, 50000 + i * 10000
FROM generate_series(1, 6) i;

-- 库存 300 条
INSERT INTO inventory
SELECT i, 1 + (i % 50), 1 + (i % 6),
    50 + (i * 7 % 500), 20 + (i % 30),
    CURRENT_DATE - (i % 60 || ' days')::INTERVAL
FROM generate_series(1, 300) i;

-- 物流单 600 条
INSERT INTO shipments
SELECT i, 1 + (i % 1000),
    (ARRAY['顺丰速运','中通快递','圆通速递','韵达快递','极兔速递'])[1 + (i % 5)],
    'SF' || (1000000000 + i),
    NOW() - (i % 30 || ' days')::INTERVAL,
    CASE WHEN i % 4 != 0 THEN NOW() - ((i % 30) - 2 || ' days')::INTERVAL ELSE NULL END,
    (ARRAY['已签收','已签收','已签收','运输中','已揽收','异常'])[1 + (i % 6)],
    (ARRAY['北京','上海','广州','深圳','杭州','成都','武汉','南京'])[1 + (i % 8)]
FROM generate_series(1, 600) i;

-- 退货单 100 条
INSERT INTO returns
SELECT i, 1 + (i % 1000), 1 + (i % 50), 1 + (i % 2),
    (ARRAY['质量问题','尺寸不符','不喜欢','发错货','其他'])[1 + (i % 5)],
    (ARRAY['已退款','处理中','已拒绝','已退款','已退款'])[1 + (i % 5)],
    (50 + i * 3.7)::NUMERIC(12,2),
    CURRENT_DATE - (i % 90 || ' days')::INTERVAL,
    CASE WHEN i % 5 != 2 THEN CURRENT_DATE - ((i % 90) - 3 || ' days')::INTERVAL ELSE NULL END
FROM generate_series(1, 100) i;

-- 商品评价 500 条
INSERT INTO reviews
SELECT i, 1 + (i % 50), 1 + (i % 200),
    CASE WHEN i % 5 = 0 THEN 1 WHEN i % 5 = 1 THEN 3 ELSE 5 END,
    CASE WHEN i % 3 = 0 THEN '好评,物超所值!' WHEN i % 3 = 1 THEN '一般般' ELSE '非常满意,推荐购买' END,
    CURRENT_DATE - (i % 180 || ' days')::INTERVAL,
    (i % 4 != 0)
FROM generate_series(1, 500) i;

-- 员工薪资 20 条
INSERT INTO employee_salary
SELECT i, '员工_' || i,
    (ARRAY['研发部','财务部','运营部','市场部'])[1 + (i % 4)],
    (ARRAY['P5','P6','P7','P8'])[1 + (i % 4)],
    (15000 + i * 2000)::NUMERIC(12,2),
    (3000 + i * 500)::NUMERIC(12,2),
    '6222****' || (1000 + i),
    '110***' || (19700000 + i * 10000)
FROM generate_series(1, 20) i;

-- 供应商合同 15 条
INSERT INTO supplier_contracts
SELECT i, '供应商_' || i,
    (ARRAY['数码电子','家居生活','运动健康','数码电子','家居生活'])[1 + (i % 5)],
    (500000 + i * 200000)::NUMERIC(14,2),
    (0.85 + (i % 5) * 0.02)::NUMERIC(5,4),
    (ARRAY['Net30','Net60','Net90','预付30%','月结'])[1 + (i % 5)],
    CURRENT_DATE - (i * 30 || ' days')::INTERVAL,
    CURRENT_DATE + ((365 - i * 20) || ' days')::INTERVAL
FROM generate_series(1, 15) i;

-- 定价策略 10 条
INSERT INTO pricing_strategy
SELECT i,
    (ARRAY['数码电子','家居生活','运动健康','数码电子','家居生活',
           '运动健康','数码电子','家居生活','运动健康','数码电子'])[i],
    (1.2 + (i % 5) * 0.1)::NUMERIC(5,4),
    (50 + i * 20)::NUMERIC(10,2),
    (30 + i * 10)::NUMERIC(10,2),
    CURRENT_DATE - (i * 60 || ' days')::INTERVAL
FROM generate_series(1, 10) i;

-- 员工绩效 20 条
INSERT INTO employee_performance
SELECT i, i,
    (ARRAY['2024-H1','2024-H2','2025-H1'])[1 + (i % 3)],
    (ARRAY['S','A','B','C'])[1 + (i % 4)],
    (3.0 + (i % 5) * 0.5)::NUMERIC(5,2),
    (0.0 + (i % 5) * 0.05)::NUMERIC(5,4),
    CASE WHEN i % 4 = 0 THEN '表现优异,建议晋升' WHEN i % 4 = 1 THEN '符合预期'
         WHEN i % 4 = 2 THEN '需要改进' ELSE '新员工,观察期' END
FROM generate_series(1, 20) i;

-- 客服聊天 100 条
INSERT INTO chat_history
SELECT i, 1 + (i % 200),
    (ARRAY['客服小王','客服小李','客服小张'])[1 + (i % 3)],
    CASE WHEN i % 3 = 0 THEN '我的收货地址是北京市朝阳区XX小区' || i
         WHEN i % 3 = 1 THEN '我的手机号是138****' || (1000 + i)
         ELSE '退款到我的银行卡 6222****' || (2000 + i) END,
    NOW() - (i % 60 || ' days')::INTERVAL,
    (ARRAY['退款','物流','商品咨询','投诉'])[1 + (i % 4)]
FROM generate_series(1, 100) i;

步骤五:创建语义模型(DBA 操作)

什么是语义模型? 语义模型是 Agent 的「数据字典」。只有在语义模型中注册的表和字段,Agent 才能查询。未注册的表对 Agent 完全不可见——这是核心安全机制。

5.1 创建元数据 Schema

CREATE SCHEMA IF NOT EXISTS _agent_meta;

5.2 创建五张元数据表

元数据表

作用

是否必须

_agent_meta.tables

注册哪些表可以被 Agent 查询

必须

_agent_meta.columns

定义字段含义、同义词、聚合方式

必须

_agent_meta.joins

声明表间关联关系

推荐

_agent_meta.metric_meta

预定义复合业务指标

可选

_agent_meta.filters

预定义常用过滤条件

可选

CREATE TABLE IF NOT EXISTS _agent_meta.tables (
    table_schema VARCHAR(64) NOT NULL, table_name VARCHAR(128) NOT NULL,
    display_name VARCHAR(128), description TEXT, business_domain VARCHAR(64),
    PRIMARY KEY (table_schema, table_name)
);

CREATE TABLE IF NOT EXISTS _agent_meta.columns (
    table_schema VARCHAR(64) NOT NULL, table_name VARCHAR(128) NOT NULL,
    column_name VARCHAR(128) NOT NULL, display_name VARCHAR(128),
    column_role VARCHAR(16), data_type VARCHAR(64), column_description TEXT,
    synonyms TEXT[], agg_default VARCHAR(16), example_values TEXT[],
    PRIMARY KEY (table_schema, table_name, column_name)
);

CREATE TABLE IF NOT EXISTS _agent_meta.joins (
    join_id SERIAL, left_schema VARCHAR(64), left_table VARCHAR(128),
    left_column VARCHAR(128), right_schema VARCHAR(64), right_table VARCHAR(128),
    right_column VARCHAR(128), join_type VARCHAR(10) DEFAULT 'LEFT',
    relationship_type VARCHAR(20) DEFAULT 'many_to_one', description TEXT
);

CREATE TABLE IF NOT EXISTS _agent_meta.metric_meta (
    metric_name VARCHAR(64) PRIMARY KEY, business_desc TEXT NOT NULL,
    metric_type VARCHAR(16) NOT NULL DEFAULT 'derived', sql_expression TEXT NOT NULL,
    numerator TEXT, denominator TEXT, dimensions TEXT[], filters TEXT[],
    owner VARCHAR(64), ai_hints TEXT
);

CREATE TABLE IF NOT EXISTS _agent_meta.filters (
    filter_name VARCHAR(64) PRIMARY KEY, description TEXT NOT NULL,
    sql_fragment TEXT NOT NULL, filter_scope VARCHAR(16) NOT NULL DEFAULT 'where',
    owner VARCHAR(64)
);

5.3 注册可查询的表

INSERT INTO _agent_meta.tables VALUES
('public','customers',   '客户主表',   '注册客户基础信息,含等级、渠道、消费汇总', '销售'),
('public','products',    '商品主表',   '在售商品信息,含类目、价格、库存',         '商品'),
('public','orders',      '订单主表',   '订单头信息,含金额、状态、渠道、地区',     '销售'),
('public','order_items', '订单明细表', '订单中每个商品的购买数量和金额',           '销售'),
('public','campaigns',   '营销活动表', '营销活动定义,含渠道、预算、效果指标',     '营销');

5.4 注册字段语义(核心步骤)

为每张表的字段定义角色和同义词,让 Agent 能理解自然语言:

-- orders 表字段
INSERT INTO _agent_meta.columns (table_schema, table_name, column_name, display_name, column_role, data_type, column_description, synonyms, agg_default, example_values) VALUES
('public','orders','order_id',       '订单ID',     'identifier','BIGINT',  '订单唯一标识',                NULL,                                                    NULL,   NULL),
('public','orders','customer_id',   '客户ID',     'identifier','BIGINT',  '关联客户主表',                NULL,                                                    NULL,   NULL),
('public','orders','order_date',    '下单日期',   'dimension', 'DATE',    '订单创建日期',                ARRAY['日期','购买日期','交易日期','成交日期'],            NULL,   NULL),
('public','orders','order_status',  '订单状态',   'dimension', 'VARCHAR', '订单当前状态',                ARRAY['状态','交易状态'],                                  NULL,   ARRAY['已支付','已完成','已退款','已发货','待支付']),
('public','orders','payment_method','支付方式',   'dimension', 'VARCHAR', '客户使用的支付渠道',          ARRAY['支付','付款方式'],                                  NULL,   ARRAY['支付宝','微信支付','银行卡','花呗']),
('public','orders','channel',       '下单渠道',   'dimension', 'VARCHAR', '客户下单使用的终端',          ARRAY['渠道','终端','平台'],                               NULL,   ARRAY['APP','小程序','PC','H5']),
('public','orders','province',      '省份',       'dimension', 'VARCHAR', '订单收货省份',                ARRAY['地区','省','收货地'],                               NULL,   NULL),
('public','orders','actual_amount', '实付金额',   'measure',   'NUMERIC', '扣除优惠后实际支付金额(元)',ARRAY['实付','支付金额','成交金额','GMV','流水'],          'SUM',  NULL),
('public','orders','total_amount',  '订单原价',   'measure',   'NUMERIC', '未扣除优惠的原始订单金额(元)',ARRAY['原价','订单金额','下单金额'],                     'SUM',  NULL),
('public','orders','discount_amount','优惠金额',  'measure',   'NUMERIC', '本单折扣/优惠金额(元)',    ARRAY['折扣','优惠','减免金额'],                           'SUM',  NULL),
('public','orders','is_refunded',   '是否退款',   'dimension', 'BOOLEAN', '订单是否已退款',              ARRAY['退款','是否退货'],                                  NULL,   ARRAY['true','false'])
ON CONFLICT DO NOTHING;

-- order_items 关键字段
INSERT INTO _agent_meta.columns (table_schema, table_name, column_name, display_name, column_role, data_type, column_description, synonyms, agg_default, example_values) VALUES
('public','order_items','item_id',     '明细ID',   'identifier','BIGINT',  '明细行唯一标识',              NULL,                                         NULL,   NULL),
('public','order_items','order_id',    '订单ID',   'identifier','BIGINT',  '关联订单主表',                NULL,                                         NULL,   NULL),
('public','order_items','product_id',  '商品ID',   'identifier','BIGINT',  '关联商品主表',                NULL,                                         NULL,   NULL),
('public','order_items','qty',         '购买数量', 'measure',   'INT',     '本次购买数量',                ARRAY['数量','购买量','件数','销量'],         'SUM',  NULL),
('public','order_items','subtotal',    '小计金额', 'measure',   'NUMERIC', '本行商品合计金额(元)',      ARRAY['金额','小计','明细金额','销售额'],      'SUM',  NULL)
ON CONFLICT DO NOTHING;

-- customers 关键字段
INSERT INTO _agent_meta.columns (table_schema, table_name, column_name, display_name, column_role, data_type, column_description, synonyms, agg_default, example_values) VALUES
('public','customers','customer_id',   '客户ID',     'identifier','BIGINT',  '客户唯一标识',                NULL,                                         NULL,   NULL),
('public','customers','customer_name', '客户名称',   'dimension', 'VARCHAR', '客户姓名',                    ARRAY['客户','买家','用户名'],                 NULL,   NULL),
('public','customers','city',          '城市',       'dimension', 'VARCHAR', '客户所在城市',                ARRAY['城','所在城市'],                        NULL,   ARRAY['北京','上海','广州']),
('public','customers','channel',       '获客渠道',   'dimension', 'VARCHAR', '客户注册来源渠道',            ARRAY['渠道','来源','获客方式'],               NULL,   ARRAY['自然流量','付费广告','老客转介绍','活动拉新']),
('public','customers','is_member',     '是否会员',   'dimension', 'BOOLEAN', '是否为会员用户',              ARRAY['会员','会员状态'],                      NULL,   ARRAY['true','false']),
('public','customers','member_level',  '会员等级',   'dimension', 'VARCHAR', '会员等级分类',                ARRAY['等级','会员级别'],                      NULL,   ARRAY['普通','银卡','金卡','钻石']),
('public','customers','total_orders',  '历史订单数', 'measure',   'INT',     '客户历史总订单数',            ARRAY['下单次数','购买次数','订单量'],          'SUM',  NULL),
('public','customers','total_amount',  '历史消费额', 'measure',   'NUMERIC', '客户历史累计消费金额(元)',   ARRAY['消费总额','历史金额','累计消费'],        'SUM',  NULL)
ON CONFLICT DO NOTHING;

-- products 关键字段
INSERT INTO _agent_meta.columns (table_schema, table_name, column_name, display_name, column_role, data_type, column_description, synonyms, agg_default, example_values) VALUES
('public','products','product_id',  '商品ID',   'identifier','BIGINT',  '商品唯一标识',                NULL,                                          NULL,   NULL),
('public','products','product_name','商品名称', 'dimension', 'VARCHAR', '商品名称',                    ARRAY['商品','产品','货品','SKU'],               NULL,   NULL),
('public','products','category_l1',  '一级类目', 'dimension', 'VARCHAR', '商品一级分类',                ARRAY['大类','主类目','品类'],                   NULL,   ARRAY['数码电子','家居生活','运动健康']),
('public','products','brand',       '品牌',     'dimension', 'VARCHAR', '商品品牌',                    ARRAY['牌子','厂牌'],                            NULL,   NULL),
('public','products','unit_price',  '售价',     'measure',   'NUMERIC', '商品零售价(元)',            ARRAY['价格','定价','销售价','原价'],             'AVG',  NULL),
('public','products','stock_qty',   '库存量',   'measure',   'INT',     '当前可售库存数量',            ARRAY['库存','剩余库存','现货量'],               'SUM',  NULL),
('public','products','status',      '商品状态', 'dimension', 'VARCHAR', '商品当前上架状态',            ARRAY['状态','是否在售'],                        NULL,   ARRAY['在售','下架','预售'])
ON CONFLICT DO NOTHING;

-- campaigns 关键字段
INSERT INTO _agent_meta.columns (table_schema, table_name, column_name, display_name, column_role, data_type, column_description, synonyms, agg_default, example_values) VALUES
('public','campaigns','campaign_id',  '活动ID',     'identifier','BIGINT',  '营销活动唯一标识',            NULL,                                          NULL,   NULL),
('public','campaigns','campaign_name','活动名称',   'dimension', 'VARCHAR', '活动名称',                    ARRAY['活动','营销活动','促销'],                 NULL,   NULL),
('public','campaigns','channel',      '投放渠道',   'dimension', 'VARCHAR', '活动投放渠道',                ARRAY['渠道','投放平台'],                       NULL,   ARRAY['APP','小程序','PC','全渠道']),
('public','campaigns','budget',      '活动预算',   'measure',   'NUMERIC', '活动总预算(元)',            ARRAY['预算','投放预算'],                       'SUM',  NULL),
('public','campaigns','actual_cost',  '实际花费',   'measure',   'NUMERIC', '活动实际花费(元)',          ARRAY['花费','消耗','实际投放额'],              'SUM',  NULL),
('public','campaigns','impressions',  '曝光量',     'measure',   'INT',     '活动总曝光次数',              ARRAY['曝光','展示量','展现量'],                 'SUM',  NULL),
('public','campaigns','clicks',      '点击量',     'measure',   'INT',     '活动总点击次数',              ARRAY['点击','点击数'],                         'SUM',  NULL),
('public','campaigns','conversions',  '转化量',     'measure',   'INT',     '活动带来的下单转化次数',      ARRAY['转化','转化数','成交数'],                 'SUM',  NULL)
ON CONFLICT DO NOTHING;

核心价值synonyms 字段让 Agent 能理解自然语言。当用户说「查一下 GMV」「看看流水」「商品销量」时,Agent 都能正确匹配到对应字段。

5.5 定义表间关联

INSERT INTO _agent_meta.joins
(left_schema, left_table, left_column, right_schema, right_table, right_column, join_type, relationship_type, description)
VALUES
('public','order_items','order_id',   'public','orders',   'order_id',   'LEFT','many_to_one','订单明细关联订单头'),
('public','order_items','product_id', 'public','products', 'product_id', 'LEFT','many_to_one','订单明细关联商品'),
('public','orders',    'customer_id','public','customers','customer_id','LEFT','many_to_one','订单关联客户');

5.6(可选)定义业务指标

INSERT INTO _agent_meta.metric_meta VALUES
('net_revenue', '净收入 = 实付金额 - 优惠金额', 'derived',
 'SUM(actual_amount) - SUM(discount_amount)', NULL, NULL,
 '{public.orders.province, public.orders.channel, public.orders.order_date}',
 '{paid_or_completed, not_refunded}', 'finance_team', '必须按省份或渠道聚合'),
('refund_rate', '退款率 = 退款订单数 / 总订单数', 'ratio',
 'SUM(CASE WHEN is_refunded THEN 1 ELSE 0 END)::numeric / COUNT(*)',
 'SUM(CASE WHEN is_refunded THEN 1 ELSE 0 END)', 'COUNT(*)',
 '{public.orders.province, public.orders.channel}', NULL, 'ops_team', NULL),
('cumulative_gmv', '按日期累计 GMV(实付金额)', 'cumulative',
 'SUM(SUM(actual_amount)) OVER (ORDER BY order_date ROWS UNBOUNDED PRECEDING)',
 NULL, NULL, '{public.orders.order_date}', '{not_refunded}', 'finance_team', NULL),
('completed_gmv', '已完成订单的 GMV', 'simple',
 'SUM(actual_amount) FILTER (WHERE order_status = ''已完成'')',
 NULL, NULL, '{public.orders.province, public.orders.channel}',
 '{completed_measure}', 'ops_team', NULL),
('campaign_roi', '营销活动 ROI = 转化量 / 实际花费', 'ratio',
 'SUM(conversions)::numeric / NULLIF(SUM(actual_cost), 0)',
 'SUM(conversions)', 'NULLIF(SUM(actual_cost), 0)',
 '{public.campaigns.channel}', NULL, 'marketing_team', '仅适用于 campaigns 表');

5.7(可选)定义过滤器

INSERT INTO _agent_meta.filters VALUES
('paid_or_completed', '已支付或已完成的订单',
 'order_status IN (''已支付'',''已完成'')', 'where', 'ops_team'),
('not_refunded', '排除退款订单',
 'is_refunded = false', 'where', 'finance_team'),
('completed_measure', '已完成订单的度量过滤',
 'order_status = ''已完成''', 'measure', 'ops_team');

步骤六:为只读账号授权(DBA 操作)

语义模型创建完成后,授予只读账号对应表的 SELECT 权限:

-- 仅授权语义模型中注册的 5 张表
GRANT SELECT ON public.customers, public.products, public.orders,
               public.order_items, public.campaigns TO readonly_agent;

-- 授权语义模型元数据表的读取权限
GRANT USAGE ON SCHEMA _agent_meta TO readonly_agent;
GRANT SELECT ON ALL TABLES IN SCHEMA _agent_meta TO readonly_agent;

安全原则:仅授权语义模型中注册的表。即使 Agent 因某种原因构造了对其他表的查询,数据库权限层也会拒绝。

步骤七:验证一切就绪

回到本地终端,用只读账号验证整体配置:

# 1. 验证连接
psql "service=adbpg" -c "SELECT current_user;"
# 预期输出:readonly_agent

# 2. 验证只读模式
psql "service=adbpg" -c "CREATE TABLE test_write (id int);"
# 预期报错:ERROR: cannot execute CREATE TABLE in a read-only transaction

# 3. 验证语义模型可读
psql "service=adbpg" -c "SELECT COUNT(*) FROM _agent_meta.tables;"
# 预期输出:5

# 4. 验证未注册表不可访问(权限拒绝)
psql "service=adbpg" -c "SELECT * FROM employee_salary LIMIT 1;"
# 预期报错:ERROR: permission denied for table employee_salary

全部通过后,环境配置完成。

步骤八:开始与 Agent 对话

现在打开 Agent(Claude / Qoder / ClawHub 等),直接用自然语言提问即可。

Agent 首次启动时会自动执行安全检查

  1. 验证连接 → 2. 检查账号权限 → 3. 检查资源组 → 4. 检测语义模型

通过后即可开始查询。

实际对话示例

示例 1:简单聚合查询

你:帮我查一下最近 30 天各省份的实付金额排名

Agent:根据语义模型,为您生成以下查询:

  SELECT province, SUM(actual_amount) AS total_amount
  FROM orders
  WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY province
  ORDER BY total_amount DESC
  LIMIT 50000;

  是否确认执行?

你:确认

Agent:查询结果如下:
  | province | total_amount |
  |----------|--------------|
  | 广东省    | 128,350.00   |
  | 浙江省    | 96,720.00    |

示例 2:使用预定义业务指标

你:查一下各渠道的净收入

Agent:匹配到预定义指标「净收入」(net_revenue),自动应用标准过滤条件:
  - 仅统计已支付/已完成订单
  - 排除退款订单

  SELECT channel,
         SUM(actual_amount) - SUM(discount_amount) AS net_revenue
  FROM orders
  WHERE order_status IN ('已支付','已完成')
    AND is_refunded = false
  GROUP BY channel
  ORDER BY net_revenue DESC;

  是否确认执行?

示例 3:跨表关联查询

你:帮我看一下品牌A各商品的销量排名

Agent:需要关联 products 和 order_items,已根据语义模型的 JOIN 定义生成:

  SELECT p.product_name, SUM(oi.qty) AS total_qty
  FROM order_items oi
  LEFT JOIN products p ON oi.product_id = p.product_id
  WHERE p.brand = '品牌A'
  GROUP BY p.product_name
  ORDER BY total_qty DESC
  LIMIT 50000;

  是否确认执行?

示例 4:查询被安全边界拒绝

你:帮我查一下员工的薪资情况

Agent:无法执行此查询。
  employee_salary 表未在语义模型中注册,不在可查询范围内。
  如需访问此数据,请联系 DBA 将该表添加到 _agent_meta 语义模型中。

示例 5:导出 CSV

你:把上个月的订单数据导出成 CSV

Agent:将为您导出上月订单数据,预计约 80 条。
  \copy (SELECT order_id, customer_id, order_date, actual_amount, channel
         FROM orders WHERE order_date >= '2026-05-01' AND order_date < '2026-06-01')
  TO '/tmp/orders_20260623_143022.csv' WITH CSV HEADER;

  导出文件可能包含业务数据,请按组织数据安全策略处理。
  是否确认执行?

流程总结

完整流程一共 8 步,按角色分工如下:

DBA 负责(步骤二/四/五/六):
  创建只读账号 → 准备业务表 → 配置语义模型 → 授权

用户负责(步骤一/三/七/八):
  环境准备 → 配置本地连接 → 验证 → 开始对话查询

步骤

执行者

操作

一次性/每次

步骤一

用户

确认环境、安装 psql 和 Skill

一次性

步骤二

DBA

创建只读账号和资源组

一次性

步骤三

用户

配置本地 pg_service.conf / pgpass

一次性

步骤四

DBA

准备业务表(实际场景通常已存在)

一次性

步骤五

DBA

创建语义模型

一次性(后续增量维护)

步骤六

DBA

授权只读账号

一次性(新增表时追加)

步骤七

用户

验证配置

一次性

步骤八

用户

与 Agent 对话查询

每次使用

安全防护总结

防护层

机制

效果

语义模型边界

仅注册指定表

未注册的敏感表对 Agent 完全不可见

数据库权限

仅 GRANT 语义模型内的表

即使绕过语义模型,数据库也会拒绝访问

只读连接

default_transaction_read_only=on

任何写操作都被数据库拒绝

人工确认

HITL 机制

每条 SQL 执行前必须用户明确批准

资源隔离

专用 Resource Group

Agent 查询不影响业务负载

反绕过条款

硬编码禁止规则

Agent 不会因「紧急」等理由绕过安全边界

常见问题

Q:Agent 提示「语义模型不存在」怎么办?

A: 需要 DBA 在数据库中创建 _agent_meta schema 并注册表元数据。Agent 可以协助生成建表 SQL,但不会自行执行 DDL。

Q: 可以直接查询未注册的表吗?

A: 不可以。语义模型是硬性访问边界,Agent 会拒绝查询未注册的表并提示联系 DBA 扩展语义模型。

Q: 导出的 CSV 文件有安全风险吗?

A: CSV 文件可能包含敏感业务数据,请按照组织的数据安全策略处理导出文件(如加密存储、定期清理、限制访问权限)。

Q: 支持哪些 AnalyticDB PostgreSQL本?

A: 支持存储弹性模式 AnalyticDB PostgreSQL 7.0 及以上版本。