extension-querying-oql
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuerying OQL — quick reference
OQL 查询快速参考
An OQL canister exposes two read-only methods:
| Method | Returns | Purpose |
|---|---|---|
| one JSON | Catalogue of the canister's entities: each entity's primary key, fields, and edges. |
| typed Candid | Runs a JSON-encoded query and returns matching rows. |
暴露OQL接口的canister提供两个只读方法:
| 方法 | 返回值 | 用途 |
|---|---|---|
| 一段JSON格式的 | canister的实体目录:包含每个实体的主键、字段和关联关系。 |
| 带类型的Candid | 执行JSON编码的查询并返回匹配的行。 |
Calling the canister
调用canister
The CLI is already installed and configured in the sandbox; the
canister name resolves to the project's canister (no identity,
no canister ID). Both methods are calls, so every invocation
uses :
icpbackendquery--querybash
icp canister call backend schema '()' --query
icp canister call backend execute '("<json-query>")' --queryexecutetext("...")"\"{"start":"customer","limit":3}bash
icp canister call backend execute '("{\"start\":\"customer\",\"limit\":3}")' --queryschema()text("...escaped json...")\""\\\--branch live'\''沙箱中已安装并配置好 CLI;canister名称会解析为项目对应的canister(无需身份验证,无需canister ID)。两个方法均为调用,因此每次调用都需使用参数:
icpbackendquery--querybash
icp canister call backend schema '()' --query
icp canister call backend execute '("<json-query>")' --queryexecutetext("...")"\"{"start":"customer","limit":3}bash
icp canister call backend execute '("{\"start\":\"customer\",\"limit\":3}")' --queryschema()("...转义后的json...")\""\\\--branch live'\''Recipe
操作步骤
- Get the schema once. — cache it for the session; it changes only between deployments (§1).
icp canister call backend schema '()' --query - Map the request to entities. Pick the entity that holds the answer. Use each field's and
typeNameto choose literal types, andvaluesto see how entities connect.role: {"edge": ...} - Translate into one or more queries. Start from the entity whose rows you want (§2). Add (§2.1),
where/orderBy/limit,offset, andselect/aggregate(§2.2). Cross a forward edge with a dotted path in a single query (§4.1); a reverse one-to-many needs the parent keys first, thengroupBy(§4.2).in - Run and read. — parse the Candid rows by cell
icp canister call backend execute '("<json>")' --query(§3); ifname, page withhasMore(§5).offset - Retry on traps. There is no error envelope — re-read the schema, fix the query, rerun (§6).
- 获取schema并缓存。执行——将结果在会话中缓存;仅在canister部署时schema才会变更(第1节)。
icp canister call backend schema '()' --query - 将需求映射到实体。选择包含目标数据的实体。使用每个字段的和
typeName确定字面量类型,通过values查看实体间的关联方式。role: {"edge": ...} - 转换为一个或多个查询。从目标数据所在的实体开始(第2节)。添加(第2.1节)、
where/orderBy/limit、offset以及select/aggregate(第2.2节)。在单个查询中使用点路径跨正向关联(第4.1节);反向一对多关联需先获取父键,再使用groupBy操作符(第4.2节)。in - 执行查询并读取结果。执行——通过单元格
icp canister call backend execute '("<json>")' --query解析Candid结果行(第3节);若name为真,使用hasMore进行分页(第5节)。offset - 陷阱错误重试。无错误信封机制——重新读取schema,修正查询后重新执行(第6节)。
1. Discover — schema
schema1. 发现实体——schema
schemaFetch once and cache for the session — it only changes between canister
deployments.
bash
icp canister call backend schema '()' --queryRead it like this:
- → entity name; use it as
namein queries.start - → field whose value identifies a row. An edge
primaryKeyvalue is a primary-key value in that target.{"to": "<entity>"} - → each field's
fields, scalarname, andtypeName:role(plain field) or"payload"(a foreign key — how you traverse the graph). Names may carry a{"edge": {"to": "<entity>"}},__1, … suffix when two columns would share a name — use the exact names__2reports.schema() - (optional) → the exact literals a field can hold (typically a variant's arms). Filter with those literals, not guesses:
valuesmeans query["free","pro","enterprise"], not"enterprise". Absent ⇒ unbounded — sample it with a query if you need candidates."Enterprise" - → JSON literal type for
typeName:value- → unsigned integer (
"Nat",0, …)1 - → signed integer (
"Int",-1,0, …)1 - → JSON number with a decimal point (
"Float",0.5,-3.14). A bare integer (1.0e2) is also accepted — numeric variants bridge, so10matches agt(price, 10)row. Float equality is bitwise IEEE-754; use a range (price : Float = 12.5+ge) for decimals likelewith no exact binary form.0.42 - →
"Bool"/truefalse - → JSON string.
"Text"fields report asPrincipal(canonical textual form) — filter them with a string value."Text"
仅需获取一次并在会话中缓存——仅在canister部署时schema才会变更。
bash
icp canister call backend schema '()' --query按以下方式解读结果:
- → 实体名称;在查询中作为
name的值使用。start - → 用于标识行的字段。关联关系
primaryKey的值是目标实体中的主键值。{"to": "<entity>"} - → 每个字段的
fields、标量name和typeName:role(普通字段)或"payload"(外键——用于遍历关联图)。当两列名称冲突时,名称可能带有{"edge": {"to": "<entity>"}}、__1等后缀——需使用__2返回的精确名称。schema() - (可选)→ 字段可容纳的精确字面量(通常是变体的分支)。需使用这些字面量进行过滤,而非猜测值:例如
values意味着查询时需使用["free","pro","enterprise"],而非"enterprise"。若该字段不存在,则表示值无限制——若需要候选值,可通过查询获取样本。"Enterprise" - →
typeName对应的JSON字面量类型:value- → 无符号整数(
"Nat"、0等)1 - → 有符号整数(
"Int"、-1、0等)1 - → 带小数点的JSON数字(
"Float"、0.5、-3.14)。也接受纯整数(1.0e2)——数值类型可兼容,因此10会匹配gt(price, 10)的行。浮点数相等性基于IEEE-754位运算;对于price : Float = 12.5这类无精确二进制形式的小数,需使用范围查询(0.42+ge)。le - →
"Bool"/truefalse - → JSON字符串。
"Text"字段会被标记为Principal(标准文本格式)——需使用字符串值进行过滤。"Text"
2. Form a query — execute
execute2. 构建查询——execute
executeA query is a single JSON object. Only is required.
startjson
{
"start": "<entityName>",
"where": <Predicate>,
"groupBy": ["<fieldName>", ...],
"aggregate": [{ "fn": "count|sum|avg|min|max", "field": "<fieldName>", "as": "<outName>" }, ...],
"orderBy": [{ "field": "<fieldName>", "dir": "asc|desc" }, ...],
"offset": <Nat>,
"limit": <Nat>,
"select": ["<fieldName>", ...]
}| Field | Default | Notes |
|---|---|---|
| (required) | An entity |
| omit ⇒ no filter | A single predicate (§2.1) — not wrapped in |
| | Bucket rows by these fields; one output row per distinct combination (§2.2). |
| | Aggregates per bucket, or over all rows when |
| | Multi-key sort, first clause primary. |
| | Drop the first N matches. |
| every match | Keep at most N. |
| every non-hidden field (or, when aggregating, group-key + aggregate columns) | Subset projection. |
bash
icp canister call backend execute '("{\"start\":\"customer\",\"limit\":3}")' --queryFilter + sort + project — the core shape ( + + + ):
whereorderBylimitselectbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},\"orderBy\":[{\"field\":\"monthlyRevenueUsd\",\"dir\":\"desc\"}],\"limit\":5,\"select\":[\"companyName\",\"monthlyRevenueUsd\",\"accountManagerName\"]}")' --query查询是单个JSON对象。仅为必填项。
startjson
{
"start": "<entityName>",
"where": <Predicate>,
"groupBy": ["<fieldName>", ...],
"aggregate": [{ "fn": "count|sum|avg|min|max", "field": "<fieldName>", "as": "<outName>" }, ...],
"orderBy": [{ "field": "<fieldName>", "dir": "asc|desc" }, ...],
"offset": <Nat>,
"limit": <Nat>,
"select": ["<fieldName>", ...]
}| 字段 | 默认值 | 说明 |
|---|---|---|
| 必填 | 来自 |
| 省略则无过滤 | 单个谓词(第2.1节)——不要包裹在 |
| | 按这些字段对行进行分组;每个不同的组合对应一行输出(第2.2节)。 |
| | 按分组进行聚合;若 |
| | 多键排序,第一个子句为首要排序条件。 |
| | 跳过前N个匹配项。 |
| 返回所有匹配项 | 最多保留N个结果。结果中的 |
| 所有非隐藏字段(聚合时为分组键+聚合列) | 子集投影。 |
bash
icp canister call backend execute '("{\"start\":\"customer\",\"limit\":3}")' --query过滤+排序+投影——核心结构(+++):
whereorderBylimitselectbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},\"orderBy\":[{\"field\":\"monthlyRevenueUsd\",\"dir\":\"desc\"}],\"limit\":5,\"select\":[\"companyName\",\"monthlyRevenueUsd\",\"accountManagerName\"]}")' --query2.1 Predicate operators
2.1 谓词操作符
A is a JSON object with exactly one key that names the
operator.
Predicate| Operator | Shape | Meaning |
|---|---|---|
| | Scalar relation. |
| | Membership; empty array matches nothing. |
| | Case-sensitive substring / prefix / suffix on |
| | Case-insensitive |
| | Boolean composition. |
Text search runs server-side — "the customer whose name mentions north" is one query, not a row scan into context:
bash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"icontains\":{\"field\":\"companyName\",\"value\":\"north\"}},\"select\":[\"companyName\",\"accountManagerName\"]}")' --query<scalar>typeName| JSON | Maps to | Use for fields with typeName |
|---|---|---|
| | any nullable field (rare in |
| | |
| | |
| | |
| | |
| | |
A row whose field is fails every relation except . Filter
by relationship with = and = the target
entity's primary-key value; or read through an edge with
(§4.1).
null_nefield"<edge>"value"<edge>.<targetField>"Predicate| 操作符 | 格式 | 含义 |
|---|---|---|
| | 标量比较。 |
| | 成员判断;空数组不匹配任何项。 |
| | 对 |
| | 大小写不敏感的 |
| | 布尔逻辑组合。 |
文本搜索在服务器端执行——“名称包含north的客户”可通过单个查询实现,无需扫描所有行到上下文:
bash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"icontains\":{\"field\":\"companyName\",\"value\":\"north\"}},\"select\":[\"companyName\",\"accountManagerName\"]}")' --query<scalar>typeName| JSON值 | 映射类型 | 适用于typeName为以下的字段 |
|---|---|---|
| | 任何可空字段(在 |
| | |
| | |
| | |
| | |
| | |
字段值为的行将不满足所有比较操作,除了。使用 = 和 = 目标实体的主键值进行关联过滤;或使用跨关联查询(第4.1节)。
null_nefield"<edge>"value"<edge>.<targetField>"2.2 Aggregate — count, groupBy, sum/avg/min/max
2.2 聚合——count、groupBy、sum/avg/min/max
Compute on the canister instead of fetching every row and tallying
client-side. is ////; is required
for every fn except ; / also work on text. renames
the output column (default , , …) and must not
contain (dots are the edge-traversal separator — parse error). For a
dotted the default joins segments with ( of
→ ). with no → one
row over the whole filtered set ( of an empty match is ).
with no → a server-side DISTINCT. Output rows
contain only the group-key + aggregate columns.
fncountsumavgminmaxfieldcountminmaxascountsum_<field>.field_sumdept.budgetsum_dept_budgetaggregategroupBycount0groupByaggregate"How many enterprise customers?" — over a filtered set, one row out:
countbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},\"aggregate\":[{\"fn\":\"count\"}]}")' --query"Which account manager has the most customers, and total MRR?" — + + :
groupBycountsumbash
icp canister call backend execute '("{\"start\":\"customer\",\"groupBy\":[\"accountManager\"],\"aggregate\":[{\"fn\":\"count\"},{\"fn\":\"sum\",\"field\":\"monthlyRevenueUsd\",\"as\":\"mrr\"}],\"orderBy\":[{\"field\":\"count\",\"dir\":\"desc\"}],\"limit\":1}")' --query在canister端计算,而非获取所有行后在客户端统计。可选值为////;除外,其他函数均需指定;/也适用于文本类型。用于重命名输出列(默认值为、等),且名称中不能包含(点是关联遍历的分隔符——会导致解析错误)。若为点路径,默认会用连接各段(例如的结果列名为)。无的会对所有过滤后的行生成一行结果(无匹配项时结果为)。无的相当于服务器端的DISTINCT操作。输出行仅包含分组键和聚合列。
fncountsumavgminmaxcountfieldminmaxascountsum_<field>.field_dept.budgetsumsum_dept_budgetgroupByaggregatecount0aggregategroupBy“有多少个企业版客户?”——对过滤后的集合执行,返回一行结果:
countbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},\"aggregate\":[{\"fn\":\"count\"}]}")' --query“哪个客户经理的客户最多,且总MRR是多少?”——++:
groupBycountsumbash
icp canister call backend execute '("{\"start\":\"customer\",\"groupBy\":[\"accountManager\"],\"aggregate\":[{\"fn\":\"count\"},{\"fn\":\"sum\",\"field\":\"monthlyRevenueUsd\",\"as\":\"mrr\"}],\"orderBy\":[{\"field\":\"count\",\"dir\":\"desc\"}],\"limit\":1}")' --query3. Read the result
3. 读取结果
candid
type Value = variant { null_; bool : bool; nat : nat; int : int; float : float; text : text };
type Cell = record { name : text; value : Value };
type Result = record { rows : vec vec Cell; hasMore : bool };The outer is the row list; each inner
is one row. Each
is one cell — tells you which field, the tells you the
scalar type, the payload is the value. underscores are
digit separators — strip them if parsing. ⇒ you got
every match; ⇒ truncated, fetch the next page. Look
cells up by , not position — order shifts if changes.
rows = vec { ... }vec { ... }record { value = variant { "<tag>" = <payload> }; name = "<field>" }name<tag>35_000 : nathasMore = falsehasMore = truenameselectcandid
type Value = variant { null_; bool : bool; nat : nat; int : int; float : float; text : text };
type Cell = record { name : text; value : Value };
type Result = record { rows : vec vec Cell; hasMore : bool };外层是行列表;每个内层代表一行。每个代表一个单元格——标识字段,标识标量类型,是字段值。中的下划线是数字分隔符——解析时需移除。表示已获取所有匹配项;表示结果被截断,需获取下一页。需通过查找单元格,而非位置——若变更,位置会发生变化。
rows = vec { ... }vec { ... }record { value = variant { "<tag>" = <payload> }; name = "<field>" }name<tag>payload35_000 : nathasMore = falsehasMore = truenameselect4. Walk edges (joins)
4. 遍历关联(连接)
Forward (single-valued) relationships are one query: a dotted path
crosses a declared edge, in any field position. Reverse (one-to-many)
relationships stay two queries with the pattern (§4.2).
in正向(单值)关联可通过单个查询实现:点路径可跨越已声明的关联,适用于任何字段位置。反向(一对多)关联需通过两个查询实现,使用模式(第4.2节)。
in4.1 Forward (child → parent): dotted paths
4.1 正向(子→父):点路径
"<edgeField>.<targetField>"wheregroupByorderByaggregate.fieldselectbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"companyName\",\"value\":\"Northstar Public\"}},\"select\":[\"companyName\",\"accountManager.name\",\"accountManager.office\"]}")' --queryMulti-hop chains work (, max 4 hops), and it
composes with aggregation — "average revenue by the account manager's
office" is one call:
"manager.department.name"bash
icp canister call backend execute '("{\"start\":\"customer\",\"groupBy\":[\"accountManager.office\"],\"aggregate\":[{\"fn\":\"avg\",\"field\":\"monthlyRevenueUsd\",\"as\":\"avg_mrr\"}],\"orderBy\":[{\"field\":\"avg_mrr\",\"dir\":\"desc\"}]}")' --queryRules:
- The head segment must be a field whose is
rolein{"edge": {"to": ... }}— a dotted path into a non-edge field traps, even if its values look like foreign keys (traversal is schema-driven, not name-guessed). If the author didn't declare the edge, fall back to the two-query pattern below.schema() - A null or dangling FK resolves the whole dotted path to (left-join): the row fails every relation except
null, and projects the cell asne.null - Aggregate from the many side. Cross-entity aggregates run over the
start entity's rows: of
avgfrom"department.budget"is employee-weighted. For per-department numbers, start fromemployee— or group by the dotted path and aggregate start-entity fields.department - Selecting the bare edge field () still returns the FK scalar; there is no
"accountManager"— name each target field you want..*
"<edgeField>.<targetField>"wheregroupByorderByaggregate.fieldselectbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"companyName\",\"value\":\"Northstar Public\"}},\"select\":[\"companyName\",\"accountManager.name\",\"accountManager.office\"]}")' --query多跳关联也支持(例如,最多4跳),且可与聚合组合——“按客户经理所在办公室统计平均收入”可通过一次调用实现:
"manager.department.name"bash
icp canister call backend execute '("{\"start\":\"customer\",\"groupBy\":[\"accountManager.office\"],\"aggregate\":[{\"fn\":\"avg\",\"field\":\"monthlyRevenueUsd\",\"as\":\"avg_mrr\"}],\"orderBy\":[{\"field\":\"avg_mrr\",\"dir\":\"desc\"}]}")' --query规则:
- 首段必须是中
schema()为role的字段——点路径指向非关联字段会触发陷阱,即使其值看起来像外键(遍历由schema驱动,而非名称猜测)。若开发者未声明该关联,需退回到下文的双查询模式。{"edge": {"to": ... }} - 空值或无效外键会将整个点路径解析为(左连接):该行不满足所有比较操作,除了
null,且投影的单元格为ne。null - 从多侧进行聚合。跨实体聚合基于起始实体的行:从实体对
employee执行"department.budget"是按员工加权的平均值。如需按部门统计,需从avg实体开始——或按点路径分组并聚合起始实体的字段。department - 选择关联字段本身(例如)仍会返回外键标量;不支持
"accountManager"——需明确指定每个目标字段。.*
4.2 Reverse (one parent → many children)
4.2 反向(一父→多子)
eqinbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"accountManager\",\"value\":\"daniel@helix.systems\"}},\"select\":[\"companyName\",\"monthlyRevenueUsd\"]}")' --queryWhen the parent condition is a plain predicate, you don't need the batch
— it's a forward filter through the edge (§4.1). "All customers managed
by anyone in the Berlin office" is one query:
bash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"accountManager.office\",\"value\":\"Berlin\"}},\"select\":[\"companyName\",\"monthlyRevenueUsd\"]}")' --queryThe batch pattern is required when the parent set needs its own
query shape (top-N, ordered, paginated): collect the keys first, then
on the edge field. "Customers managed by the three most senior
employees" is two queries:
ininbash
icp canister call backend execute '("{\"start\":\"employee\",\"orderBy\":[{\"field\":\"level\",\"dir\":\"desc\"}],\"limit\":3,\"select\":[\"email\"]}")' --query单个父主键使用,批量父主键使用——针对关联字段,值为目标实体的主键值。
eqinbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"accountManager\",\"value\":\"daniel@helix.systems\"}},\"select\":[\"companyName\",\"monthlyRevenueUsd\"]}")' --query若父条件为普通谓词,无需批量查询——可通过正向关联过滤(第4.1节)。“所有由柏林办公室员工管理的客户”可通过单个查询实现:
bash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"eq\":{\"field\":\"accountManager.office\",\"value\":\"Berlin\"}},\"select\":[\"companyName\",\"monthlyRevenueUsd\"]}")' --query当父集合需要特定查询结构(Top-N、排序、分页)时,必须使用批量模式:先收集主键,再在关联字段上使用。“由三位最资深员工管理的客户”需两个查询:
ininbash
icp canister call backend execute '("{\"start\":\"employee\",\"orderBy\":[{\"field\":\"level\",\"dir\":\"desc\"}],\"limit\":3,\"select\":[\"email\"]}")' --querycollect the three emails from the rows, then:
从结果行中收集三个邮箱地址,然后执行:
icp canister call backend execute '("{"start":"customer","where":{"in":{"field":"accountManager","value":["alex@helix.systems","james@helix.systems","sarah@helix.systems"]}},"select":["companyName","monthlyRevenueUsd"]}")' --query
Always batch with `in` rather than running N separate `eq` queries.icp canister call backend execute '("{"start":"customer","where":{"in":{"field":"accountManager","value":["alex@helix.systems","james@helix.systems","sarah@helix.systems"]}},"select":["companyName","monthlyRevenueUsd"]}")' --query
始终使用`in`进行批量查询,而非执行N次单独的`eq`查询。4.3 Compound conditions
4.3 复合条件
Stack with / :
andorbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"and\":[{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},{\"in\":{\"field\":\"country\",\"value\":[\"US\",\"CA\",\"DE\"]}},{\"ge\":{\"field\":\"monthlyRevenueUsd\",\"value\":20000}}]},\"orderBy\":[{\"field\":\"monthlyRevenueUsd\",\"dir\":\"desc\"}]}")' --query可通过/组合条件:
andorbash
icp canister call backend execute '("{\"start\":\"customer\",\"where\":{\"and\":[{\"eq\":{\"field\":\"plan\",\"value\":\"enterprise\"}},{\"in\":{\"field\":\"country\",\"value\":[\"US\",\"CA\",\"DE\"]}},{\"ge\":{\"field\":\"monthlyRevenueUsd\",\"value\":20000}}]},\"orderBy\":[{\"field\":\"monthlyRevenueUsd\",\"dir\":\"desc\"}]}")' --query4.4 Two-hop / self-edge join
4.4 双跳/自关联连接
When the parent key isn't given but must be looked up first — e.g. "who
reports to the lead of project ?" — run two queries. The second
filters on a self-edge ( → ) by the key the
first query returned:
forge20employee.manageremployeebash
icp canister call backend execute '("{\"start\":\"project\",\"where\":{\"eq\":{\"field\":\"codename\",\"value\":\"forge20\"}},\"select\":[\"lead\"]}")' --query当父键未给出需先查询时——例如“谁向项目的负责人汇报?”——需执行两个查询。第二个查询通过自关联(→)过滤,使用第一个查询返回的键:
forge20employee.manageremployeebash
icp canister call backend execute '("{\"start\":\"project\",\"where\":{\"eq\":{\"field\":\"codename\",\"value\":\"forge20\"}},\"select\":[\"lead\"]}")' --querythe row's lead
cell is the lead's email, e.g. priya@helix.systems — use it as the parent key:
lead结果行的lead
单元格是负责人的邮箱,例如priya@helix.systems——将其作为父键使用:
leadicp canister call backend execute '("{"start":"employee","where":{"eq":{"field":"manager","value":"priya@helix.systems"}},"select":["name","jobTitle","level"]}")' --query
---icp canister call backend execute '("{"start":"employee","where":{"eq":{"field":"manager","value":"priya@helix.systems"}},"select":["name","jobTitle","level"]}")' --query
---5. Pagination
5. 分页
limithasMoreoffsettext
offset = 0
limit = 25
loop:
result = icp canister call backend execute '("{\"start\":\"...\",\"limit\":25,\"offset\":<offset>,...}")' --query
consume result.rows
if not result.hasMore: break
offset += limitAlways set explicitly. OQL itself imposes no cap (omitting
returns every match), and a canister author may add one — in
which case over-asking is silently truncated.
limitlimitlimithasMoreoffsettext
offset = 0
limit = 25
loop:
result = icp canister call backend execute '("{\"start\":\"...\",\"limit\":25,\"offset\":<offset>,...}")' --query
处理result.rows
if not result.hasMore: break
offset += limit始终显式设置。OQL本身无限制(省略会返回所有匹配项),但canister开发者可能会添加限制——此时超出限制的结果会被静默截断。
limitlimit6. Pitfalls
6. 常见陷阱
| Symptom | Cause / Fix |
|---|---|
| |
| The JSON was malformed (trailing comma, single quotes), or not escaped as a Candid text literal — wrap as |
| No rows returned for a filter you expect to match | (1) |
| Mixed-type comparisons aren't defined. Make sure both operands are the same |
| |
Dotted path traps | The head segment isn't a declared edge — traversal is schema-driven even when values look like FKs. Use the two-query |
| Cross-entity average looks wrong | Aggregates run over the start entity's rows. Start from the entity whose rows you want averaged, or group by the dotted path and aggregate start-entity fields. |
| A field you |
There is no structured error envelope. Any failure is a trap — fix
the query and retry.
| 症状 | 原因/修复方案 |
|---|---|
| |
| JSON格式错误( trailing逗号、单引号),或未正确转义为Candid文本字面量——需将JSON包裹在 |
| 预期匹配的过滤条件未返回任何行 | (1) |
| 混合类型比较未定义。确保两个操作数的 |
| |
点路径触发陷阱 | 首段不是已声明的关联——即使值看起来像外键,遍历仍由schema驱动。请改用双查询 |
| 跨实体平均值结果异常 | 聚合基于起始实体的行。需从要统计平均值的实体开始查询,或按点路径分组并聚合起始实体的字段。 |
| |
无结构化错误信封。任何失败都会触发陷阱——修正查询后重试。