# 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`。