Files
playbook/docs/tsl/syntax/14_ts_sql.md
T
cshandClaude Fable 5 014c23d386 🎨 style(syntax): normalize naming conventions
Align all TSL syntax documentation examples with docs/tsl/naming.md:

- Classes/types: PascalCase, drop Hungarian prefix (THuman→Human)
- Parameters/locals: snake_case with meaningful names (isLeft→is_left, maxb→max_b)
- Private members: snake_case_ with trailing underscore (real_part_, imaginary_part_)
- Public members: PascalCase (value→Value)
- Top-level functions: PascalCase (test→Test)
- Module constants: kPascalCase (kernel_dll→kKernelDll)

Affected: 01-24 syntax docs (23 files)
Verified: 150+ code blocks locally tested, output unchanged
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:34:19 +08:00

917 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
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:先按本页候选页继续判断;[13_resultset_and_filters.md](13_resultset_and_filters.md)、[12_matrix_and_collections.md](12_matrix_and_collections.md)、[23_fmarray.md](23_fmarray.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
这一篇是 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 判断流程
1. 先判断要写查询(`select`/`sselect`/`vselect`/`mselect`)还是写回(`insert`/`update`/`delete`)。
2. 内存数组查询优先从 `select ... from source_rows end` 最小骨架起手。
3. 二维结果集字段访问用 `["字段名"]`;多表查询字段访问用 `[表序号].["字段名"]`
4. 在一维数组上做 TS-SQL 时,优先使用 `thisRow``thisRowIndex`
5. 联接选 `join` / `left join` / `right join` / `full join` / `cross join` / 逗号联接;等值联接可用 `with(... on ...)` 优化。
6. 聚集统一形态 `(Expr, Cond, N, MovingFirst, CacheId)`:条件聚集传 `Cond`,移动聚集传 `N`;分组后筛选用 `having`;自定义聚集用 `aggof`
7. 只想按已有结果集保留/排除行时跳到 [13_resultset_and_filters.md](13_resultset_and_filters.md);要做去重型集合关系时跳到 [12_matrix_and_collections.md](12_matrix_and_collections.md);要在 `FMArray` 上做查询或写回边界时跳到 [23_fmarray.md](23_fmarray.md)。
8. 没有对应代码块时不要发明 TS-SQL 写法。
## 核心规则
- TS-SQL 是 TSL 自带的类 SQL 查询语法,不是金融业务函数库。
- 基础查询文档骨架是:以 `select` / `sselect` / `vselect` / `mselect` 开始,以 `end` 收尾。
- `from` 后面可以直接跟内存数组结果集。
- 在内存二维结果集上,文档字段访问写法是 `["字段名"]`
- 在一维数组上做 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` 直接修改原内存数组,不返回新数组。
- 访问金融表、时间序列或业务数据源时,语法和业务语义要分开看;本页只讲语言层查询骨架。
## 可直接照写示例
### 最小查询骨架
代码块身份:可直接照写示例
```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
```
### 字段选择
代码块身份:可直接照写示例
```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` 会按原顺序返回二维结果集
### 四个查询入口怎样分工
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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` 在排序后仍指向原始位置
代码块身份:可直接照写示例
```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`
代码块身份:可直接照写示例
```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` 多表联接
`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`(笛卡尔积)
### JOIN 家族其余形态
`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` 写回
`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``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` 写回
`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` 写回
`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` 结果集去重
`select distinct` 对结果集去重;聚集函数内也可用 `distinct` 前缀:
代码块身份:可直接照写示例
```tsl
a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "B", "en": 85));
distinct_rows := select distinct ["cls"] from a end;
distinct_sum := vselect sumof(distinct ["en"]) from a end;
writeLn(mrows(distinct_rows));
writeLn(distinct_sum);
```
代码块身份:输出片段
```text
2
255
```
说明:
- `select distinct [字段]` 折叠重复行
- `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255`
### `as` 别名、`as nil` 与字段区间
代码块身份:可直接照写示例
```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` 取行区间
代码块身份:可直接照写示例
```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` 位选项
代码块身份:可直接照写示例
```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`
聚集函数统一支持 `(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`
`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`
代码块身份:可直接照写示例
```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` 引用上级结果集
在嵌套子查询里,`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]` 取字段类型
代码块身份:可直接照写示例
```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` 自定义聚集扩展
> 代码块身份:仅服务端可执行示例
> 本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过。
`aggof('名称', 表达式)` 调用一个自定义回调函数做聚集:
代码块身份:仅服务端可执行示例
```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` 等数据源)
这些内容不作为本页可生成事实;业务数据源先查 [../codegen/dotnet/datawarehouse/](../codegen/dotnet/datawarehouse/)、[../modules/pytsl_api.md](../modules/pytsl_api.md) 或项目实际接口。
## 默认生成模板
TS-SQL 的最短默认骨架如下:
代码块身份:可直接照写示例
```tsl
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]` 的写法虽然返回长度为 `3` 的结果,但取到的值是 `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`