# TSL TS-SQL

这一篇是 TS-SQL 的唯一语法入口：内存数组查询、返回形态、字段访问、`where` / `group by` / `order by`、一维数组查询、多表 `join`（含 `left join`）、`insert` / `update` / `delete` 写回、`thisGroup`、`thisRowIndex`、`refMaxOf` / `refMinOf` 都在这里收拢。

## 本篇职责

<!-- section-id: syntax-13-001 -->

回答“写 TS-SQL 查询和写回时，怎样从最小 `select ... from ... end` 骨架开始，逐步处理筛选、分组、排序、多表联接（含 LEFT JOIN）、组内子查询、极值引用，以及如何用 `insert`/`update`/`delete` 修改内存数组”。

## 核心规则

<!-- section-id: syntax-13-002 -->

- 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` 不能用聚集）；计数可用 `countof([字段])`、`countof(1)`、`countof()`，也可用带空格的 `countof( * )`。无空格的 `countof(*)` 会与块注释起始符 `(*` 冲突。
- 多表 `join` 时，字段访问应写成 `[表序号].["字段名"]`；`on` 可用 `and` 写多条件；`[表序号].*` 取整表列。
- 多表联接中，`thisRow(表序号)` 和 `thisRowIndex(表序号)` 分别取得指定来源表的当前整行与原始下标。
- 联接类型：`left join` 保留左表、`right join` 保留右表、`full join` 保留双方、`cross join` 笛卡尔积、逗号联接等价于 `cross join`；不匹配处用 `nil` 填充。
- `select` 列表支持 `distinct` 去重、`as 别名`、`as nil`（参与计算但不返回）、`起始列 to 结束列` 字段区间、`selectopt(位选项)`、`drange(区间/M of N)`。
- `select` 输出列表里出现同名列时，靠后的列会覆盖靠前的，结果集里只留一列；多表 `join` 两侧有同名字段时尤其容易踩。要两列都保留，用 `as` 给其中一个改名。
- 聚集函数统一形态 `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` 直接修改原内存数组，不返回新数组。
- 访问金融表、时间序列或业务数据源时，语法和业务语义要分开看；本页只讲语言层查询骨架。

## 可直接照写示例

<!-- section-id: syntax-13-003 -->

### 最小查询骨架

<!-- section-id: syntax-13-004 -->

<!-- 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
```

### 字段选择

<!-- section-id: syntax-13-005 -->

<!-- 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` 会按原顺序返回二维结果集

### 四个查询入口怎样分工

<!-- section-id: syntax-13-006 -->

<!-- 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`

<!-- section-id: syntax-13-007 -->

<!-- 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`

<!-- section-id: syntax-13-008 -->

<!-- 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`

<!-- section-id: syntax-13-009 -->

<!-- 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`

<!-- section-id: syntax-13-010 -->

<!-- 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
```

联接上下文里的指定来源当前行：

代码块身份：可直接照写示例

```tsl
left_rows := array(("id": 1, "v": 10), ("id": 2, "v": 20));
right_rows := array(("id": 2, "v": 20), ("id": 3, "v": 30));
join_rows := select thisRow(1) as "LeftRow",
                    thisRow(2) as "RightRow",
                    thisRowIndex(1) as "LeftIndex",
                    thisRowIndex(2) as "RightIndex"
             from left_rows join right_rows on [1].["id"] = [2].["id"]
             end;
```

结果说明：唯一匹配行中，`LeftRow["v"] = 20`、`RightRow["v"] = 20`、`LeftIndex = 1`、`RightIndex = 0`。

### `thisGroup`

<!-- section-id: syntax-13-011 -->

<!-- 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` 在排序后仍指向原始位置

<!-- section-id: syntax-13-012 -->

<!-- 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`

<!-- section-id: syntax-13-013 -->

<!-- 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` 多表联接

<!-- section-id: syntax-13-014 -->

<!-- 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` 与逗号联接

<!-- section-id: syntax-13-015 -->

<!-- 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` 写回

<!-- section-id: syntax-13-016 -->

<!-- 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` 写回

<!-- section-id: syntax-13-017 -->

<!-- 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` 写回

<!-- section-id: syntax-13-018 -->

<!-- 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` 结果集去重

<!-- section-id: syntax-13-019 -->

<!-- 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` 与字段区间

<!-- section-id: syntax-13-020 -->

<!-- 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 两列

### 输出列表里的同名列会被覆盖

<!-- section-id: syntax-13-032 -->

<!-- tags: 同名列丢失, join 后值不对, 列被覆盖, 两个表同字段, 必须起别名 -->

`join` 两侧有同名字段又不改名时，靠后的列覆盖靠前的：

代码块身份：可直接照写示例

```tsl
t1 := array(("A": 1, "B": 2, "C": 7),
            ("A": 10, "B": 3, "C": 12),
            ("A": 4, "B": 20, "C": 34));
t2 := array(("A": 10, "B": 10, "D": 3),
            ("A": 4, "B": 10, "D": 3));

dup_rows := select [1].*, [2].["B"], [2].["D"]
            from t1 join t2 on [1].["A"] = [2].["A"] end;
writeLn("dup B:", dup_rows[0]["B"]);

alias_rows := select [1].*, [2].["B"] as "B2", [2].["D"]
              from t1 join t2 on [1].["A"] = [2].["A"] end;
writeLn("kept B:", alias_rows[0]["B"]);
writeLn("added B2:", alias_rows[0]["B2"]);
```

结果说明：

- `dup B` 输出 `10`：`[1].*` 已带来左表的 `B`（值 `3`），后写的 `[2].["B"]` 把它覆盖成 `10`
- 结果集里只有一列 `B`，左表原值无法再取回，且不会有任何报错
- `kept B` 输出 `3`、`added B2` 输出 `10`：给右侧列写 `as "B2"` 后两列并存
- 所以 `[表序号].*` 与显式列混写时，要先确认展开后有没有撞名

代码块身份：输出片段

```text
dup B:10
kept B:3
added B2:10
```

### `drange` 取行区间

<!-- section-id: syntax-13-021 -->

<!-- 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` 位选项

<!-- section-id: syntax-13-022 -->

<!-- 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`

<!-- section-id: syntax-13-023 -->

<!-- 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`

`*` 作为聚集输入时必须和左括号留空格，避免 `(*` 被词法层识别为块注释：

代码块身份：可直接照写示例

```tsl
a := array(("x": 1, "y": 10), ("x": 3, "y": 20));
row_count := vselect countof( * ) from a end;
column_avg := select avgof( * ) from a end;
writeLn(row_count);
writeLn(column_avg[0]["Expr1"]);
writeLn(column_avg[0]["Expr2"]);
```

代码块身份：输出片段

```text
2
2.0
15.0
```

说明：`avgof( * )` 对每列分别聚集；这里两列平均值依次为 `2`、`15`。

### `group by ... having`

<!-- section-id: syntax-13-024 -->

<!-- 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)` 或 `countof()`；确需星号时写成 `countof( * )`

代码块身份：反例 / 不可照写

```text
having_rows := select ["cls"] from a group by ["cls"] having countof(*) > 1 end;
```

无空格的 `countof(*)` 会把 `(*` 词法组合解释成块注释开头，随后报 `CountOf ( not found`。改用 `countof( * )`、`countof()`、`countof([字段])` 或 `countof(1)`。

### `thisOrder` 与多列 `order by`

<!-- section-id: syntax-13-025 -->

<!-- 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` 引用上级结果集

<!-- section-id: syntax-13-026 -->

<!-- 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]` 取字段类型

<!-- section-id: syntax-13-027 -->

<!-- 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` 自定义聚集扩展

<!-- section-id: syntax-13-028 -->

<!-- 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`

## 本页不生成的范围

<!-- section-id: syntax-13-029 -->

- `TSQLInsert` / `TSQLSetValue` / `TSQLBatchInsert` / `TSQLEdit` / `TSQLPost` / `TSQLFinal` 对象被 TS-SQL 查询的回调机制
- 面向 SQL 表、业务表或时间序列的数据查询与写回（`marketTable` / `infoTable` / `tradeTable` / `sqlTable` / `hugeSqlTable` 等数据源）

这些内容不作为本页可生成事实；业务数据源先交给 `tsl-api-reference` skill、pyTSL 模块事实所有者或项目实际接口。

## 默认生成模板

<!-- section-id: syntax-13-030 -->

TS-SQL 的最短默认骨架如下：

代码块身份：可直接照写示例

```tsl
source_rows := array((1, 10), (2, 20));
query_result := select * from source_rows end;
```

## 禁止项

<!-- section-id: syntax-13-031 -->

- 不要把数据库 SQL 方言直接迁移成 TS-SQL 代码。
- 不要把 `select` 当成普通函数调用，忘了以 `end` 收尾。
- 在二维结果集里直接写 `A` 而不是 `["A"]`。
- 多表联接时继续写成 `["ID"]`，没有加表序号。
- 处理一维数组时直接把 `[0]` 当成稳定列访问。
- 把 `thisGroup` 当成普通字段或普通变量。
- 把排序后的 `thisRowIndex` 误当成排序序号。
- 让 `[表序号].*` 和两侧同名的显式列同时出现在选择列表里，却不给其中一个写 `as` 别名。
- 在 `left join` 时省略 `on` 子句或不用 `[表序号].["字段"]` 形式。
- 在 `insert` 时漏掉 `insertfields` 或字段数与值数不匹配。
- 期望 `update`/`delete` 返回新数组；它们直接修改原数组。
- 写无空格的 `countof(*)`，使 `(*` 与块注释起始符冲突；改用 `countof( * )`、`countof()`、`countof([字段])` 或 `countof(1)`。

代码块身份：反例 / 不可照写

```text
n := vselect countof(*) from source_rows end;
```

`countof(*)` 会报 `CountOf ( not found`，原因是 `(*` 与块注释起始符冲突。星号写法加空格为 `countof( * )`，或改用 `countof()` / 明确表达式。

代码块身份：反例 / 不可照写

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