Files
playbook/skills/tsl-syntax-reference/references/13_ts_sql.md
T
csh 40a9885eb4 feat(tsl-syntax-reference): harden retrieval and restructure pages
- flag weak candidates (no intent/heading/identifier/tag hit) and exit 2
  when every candidate is weak: mis-hits used to be indistinguishable
  from real hits, so the retry-with-better-terms loop never fired
- accept multiple ids per --section for batch fetch, failing atomically
  on any unknown id so a partial fetch cannot pass as complete
- move query synonyms and page intent aliases to data/lexicon.json and
  enforce alias/page correspondence in --check; curation data no longer
  lives in the engine
- document the weak-hit rule, batch fetch and prelude-once guidance in
  SKILL.md, with curation discipline in data/README.md
- drop 11_pitfalls.md, renumber the trailing pages and spread retrieval
  tags across topics; lexicon keys are page filenames, so the renumbering
  and the new --check rule cannot land in separate commits
2026-07-29 15:45:47 +08:00

951 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TSL TS-SQL
这一篇是 TS-SQL 的唯一语法入口:内存数组查询、返回形态、字段访问、`where` / `group by` / `order by`、一维数组查询、多表 `join`(含 `left join`)、`insert` / `update` / `delete` 写回、`thisGroup``thisRowIndex``refMaxOf` / `refMinOf` 都在这里收拢。
## 本篇职责
回答“写 TS-SQL 查询和写回时,怎样从最小 `select ... from ... end` 骨架开始,逐步处理筛选、分组、排序、多表联接(含 LEFT JOIN)、组内子查询、极值引用,以及如何用 `insert`/`update`/`delete` 修改内存数组”。
## 核心规则
- TS-SQL 是 TSL 自带的类 SQL 查询语法,不是金融业务函数库。
- 基础查询文档骨架是:以 `select` / `sselect` / `vselect` / `mselect` 开始,以 `end` 收尾。
- `from` 后面可以直接跟内存数组结果集。
- 在内存二维结果集上,文档字段访问写法是 `["字段名"]`;列没有名字(如直接来自数组)时用位置下标 `[0]``[1]` 访问。
- 在一维数组上做 TS-SQL 时,优先使用 `thisRow``thisRowIndex`
- `select` 返回二维结果,`sselect` 返回一维结果,`vselect` 返回单值,`mselect` 返回 `Matrix`
- `where``group by``order by` 可以直接接在 `from` 后面继续使用;`order by` 支持 `asc`/`desc` 与多列逗号分隔。
- 分组后按聚集条件筛选用 `having``where` 不能用聚集);`having` 里用 `countof([字段])``countof(1)`,不要用 `countof(*)`
- 多表 `join` 时,字段访问应写成 `[表序号].["字段名"]``on` 可用 `and` 写多条件;`[表序号].*` 取整表列。
- 联接类型:`left join` 保留左表、`right join` 保留右表、`full join` 保留双方、`cross join` 笛卡尔积、逗号联接等价于 `cross join`;不匹配处用 `nil` 填充。
- `select` 列表支持 `distinct` 去重、`as 别名``as nil`(参与计算但不返回)、`起始列 to 结束列` 字段区间、`selectopt(位选项)``drange(区间/M of N)`
- 聚集函数统一形态 `Func(Expr[, Cond[, N[, MovingFirst[, CacheId]]]])`:条件聚集、移动聚集、多字段聚集、`refof(Expr, N)` 引用相对行;`aggof('名', Expr)` 调用自定义聚集回调。
- `thisGroup` 不是普通值,而是分组后的子结果集;要通过子 `select` / `vselect``from thisGroup` 来访问。
- `thisRowIndex``order by` 之后仍可返回原始行位置;`thisOrder` 返回排序后的自然排名。
- `refMaxOf(...)``refMinOf(...)` 可与 `maxOf(...)` / `minOf(...)` 配合,取极值所在行的另一列值。
- `[@字段]` 返回该字段的数据类型字符串。
- 写回语句 `insert` / `update` / `delete` 直接修改原内存数组,不返回新数组。
- 访问金融表、时间序列或业务数据源时,语法和业务语义要分开看;本页只讲语言层查询骨架。
## 可直接照写示例
### 最小查询骨架
<!-- tags: 怎么写查询, 最简 select, 查数组, 内存表查询 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1)
);
query_result := select * from source_rows end;
writeLn(length(query_result));
```
结果说明:
- `query_result` 的长度是 `2`
- 两行依次是 `(1,3)``(2,1)`
- 说明 TS-SQL 的最短可靠入口就是“准备结果集,然后 `select ... from source_rows end`
代码块身份:输出片段
```text
2
```
### 字段选择
<!-- tags: 选哪些列, 只取部分字段, 指定列, 输出字段 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1),
("A": 1, "B": 2)
);
query_result := select ["A"], ["B"] from source_rows end;
writeLn(length(query_result));
```
结果说明:
- `query_result` 的长度是 `3`
- 三行依次是 `(1,3)``(2,1)``(1,2)`
- 说明 `select ["A"], ["B"] from source_rows end` 会按原顺序返回二维结果集
### 四个查询入口怎样分工
<!-- tags: select 和 sselect 区别, vselect, mselect, 返回什么形态, 该用哪个查询 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1),
("A": 1, "B": 2)
);
selected_values := sselect ["A"] from source_rows end;
sum_value := vselect sumOf(["B"]) from source_rows end;
matrix_result := mselect * from source_rows end;
col_index := mcols(matrix_result, 1);
```
结果说明:
- `sselect ["A"] from source_rows end` 返回一维数组 `array(1, 2, 1)`
- `vselect sumOf(["B"]) from source_rows end` 返回单值 `6`
- `mselect * from source_rows end` 的行数是 `3`、列数是 `2`
- `mcols(matrix_result, 1)` 返回列索引 `array("A", "B")`
- 本页只把 `mselect` 的“返回 Matrix 且保留行列信息”写成文档主干;不要在本页发明直接单元格读取规则
### `where` 和 `order by`
<!-- tags: 条件筛选, 排序, 按某列排, 倒序, 只要满足条件的行 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1),
("A": 1, "B": 2)
);
query_result := select * from source_rows where ["B"] > 1 order by ["B"] end;
```
结果说明:
- `query_result` 的长度是 `2`
- 两行依次是 `(1,2)``(1,3)`
- 说明 `where ["B"] > 1` 会先筛选,再按 `order by ["B"]` 的升序返回
### `group by`
<!-- tags: 分组, 汇总, 聚合统计, 按类别合计 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1),
("A": 1, "B": 2)
);
group_result := select ["A"], sumOf(["B"]) as "SumB"
from source_rows
group by ["A"]
order by ["A"]
end;
```
结果说明:
- `group_result` 的长度是 `2`
- 第一行是 `(1,5)`
- 第二行是 `(2,1)`
- 说明 `group by ["A"]` 后可以直接接聚集函数,并用 `as "SumB"` 指定返回列名
### 一维数组上的 `thisRow` 与 `thisRowIndex`
<!-- tags: 当前行, 当前行号, 一维数组查询 -->
代码块身份:可直接照写示例
```tsl
values := array(10, 20, 30);
row_values := sselect thisRow from values end;
row_indexes := sselect thisRowIndex from values end;
query_result := select thisRow as "Value", thisRowIndex as "Idx"
from values
where thisRow > 15
order by thisRow
end;
```
结果说明:
- `sselect thisRow from values end` 返回 `array(10, 20, 30)`
- `sselect thisRowIndex from values end` 返回 `array(0, 1, 2)`
- 上面的 `select ... from values where thisRow > 15 order by thisRow end` 返回两行:第一行 `Value=20, Idx=1`,第二行 `Value=30, Idx=2`
### `join`
<!-- tags: 两表关联, 表连接, 按键匹配, 拼接两张表 -->
代码块身份:可直接照写示例
```tsl
left_rows := array(
("ID": 1, "V1": 10),
("ID": 2, "V1": 20)
);
right_rows := array(
("ID": 1, "V2": 100),
("ID": 3, "V2": 300)
);
join_result := select [1].["ID"], [1].["V1"], [2].["V2"]
from left_rows join right_rows on [1].["ID"] = [2].["ID"]
end;
writeLn(length(join_result));
writeLn(join_result[0]["ID"]);
writeLn(join_result[0]["V1"]);
writeLn(join_result[0]["V2"]);
```
结果说明:
- `join_result` 的长度是 `1`
- 唯一一行是 `(1,10,100)`
- 说明 `from left_rows join right_rows on ...``[1].["字段"]``[2].["字段"]` 这种多表字段访问属于文档明确写法
代码块身份:输出片段
```text
1
1
10
100
```
### `thisGroup`
<!-- tags: 组内数据, 分组明细, 取当前组 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3, "Name": "x"),
("A": 2, "B": 1, "Name": "y"),
("A": 1, "B": 2, "Name": "z")
);
group_result := select ["A"], max_b := maxOf(["B"]) as "MaxB",
vselect ["Name"] from thisGroup where ["B"] = max_b end as "TopName"
from source_rows
group by ["A"]
order by ["A"]
end;
```
结果说明:
- `group_result` 的长度是 `2`
- 第一行是 `(1,3,"x")`
- 第二行是 `(2,1,"y")`
- 说明 `thisGroup` 可以在分组上下文里作为子结果集继续 `vselect`
### `thisRowIndex` 在排序后仍指向原始位置
<!-- tags: 排序后原始行号, 原位置, 排序不改下标 -->
代码块身份:可直接照写示例
```tsl
source_rows := array(
("A": 1, "B": 3),
("A": 2, "B": 1),
("A": 1, "B": 2)
);
query_result := select thisRowIndex as "Idx", ["B"]
from source_rows
order by ["B"]
end;
```
结果说明:
- `query_result` 的三行依次是 `(1,1)``(2,2)``(0,3)`
- 说明 `order by ["B"]` 之后,`thisRowIndex` 仍返回原表中的原始下标
### `refMaxOf` 与 `refMinOf`
<!-- tags: 取最大值那行, 取最小值对应字段, 谁最大, 极值行 -->
代码块身份:可直接照写示例
```tsl
source_rows := array((6, 20), (5, 20), (9, 2), (2, 20), (7, 18));
max_ref_result := select maxOf([0]) as "MaxA", refMaxOf([1]) as "RefB" from source_rows end;
min_ref_result := select minOf([0]) as "MinA", refMinOf([1]) as "RefB" from source_rows end;
```
结果说明:
- `max_ref_result` 只有一行,结果是 `(9,2)`
- `min_ref_result` 只有一行,结果是 `(2,20)`
- 说明 `refMaxOf([1])` 取到了 `[0]` 最大值所在行的 `[1]``refMinOf([1])` 取到了 `[0]` 最小值所在行的 `[1]`
### `LEFT JOIN` 多表联接
<!-- tags: 左连接, 左联接, 保留左表, 右边没有就空 -->
`left join` 保留左表所有行,右表不匹配时用 `nil` 填充:
代码块身份:可直接照写示例
```tsl
left_rows := array(("k": 1, "va": "a1"), ("k": 2, "va": "a2"));
right_rows := array(("k": 1, "vb": "b1"), ("k": 3, "vb": "b3"));
result := select [1].["va"], [2].["vb"]
from left_rows left join right_rows
on [1].["k"] = [2].["k"]
end;
writeLn("行数:", mrows(result));
writeLn("(0,0):", result[0]["va"]);
writeLn("(0,1):", result[0]["vb"]);
writeLn("(1,0):", result[1]["va"]);
writeLn("(1,1):", result[1]["vb"]);
```
代码块身份:输出片段
```text
行数: 2
(0,0): a1
(0,1): b1
(1,0): a2
(1,1): nil
```
说明:
- `left join` 保留左表所有行(`k=1``k=2`
- 右表 `k=2` 不存在,对应列用 `nil` 填充
- `on` 子句指定联接条件,用 `[1].["k"] = [2].["k"]` 匹配键
- 其他联接类型:`right join`(保留右表)、`full join`(保留双方)、`cross join`(笛卡尔积)
### `right join` / `full join` / `cross join` 与逗号联接
<!-- tags: 右连接, 全连接, 笛卡尔积, 交叉联接 -->
`right join` / `full join` 的不匹配行同样用 `nil` 填充:
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80));
b := array(("id": "01", "math": 70), ("id": "05", "math": 50));
right_result := select [1].["id"], [2].["math"] from a right join b on [1].["id"] = [2].["id"] end;
full_result := select [1].["id"], [2].["math"] from a full join b on [1].["id"] = [2].["id"] end;
writeLn("right行数:", mrows(right_result));
writeLn("full行数:", mrows(full_result));
```
代码块身份:输出片段
```text
right行数: 2
full行数: 3
```
说明:
- `right join` 保留右表所有行,左表无匹配的记录里,取自左表的字段为 `nil`(上例右表 `id=05` 那行 `[1].["id"]``nil`
- `full join` 保留双方所有行,任一侧无匹配的字段用 `nil` 填充(上例既有左表独占的 `02`,也有右表独占的 `05`
`cross join` 是笛卡尔积,不带 `on`;等价的逗号联接可带 `where`
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80));
b := array(("id": "01", "math": 70), ("id": "05", "math": 50));
cross_result := select [1].["id"], [2].["id"] from a cross join b end;
comma_result := select [1].["id"] from a, b where [1].["id"] = [2].["id"] end;
writeLn("cross行数:", mrows(cross_result));
writeLn("comma行数:", mrows(comma_result));
```
代码块身份:输出片段
```text
cross行数: 4
comma行数: 1
```
说明:
- `cross join` 不写 `on`,产生左右两表的全组合(2 × 2 = 4 行)
- `from A, B where ...` 是等价的逗号联接写法,用 `where` 表达匹配条件
`with(... on ...)` 是优化联接:把 `on` 的 N×M 匹配复杂度降到 N+M,多个等值约束用逗号分隔:
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "03", "en": 85));
b := array(("id": "01", "math": 70), ("id": "03", "math": 60), ("id": "05", "math": 50));
result := select [1].["id"], [2].["math"] from a join b with([1].["id"] on [2].["id"]) end;
writeLn("行数:", mrows(result));
```
代码块身份:输出片段
```text
行数: 2
```
说明:
- `join B with(左表达式组 on 右表达式组)` 只支持等值约束,靠哈希把复杂度降到 N+M
- 多个等值约束写成 `with([1].["id"], [1].["cls"] on [2].["id"], [2].["cls"])`
- 需要非等值条件(`>``<`)时回到普通 `join ... on`
`on` 支持多条件 `and``[表序号].*` 取整表所有列:
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "03", "en": 85));
b := array(("id": "01", "math": 70), ("id": "03", "math": 95));
result := select [1].* from a join b on [1].["id"] = [2].["id"] and [1].["en"] > [2].["math"] end;
writeLn("行数:", mrows(result));
writeLn("(0,0):", result[0]["id"]);
```
代码块身份:输出片段
```text
行数: 1
(0,0): 01
```
说明:
- `on` 后面可以用 `and` 串联多个条件
- `[1].*` 返回第一个表的所有列;三表以上时链式写 `join C on ...`
### `INSERT` 写回
<!-- tags: 插入行, 新增记录, 往数组加数据 -->
`insert into` 向内存数组插入新行:
代码块身份:可直接照写示例
```tsl
a := array(("id": "x"));
insert into a insertfields(["id"]) values("y");
writeLn("行数:", mrows(a));
writeLn("(0,0):", a[0]["id"]);
writeLn("(1,0):", a[1]["id"]);
```
代码块身份:输出片段
```text
行数: 2
(0,0): x
(1,0): y
```
说明:
- `insert into 数组变量` 直接修改原数组
- `insertfields([字段列表])` 指定要插入的字段
- `values(...)` 提供对应值,可以连续写多组 `values(...)` 实现批量插入
批量插入可以直接跟一个同结构数组,`insertfields` 也支持一次给多字段赋值:
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "cls": "A"));
rows := array(("id": "09", "cls": "C"));
insert into a rows;
insert into a insertfields(["id"], ["cls"]) values("07", "B");
writeLn("行数:", mrows(a));
writeLn(a[1]["id"], a[1]["cls"]);
writeLn(a[2]["id"], a[2]["cls"]);
```
代码块身份:输出片段
```text
行数: 3
09C
07B
```
说明:
- `insert into a rows;` 把整个 `rows` 数组的行批量追加进 `a`
- `insertfields(["id"], ["cls"]) values("07", "B")` 一次插入多字段
### `UPDATE` 写回
<!-- tags: 更新行, 改字段值, 批量修改 -->
`update` 修改符合条件的行:
代码块身份:可直接照写示例
```tsl
a := array(("id": "x", "v": 1), ("id": "y", "v": 2));
update a set ["v"] = 99 where ["id"] = "y" end;
writeLn(a[0]["v"]);
writeLn(a[1]["v"]);
```
代码块身份:输出片段
```text
1
99
```
说明:
- `update 数组变量 set [字段] = 新值` 直接修改原数组
- `where` 子句筛选要更新的行
- 可以同时更新多个字段:`set ["v1"] = 10, ["v2"] = 20`
`set` 一个不存在的列会自动新增该列;`set thisrow = ...` 可以整行替换(一维数组上尤其常用):
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80));
update a set ["total"] = ["en"] + 1 end;
r := array(1, 2, 3);
update r set thisrow = thisrow * 10 end;
writeLn(a[0]["total"]);
writeLn(r[0], r[1], r[2]);
```
代码块身份:输出片段
```text
91
102030
```
说明:
- `set ["total"] = ...``total` 原本不存在,执行后自动作为新列加到每一行
- `set thisrow = thisrow * 10` 用当前行整体做表达式并写回;一维数组上 `thisrow` 就是元素本身
### `DELETE` 写回
<!-- tags: 删除行, 删掉记录, 按条件删 -->
`delete` 删除符合条件的行:
代码块身份:可直接照写示例
```tsl
a := array(("id": "x"), ("id": "y"));
delete from a where ["id"] = "x";
writeLn("行数:", mrows(a));
writeLn("(0,0):", a[0]["id"]);
```
代码块身份:输出片段
```text
行数: 1
(0,0): y
```
说明:
- `delete from 数组变量 where 条件` 删除符合条件的行
- 直接修改原数组
- 省略 `where` 会删除所有行
`deleteopt(Option)` 控制删除后是否重排下标:
代码块身份:可直接照写示例
```tsl
a := array(("cls": "A"), ("cls": "A"), ("cls": "B"), ("cls": "B"));
delete deleteopt(1) from a where ["cls"] = "A";
writeLn(mrows(a));
```
代码块身份:输出片段
```text
2
```
说明:
- `deleteopt(0)`(默认)删除后重排剩余行下标
- `deleteopt(1)` 删除后保留原始下标,不重排
### `distinct` 结果集去重
<!-- tags: 去重, 不要重复行, 唯一值 -->
`select distinct` 对结果集去重;聚集函数内也可用 `distinct` 前缀:
代码块身份:可直接照写示例
```tsl
a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "A", "en": 90), ("cls": "B", "en": 85));
distinct_rows := select distinct ["cls"] from a end;
distinct_sum := vselect sumof(distinct ["en"]) from a end;
plain_sum := vselect sumof(["en"]) from a end;
writeLn(mrows(distinct_rows));
writeLn(distinct_sum);
writeLn(plain_sum);
```
代码块身份:输出片段
```text
2
255
345
```
说明:
- `select distinct [字段]` 折叠重复行
- `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255`;不加 `distinct` 时重复的 `90` 计两次,得 `345`
### `as` 别名、`as nil` 与字段区间
<!-- tags: 改列名, 起别名, 丢弃字段, 字段区间 -->
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "cls": "A", "en": 90), ("id": "02", "cls": "B", "en": 80));
alias_rows := select ["en"] as "score" from a end;
nil_rows := select ["id"], ["en"] * 2 as nil from a end;
range_rows := select 0 to 1 from a end;
writeLn(alias_rows[0]["score"]);
writeLn(mcols(nil_rows));
writeLn(mcols(range_rows));
```
代码块身份:输出片段
```text
90
1
2
```
说明:
- `字段 as "别名"` 给结果列改名
- `表达式 as nil` 让该列只参与临时计算,不出现在结果集里(上例 `nil_rows` 只剩 `id` 一列)
- `StartIndex to EndIndex` 在选择列表里取列区间;`0 to 1` 返回第 0、1 两列
### `drange` 取行区间
<!-- tags: 取前几行, 分页, 行区间, 只要一段 -->
代码块身份:可直接照写示例
```tsl
a := array(("id": "01"), ("id": "02"), ("id": "03"), ("id": "04"));
head_rows := select drange(0 to 1) * from a end;
tail_rows := select drange(-2 to -1) * from a end;
part_rows := select drange(1 of 2) * from a end;
writeLn(mrows(head_rows));
writeLn(tail_rows[0]["id"]);
writeLn(mrows(part_rows));
```
代码块身份:输出片段
```text
2
03
2
```
说明:
- `drange(begin to end)` 取行区间;负数从末尾计(`-1` 是最后一行)
- `drange(M of N)` 把结果集等分成 N 份,取第 M 份
### `selectopt` 位选项
<!-- tags: 查询选项, 返回形态控制, 位标志 -->
代码块身份:可直接照写示例
```tsl
a := array(("en": 90), ("en": 80), ("en": 85), ("en": 85));
opt_rows := select selectopt(2) ["en"] from a end;
writeLn(dataType(opt_rows));
writeLn(opt_rows[0]);
```
代码块身份:输出片段
```text
4
90
```
说明:
- `selectopt(N)` 用位组合改变返回形态;`selectopt(2)``select` 返回一维数组(不再是二维结果集)
- 常用位:`1`=单值、`2`=一维、`4`=Matrix、`16`=多字段聚集保留原名、`64`=MovingFirst 变换
### 条件聚集、移动聚集与 `refof`
<!-- tags: 条件求和, 滑动窗口, 移动平均, 带条件的聚合 -->
聚集函数统一支持 `(Expr, BoolConditionExp, N, MovingFirst, CacheId)` 形态:
代码块身份:可直接照写示例
```tsl
a := array(("en": 90), ("en": 80), ("en": 85), ("en": 85));
cond_sum := vselect sumof(["en"], ["en"] >= 85) from a end;
moving := select avgof(["en"], true, 2, true) from a end;
ref_prev := select ["en"], refof(["en"], 1) from a end;
writeLn(cond_sum);
writeLn(moving[2]["Expr1"]);
writeLn(ref_prev[1]["Expr1"]);
```
代码块身份:输出片段
```text
260
82.5
90
```
说明:
- 条件聚集:`sumof(表达式, 布尔条件)` 只统计条件为真的行(`90 + 85 + 85 = 260`
- 移动聚集:`avgof(表达式, 条件, N, MovingFirst)` 取当前行往前 N 条的滑动统计
- `refof(表达式, N)` 引用前 N 行的值(`N` 为负则往后);首行无前值时返回 `0`
### `group by ... having`
<!-- tags: 分组后筛选, 聚合条件, 组级过滤 -->
`having` 用聚集条件筛选分组(`where` 不能用聚集):
代码块身份:可直接照写示例
```tsl
a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "B", "en": 85));
having_rows := select ["cls"] from a group by ["cls"] having countof(["cls"]) > 1 end;
writeLn(mrows(having_rows));
writeLn(having_rows[0]["cls"]);
```
代码块身份:输出片段
```text
1
A
```
说明:
- `having 聚集条件` 在分组后筛选(上例只保留成员数大于 1 的 `A` 组)
- `having` 里的计数用 `countof([字段])``countof(1)`
代码块身份:反例 / 不可照写
```text
having_rows := select ["cls"] from a group by ["cls"] having countof(*) > 1 end;
```
`countof(*)` 这种带 `*` 的写法不成立,会报 `CountOf ( not found`。计数改用 `countof([字段])``countof(1)`
### `thisOrder` 与多列 `order by`
<!-- tags: 多列排序, 排名, 序号, 先按 A 再按 B -->
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80), ("id": "03", "en": 85), ("id": "04", "en": 85));
order_rows := select ["id"], thisOrder as "ord" from a order by ["en"] end;
desc_rows := select ["id"] from a order by ["en"] desc end;
writeLn(order_rows[0]["id"], ",", order_rows[0]["ord"]);
writeLn(desc_rows[0]["id"]);
```
代码块身份:输出片段
```text
02,1
01
```
说明:
- `thisOrder` 返回排序后的自然排名(从 1 起,值相同则同名次,名次可不连续)
- `order by [字段] desc` 降序;多列排序用逗号分隔:`order by ["cls"] asc, ["en"] desc`
- `thisOrder``thisRowIndex` 不同:后者返回排序前的原始行位置
### `refsof` 引用上级结果集
<!-- tags: 子查询取外层, 嵌套查询, 引用上一层 -->
在嵌套子查询里,`refsof(Exp, UpLevel)` 用上 N 级结果集计算 `Exp`
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80));
r := select ["id"], (vselect refsof(["en"], 1) from a end) as "up" from a end;
writeLn(r[0]["id"], ",", r[0]["up"]);
writeLn(r[1]["id"], ",", r[1]["up"]);
```
代码块身份:输出片段
```text
01,90
02,80
```
说明:
- `refsof(表达式, 1)` 在内层子查询里引用上一级(外层)结果集当前行的值
- 数字越大引用越靠外层;脱离嵌套单独使用会报 `no result set for reference`
### `[@Field]` 取字段类型
<!-- tags: 字段类型, 列的数据类型 -->
代码块身份:可直接照写示例
```tsl
a := array(("id": "01", "en": 90), ("id": "02", "en": 80));
t := select ["id"], [@"en"] from a end;
writeLn(t[0]["Expr1"]);
```
代码块身份:输出片段
```text
integer
```
说明:
- `[@字段名]` 返回该字段的数据类型名(字符串),可用于 select 列表与 update set
### `aggof` 自定义聚集扩展
<!-- tags: 自定义聚合, 自己写聚集函数 -->
`aggof('名称', 表达式)` 调用一个自定义回调函数做聚集。本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过:
代码块身份:仅服务端可执行示例
```tsl
Table1 := array(("C": 1), ("C": 2), ("C": 3), ("C": 4), ("C": 5));
return vselect aggof('AggSumSample', ['C']) from Table1 end;
function AggSumSample(Flag, Value);
begin
if Flag = 0 then
begin
sysParams['SumSample'] := 0;
return true;
end
else if Flag = 1 then
begin
sysParams['SumSample'] := sysParams['SumSample'] + Value;
return true;
end
else
return sysParams['SumSample'];
end;
```
说明(上例返回 `15`):
- 回调签名固定为 `function Name(Flag, Value)`
- `Flag=0` 初始化(`Value` 为真表示 `distinct`),返回 `true`/`false` 表示成败
- `Flag=1` 每行数据,`Value` 是当前行表达式的值,返回 `true`/`false`
- `Flag=2` 结束,返回最终聚集结果
- 状态用 `sysParams[...]` 缓存,不要用 `static`
## 本页不生成的范围
- `TSQLInsert` / `TSQLSetValue` / `TSQLBatchInsert` / `TSQLEdit` / `TSQLPost` / `TSQLFinal` 对象被 TS-SQL 查询的回调机制
- 面向 SQL 表、业务表或时间序列的数据查询与写回(`marketTable` / `infoTable` / `tradeTable` / `sqlTable` / `hugeSqlTable` 等数据源)
这些内容不作为本页可生成事实;业务数据源先交给 `tsl-api-reference` skill、pyTSL 模块事实所有者或项目实际接口。
## 默认生成模板
TS-SQL 的最短默认骨架如下:
代码块身份:可直接照写示例
```tsl
source_rows := array((1, 10), (2, 20));
query_result := select * from source_rows end;
```
## 禁止项
- 不要把数据库 SQL 方言直接迁移成 TS-SQL 代码。
- 不要把 `select` 当成普通函数调用,忘了以 `end` 收尾。
- 在二维结果集里直接写 `A` 而不是 `["A"]`
- 多表联接时继续写成 `["ID"]`,没有加表序号。
- 处理一维数组时直接把 `[0]` 当成稳定列访问。
-`thisGroup` 当成普通字段或普通变量。
- 把排序后的 `thisRowIndex` 误当成排序序号。
-`left join` 时省略 `on` 子句或不用 `[表序号].["字段"]` 形式。
-`insert` 时漏掉 `insertfields` 或字段数与值数不匹配。
- 期望 `update`/`delete` 返回新数组;它们直接修改原数组。
-`countof(*)` 数行数;`*` 星号形式不被支持,改用 `countof([字段])``countof(1)`
代码块身份:反例 / 不可照写
```text
n := vselect countof(*) from source_rows end;
```
`countof(*)` 会报 `CountOf ( not found`。数行数改用 `countof([字段])``countof(1)`
代码块身份:反例 / 不可照写
```text
query_result := select A from source_rows end;
```
上面这种写法不要当成本页可靠规则。二维结果集字段访问,本页只把 `["A"]` 这种写法写成文档主干。
代码块身份:反例 / 不可照写
```text
query_result := select [0] from values end;
```
这种对一维数组直接用 `[0]` 的写法虽然能返回与源数组等长的结果,但取到的值是 `nil`,不能当成可靠入口。对一维数组应改用 `thisRow``thisRowIndex`
代码块身份:反例 / 不可照写
```text
query_result := select ["ID"], ["V1"], ["V2"]
from left_rows join right_rows on ["ID"] = ["ID"]
end;
```
上面这种写法不要当成本页可靠规则。多表查询里,本页只把 `[1].["字段"]``[2].["字段"]` 这种带表序号的访问方式写成文档主干。
代码块身份:反例 / 不可照写
```text
group_value := thisGroup;
```
不要把 `thisGroup` 当成普通值直接使用。可靠入口是 `select ... from thisGroup end``vselect ... from thisGroup end`