# SQL 与数据库

Ontology SQL 让你用一条 `SELECT` 查对象。表名是 Object Type 的 API name，列名是属性的 API name。SQL 只读。写入只能走 Action。

Ontology SQL 有两种方言。Postgres 方言是命令行和 SDK 的缺省。标准方言是 Spark SQL，结果是 Apache Arrow。见[选方言](#选方言)。

> [!NOTE]
> 前提：你是组织的成员，或持有能查询的 Agent Key。应用票据不能用 SQL。在应用里，用 `semantic.ontology()` 读对象。

## 快速上手

用 SDK 查询时，参数用 `$1`、`$2` 等占位符，按位置传入。

```js
import { semantic } from "/developer/sdk/v1/aidc.js";

const r = await semantic.sql(
  "SELECT name, seats FROM customer WHERE stage = $1 AND seats > $2 ORDER BY seats DESC",
  { parameters: ["付费", 30] },
);
console.log(r.rows);
```

命令行的写法相同。`--param` 按顺序对应 `$1`、`$2`。

```terminal title="按条件查 SQL"
$ aidc semantic sql 'SELECT name, seats FROM customer WHERE stage = $1 AND seats > $2 ORDER BY seats DESC' --param 付费 --param 30
name	seats
Demo Customer A	48
Demo Customer B	36
（2 行 · 12 ms · 角色 reader）
```

最后一行是行数、耗时和角色。耗时因环境而异。

## 选方言

两种方言查的是同一份数据。下表列出它们的区别。

| 项目 | Postgres 方言（缺省） | 标准方言（Spark SQL） |
| --- | --- | --- |
| 接口 | `POST /api/v1/sqlQueries/executeOntology` | `POST /api/v2/sqlQueries/executeOntology?preview=true` |
| 表名 | API name。有大写字母时加双引号：`"Customer"` | 反引号括起来的 RID，或 API name。不分大小写 |
| 字符串与标识符 | 单引号是字符串，双引号是标识符 | 单引号和双引号都是字符串，反引号是标识符 |
| 参数 | `$1`、`$2`（位置参数） | `?`（位置参数）或 `:name`（命名参数），值带类型 |
| 变量 | 不支持 | `DECLARE @n INT = 5;` |
| 结果 | JSON | Arrow。带 `Accept: application/json` 时返回 JSON |
| 命令行 | `aidc semantic sql "…"` | `aidc semantic sql "…" --dialect spark` |
| SDK | `semantic.sql(q)` | `semantic.sqlOntology(q)`（Arrow）或 `semantic.sql(q, { dialect: "spark" })`（JSON） |

- 写报表、接 BI 工具、用直连：用 Postgres 方言。它和[数据库直连](#数据库视图与直连)用同一套视图。
- 要 Arrow 结果、按 RID 引用表、用命名参数或 `DECLARE`：用标准方言。见[标准方言：Spark SQL 与 Arrow](#标准方言spark-sql-与-arrow)。

## 写查询

本节是 Postgres 方言的写法。一条查询的写法有以下规则。

- 只能写一条 `SELECT`。查询可以用 `WITH`、`VALUES` 或 `TABLE` 开头。服务端去掉末尾的分号。
- 不能写入，不能改定义。`DECLARE` 和 `SET` 不支持。
- 参数用 `$1`、`$2` 等占位符。JavaScript 用 `parameters` 数组。命令行重复使用 `--param`。Python 用参数列表。最多 100 个参数。
- 表名是 Object Type 的 API name，列名是属性的 API name。名字里有大写字母的，要加双引号，例如 `"modelUsage"` 和 `"Billable"`。
- 派生属性不在 SQL 里。要用派生结果，用 JOIN 或子查询自己算。
- 一对多链接用外键列 JOIN。例如 `agent."customerId"` 指向 `customer."customerId"`。
- 多对多链接有自己的视图，视图名是链接的 API name。两端的列名是 `<对象类型>_<链接名>`，例如 `application_agents`。
- 接口有自己的视图，合并了所有实现它的 Object Type。视图多两列：`__objectType` 标明这一行来自哪个 Object Type，`__primaryKey` 是主键。
- SQL 只查 main 上的定义，不支持分支。

## 标准方言：Spark SQL 与 Arrow

标准方言是 Spark SQL 的一个子集。一次请求执行一条只读的 `SELECT`。`SELECT` 前面可以有一条 `DECLARE`。

```js
const { arrow, truncated } = await semantic.sqlOntology(
  "SELECT name, seats FROM customer WHERE seats > ? ORDER BY seats DESC",
  { parameters: [10] },
);
// import { tableFromIPC } from "apache-arrow"; const table = tableFromIPC(arrow);

const r = await semantic.sql(
  "SELECT c.name, count(a.agentId) AS agents FROM customer c LEFT JOIN agent a ON a.customerId = c.customerId GROUP BY c.name",
  { dialect: "spark" },
);   // JSON 结果，每行是一个对象
```

```bash
aidc semantic sql 'SELECT name, seats FROM customer WHERE seats > ?' --dialect spark --param 10
aidc semantic sql 'SELECT name FROM customer WHERE stage = :stage' --dialect spark --named stage=付费 --json
aidc semantic sql 'DECLARE @n INT = 5; SELECT name FROM customer LIMIT @n' --dialect spark
aidc semantic sql 'SELECT * FROM customer' --dialect spark --arrow customers.arrow
```

### 表名与列名

- 表名写反引号括起来的 RID。Object Type 是 `` `ri.ontology.main.object-type.<uuid>` ``，多对多链接是 `` `ri.ontology.main.relation.<uuid>` ``，接口是 `` `ri.ontology.main.interface.<uuid>` ``。
- 用 `GET /api/v2/ontologies/{命名空间}/objectTypes` 查 RID。
- API name 也能当表名，例如 `FROM customer` 或 `` FROM `Customer` ``。名字不分大小写。
- 列名是属性的 API name。多对多链接两端的列名是 `<对象类型>_<链接名>`。接口的表另有 `__objectType` 和 `__primaryKey` 两列。
- `tableProviders` 把一个表名绑到一个对象集。

### 参数与变量

- 位置参数写 `?`，请求体用 `unnamedParameterValues`。命名参数写 `:name`，请求体用 `namedParameterMapping`。两种不能混用。
- 参数值带类型，例如 `{ "type": "string", "value": "付费" }`。类型有 `string`、`integer`、`long`、`double`、`boolean`、`date`、`timestamp`、`null` 和 `list`。
- SDK 按 JavaScript 的值推断类型。也可以直接写 `{ type, value }`。
- 命令行的 `--param` 和 `--named` 按值的写法推断类型。`007`、`1.50` 这类值按字符串传。
- 变量写 `DECLARE @minSeats INT = 10;`。对象集变量写 ``DECLARE @c `ri.ontology.main.object-type.…`;``。

### 支持的语法与函数

- 语法：`WITH`、`UNION`、`INTERSECT`、`EXCEPT`、各种 `JOIN`、子查询、`EXISTS`、`IN`、`LIKE`、`BETWEEN`、`CASE`、`CAST`，以及带 `PARTITION BY` 和 `ORDER BY` 的窗口函数。
- 不支持：`IF`（用 `CASE WHEN`）、`RLIKE`、`INTERVAL`、`||`（用 `concat`）、`%`（用 `mod`）、窗口的 frame 子句。

| 类别 | 函数 |
| --- | --- |
| 数组 | `array`、`array_join`、`array_size`、`array_contains`、`get`、`size`、`cardinality`、`explode` |
| 日期 | `current_date`、`current_timestamp`、`date_add`、`date_sub`、`date_diff`、`date_format`、`day`、`month`、`quarter`、`year` |
| 数学 | `abs`、`ceil`、`floor`、`mod`、`power`、`round`、`sqrt` |
| 字符串 | `concat`、`contains`、`length`、`lower`、`upper`、`trim`、`replace`、`substr`、`startswith` |
| 条件 | `coalesce`、`nullif`、`greatest`、`least`、`regexp_extract`、`regexp_replace` |
| 聚合 | `count`、`count(DISTINCT 列)`、`sum`、`avg`、`min`、`max`、`stddev` |
| 窗口 | `row_number`、`rank`、`dense_rank`、`lag`、`lead`、`first_value`、`last_value`、`nth_value` |

### 权限与结果

- 平台按调用的人判定权限。看不见的 Object Type 等于不存在，返回 `OntologyObjectTypeNotFound`。
- 看不见的对象不在结果里。看不见的属性是 `NULL`。Markings 和对象、属性的安全策略照常生效。
- 查询在本组织的只读角色、只读事务里执行。
- 结果是 Apache Arrow IPC stream（`application/octet-stream`）。带 `Accept: application/json` 时，返回 `{ columns, rows, rowCount, truncated, rowLimit, elapsedMs }`。
- 结果被截断时，响应头 `X-AIDC-Truncated` 是 `true`。

## 例子

本节的例子用 Postgres 方言。按月汇总费用。`Billable` 视图合并了模型用量和云费用两类对象。

```sql
SELECT month, sum("costUsd") AS usd
FROM "Billable"
GROUP BY month
ORDER BY month
```

只要模型用量，把视图换成 `"modelUsage"`。

连接两个 Object Type。下面统计每家公司的智能体数。查询存进 `agents.sql` 后，用 `--file` 运行。

```sql
SELECT c."name", count(a."agentId") AS agents
FROM customer c
LEFT JOIN agent a ON a."customerId" = c."customerId"
GROUP BY c."name"
ORDER BY agents DESC, c."name"
```

```terminal title="每家公司的智能体数"
$ aidc semantic sql --file agents.sql
name	agents
Demo Customer A	4
Demo Customer B	4
Demo Customer D	3
Demo Customer C	2
Demo Customer E	2
Demo Customer F	0
（6 行 · 15 ms · 角色 reader）
```

Python 客户端的写法如下。`sql` 的第二个参数是参数列表。

```python
from aidc_semantic import sql

rows = sql("SELECT name, seats FROM customer WHERE stage = $1 AND seats > $2 ORDER BY seats DESC", ["付费", 30])
```

## 命令行选项

`aidc semantic sql` 的选项如下。

| 选项 | 做什么 |
| --- | --- |
| `--param 值` | 位置参数，按顺序对应 `$1`、`$2`。可以重复写 |
| `--row-limit N` | 最多返回几行。缺省 10,000，不能超过 10,000 |
| `--explain` | 不执行。Postgres 方言返回 `EXPLAIN (FORMAT JSON)` 的执行计划。标准方言只校验，返回结果的列 |
| `--csv` | 以 CSV 输出。输出只有表头和数据行，没有行数和耗时 |
| `--file <查询.sql>` | 从文件读查询 |
| `--dialect spark` | 用标准方言。缺省是 `postgres` |
| `--named 名=值` | 标准方言的命名参数，对应 `:名`。可以重复写。不能和 `--param` 一起用 |
| `--json` | 输出 JSON |
| `--arrow <文件>` | 标准方言：把 Arrow 结果原样存进文件 |

## 上限

下表列出查询的上限。

| 项目 | 上限 |
| --- | --- |
| 语句 | 一条，可以是 `SELECT`、`WITH`、`VALUES` 或 `TABLE` 开头的查询 |
| 查询文本 | 20,000 个字符 |
| 请求体 | 64 KB |
| 参数 | 最多 100 个。字符串参数每个最多 10,000 个字符 |
| 返回行数 | 最多 10,000 行。超过时，结果的 `truncated` 为 `true` |
| 执行时间 | 20 秒。服务端取消超时查询 |
| 查询次数 | 每个凭证每分钟 30 次。超过时返回 429，等一会儿再查 |
| 标准方言的分页 | `OFFSET` 加 `LIMIT` 不能超过 10,000 |
| 拿连接的等待 | 最多 15 秒 |
| 直连，reader 角色 | 最多 10 个连接 |
| 直连，public 角色 | 最多 5 个连接 |
| 直连的事务 | 只读。空闲事务超过 30 秒后结束 |
| 分支 | 不支持，只查 main |

## 角色与权限

本节是 Postgres 方言和数据库直连的权限。标准方言按调用的人逐个判定，见[权限与结果](#权限与结果)。

Postgres 方言有两个只读角色。`reader` 能看到本组织看得见的全部 Object Type。`public` 只能看到授予了所有人（Everyone）的 Object Type。

| 谁 | 用哪个角色 | 说明 |
| --- | --- | --- |
| 开发者 Key | `reader` | 能查本组织符合建视图条件的对象与构件 |
| 本组织的成员 | `reader` | 能查本组织看得见的全部对象 |
| 在本体上有 Viewer 以上授予的人 | `reader` | 同上 |
| 只在部分 Object Type 上有授予的账号 | `public` | 只能查授予了所有人的 Object Type |
| Agent Key，在本体上有 Viewer 以上授予 | `reader` | 必须同时有 `api:use-ontologies-read` 与 `api:use-sql-queries-execute` 权限，且 `objectTypes` 为 `["*"]` |
| Agent Key，只被授予了部分 Object Type | 不能用 SQL | 用对象接口，按类型读 |
| 应用票据，匿名访客 | 不能用 SQL | 应用里用 `semantic.ontology()` |

挂了 Marking 的 Object Type 不进 Postgres 方言的视图。见 [访问与安全](auth.md)。

## 数据库：视图与直连

每个组织有一个 Postgres schema。默认前缀是 `ont`。schema 名由前缀、`_` 和名字部分组成。名字部分按下面的步骤得出：

1. 去掉 `cell-`。
2. 把连续的非小写字母和数字的字符换成 `_`。
3. 去掉首尾的 `_`。
4. 取前 40 个字符。

例如 `cell-demo` 对应 `ont_demo`。第一次查询时建好。定义变了，下一次查询时自动重建。

- 符合条件的 Object Type 有一个视图，视图名是 Object Type 的 API name。下列类型不建视图：
  - 部门级类型。
  - 受 Markings 或安全策略保护的类型。
  - 名称超过 63 字节的类型。
  - 没有存储属性的类型。
- 符合条件的多对多链接有一个视图。两端的 Object Type 都有视图，且链接名不超过 63 字节。
- 符合条件的接口有一个视图，合并了实现它的 Object Type。接口要有属性，名称不超过 63 字节，并且至少有一个实现它的 Object Type 有视图。
- 视图直接读当前值，即数据源的值加上语义层的改动。视图不复制数据。被删除的对象，和源头已消失的对象，都不出现。
- 派生属性不进视图。
- 属性类型对应的 SQL 类型如下表。

| 属性类型 | SQL 类型 |
| --- | --- |
| `string`、`marking`、`cipherText` | `text` |
| `integer`、`short`、`byte`、`long` | `bigint` |
| `float`、`double` | `double precision` |
| `decimal` | `numeric` |
| `boolean` | `boolean` |
| `date` | `date` |
| `timestamp` | `timestamptz` |
| 其余 | `jsonb` |

```terminal title="看数据库"
$ aidc semantic database
schema ont_demo · 我用 reader 角色 · active · 视图建于 2026-10-08 09:12
  表  customer                     11 列  客户公司
  表  agent                        9 列  智能体
  …
直连：ont_demo_reader@<主机>:5432/<库>（口令用 aidc semantic database --rotate 拿）
```

`…` 表示省略的视图。`表` 对应 Object Type，`链接` 对应多对多链接，`接口` 对应接口。

开发者可以拿到直连口令。轮换口令会返回一次新的连接串。旧口令立即失效。

```terminal title="轮换直连口令"
$ aidc semantic database --rotate
只读直连（口令只显示这一次，再轮换即失效）：
  psql "…"
  schema ont_demo · user ont_demo_reader
```

- 直连只给本组织的开发者。把连接串交给 psql、BI 工具或笔记本即可。
- 直连只能看到本组织看得见的视图。事务默认只读。单条查询超过 20 秒，服务端取消超时查询。
- 口令由服务端派生，不存进数据库。数据库里只存校验值。再轮换一次，旧口令立即失效。
- 连接要求 TLS（`sslmode=require`），只有 localhost 例外。
- `aidc semantic database --sync` 按当前定义重建视图。定义变了，查询时也会自动重建。

## SQL Console 网页

网页地址是 `/semantic/<组织>/sql`。在这里运行 SQL，查看结果。开发者在「连接」里轮换直连口令。

## 错误

下表列出 SQL 的常见错误码。

| 错误码 | HTTP | 原因 | 怎么办 |
| --- | --- | --- | --- |
| `invalid_payload` | 400 | 查询是空的 | 写一条 `SELECT` |
| `sql_query_invalid` | 400 | 查询开头不是 `SELECT`、`WITH`、`VALUES`、`TABLE` 或 `(` | 只写一条查询，不写 `DECLARE` 或 `SET` |
| `unsupported_query` | 400 | 指定了分支 | 去掉分支。SQL 只查 main |
| `sql_query_failed` | 422 | 查询超过 20 秒，平台取消了它 | 加过滤条件，或加 `LIMIT` |
| `forbidden` | 403 | 应用票据、匿名访客，或 Agent Key 没有 Viewer 以上授予 | 应用里用 `semantic.ontology()`。Agent Key 要在本体上有 Viewer 以上授予 |
| `semantic_database_unavailable` | 503 | 数据库暂时没有连接配置 | 稍后重试 |
| `rate_limited` | 429 | 每分钟查询次数到了上限 | 等一会儿再查 |
| `password authentication failed` | — | 直连口令已轮换，旧口令失效 | 运行 `aidc semantic database --rotate` 拿新口令 |

标准方言的错误体是 `{ errorCode, errorName, errorInstanceId, parameters }`。

| errorName | 原因 | 怎么办 |
| --- | --- | --- |
| `QueryParseError` | 查询写错了。`parameters.errorMessage` 说明哪一行哪一列 | 照提示改查询 |
| `OntologyObjectTypeNotFound` | 表不存在，或你看不见这个 Object Type | 核对 RID 或 API name，或请人授予访问 |
| `ExecuteOntologySqlQueryPermissionDenied` | 凭证不能执行 SQL | 换有权限的凭证 |
| `OntologyQueryFailed` | 执行失败 | 简化查询后重试 |
| `OntologyQueryStringColumnTooLong` | 字符串列的值太长 | 用 `substr` 截短 |
| `NotEnoughSparkResources` | 超过 20 秒 | 加过滤条件，或加 `LIMIT` |
| `ApiFeaturePreviewUsageOnly` | 地址没带 `?preview=true` | 在地址后加 `?preview=true` |

## 命令行

本页相关的命令如下。全部参数见 [参考 · Semantic 本体与数据](cli-semantic.md)。

| 命令 | 做什么 |
| --- | --- |
| `aidc semantic sql "<SELECT …>"` | 执行一条查询。选项见上文 |
| `aidc semantic sql --file <查询.sql>` | 从文件读查询并执行 |
| `aidc semantic sql "<SELECT …>" --dialect spark` | 用标准方言执行。`--arrow` 存 Arrow 结果 |
| `aidc semantic database` | 看数据库：视图、列、我的角色、直连信息 |
| `aidc semantic database --sync` | 按当前定义重建视图（开发者） |
| `aidc semantic database --rotate` | 轮换直连口令，只显示一次（开发者） |

## API

下表列出 SQL 与数据库的接口。前缀里的 `{命名空间}` 是组织的命名空间，例如 `cell-demo`。

| 方法 | 路径 | 做什么 |
| --- | --- | --- |
| POST | `/api/v2/sqlQueries/executeOntology?preview=true` | 标准方言：Spark SQL，结果是 Arrow |
| POST | `/api/v1/sqlQueries/executeOntology` | Postgres 方言，结果是 JSON |
| GET | `/api/v1/ontologies/{命名空间}/database` | 数据库信息。开发者另有直连信息，不含口令 |
| POST | `/api/v1/ontologies/{命名空间}/database/sync` | 重建视图（开发者） |
| POST | `/api/v1/ontologies/{命名空间}/database/credentials` | 轮换直连口令（开发者）。新的连接串只返回这一次 |

两个接口的请求体字段相同：`query`、`parameters`、`rowLimit`、`dryRun` 和 `ontologyIdentifier`。标准方言另有 `tableProviders`。Postgres 方言的响应带 `Link: rel="successor-version"` 头，指向标准方言的接口。

## 下一步

- [读写对象](data.md)：对象集、Action 与订阅。
- [定义本体](ontology.md)：视图由 Object Type、链接和接口生成。
- [访问与安全](auth.md)：谁能查询，Marking 怎样限制行和列。
- [数据管道](pipeline.md)：加工结果怎样变成数据集和对象。
