playbook/docs/tsl/syntax_book/06_extended_syntax.md

197 KiB
Raw Blame History

06 扩展语法(矩阵/集合/结果集过滤/TS-SQL

本章覆盖集合运算、结果集过滤、矩阵计算与 TS-SQL 相关语法。

目录

集合运算

集合运算简介

在 TSL 语言中,和大多数的语言不同,省略了集合这种独立的数据类型,在各种语言中,集合的实现有些差异,在元素不多的时候,许多采用整数来描述集合,用对位的设置来描述集合中的元素存在与否。事实上,在矩阵计算以及以行为单位的运算中(TS-SQL),传统语言的集合概念不是很吻合具体的应用。

TSL 语言中以数组矩阵作为集合运算的基础而数组也可以理解为包含其元素的结合和数学意义上的集合不同TSL 中的数组允许同时具有不同数据类型的数据,例如同时具有数字和字符串,因此 TSL 的集合运算中的元素也是同时允许多种数据类型的。

集合运算有一个特征,由于在数学意义上集合的元素是不允许重复的,而在数组中的元素是允许重复的,在集合运算中,结果集均为已去除重复项的结果。

IN 存在判断符

用户经常会有这个的需求,即某个数字或者字符串是否出现在数组中,另外,用户也可能会需要知道,数组中的所有的元素是否均存在于另外一个数组中,这样的关系对于集合而言,一个称之为属于,另一个称之为包含,在 TSL 中,使用 IN 操作符同时支持属于和包含的关系。

语法V IN R

含义V 是否存在于 R 结果集中或者 V 是否是 R 的子集V 即可是元素,也可以是集合。

返回值:布尔类型,真假

In 是以最小的元素进行判断。

例如 1 in array(1,2,2)为真1 in array(0,2)为假需要注意的是in 操作符号允许后边的结果集为多维数组,例如 1 in array((1),(2))同样会返回为真。

in 操作符同样支持子集判断,

例如 array(1,2) in array(1,2,3,4)为真,

array(1,3) in array((1,2),(3,4))为真

array(1,2) in array(1)为假。

2025/8 月版本支持 Not IN 这样 not(a in b)可以简单写成 a not in b

基于行的集合运算

传统数学意义上的集合运算是以元素为单位的,由于实际应用中处理结果集经常需要使用到以行为单位的集合运算,所以 TSL 中的集合运算符号大多支持的是以行为单位。

SQLIn 运算符

集合 SQLIN 的判断,和 IN 不同,是以行的模式来判定是否在其中,左边一定是右边的元素才返回真。

语法V SQLIn R

含义V 这一条记录是否存在于表 R 中V 为一条记录。

SQLIn 是以行为单位进行判断。

例如:

1 SQLIn array(1,2)为真

Array(1,2) SQLIn array(1,2,3)为假

Array(1,2) SQLIn array((1,2),(3,4))为真

2025/8 月版本支持 Not sqlin这样 not(a sqlin b)可以简单写成 a not sqlin b

sqlIn 与 In 的差异表现可参考Q数组存在于的判断SQLIN 与 IN 在集合运算中的应用

Union2 并集算符

集合的并集运算是将两个集合中的元素的合集去重后的结果。和 IN 运算不同TSL 在处理并集的时候的时候元素是以行为单位的,而不是以单元格为单位。

因此,集合的并集是行的并集,而不是元素的并集,所以 Union2 是去除重复行的行的合集。

语法A Union2 B

含义:求 A 和 B 的并集

返回值:去掉重复项的并集。

例如:

结果集 A 内容为:

1 2 3 4

2 3 4 5

1 1 1 1

结果集 B 内容为:

1 2 3 4

3 4 5 6

2 2 2 2

A UNION2 B 的结果为:

1 2 3 4

2 3 4 5

1 1 1 1

3 4 5 6

2 2 2 2

Intersect 交集运算算符

集合的交集运算与并集运算类似也是以行为单位的,而不是以单元格为单位。

因此,集合的交集是行的交集,而不是元素的交集,所以 Intersect 是重复行集。

语法A Intersect B

含义:求 A 和 B 的交集

返回值:交集。

例如:

结果集 A 内容为:

1 2 3 4

2 3 4 5

1 1 1 1

结果集 B 内容为:

1 2 3 4

3 4 5 6

2 2 2 2

A Intersect B 的结果为:

1 2 3 4

Minus 差集运算符

集合的差集运算与并集运算类似也是以行为单位的,而不是以单元格为单位。

因此,集合的差集是行的差集,而不是元素的差集,所以 Minus 是减去重复行的集。

语法A Minus B

含义:求 A 和 B 的差集

返回值:差集。

例如:

结果集 A 内容为:

1 2 3 4

2 3 4 5

1 1 1 1

结果集 B 内容为:

1 2 3 4

3 4 5 6

2 2 2 2

A Minus B 的结果为:

2 3 4 5

1 1 1 1

Outersect 对称差集运算符

集合的对称差集运算与并集运算类似也是以行为单位的,而不是以单元格为单位。

因此,集合的对称差集是行的对称差集,而不是元素的对称差集,所以 Outersect 是减去重复行的并集。

Outersect 比较特殊在其他语言很少提供这类功能但是在许多应用中会应用到A Outersect B 相当于(A Union2 B) Minus (A Intersect B),也相当于(A Minus B) Union2 (B Minus A)。

语法A Outersect B

含义:求 A 和 B 的并集去交集

返回值:对称差集。

例如:

结果集 A 内容为:

1 2 3 4

2 3 4 5

1 1 1 1

结果集 B 内容为:

1 2 3 4

3 4 5 6

2 2 2 2

A Outersect B 的结果为:

2 3 4 5

1 1 1 1

3 4 5 6

2 2 2 2

基于元素的集合运算的实现

如果用户的结果集本身是一个一维数组,那么以行为单位的集合运算的结果本身就是以元素为单位的集合运算。

对于二维矩阵而言,怎么做到以元素为单位的集合运算呢?其实很简单,我们只要把二维的矩阵转换成为一维数组再利用集合运算符进行集合运算即可。

将二维数组转换为一维数组的方法最简单的是利用 sselect 语法,关于 SSelect 的使用见 ts-sql 专题。

// 假定我们有一个二维矩阵,我们用 R1:=sselect \* from R
end;就可以把 R 转换为一个一维数组到结果集 R1 了。然后我们的运算可以基于转换后的结果集。

结果集过滤运算

结果集过滤运算简介

过滤集运算是过滤掉二维结果集中某列的内容存在或者不存在于某个一维结果集中的运算,返回的结果可以是过滤后的二维结果集,也可以是二维结果集的下标列表,事实上,结果集过滤完全可以利用 TS-SQL 的 Select 来实现,但是相对于 Select 而言,结果集过滤的效率会更高效也更容易理解。

过滤集运算也可以对二维结果集的整行内容进行过滤,这样的操作非常类似于集合运算,过滤在过滤集内的类似于交集,而反之类似于减集。但是过滤运算和集合运算的最大差异是:集合运算的结果内容不允许有重复项,而过滤运算的结果允许有重复项,也就是说于被过滤集中的重复项目是不会被自动去重的。

结果集过滤关键字定义

TSL 语言种结果集过滤使用两个关键字 FilterIn 和 FilterNotIn 来实现其功能。

结果集过滤范例

在实际应用需求中,用户经常有这样的需求:

假定结果集 R 的下标"Code"列里存贮了代码,我们还有一个代码数组 CodeArr有时候我们希望得到 R 的结果集内所有 Code 列包含在代码数组 CodeArr 中的子集,有时候希望得到 R 的结果集内所有 Code 列不包含在代码数组 CodeArr 中的子集。读起来似乎有点拗口,但是相信大家很清楚我们描述的是什么。

假定 R 的内容为

Code V1 V2

0001 8.9 12

0002 9 10

0003 8 8

0004 1 2

假定 CodeArr 的内容为一维数组 array("0001","0003")

FilterIn 取包含的子集

R1 := FilterIn (R, CodeArr, "Code")

结果集 R1 为

Code V1 V2

0001 8.9 12

0003 8 8

FilterNotIn 取不包含的子集

R1 := FilterNotIn (R, CodeArr, "Code")

结果集 R2 为

Code V1 V2

0002 9 10

0004 1 2

返回结果集过滤的行标

上面我们的应用可以返回子结果集,但是往往用户有时需要的只要结果集的行标就可以了,因为我们可能会继续使用返回的行标,例如利用这个行标处理其他结果集。

FilterIn 以及 FilterNotIn 均支持返回下标的功能。

上述的范例中,如果我们使用 FilterIn(R,CodeArr,"Code",false),返回的结果就只有下标了,结果为 array(0,2),代表下标 0 和下标 2 符合过滤条件,而使用 FilterNotIn(R,CodeArr,"Code",false)的结果则为 array(1,3)。

从上边的范例我们知道 FilterIn/FilterNotIn 的定义为:

FilterIn/FilterNotIn(R,FilterArray,Field,bReturnSubResult)

其中参数对应的含义为:结果集,过滤值数组,列标,是否返回子结果集;最后一个参数可以省略,缺省为真。

不是题外的题外话,过滤后取子结果集:

TSL 的子矩阵功能可以直接利用 FilterIn/FilterNotIn 返回的行标数组来获取子矩阵。

R[array(1,3)]的结果就是 R 的行标为 1 和 3 的子结果集,假使用户只需要取 V1 列,那么用户使用 R[array(1,3),"V1"]可以返回 V1 列的结果集(类型为一维的数组)。

在实际使用中,用户可能需要的是,如果用户要返回的是二维的多列结果集,例如,用户希望返回符合条件的某些特定列,我们怎么做呢?

这很像是一个 SELECT 的操作,我们假定 R 的列还有 V3V4V5...列,我们希望取出符合结果的 Code,V1,V2 列:

SubIndex := FilterIn(R, CodeArr, "Code", false);
SubResult := R[SubIndex, array("Code", "V1", "V2")];

对一维数组或者二维数组的行进行结果集过滤的方法

上一个内容是针对二维数组的列来过滤的,如果我们需要过滤整行呢?或者我们的需要过滤的结果集没有列(一维数组),我们如何进行过滤呢?

带着这个问题,我们需要先回顾下我们之前所学到的知识,事实上,我们这个问题是否非常类似于集合运算呢?以行为单位只要过滤子集中出现的内容,是否就是集合运算的交集呢?以行为单位去除掉过滤子集中出现的内容,是否就是集合运算的减集呢?

是的,在许多情况下,这种需求集合运算更为直观和合理,但是并不完全。因为集合运算的结果集表示

例如一维数组里的内容在另一个一维数组里边的和不在其中的,这类需求其实可以转化成为一个集合运算,在其中的则为交集,不在其中的为减集,但需要注意的是集合运算不可重复,而过滤运算则允许重复。

处理二维数组的行过滤以及处理一维数组,其关键则是将 Field 参数设置为 NIL。

例如array(1,2,3,4,5,5,6,7),过滤留下不在 array(1,2,3,4)其中的结果

FilterNotIn(array(1,2,3,4,5,5,6,7),array(1,2,3,4),nil),其结果为 array(5,5,6,7)

再如,结果集 R有字段 A B C

A B C

1 2 3

2 3 4

1 2 3

4 5 6

4 5 6

结果集 R1 有字段 A B C:

A B C

1 2 3

2 3 4

我们要过滤掉保留 R 中存在于 R1 的:

FilterIn(R,R1,nil),其结果为:

A B C

1 2 3

2 3 4

1 2 3

对二维数组指定字段列表进行结果集过滤的方法

对于一个二维的结构,如果我们希望过滤的不是一整行,也不是单一列,而是某几列,我们可以将 Field 参数设置为包含字段列表的数组。

例如:

结果集 R有字段 A B C

A B C

1 2 3

2 3 4

1 2 5

4 5 6

结果集 R1 有字段 A B:

A B

1 2

2 3

我们要过滤掉保留 R 中字段 A,B 存在于 R1 的:

FilterIn(R,R1,array("A","B")),其结果为:

A B C

1 2 3

2 3 4

1 2 5

矩阵计算

矩阵计算简介

本节点主要介绍矩阵计算,包括以下内容:

所讲述的矩阵计算是广义的矩阵计算,包括狭义的矩阵计算方法,如:矩阵乘法、矩阵求逆,矩阵除法,矩阵左除,矩阵乘方等等操作,除此以外,矩阵转置、矩阵右并、矩阵下并、矩阵初始化、矩阵循环、矩阵循环赋值、矩阵查找、矩阵大小、子矩阵操作、以及有关数组的基础运算我们也将其作为广义的矩阵计算而纳入本章节,但大多数矩阵相关的函数则不会出现在这个章节,例如矩阵分解,特征根等,那些可以在 TSL 的函数大全中找到相应的内容。

矩阵初始化

TSL 的矩阵支持多维矩阵。

矩阵初始化系统内置支持如下:

Zeros 初始化 0 矩阵,

ones 初始化 1 矩阵,

eye 初始化单位矩阵,

rand 初始化随机矩阵,

Nils 初始化空矩阵,->初始化一个序列。

除了 eye 以外,其他的几个初始化在带一个参数的时候为初始化一个二维数组,带两个参数则初始化一个二维数组(矩阵)。带 N 个参数则为 N 维矩阵(rand 例外,如有第三个参数其恒定为随机产生的方法)。

例如:

Zeros(10)返回的是一个 10 个元素的 0 数组。

Zeros(10,10)返回的是一个 10*10 的全零二维数组。

Eye(10)和 Eye(10,10)的结果一样,都是返回一个 10*10 的单位矩阵。

初始化也可以初始化字符串下标的数组(数据表类型)

例如:

Zeros(10,array("F1","F2"))返回的是一个 10 行两列,列名为 F1 和 F2 的全 0 数组。

而 Zeros(array("L1","L2"),array("F1","F2"))则返回一个以 L1 和 L2 名的下标F1,F2 为列名的全零矩阵。

具体函数定义参考矩阵初始化运算相关的保留字和算符

矩阵初始化运算相关的保留字和算符

Zeros

范例

t1 := zeros(10); // 返回一个数值全是0的10行的一维数组
t2 := zeros(5, 3); // 返回一个数值全是0的5行3列的自然下标二维数组
t3 := zeros(5, array("A", "B")); // 返回一个数值全是0的5行2列的列名分别为"A","B"的二维数组
t4 := zeros(2, 5, 3); // 返回一个数值全是0的2*5*3的自然下标三维数组

Ones

范例

t1 := ones(10); // 返回一个数值全是1的10行的一维数组
t2 := ones(5, 3); // 返回一个数值全是1的5行3列的自然下标二维数组
t3 := ones(5, array("A", "B")); // 返回一个数值全是1的5行2列的列名分别为"A","B"的二维数组
t4 := ones(2, 5, 3); // 返回一个数值全是1的2*5*3的自然下标三维数组

Rand

范例

范例 01

// 生成0-1,10*10的随机矩阵
SetSysParam(PN_Precision(), 4);
return rand(10, 10);

范例 02

// 生成3*3,矩阵内的元素服从标准正态分布的矩阵
SetSysParam(PN_Precision(), 4);
return rand(3, 3, array("normal", 0, 1));

范例 03

// 生成3*3*3,矩阵内的元素服从标准正态分布的矩阵
SetSysParam(PN_Precision(), 4);
return rand(3, 3, array("normal", 0, 1), 3);

Nils

范例

t1 := nils(10); // 返回一个数值全是nil的10行的一维数组
t2 := nils(5, 3); // 返回一个数值全是nil的5行3列的自然下标二维数组
t3 := nils(5, array("A", "B")); // 返回一个数值全是nil的5行2列的列名分别为"A","B"的二维数组
t4 := nils(2, 5, 3); // 返回一个数值全是nil的2*5*3的自然下标三维数组

Eye

范例

t1 := eye(10); // 返回10*10的单位矩阵
t2 := eye(5, 3); // 返回5*3的单位矩阵
t3 := eye(5, array("A", "B")); // 返回5行2列的列名分别为"A","B"的单位矩阵

->数列数组初始化算符

定义一BegValue->EndValue

说明:生成一个从 BegValue 值开始到不大于 EndValue 的以 1 递增的数列

举例:

1->3 的结果为 array(1,2,3)

1.5->5 的结果为 array(1.5,2.5,3.5,4.5)

定义二array(BegValue,StepValue)->EndValue

说明:生成一个从 BegValue 值开始到不大于 EndValue 的以 StepValue 递增的数列。

举例:

a := array(2.5, 0.5);
B := a -  > 5;

B 的结果为 array(2.5,3,3.5,4,4.5,5)

定义三array(BegValue,StepValue,IndexArray)->EndValue

说明:生成一个从 BegValue 值开始到不大于 EndValue 的以 StepValue 递增的以 IndexArray 为下标数列。

举例:

a := array(0, 1, array("A", "B", "C", "D", "E", "F"));
B := a -  > 5;

B 的结果为 array("A":0,"B":1,"C":2,"D":3,"E":4,"F":5)

获得矩阵的大小

利用 MSize,MRows,MCols 可以很方便地获得矩阵的大小以及行列等相关信息。

例如:

// A为一个三行两列的随机数组
A := Rand(3, array("F1", "F2"));
B := MSize(A, 0); // B的内容为array(3,2)表示三行两列
C := MSize(A, 1); // C的内容为array((0,1,2),("F1","F2"))

也就是说 MSize 可以获得行列数,也可以获得行列下标的具体值。

如果只要获得行数或者列数或者行下标、列下标

可以利用 mRows 和 mCols

例如:

A := Rand(3, array("F1", "F2"));
B := MRows(A, 0); // B的内容为3
C := MCols(A, 1); // C的内容为2
D := MRows(A, 1); // D的内容为array(0,1,2)
E := MCols(A, 1); // E的内容为Array("F1","F2")

与 MSize 类似的MRows 和 MCols 既可以返回行数列数,也可以返回行下标或者列下标

相关计算可以参照矩阵大小运算相关的保留字

矩阵大小运算相关的保留字

MSize

范例

返回数组 t 的所有行列长度

t := array(("A":1, "B":2), ("A":11, "B":22), ("A":21, "B":32));
return msize(t);

返回结果array(3,2)

返回数组 t 的所有行列下标

t := array(("A":1, "B":2), ("A":11, "B":22), ("A":21, "B":32));
return msize(t, 1);

返回结果array((0,1,2),("A","B"))

MRows

范例

返回数组 t 的行数

t := array(("A":1, "B":2), ("A":11, "B":22), ("A":21, "B":32));
return mrows(t);

返回结果3

返回数组 t 的所有行列下标

t := array(("A":1, "B":2), ("A":11, "B":22), ("A":21, "B":32));
return mrows(t, 1);

返回结果array(0,1,2)

MCols

范例

返回数组 t 的列数

t := array(("A":1, "B":2), ("A":11, "B":22), ("A":21, "B":32));
return mcols(t);

返回结果2

返回数组 t 的所有列下标

t := array(("A":1, "B":2), ("A":11, "B":22));
return mcols(t, 1);

返回结果array("A","B")

矩阵查找和遍历的保留字和算符

矩阵的查找和遍历是对应于矩阵的每一个单元格的,这样可以将以往需要使用 FOR 循环语句的代码极大地便捷化。

在其他的矩阵计算语言中,同样有类似的功能,但是 TSL 的矩阵查找和遍历的能力更强大,因为其他的矩阵计算语言中,仅仅只能处理单元格的值,而 TSL 还可以利用 MCol,MRow,MIndex,MCell 关键字实现复杂的计算功能。这种功能是 TSL 所独有的特色。

MFind

范例

范例 01一个参数返回二维数组的项为真的对应的下标

t := array((0, 2, 3), (2, 3, nil));
return Mfind(t);

返回:

范例 02一个参数返回一维数组的项为真的对应的下标

t := array(0, 1, 2, 3, nil, 4);
return Mfind(t);

范例 03多个参数指定条件的值的下标、带值输出替换等结果的展示

A := array((1, 2, 3, 4, 5), (2, 3, 4, 5));
B := Mfind(A, Mcell > 3);
C := Mfind(A, Mcell > 3, true);
D := Mfind(A, Mcell > 3, false, 3);

B 与 D 的结果为: C 的结果为(列名为 2 的为该项的值)

// 执行完 D:=Mfind(A,Mcell>3,false,3);后,数组 A 的结果为:

PSC 和 B 的结果的差异就是 C 多出一列为符合条件的值,而 0 列为行标1 列为列标。

D 的结果与 B 一致,而数组 A 的结果中>3 的值被替换成 3

MFind 单参数模式
Mfind(Matrix:array):array;

Mfind 在一个参数的时候,则为查找为真的下标,如果参数为一个一维数组,则其返回的结果集也为一个一维数组

例如:找出数组中指定列符合指定条件的子集

// 假定有随机数组 b 如 b:=rand(1000,10);
select 的代码 c := select \ * from b where [0] > 0.5 end;可以获得列 0 大于 0.5 的子集
// 可以很便捷地使用矩阵模式代码替代c:=b[mFind(b[:,0].>0.5)];

b := rand(1000, 10);
c := b[mFind(b[:, 0] . > 0.5)];
return c;

c 的结果为第 0 列数值大于 0.5 的那些行集合。

其中,理解运行步骤:

第一步b[:,0].>0.5 得到一个一维的真假数组,即值大于 0.5 的行为真,否则为假。

第二步mFind(b[:,0].>0.5)获得元素值为真的行标集合,

第三步b[mFind(b[:,0].>0.5)]根据指定行标集合获取子矩阵,即获得了符合条件的 b 的子集。

MFind 矩阵查找

MFind 可以查找矩阵中的符合条件的行列以及值。

例如,我们在一个随机矩阵中寻找值大于 0.9 的值所处在的位置:

A := Rand(10, 10);
B := MFind(A, MCell > 0.9);

B 的返回结果为一个二维数组,第 0 列为符合条件的行号,第 1 列为符合条件的列号。

如果我们除了要返回行列号,还需要返回符合条件的结果,则用 MFind(A,MCell>0.9,True)即可,这样第 2 列为符合条件的值。

在查找的时候,第二个参数为一个条件表达式,我们可以利用 MCell 获得值,也可以利用 MRow 获得行下标MCol 获得列下标,所以我们也可以做出复杂的查询。

复杂应用案例:

例如:

A 为矩阵,内容为股票的最近 N 日的日期,我们假定我们的范例中的股票均已上市 N 日以上。

N := 100; // 一百个交易日
A := Zeros(N, array("SZ000001", "SZ000002")); // 得到一个N行两列的0矩阵列名为股票代码
Cols := MCols(A, 1);
for j := 0 to length(Cols) - 1 do//初始化时间数组
begin
    SetSysParam(pn_stock(), Cols[j]);
    SetSysParam(pn_date(), now());
    A[:, j:j] := `NDay3(N, sp_time()); // 给第J列赋值为时间
end;
B := MFind(A, Spec(SpecDate(Close() > Ref(Close(), 1) * 1.02, MCell), MCol), 1); // MCell为时间MCol为股票代码
return B;

返回的 B 的结果为股票涨幅大于 2%的股票和时间。

MFind 矩阵替换

Mfind 处理可以用来查找,还可以用 Mfind 替换掉特殊值,例如:

A := Rand(10);

Mfind(A,Mcell>0.9,NIL,0.9);可以将大于 0.9 的值替换为 0.9

Mfind 用于将逻辑数组变换为下标数组

在 TSL 的子矩阵下标值允许使用下标数组而某些矩阵语言则还允许使用逻辑数组即只取出为真的下标MFIND 可以将逻辑数组转换为下标数组。

A := Rand(100, 3);
B := A[:, 0] . > 0.5; // 返回0列为真的逻辑数组
C := Mfind(B); // 返回为真的下标数组
D := A[C]; // 取出A的子矩阵

等价写法:D := A[Mfind(A[:, 0] .> 0.5)],等同于 D := select * from A where [0] > 0.5 end;

MRow

说明:行下标。遍历矩阵时,MRow 返回当前行下标。

MCol

说明:列下标。遍历矩阵时,MCol 返回当前列下标。

MCell

说明:单元格值。遍历矩阵时,MCell 返回当前单元格值,也可对 MCell 赋值以修改。

MIndexcount

说明:多维矩阵遍历时,MIndexCount 表示当前元素所在的总维度。 范例:

打印当前遍历时的维度数。

a := Rand(2, 1, nil, 2); // 2*1*2 的三维数组
r := array();
a:.
begin
    echo mIndexCount; // 三维数组,当前维度为 3
end;

MIndex

说明:多维矩阵遍历时,MIndex(N) 表示第 N 维的下标。 范例:

a := Rand(3, 4, nil, 5); // 三维数组
a[:, :, :]::
begin
    echo mIndex(0), "->", mIndex(1), "->", mIndex(2), "->", mCell, "\r\n";
end;
return 1;

打印各维度下标结果:

::

矩阵遍历算符,支持子矩阵循环。

B := array();
A := array((1, 2, 3), (2, 3, 4));
A::begin
    B[mRow][mCol] := mCell;
end;

对 A 遍历时给 B 赋值,结果 B 与 A 相同,等价于 B := A

::=

矩阵遍历赋值算符,支持子矩阵循环赋值。

b := array(-1, 2, 3, -5);
b : := abs(mcell);

B 的结果为 array(1,2,3,5)

::=矩阵遍历设置值

绝大多数 TSL 语言的基础函数已经支持矩阵计算。

如果某个函数不支持矩阵,可用 ::= 将其逐元素应用到矩阵或数组。

示例:

a := array(-1, "AAA", 1);
a := ifReal(a);
a : := ifReal(mcell);

mcell 表示当前遍历单元的值。 而 MCell 可以获得当前遍历的项的值,所以 ifReal(MCell)就是判定当前遍历项是否为浮点数。

同样,:.=也有该功能。

::=的复杂应用-取数据

::= 也可用于按行列索引生成矩阵结果。例如,数组列下标为时间字符串,行下标为股票代码:

stks := array("SZ000001", "SZ000002");
times := array("2008-12-31", "2007-12-31", "2006-12-31");

我们要一次性获得这些股票在这些时间的收盘价,怎么做呢?

a := zeros(stks, times); // 生成列下标为时间字符串、行下标为股票的矩阵
a : := spec(specDate(close(), strToDate(mcol)), mrow);

检查下结果,内容是否是你想象的结果呢?

这里边,使用了 MCol 获得当前的列下标MRow 获得当前的行下标,我们再利用了 Spec 和 SpecDate 函数,计算出了指定股票指定时间的收盘价。最后生成了一个收盘价矩阵。

同样,上面的 ::= 改用 :.= 也可以。

矩阵的多维遍历

对矩阵使用 ::/::= 只能做二维遍历,结合子矩阵算符可进行多维遍历。 遍历的时候用 mIndex(N)可以访问第 N 维下标mIndexCount 可以访问维度数。

a := Rand(10, 10, nil, 10);
a[:, :, :] : := mIndex(0) * mIndex(1) * mIndex(2);

可以给一个三维矩阵赋为一个乘法矩阵。

利用矩阵循环赋值使不支持矩阵的函数支持矩阵

绝大多数 TSL 语言的基础函数已经支持矩阵计算。

如果某个函数不支持矩阵,可使用 ::= 逐元素应用函数;:.= 会遍历到最深维度。

示例:

a := array(-1, "AAA", 1);
a: := ifReal(mCell);

例如,用户自定义一个分类函数并对集合逐元素应用:

function VFlag(v)
begin
    if v > 50 then
        return ">50";
    if v > 0 then
        return "(0,50]";
    if v == 0 then
        return "零";
    if v > -50 then
        return "[-50,0)";
    return "<-50";
end;

t := array(-999, 100, -3, 30, 0, -5);
t: := VFlag(mCell);
return t;

MfindSparse

范例

范例 01对比 MFindSparseMFind

t := array(1, 2, 0, ('A': array(2), 'B': 2), ('A': array(1, 2), 'B': 12));
return MFindSparse(t);

t := array(1, 2, 0, ('A': array(2), 'B': 2), ('A': array(1, 2), 'B': 12));
return MFind(t);

差异要点:

  • MFindSparse 的结果统一为二维数组,可深入到更深维度。
  • MFind 最多判断到二维,返回的下标维度更浅。

范例 02指定条件

t := array(1, 2, 0, ('A': array(2), 'B': 2), ('A': array(1, 2), 'B': 12));
return MFindSparse(t, mCell = 2);

t := array(1, 2, 0, ('A': array(2), 'B': 2), ('A': array(1, 2), 'B': 12));
return MFind(t, mCell = 2);

更多范例可参考 MFind

:.

矩阵深度遍历算符,不假定维度数,而是对数组的节点进行深度遍历。是对::算符的功能扩展,使用方式和::一致,对于 fmarray 而言行为一致,但对于数组 Array::最多只能遍历两维,而:.是遍历到最深维度。

一般用法同::,比如对二维数组的遍历:

B := array();
A := array((1, 2, 3), (2, 3, 4));
A:.begin
    B[mRow][mCol] := mCell;
end;

说明:对 A 遍历并写入 B,最终 BA 内容相同,相当于 B := A。 再比如,多维数组与不完全矩阵的遍历差异:

t := array(10, 12, ("A": (30), "B": 22), ("A": (31, 32), "B": 23));
t2 := array();
k := 0;
t:.begin
    mc := mIndexCount; // 总维度
    k := k + 1;
    t2[k] := array("v": mCell, "总维度": mc);
end;
return t2;

t := array(10, 12, ("A": (30), "B": 22), ("A": (31, 32), "B": 23));
t2 := array();
k := 0;
t::begin
    mc := mIndexCount; // 总维度
    k := k + 1;
    t2[k] := array("v": mCell, "总维度": mc);
end;
return t2;

:. 遍历到最深维度,:: 最多遍历到二维。

:.=

矩阵深度遍历赋值算符,不假定维度数,对数组节点进行深度遍历并赋值。是对 ::= 的扩展:::= 最多遍历到二维,:.= 遍历到最深维度。

示例:

b := array(-1, 2, 3, -5);
b:.= abs(mCell);

结果:array(1, 2, 3, 5)

对比 :.=::=

t := array(("A": (30, "tsl"), "B": "Tinysoft"), ("A": (31, 32), "B": 23));
return t:.= ifnumber(mCell);

t := array(("A": (30, "tsl"), "B": "Tinysoft"), ("A": (31, 32), "B": 23));
return t: := ifnumber(mCell);

:.= 遍历到最深维度,::= 最多到二维。

矩阵运算

基础算符对矩阵计算的支持

TSL 的基础算符+,-,*,/,,%,mod,div,^,~,.=,.>,.<,.<>,.>=,.<=,.!,.&,.|,.^,.||,.&&,.!!,like,++,--都支持矩阵(数组)的计算,可以支持简单类型和矩阵进行计算,也支持矩阵和矩阵一起进行计算。

用二元运算符支持矩阵对矩阵,矩阵对常量,常量对矩阵,加法为例:

array(1,2,3)+array(2,3,4)为 array(3,5,7)

1+array(1,2)和 array(1,2)+1 为 array(2,3)

和其他的支持矩阵计算的语言不同TSL 的基础的算符对矩阵计算依旧是原来基础算符的含义,例如矩阵和矩阵用*来计算,是每个相应单元格之间相乘。

基础算符的运算既支持二维数组(矩阵),也支持一维数组或者其他维度的数组。

默认情况下,也支持对非完全矩阵的计算,即当对应位置不存在时或为 nil 时,当 0 处理。

例如Array(“A”:100,”B”:200,”C”:300)+array(“A”:1,”C”:3,”D”:4)

可以计算的结果为 Array(“A”:101,”B”:200,”C”:303,”D”:4)。

若希望该种计算时进行报错或提示,可通过系统参数设置 FAQQCalcCTRLWord 系统参数设置:控制 nil 参与计算以及浮点除 0 的警告或者出错

例子 1矩阵和简单类型的计算。

A := array(1, 2, 3);
A := A + 1; // 该写法也可以写成 A+=1;
// 结果A是array(2,3,4)也就是每个单元格都加1。
A := Ones(10, 10); // 初始化一个全1的10*10矩阵。
A := A * 3;
// 结果A是一个10*10的全3矩阵。

例子 2矩阵和矩阵的计算

A := array(1, 2, 3);
B := array(2, 3, 4);
A := A * B; // 也可以写成 A *= B;
// 结果 A 是 array(2, 6, 12)
A := Eye(10, 10);
B := Ones(10, 10);
A := A + B;
// 结果 A 是对角线为全 2、其他为全 1 的矩阵。

例子 3非完全矩阵的计算-矩阵与矩阵的大小不一致时的计算

A := array("A": 1, "B": 1, "C": 1);
B := array("A": 2, "C": 2);
return A + B;
// 结果 A 为array("A": 3, "B": 1, "C": 3)

目前,矩阵和矩阵以及矩阵和简单类型的计算支持所有常用的基础算符有+-,*,/,,mod,div,^,~等简单算符。

支持不同维度的矩阵计算规则:

  • 标量与矩阵:对每个单元格逐元素运算。
  • 矩阵与矩阵:对应单元格逐元素运算。

支持的运算符:+, -, *, /, \, mod, div, ^, ~, .= , .>, .<, .<>, .>=, .<=, .&, .|, .!, .^, shl, rol, shr, ror, .&&, .||, .!!, like, ++, --

范例:

范例 1

// 实数与数组相加
num := 5;
arr := array(1, 2, 3, 4, 5);
return num + arr;
// 返回array(6, 7, 8, 9, 10)

范例 2

// 数组与数组相加
arr1 := array(1, 2, 3, 4, 5);
arr2 := array(6, 7, 8, 9, 10);
return arr1 + arr2;
// 返回array(7, 9, 11, 13, 15)

范例 3

// 一维数组与二维数组相加
arr1 := array(1, 2, 3, 4, 5);
arr2 := Ones(5, array("a", "b", "c"));
return arr1 + arr2;

范例 4

arr := array(1, 2, 3, 4, 5);
return ++arr;

返回:array(2, 3, 4, 5, 6)

矩阵转置算符

` 表示矩阵转置(行列互换)。

a := array((1, 2, 3), (2, 3, 4));
a := `a;

A 的结果为 array((1, 2), (2, 3), (3, 4))

转置对一维数组进行转置得到的结果为一个列向量。

a := array(1, 2, 3);
a := `a;

A 的结果为 array((1), (2), (3))

Union 矩阵行相加算符

Union 可以把两个矩阵的行连接起来,例如:

a := array((1, 2, 3), (2, 3, 4));
b := array((11, 22, 33), (22, 33, 44));
a := a union b;

A 的结果为 array((1, 2, 3), (2, 3, 4), (11, 22, 33), (22, 33, 44))。 以上也可以写成 A&=B;记住这个是个特例TSL 采用了&=而不是 union=,这更直观。

事实上union 对一维数组同样有效,例如 array(1,2) union array(3,4)的结果为 array(1,2,3,4)

如果需要将一个一维数组加到一个二维数组的最后一行,怎么做呢?

我们把一个一维数组转置两次,就可以获得一个二维的横向量。

例如

a := array((1, 2, 3), (2, 3, 4));
b := array(3, 4, 5);
a := a union ``b; // ``两次转置

|矩阵列相加算符

|可以把两个矩阵的列连接起来,例如:

A := array((1, 2, 3), (2, 3, 4));
B := array((11, 22, 33), (22, 33, 44));
A := A | B;

A 的结果为 array((1, 2, 3, 11, 22, 33), (2, 3, 4, 22, 33, 44));

以上也可以写成 A|=B。

事实上,|对一维数组同样有效,例如 array(1,2) | array(3,4)的结果为 array((1,3),(2,4))

也支持直接将一个一维数组直接用|加到一个二维数组上。

:|非完全矩阵列相加算符

:|和|类似,都可以把两个矩阵的列连接起来。但两者又有差异,|的处理是按照每行进行进行 union在处理非完整矩阵的时候这个时候结果会出现和预计的不一样。

A := array((1, 2, 3), (2, 3));
B := array((11, 22, 33), (22, 33, 44));
A:| = B; // 也可以写成A := A:|B;

A 的结果为 array((1, 2, 3, 11, 22, 33), (2, 3, nil, 22, 33, 44));
// 如果采用 A|=B 结果就会是 array((1,2,3,11,22,33),(2,3,22,33,44));

!求逆与广义逆

矩阵求逆

!可以将矩阵求逆,例如:

A := array((1, 2), (5, 8));
A := !A;

A 的结果为 array((-4,1),(2.5,-0.5))

当矩阵为奇异矩阵或者非方阵的时候,!会自动计算其广义逆。

A := array((1, 2, 3), (3, 5, 6));
A := !A;

A 的结果为

array(

(-1.21052631578947,0.578947368421051),

(-0.789473684210525,0.421052631578947),

(1.26315789473684,-0.473684210526314))

对于某些不完全的奇异矩阵,如果需要用广义逆而非逆计算,需要使用函数 pinv。

:*矩阵乘法

与一些矩阵计算语言不同TSL 语言相当于.,矩阵乘为:*

A := array((1, 2, 3), (2, 3, 4));
B := array((2, 3, 4, 5), (3, 4, 5, 6), (5, 6, 7, 8));
A := A: * B;

A 结果为

array(

(23.00,29.00,35.00,41.00),

(33.00,42.00,51.00,60.00))

// 上述写法也可以为A:\*=B;

:/矩阵除法

与某些矩阵计算语言不同TSL 语言的/相当于./,而矩阵除法采用:/

A:/B 的算法为 A:*!B

当 B 为非方阵的时候,!B 采用广义逆。

:\矩阵左除

A:\B 的算法为!A:*B

当 A 为非方阵的时候,!A 采用广义逆

:^矩阵乘方

TSL 用:^表示矩阵乘方

例如 A:^3 等于 A:*A:*A乘方应该是个方阵。

子矩阵运算

子矩阵-取子矩阵

以下均假设 A 是一个矩阵。

数组取数是使用[]运算符,例如 A[2,3]表示行下标为 2 列下标为 3 的值。

如果要取出第 2 到第 5 行,第 3 到第 6 列的值TSL 是怎么支持的呢?

事实上A[2:5,3:6]就可以描述了。注:序号从 0 开始。

如果需要 2 到第 5 行,如果取出下标为 3 的列的值返回为一维数组,则可以采用 A[2:5,3],如果依旧返回一个矩阵,则采用 A[2:5,3:3]。

:两边的开始和截止序号均可以省略。

例如A[:,3:6]表示第三列到第 6 列的数据,用户也可以用 A[3:,3:6]来描述第 3 到第 6 列的从第 3 行到最后一行的数据。

常用取子矩阵的用法:

取第三行作为子矩阵(二维数组)

A[3:3,:]

取第下标为 3 的行向量作为一维数组

A[3,:]

取第三列作为子矩阵

A[:,3:3]

取下标为 3 的列向量作为一维数组

A[:,3]

取下标为"A"的列向量作为一维数组

A[:,"A"]

子矩阵-一维取子矩阵

对一维数组取子矩阵,可以通过 t[n:m]方式提取出数组 t 的行下标从 n 到 m 的子集。

// 假定一个一维数组 A,值为 a:=array(1,2,3,4,5),我们需要取出其下标从 1 到 3 的值,我们用 A[1:3]即可。
// 如果 A 是一个矩阵a:=array((1,2,3),(2,3,4),(3,4,5),(4,5,6));

我们一样可以用 a[1:3]这样的结果和 a[1:3,:]是一样的,因为对于 TSL 语言而言,二维的数组就是一维的一维数组。

子矩阵-利用下标数组取

有的矩阵语言支持用下标序列数组来取子矩阵TSL 同样支持。

我们先看对一个一位数组怎么取,假定有数组:

A := array(1, 2, 3, 4, 5, 6, 7);

如果我们要取出下标为 2,4,6 的数组项作为子数组,我们可以用

b := a[array(2, 4, 6)];

B 的结果为 array(3, 5, 7)

如果 A 是一个矩阵:

a := array((1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7));

如果要取出行 1、3、4列 0、2 的子矩阵:

b := a[array(1, 3, 4), array(0, 2)];

B 的结果为 array((2, 4), (4, 6), (5, 7))

子矩阵设置-给子矩阵的内容赋值为确切简单值

a := zeros(10, 10);
a[1:8, 1:8] := 1;

A 的结果为周边为 0里边为 1 的矩阵

子矩阵设置-给子矩阵赋值一个同等大小的矩阵

a := zeros(10, 10);
a[1:8, 1:8] := eye(8, 8);

A 的结果为周边为 0里边为一个 8*8 单位矩阵。

子矩阵循环赋值::=

::= 除了支持矩阵循环赋值,还支持对子矩阵逐元素赋值。 例如:

a := zeros(10, 10);
a[1:8, 1:8] : := mrow * mcol;

A 的结果为:

多维矩阵的子矩阵

TSL 支持多维矩阵的子矩阵。

a := rand(8, 8, nil, 8, 8); // 8*8*8*8 的四维矩阵
b := a[:, 0, :, :]; // b 为一个三维矩阵
c := a[:, 3:5, 0, 0]; // c 为一个二维矩阵
a[:, :, 0, 0] := 1; // 给第 3、4 维为 0 的位置设置 1
a[3, :, :, 5] := rand(8, 8); // 给指定子矩阵设置新的二维随机矩阵

子矩阵循环

::除了可以直接对矩阵进行循环,还可以对子矩阵进行赋值修改。

a := array((1, 2, 3), (2, 3, 4), (3, 4, 5));
a[1:2, 1:2] :: begin
    mcell := mcol * mrow;
end;

子矩阵算符对字符串下标的处理规则

对于有字符串下标的矩阵,我们的子矩阵算符:一样是支持的,但是:支持的依旧是序号。

a := array("A": 1, "B": 2, "C": 3, "D": 4);
b := a[1:2];
B的结果是什么呢

答案如下:

因为 B 下标的序号为 1C 小标的序号为 2。

但是如果要用下标数组去取值,则数组内应该为下标的值,而非序列。

b := a[array("B", "C")];

不要使用 b := a[array(1, 2)] 来表示字符串下标。

子矩阵计算进行字符串下标和数字下标的转换

我们可能经常需要利用字符串下标数组进行显示,而使用数字下标数组进行运算,这样经常会需要有互相转换的方法。

例如我们要把一个 array((1,2,3),(2,3,4))的数组 A 转换到名为 B 的字符串下标数组 array(("A":1,"B":2,"C":3),("A":2,"B":3,"C":4))。

我们可以写如下代码:

A := array((1, 2, 3), (2, 3, 4));
B := Nils(2, array("A", "B", "C"));
B[:, :] := A;

反过来一样可以

B := array(("A":1, "B":2, "C":3), ("A":2, "B":3, "C":4));
A := nils(2, 3);
A[:, :] := B;

矩阵的重构与下标的变换

矩阵的重构Reshape 函数可以将一个多维数组拉伸为一维数组,或重构为指定大小的矩阵。

例如:

A := Rand(3, 6);
B := Reshape(A); // B为拉伸的长度为18的一维数组
C := Reshape(A, 2, 9); // 产生2*9的矩阵
D := Reshape(A, 6, array(“A”, “B”, “C”)); // 产生6行3列矩阵
E := Reshape(A, array(“A”, “B”, “C”, “D”, “E”, “F”), array(“F1”, “F2”, “F3”)); // 全下标指定的方式产生矩阵。
F := Reshape(A, 3, 3, 2); // 产生一个3*3*2的三维数组

ReIndex,ReIndexCopy 下标变换:这两模型可以修改数组的指定下标为新下标,区别在于 Reindex 直接修改原数组,而 ReindexCopy 不修改原数组,通过返回产生一个修改后的数组。

如:

A := array("A":0, "B":1, "D":2, "E":3);
ReIndex(A, array("A":"A1", "D":nil));

A 的结果变为: array("A1":0, "B":1, "E":3);

参数 array("A":"A1","D":nil)的含义是:将字符串下标"A"改为"A1",把字符串下标"D"删除。

如果我们要对多维数组进行处理,假定我们有:

A := rand(10, array("A", "B", "D", "E"); // 初始化一个四列的二维矩阵
ReIndex(A, nil, array("A":"A1", "D":nil));

第 2 个参数为 nil 表示对第一维的下标不进行变动,因此我们修改列"A"为"A1",并删除"D"列。

更多维的处理以此类推。

利用 Reindex 也可以做下标的交换

例如:

A := array(1, 2, 3, 4, 5);
reIndex(A, array(0:4, 4:0));

A 的结果为 array(5, 2, 3, 4, 1);

矩阵与函数

支持矩阵的基础函数

大多数 TSL 的基础函数是支持矩阵的例如abs我们可以用 abs(array(-1,-2,3))得到一个 array(1,2,3),我们也可以对任意维度的矩阵使用 abs 函数。

这类简单函数在支持矩阵批量运算时,若存在异常值,可通过函数末尾的参数两个可选参进行控制(即在原始函数定义的基础上增加了两个可选参数),其功能分别为:

Opt1异常值处理指定为 0 表示不做处理(默认情况);为 1 表示计算跳过 nil 值;为 2 表示跳过所有的异常值;

Opt2: 报错填充值,当某单元报错的时候,使用该参数填充。

具体使用可参考下列子节点介绍。

基础函数 NIL 值的处理

若数组中存在 nil 异常值,则可通过在末尾增加第一个可选参数,参数值给 1 对 nil 值进行跳过处理。

以一维数组为例:

A := array(1, -2, nil, 3);
B := Abs(a); // 会返回错误因为其中有NIL值。如果要允许NIL值使得原位置保留NIL,则使用
B := ABS(A, 1); // 末尾增加一个可选参数对nil进行跳过处理这样B的值为Array(1,2,NIL,3)
基础函数错误值处理

如果矩阵的内容里有错误的值,则可通过在末尾增加第一个可选参数,参数值给 2 进行跳过处理。

例如本来应该是数字的,结果出现的是字符串,例如:

A := array(1, -2, "AAA", nil, 3);

这样使用 Abs(A,1)照旧会出错,这个时候可以使用 Abs(A,2)来允许错误值的出现,错误值为其自身。

B := Abs(A, 2);

B 的结果为 Array(1,2,"AAA",nil,3)

基础函数错误值的替代

如果矩阵的内容里有不被许可的值,但又希望替代掉这些错误的值,我们可以再增加一个参数:

A := array(1, -2, "AAA", nil, 3);
B := Abs(A, 0, 0); // 第二个0为替代的值
C := Abs(A, 1, 0);
D := Abs(A, 2, 0);

当第一个附加参数为 0 的,表示 nil 和其他错误都为错误,错误位置均设置为 0那么 B 为 Array(1,2,0,0,3); 当第一个附加参数为 1 的,表示 nil 允许,但是其他错误不被允许,因此 nil 被保留,而"AAA"被设置为 0因此 C 为 Array(1,2,0,nil,3); 由于第一个附加参数为 2 代表了错误也可以接受,所以 D 为 Array(1,2,"AAA",nil,3);

基础函数的附加参数说明

以 Abs 和 RoundTo 为例:

对于标准的 Abs 定义Abs(Data:Double)

我们有扩展的版本为Abs(Data[;ErrDefine:Integer[;ErrReplace]])

其中Data 为数字或者数组(可以多维)

ErrDefine 允许为 0,1,2当为 0 的时候不允许错误值和 NIL 值1 为允许 NIL 值不允许错误值2 为错误值保留为原始值

ErrReplace 当 ErrDefine 为 0,1 的之后以 ErrReplace 替换掉错误位置

使用如Abs(array(1,nil,'AAA',-100),0,'-')返回array(1,'-','-',100)

对于多参数的基础函数ErrDefine 和 ErrReplace 总是可以作为可选参数添加在最后。

以原本为两个参数的 RoundTo 为例:

A := array(0.06, 0.001, nil, "AAA", 0.98);
B := RoundTo(A, -1,
0, 0
);

可以用增加两个参数,第一个表示不处理异常值,第二个表示异常报错时用 0 替代,则 B 结果返回 Array(0.1,0.0,0,0,1.0)

多参数的基础函数矩阵规则

如果一个函数是多参数的,我们遵循的矩阵处理原则为:为每一个参数寻找一个或者一组匹配者

例如(RoundTo 有两个参数:值与保留位数):

RoundTo(array(1.6, 2.9, 3.8), 0); // array(2.0, 3.0, 4.0)
RoundTo(1.3467, array(0, -1, -2, -3)); // array(1.0, 1.3, 1.35, 1.347)
RoundTo(array(1.5, 2.9, 3.8), array(-1, 0, 0)); // array(1.5, 3.0, 4.0)

多参数与变参的矩阵处理规则同理:按位置匹配并逐元素计算。

支持标量和多维矩阵的基础函数

常见函数如 ArccosArcsinTanCotanCosHSinHLnXp1Log10 等均支持矩阵输入;完整列表请参考函数库(数学函数)。

基础统计函数对二维数组的处理

基础统计函数支持对二维数组的处理,并支持选择行列操作,指定移动步数,选择部分字段以及异常处理模式后再做统计。

这些功能分别由五个可选的附加参数进行控制,依次分别为:

RowCol: 为 0 表示对列,为 1 表示对行。缺省时对列统计

MovingN: 移动点数,为 0 或者缺省表示不移动。

Range: 统计的范围,允许为下标或者下标数组。指定行/列或者行/列组进行统计。缺省为 NIL 表示全部。

ErrAndMovingSkip: 错误控制以及移动跳过错误数据的控制。低两位的值为0 为允许 NIL1 为允许错误2 为不允许 NIL 和错误),第三位为 1 则表示移动时跳过错误数据,三个位在一起的取值组合为 0,1,2,3,4,5,6,7。

EmptyResult:空数组的返回值。

基础统计函数统计支持指定行列

统计函数对二维数组进行计算时,默认以列为统计对象进行分别统计。若需要对每行进行统计,则可增加第一个附加参数,并设置为真。

例如:

以 Mean 平均值为例

A := Rand(10, array("A", "B"));
a[:, "A"] := 0 -  > 9;
a[:, "B"] := 2 -  > 11;

B := Mean(A)的结果为:
C := Mean(A, true)的结果为:

也就是说,在基础统计函数后的第一个附加参数为指定统计以行或以列统计,若为真表示行统计,为假或者缺省则为列统计

基础统计函数统计支持移动

默认情况下,对整列或整行进行统计,要需要指定统计移动点数,则可增加第二个附加参数,值为指定的移动点数。

如有数组 A生成如下

A := Rand(10, array("A", "B"));
A[:, "A"] := 0 -  > 9;
A[:, "B"] := 2 -  > 11;

D := Mean(A, false, 3); // 指定步长为 3即统计当前行最近的 3 个值的平均

其结果为

以列的移动 3 个数据点的平均值,第二个附加参数的值为移动点数

基础统计函数指定内容统计

默认情况下,对所有行或列进行统计,要需要指定统计范围,则可增加第三个附加参数,值为统计的列或行下标或下标数组,缺省为 NIL 表示全部。

以 Mean 平均值为例

A := Rand(10, array("A", "B"));
a[:, "A"] := 0 -  > 9;
a[:, "B"] := 2 -  > 11;

如果我们仅仅只需要求出”A”列的平均值

E := mean(a, false, 0, "A");

那么 E 的值为 4.5

如果我们需要”A”列的移动 3 日平均用 Mean(a,false,3,"A")即可

我们知道第三个附加的参数的内容为需要指定的行列(第一个附加参数决定是行还是列)

如果我们要多个列,第三个附加参数可以用数组

例如 Mean(a,false,0,array("A","B"))得到”A”列和”B”列的平均值

基础统计函数的错误数据处理

和 abs 这类函数不同,基础统计函数默认是允许有 nil 值的,例如

A := array(1, 2, 3, nil, 4);
B := array(1, 2, 3, "ABCD", 4);
C := Mean(A);
D := Mean(B);

C 的结果为 2.5,但 Mean(B)会报错。

如果要允许跳过错误的数据,我们可以这么写:

D := Mean(B, false, 0, nil, 1);

如果我们要让存在 NIL 的值也报错,则使用:

C := Mean(A, false, 0, nil, 2);

这个附加的第四个参数为错误数据处理:

其低二位允许为三种值0,1,2其中0表示允许NIL1表示允许跳过错误值2表示不允许NIL和错误值

在做移动平均的时候,存在这样的问题:

假如前边存在有错误值/NIL 值,到底移动的 N 是将其计算在内还是不计算在内呢?

其现实意义为:假定非交易日的值为 NIL在移动平均的时候是计算自然日内的平均还是交易日内的平均呢

我们可以举一个例子:

A := array(1, 2, 3, nil, 4, 5);
B := Mean(A, false, 3);

从 4,5 下标行来看,我们在默认情况是将 NIL 计算在移动的 3 日内的

在下标 4 移动 3 日的内容为4,NIL,3,其均值为 3.5

下标 5 移动 3 日的内容为:5,4,NIL,其均值为 4.5

如果我们希望在移动的时候跳过那些错误的值:

C := Mean(A, false, 3, nil, 4);

这样,下标 4 移动的值为 4,3,2 其均值为 3

下标 5 的移动的值为 5,4,3 其均值为 4

也就是说第 4 个附加参数,当设置为 4 的时候是跳过 NIL 值

基础统计函数第四附加参数说明

第四个附加参数有两个用途:

一个是用于控制是否允许 NIL是否允许错误数据。

另一个则是控制在移动的时候是否跳过错误值。

即主要用于异常值处理的参数,该参数值由 3 位二进制位来进行控制(默认为 0

第一位管理的是是否忽略异常值(不包括 nil),即 1 不报错0 报错。

第二位管理的是 nil 值是否报错,即 1 报错0 不报错。

第三位管的是计算移动长度的时候nil 值及字符串等异常值是否包括在内。即 0 不忽略1 忽略。

如此,衍生出 8 种组合场景,分别对应如下:

二进制 十进制值 功能说明
0b000 0 默认值异常值报错nil 不报错,移动时异常值不忽略
0b001 1 异常值不报错nil 不报错,移动时异常值不忽略
0b010 2 异常值与 nil 报错,移动时异常值不忽略
0b011 3 异常值不报错nil 报错,移动时异常值不忽略
0b100 4 异常值报错nil 不报错,计算移动时忽略异常值
0b101 5 异常值与 nil 不报错,计算移动时忽略异常值
0b110 6 异常值报错nil 报错,计算移动时忽略异常值
0b111 7 异常值不报错nil 报错,计算移动时忽略异常值

注:上述说明中“移动时中提到的异常值”包括 nil 值在内,而其它处异常值中则不包括 nil 值在内。

第五附加参数:空数组的统计值

在统计的时候,对空数组的处理是一件麻烦的事情,例如:

Mean(Array())的结果到底是多少呢?

TSL 默认处理成 0而有的系统则处理成 NaN

到底应该是多少,这并不是一个纯粹的数学问题,而和应用相关。由于使用频度不大,我们将这个空数组的返回值放在相对靠后的附加参数中。

Mean(Array(),false,0,nil,0,NaN)的结果为 NaN

甚至于你还可以这么用:

Mean(Array(),false,0,nil,0,"空数组")来得到”空数组”这个字符串。

支持一维数组和二维矩阵的基础统计函数

常见函数如 LargeSmallPercentileMeanSumMedian 等均支持一维数组与二维矩阵;完整列表见函数库(统计函数)。

FMArray

FMArray 数组的简介

FMArray 是一种紧缩式存贮的高效运算的支持多维度的矩阵式数组。其大小固定、单元类型一致,与 Array 形成互补,比 Array 的运算效率高,但没有 Array 使用那么灵活,需要做高效矩阵计算或算法开发时可以使用。

特性总览

1 支持多维度的矩阵式数组

2 FMArray 属于完全矩阵,不支持稀疏矩阵,

3 每一维度的下标严格从 0 开始,即不支持字符串等其它下标。

4 每一个单元格的数据类型是相同的,而且不允许运行时进行修改。

5 单元格的数据类型目前支持整数、64 位数数(L)、浮点数三种数据类型,其它数据类型暂不支持。

6 存贮是紧缩式的,因此数据读取访问以及构建的效率非常高

7 紧缩式存贮结构的特点,导致像 Array 那样随意对其进行维度以及长度的改变可能会存在性能的严重损失

8 支持基础算符,支持绝大多数基础统计函数,支持 SQL 语法,支持集合运算,支持子矩阵运算,支持 MFIND 操作等

9 支持与 Array 类型进行转换,支持和 Array 进行混合计算,混合计算的效率会受到 Array 的影响

10 无大小约束,受限于平台或者语言设置的内存限额

主要用途

1 节约内存开销

2 极速的计算效率

3 更适合于算法的开发

4 采用 fmarray 尽量将计算在矩阵内解决

用法介绍

FMArray 数组的建构与初始化
常量建构模式

一维数组:

fmarray[1,2,3] 一维整数数组,内容 123

fmarray[1L,2L,3L] 一维长整型整数数组,内容为 64 位的 123

fmarray[1.0,2.0,3.0] 一维浮点数组,内容 1.02.03.0

二维数组:

fmarray1,2,3],[2,3,4 是一个 2 行 3 列的整数数组。

多维数组:

fmarray[1,1],[2,2],[3,3,2,2],[3,3],[4,4] 是一个 232 的三维数组

其它更多维度的数组依照此方式生成fmarray 用[ ]表示维度,而 array 是用( )表示。

注:每个 fmarray 数组中的数据类型必须是一致的,否则会报错。

不支持构建空矩阵FMArray 矩阵必需要有结构信息。

TSL 优化了 FMARRAY 的常量构造,如果存在巨大的 fmarray 常量数组,采用这种模式运行时相比 ARRAY 而言会有数个数量级的提升。

表达式建构模式

表达式建构模式即为在 fmarray[]中传入表达式的方式进行构造 fmarray 数组的方式。

fmarray[1+2,2+3,strtoint("3")] 得到的数组是 fmarray[3,5,3]

FMARRAY 和 ARRAY 类似,也支持单元格采用计算构建。

注:一旦 fmarray 里存在表达式,那么矩阵将会在运行时构建,因而无法获得优化的加持。

由于 TSL 语言目前并未进行常量的运算进行编译时优化,因而强烈建议不要使用无必要的常量运算。未来也许某个版本支持,但也会导致不同的 TSL 语言版本存在性能差异。

Minit

定义一MInit(L1[,…LN],InitValue:[Int|Int64|Double]):FMArray

说明:初始化一个相同值的 N 维矩阵

参数L1…LN 为维度 1 到维度 N 的长度

最后一个参数 InitValue 为初始化矩阵值的数字常量,可以为整型、长整型、浮点数。

返回N 维矩阵

范例:

范例 01

Fm1 := Minit(5, 3); // 初始化一个单元格全为3的长度为5的一维序列
fm2 := Minit(3, 2, 1L); // 初始化一个单元格全为64位的整数1的3*2的矩阵
Fm3 := Minit(2, 3, 4, 3.5); // 为初始化一个单元格全为3.5的2*3*4的三维矩阵

其中return fm2;在客户端可以展示数组如下:

定义二MInit(LArray:Array,InitValue:[Int|Int64|Double]):FMArray

说明:功能参照定义一,与定义一的区别是将维度放到一个 array 数组中,而不是 N 个可选参数所以定义二的方式中MInit 函数的参数只有两个。

参数:

LArray维度长度数组例如 array(2,3)就是初始化一个 2*3 矩阵,维度从 1 开始。

InitValue为初始化矩阵值的数字常量。可以为整型、长整型、浮点数。

返回N 维矩阵

范例:

范例 02

// 将范例01中的三个数组的初始化可以变更如下效果一样
fm1 := Minit(array(5), 3);
fm2 := Minit(array(3, 2), 1L);
a := array(2, 3, 4);
Fm3 := Minit(a, 3.5);
MinitDiag

定义一MInitDiag(L1[,…LN],InitValue:[Int|Int64|Double]):FMArray

说明:初始化一个对角线相同值的 N 维对角矩阵

如果 N 为 1则效果同 Minit创建的是一个值相同的一维数组。

多维对角矩阵即可理解为各维度下标相同的点为对角点。

参数L1…LN 为维度 1 到维度 N 的长度

最后一个参数 InitValue 为对角线的数字常量,可以为整型、长整型、浮点数。

返回N 维矩阵

范例:

范例 01

// 初始化一个对角线为1的3*3的二维矩阵
Fmd := MinitDiag(3, 3, 1);
return Fmd;

定义二MInitDiag(Larray:Array,InitValue:[Int|Int64|Double]):FMArray

说明:功能参照定义一。与定义一的区别是将维度放到一个 array 数组中,而不是 N 个可选参数所以定义二的方式中MInitDiag 函数的参数只有两个。

参数:

LArray维度长度数组例如 array(2,3)就是 2*3 矩阵

InitValue为对角线的数字常量可以为整型、长整型、浮点数。

返回N 维矩阵

范例:

范例 02

// 将范例01中的初始化变更如下效果一样
Fmd := MinitDiag(array(3, 3), 1);
// 初始化一个3*2的对角线为1.0的矩阵
Fmd2 := MinitDiag(array(3, 2), 1.0);
return Fmd2;
MRand

定义一:MRand(L1[,…LN][,randominfo:array]):FMArray

说明:初始化一个 N 维随机数矩阵,随机矩阵是否是实数还是整数矩阵,取决于参数 RandomInfo

参数L1…LN 为维度 1 到维度 N 的长度

RandomInfo最后一个参数矩阵随机类型可省略省略时返回的是 0 到 1 之间的随机数。

设定随机方法以及参数,详情参见 rand。

返回N 维矩阵

范例:

范例 01返回一个 0 到 1 之间的随机序列

return mrand(4);

范例 02返回一个服从标准正态分布的二维矩阵

return mrand(4, 2, array("normal", 0, 1));

定义二MRand(LArray:Array[,randominfo:array]):FMArray

说明:功能参照定义一。与定义一的区别是将维度放到一个 array 数组中,而不是 N 个可选参数所以定义二的方式中MRand 函数的参数只一个固定参数与一个可选参数。

参数:

LArray维度长度数组例如 array(2,3)就是 2*3 矩阵

Randominfo矩阵随机类型可省略省略时返回的是 0 到 1 之间的随机数,详情参见 rand。

范例:

范例 03

// 将范例01与范例02中的初始化变更如下f1与f2效果一样
f1 := mrand(array(4));
f2 := mrand(array(4, 2), array("normal", 0, 1));
f3 := mrand(array(2, 3)); // 初始化一个2*3的随机数组
return f3;
数据类型的判断

在天软语言中datatype(v)可以判断任意数据的数据类型,而 FMArray 数组的数据类型对应的是分类 27。

例如Datatype(mrand(3)) ->类型是 27

也可以通示 ifFMarray(v)判断,若返回为真则为 FMArray 类型。

同时datatype(fMarray,1)还可以判断 FMArray 数组中单元格数据的类型。

例如: Datatype(fmarray[1.0,2.0,3.1],1);->返回的是 fmarray 数组中单元格的数据类型 1浮点数

Datatype(mrand(3),1) ->类型是 1代表浮点

Datatype(mInit(3,0),1) ->类型是 0代表整数

Datatype(mInit(3,0L),1) ->类型是 20代表 64 位整数

ifFMArray

范例

范例一:默认对整个值判断

return ifFMarray(FMArray[1, 2, 3]); // 返回1
return ifFMarray(array(1, 2, 3)); // 返回0

范例二:对第一维度中的每个元素进行判断

return ifFMArray(array(FMArray[1, 2], 1, array(1, 2), nil, FMArray[[1, 2]], "abc"), -1);

返回array(1,0,0,0,1,0)

单元格类型转换

可以用 integer,real,int64 三个函数转换 fmarray 的单元格数据类型到新的矩阵。

例如:将整型矩阵强制转换成实数矩阵

t := fmarray[1, 2, 3];
t1 := real(t);
return t1;

也支持多维矩阵,例如:

return real(fmarray[[-1, 2], [3, 4]]);
FMArray 与 Array 的相互转换
ArrayToFM

范例

范例 01将存在实数与整型的数组转换成整型 fmarray 矩阵

t := array(1, 2, 3.5);
return arraytofm(t, 0); // 此处的第二个参数0为一个整型所以转换结果为一个整型的fmarray

返回:

而若将第二个参数变更为 0.0,即

t := array(1, 2, 3.5);return arraytofm(t, 0.0);

则返回:

同样,若将第二个参数为更为 0L则返回的 fmarray 结果为fmarray[1L,2L,3L]其中00.00L 也可以是其它任意数值,比如 55.55L 等。

MatrixToArray

范例

范例 01将 fmarray 矩阵转换成 Array 数组

f21 := fmarray[[1, 2], [3, 8]];
return MatrixToArray(f21);

返回:源串为array((1,2),(3,8))

矩阵大小

已有的矩阵大小运算的相关函数也支持 fmarray 数据类型。具体函数说明可参考各函数的帮助文档。

如常见的:

Length 返回第一维的长度

Msize 返回所有维度的大小或者下标

Mrows 返回第一维的长度或者全部下标

Mcols 返回第二维的长度或者全部下标

SetLength 重新设定矩阵的行数

注意Msize 在 fmarray 的行为和 array 类型有所不同array 类型的数据 msize 仅仅处理最多两维,而 fmarray 类型是全部维度。

功能展示如下:

f1 := fmarray[1, 2, 3, 8]; // 一维4行
f2 := fmarray[[1.1, 2.0], [3.0, 8.5]]; // 2*2
f3 := fmarray[[[1, 1], [2, 2]], [[1, 2], [3, -3]], [[3, 8], [-8, -10]]]; // 3*2*2
// -Length返回整数第一维度的长度
lenf1 := length(f1); // 返回4
lenf2 := length(f2); // 返回2
lenf3 := length(f3); // 返回3
// -msize(t) 返回一维数组,每个维度的长度
ms1 := msize(f1); // 返回array(4)
ms2 := msize(f2); // 返回array(2,2)
ms3 := msize(f3); // 返回array(3,2,2)
// -msize(t,1) 返回二维数组,每个维度的下标集合,第一行记录第一维,第二行记录第二维,依此类推
ms11 := msize(f1, 1); // 返回array((0,1,2,3))
ms21 := msize(f2, 1); // 返回array((0,1),(0,1))
ms31 := msize(f3, 1); // 返回array((0,1,2),(0,1),(0,1))
// -mrows(t) 功能同length
mr1 := mrows(f1); // 返回4
mr2 := mrows(f2); // 返回2
mr3 := mrows(f3); // 返回3
// -mrows(t,1) 返回一维数组,第一维度的下标集合
mr11 := mrows(f1, 1); // 返回array(0,1,2,3)
mr21 := mrows(f2, 1); // 返回array(0,1)
mr31 := mrows(f3, 1); // 返回array(0,1,2)
// -mcols(t) 返回整数,返回第二维的长度
mc1 := mcols(f1); // 返回0一维数组没有第二维度
mc2 := mcols(f2); // 返回2
mc3 := mcols(f3); // 返回2
// -mcols(t,1) 返回一维数组,第二维度的下标集合
mc11 := mcols(f1, 1); // 返回array(),一维数组没有第二维度
mc21 := mcols(f2, 1); // 返回array(0,1)
mc31 := mcols(f3, 1); // 返回array(0,1)
// SetLength-重置矩阵的行数
f1 := Minit(3, 5, 1); // 3*5值为1的矩阵
SetLength(f1, 10); // 重新设置f1的行数为10
return f1; // 大小变更为10*4扩大的地方用0补充
// 返回10*4矩阵。

除此之外,还有 Getintindexs,getstrindexs,getallindexs 等获取数组下标的模型也支持 FMArray

但由于 FMArray 下标是从 0 开始的顺序下标,不存在自定义或字符串的下标,所以其中 getstrindexs 是没必要使用,而 Getintindexs、getallindexs 功能与 msize 等存在重合,因此,对于 fmarray 而言只需用 msize 来获得全部维度和维度的大小信息。

矩阵查找与遍历
运算符要点说明
Mfind、::、: := 和 mrow, mcol, mcell, mindex, mIndexCount 等矩阵运算符也支持对 fmarray 类型的计算。

其中:

Mfind、::、: := 可以遍历 fmarray 的全部维度
// 在用到 Mfind、::、::=进行遍历时,还可以用以下关键字对矩阵进行操作:

Mrow 可以获得第一维下标

Mcol 获得第二维下标

Mcell 获得遍历的当前单元格的值

Mindex(dim)可以获得指定的维度的下标dim 从 0 开始,即 0 为第一维度。

mIndexCount 可以获得当前维度数

Mcell 赋值或者::=不能改变 fmarray 的类型

由于 FMArray 的各单元格类型是一致的,因此,与普通矩阵在赋值不同类型数据时存在差异。

Mcell 允许在遍历中赋值,但赋值的类型会被强制为 FMARRAY 固定的单元格类型。

如:

A := minit(2, 3, 1); // 初始化一个2*3的值为1的整数数组
A: := 99.99; // 将A矩阵中每个单元格的值都赋值为99.99,等同于 A:: mcell := 99.99;
// 最终A的单元格都是99而不是99.99因为fmarray的类型是整型不会发生变化。
return A;

Mfind 中的替换以及 A[0, 1] := 99.99 等的赋值都是如此。
查找与遍历举例

MFind 可以查找矩阵中的符合条件的位置以及值,还可以替换符合条件的值。

:: 矩阵遍历算符,功能类似于 for 循环,支持语句段。

: := 即矩阵遍历赋值算符,不支持语句段,一般当某函数只支持数值计算,不支持数组计算时,可通过此方式快速实现。

一般而言,优先使用 Mfind效率较高。

####### Mfind 应用举例

// 数据样本
f1 := fmarray[1, 2, 0, 4, 5]; // 一维
f2 := fmarray[[1.1, -2.0], [0.0, 8.5]]; // 2*2
// MFind(t)返回单元格中为真的项的下标,二维数组,一行存放一个值的下标
mf1 := MFind(f1); // 返回array((0),(1),(3),(4))
mf2 := MFind(f2); // 返回array((0,0),(0,1),(1,1))
// MFind(t,exp)返回单元格中符合条件的项的下标
mf3 := MFind(f1, mcell >= 2); // 返回array((1),(3),(4))
mf4 := MFind(f2, mcell >= 2); // array((1,1))
// MFind(t,exp,RetV)返回符合条件的下标和值,二维数组,最后一列是单元值,前面的是下标
mf5 := MFind(f1, mcell >= 2, 1); // array((1,2),(3,4),(4,5))
mf6 := MFind(f2, mcell >= 2, 1); // array((1,1,8.5))
// MFind(t,exp,RetV,v); 替换符合条件的值,并返回,返回结果同MFind(t,exp,RetV)
mf7 := MFind(f1, mcell >= 2, 1, 100); // 将值大于等于2的单元格赋值为100
mf8 := MFind(f2, mcell >= 2, 0, 100.); // 将值大于等于2的单元格赋值为100.
// 此时f1f2的结果分别为
// 多维数组同样也支持,用法一样,此处只挑一种用法进行举例
f3 := fmarray[[[1, 1], [2, 0]], [[1, 2], [0, -3]], [[3, 8], [-8, 10]]];
mf := MFind(f3, Mcell < 0, 1, 0); // 将多维矩阵中小于0的值用0替代并返回符合条件的下标与原值
// 此时f3的值为fmarray[[[1,1],[2,0]],[[1,2],[0,0]],[[3,8],[0,10]]]
return mf;

返回 mf 如下:

####### ::与::=的应用举例

范例 01将随机矩阵中行列标相同的单元格的值赋值为 1

// 方法一:::的实现
F1 := mrand(5, 4);
F1::mcell := mrow = mcol?1:mcell;
return F1;
// 方法二:::=的实现
F2 := mrand(5, 4);
F2: := mrow = mcol?1:mcell;
return F2;

F1 与 F2 的返回结果如下:

范例 02统计多维矩阵中第三维度中每个序列中小于 0.5 的数量

// ::的实现
F1 := mrand(5, 4, 10); // 三维
A := MInit(5, 4, 0); // 记录结果
F1::begin
    A[mrow][mcol] += mcell < 0.5;
end;
return A;

范例 03::=实现数值函数的批量操作 一般的数值函数都已实现对数组的支持,可以直接使用,比如 FloatN既可以对数值 12.656 进行四舍五入,也能对数组 array(12.656,11.121)或 fmarray[12.256,11.121]进行四舍五入。但也可能存在部分函数在设计时没考虑到数组的情景,或用户自己在封装时没有考虑数组的情景,此时,则可借助::=运算来快速实现对数组的支持。 比如RoundTo5 函数只能.5 处理一个实数,那我们想要处理矩阵中每个单元格中的数值则可通过 t::= RoundTo5(mcell)来快速实现,实现如下:

F := fmarray[6.3, 5.6, 3.1, 4.2, 5.25];
F: := RoundTo5(mcell);
return F;
子矩阵的提取与赋值

FMArray 支持 array 子矩阵操作的所有形式。

子矩阵提取

以下均假设 A 是一个矩阵。

数组取数是使用[]运算符FMArray 矩阵也如此。

例如 A[2,3]表示行下标为 2 列下标为 3 的值。相对于其它方式,有降维效果。

N:M 的方式可以取矩阵的下标连续的子集。

例如 A[2:5]表示取 A 矩阵行下标为 2 到 5 之间的子矩阵。

A[2:5][0:1] 表示取 A 矩阵行下标为 2 到 5 之间,列下标为 0 到 1 之间的子矩阵。

多维矩阵依此类推。

当该维度需要取所有下标时,可以用 : 来表示全部,比如 A[:][0:1] 表示取所有行,而列为 0 到 1 之间的子矩阵。

Array(N,M,…)的方式可以取矩阵任意下标集合的子集。

例如 A[array(2,5,6)]表示取 A 矩阵中行下标为 2、5、6 的三行组成的矩阵。

A[array(2,5,6), array(0,2)] 表示取 A 矩阵中行下标为 2、5、6列下标为 0 和 2 的新矩阵。

以上几种方式可以混搭灵活使用,能满足各类子矩阵提取的需求。

fm := mrand(3, 4, 5);

fm[:,0,:]为固定维度 1 下标为 0 的二维矩阵

fm[:,array(2,3),:] 用数组做下标取多个下标

fm[:,2:3,:] 用 fromindex:toindex 模式取多个下标

子矩阵赋值

子矩阵提取后可以直接赋值,值可以是一个简单类型的值或行数相同的一维矩阵,也可以是一个与子矩阵结构一致的矩阵。

注:子矩阵赋值后不能改变原矩阵的单元格数据类型。

// 比如A[0,1]:=100即可以将矩阵 A 的行下标为 0列下标为 1 的单元格赋值为 100。
A[2:5] := 100可以将矩阵 A 的行下标从 2 到 5 的子矩阵的单元格的值都赋值为 100。

赋的值也是一个矩阵的具体案例如下:

例 01行数相同的一维矩阵给同一行内的单元格赋相同的值。

f := fmarray[[11, 12], [31, 4], [5, 5]]; // 3*2矩阵
f[0:1] := array(100, 200); // 赋值为2行的一维数组也可以是FMArray[100,200]
return f;

返回:即相同行的值一致,不同行不一样。

例 02相同结构的矩阵

t := MInit(4, 3, 0);
t[1:2][0:1] := fmarray[[1, 10], [2, 20]]; // 子矩阵为2行2列所以赋值的矩阵也需要是2行2列
return t;
行的自扩张

FMArray 支持行的自扩张,但不支持其他维度的自扩张。

// 比如,初始化一个 3 行 2 列的矩阵A:=minit(3,2,0.0);
// 而后赋值A[100]:=1;

即此时的 A 的大小变成了 101 行 2 列的矩阵,自扩张产生的中间值用 0 补充。

例如:

f := fmarray[1, 2, 3]; // 3行
f[10] := 10; // 赋值下标为10的行的值为10此时的f会进行自扩张
return f;

返回:(可以看出自扩张的地方是用 0 在补充)

同样也支持对多维数组的行进行自扩张。

比如:

f := fmarray[[[1.5, 2.2], [1.6, 1.0]], [[1.4, -1.1], [-0.4, 2.]]]; // f的大小为2*2*2
f[10] := 10;
return f; // 此时的f大小为11*2*2单元格数据类型依旧是浮点数
矩阵运算

支持矩阵运算的基础算符,也全部支持 FMArray 矩阵的计算。具体可参考天软帮助文档中的矩阵运算章节。

运算符

TSL 的基础算符有比如+,-,*,/,,%,mod,div,^,~,.=,.>,.<,.<>,.>=,.<=,.!,.&,.|,.^,.||,.&&,.!!,like,++,--等等。

矩阵运算符包括 :*, :/, :\\, :^, !, |, :|, union, ::, ::=, -> 等。 支持全部矩阵计算算符

a := fmarray[1, 2, 3] + fmarray[2, 3, 4];

A 的结果为:fmarray[3, 5, 7]

a := fmarray[1, 2, 3] + 1;

A 的结果为:fmarray[2, 3, 4]

矩阵运算支持和 array 的混合运算

计算的结果类型和左值相同

也就是 fmarray :* array -> fmarray

  array :* fmarray -> array
a := fmarray[1, 2, 3] + array(2, 3, 4);

A 的结果为:fmarray[3, 5, 7]

多维矩阵的转置

对超过 2 维矩阵的转置fmarray 的矩阵和 array 不一样fmarray 是对所有维度进行倒置,也就是说如果存在一个 1234 的四维矩阵,会转置为一个 4321 的矩阵。

如果要当成二维转置,请采用 mswap(fm,0,1)的模式,这样交换第 0 维和第 1 维的下标

Mswap

范例

// 将3*2*1的三维矩阵转换成2*3*1的矩阵
f3 := fmarray[[[1], [2]], [[1], [0]], [[3], [-8]]];
t1 := mswap(f3, 0, 1);
// t1的结果为fmarray[[[1],[1],[3]],[[2],[0],[-8]]]是一个2*3*1的矩阵只转换了第一维与第二维
// 用转置符进行转置
t2 := `f3;
// t2的结果为fmarray[[[1,1,3],[2,0,-8]]]是一个1*2*3的矩阵是对所有维度的倒置
集合运算

Fmarray 支持集合运算

Union2,intersect,outersect,minus 都支持

集合运算支持和 array 的混合运算

计算的结果的数据类型和左值相同

即 fmarray union2 array -> fmarray

  array union2 fmarray -> array

如果合并操作的结果类型为 fmarrayfmarray 的单元格类型是能包容左右操作数单元格的类型

表现如下:

整数矩阵和浮点矩阵的操作是浮点矩阵

整数矩阵和 64 位整型矩阵的操作是 64 位整型矩阵

64 位整型矩阵和浮点矩阵的操作是浮点矩阵

注:多维矩阵做集合运算时,维度必须一致,且除第一维度的长度可以不相同外,其余维度的长度必须相同。

范例 01矩阵集合并操union2将两个集合中的元素的合集去重后的结果

f1 := fmarray[1, 2, 0, 4, 5];
f2 := fmarray[1, 0, 7];
t := f1 union2 f2;

t 的值为fmarray[1,2,0,4,5,7]; 有去重效果

范例 02Fmarray 与 array 做并操作

f1 := fmarray[1, 2, 0, 4, 5];
f2 := array(1, 0, 7.2);
t1 := f1 union2 f2;
t2 := f2 unon2 f1;

t1 的结果为fmarray[1.0,2.0,0.0,4.0,5.0,7.2],可以看出矩阵中单元格类型为浮点数。

t2 的结果为array(1,0,7.2,1,2,0,4,5)

范例 03二维矩阵的并操作

f1 := fmarray[[1, 2], [0, 4], [5, 6]];
f2 := array((1, 2), (3, 3));
return f1 union2 f2;

返回结果如下Fmarray 矩阵)

还支持多矩阵合并,如 f1 union2 f2 union2 f3 等操作。

其它集运算用法同 union2。更多功能介绍请查看该关键字的帮助说明。

矩阵连接

Fmarray 支持矩阵连接运算:

Union,|,:|的矩阵连接算符都支持

Union 与 union2 的区别在于union 没有去重功能,就是把两个表的行连接起来。

连接运算支持和 array 的混合运算

计算的结果类型和左值相同

如果值为 fmarrayfmarray 的单元格类型是能包容左右操作数单元格的类型

例如整数矩阵和浮点矩阵的操作是浮点矩阵

注意点:由于 fmarray 属于固定列数的,在 union 的时候,如果右操作数的列多于左边,将会出错。

对于|,:|而言,如果右操作数行数少于左边,可以支持,少的行补 0如果行数大于左边则等于先将矩阵扩大再操作

由于 fmarray 属于完全矩阵,因而|,:|在操作的时候,当右操作数行数超过左边,两者的表现完全一致。这一点和 array 不同,因为 array 的本身行原本所具备的列数是不确定的,所以:|补齐列,而|不补齐。对于 fmarray 而言,列数是固定的,所以在这种行数不同的时候两者会具有一定的差异。

与 Array 的混合运算的表现与集合运算的表现一样,这里就不再重复举例,可参考上一章节的案例。这里就|与:|操作时,左右行数不一样的表现进行举例,如下:

例 1矩阵行列相同时做|操作

t1 := fmarray[[1, 2], [3, 4], [5, 5]];
t2 := fmarray[[3, 4], [7, 8], [6, 9]];
t := t1 |t2;
return t;

返回:(将两个表的列连接起来)

例 2左右两表行数不一致时的连接表现

t1 := fmarray[[1, 2], [3, 4], [5, 5]];
t2 := fmarray[[3, 4]];

则左表行数超过右表时,即 return t1|t2; 返回表现如下:右表少的地方用 0 补足

若右表行数超过左表时,即 return t2|t1;返回如下:是左表先自扩张同右表一致,而后进行拼接

同时,将上列中的|变更为:|效果也一样,在 FMArray 中,两者功能无区别。

SQL 语法对 FMArray 的支持

FMArray 支持 SQL 语法的大多数功能,但是由于这类语法操作不属于矩阵计算等高性能功能,因而用此类方式操作 FMArray 矩阵效率不高,用户要尽量避免使用。

Select 对 fmarray 的支持

允许对 fmarray 进行 select但返回值目前不支持产生 fmarray而是以前支持的 array 和 Matrix。

如果要产生的结果为 fmarray需要对结果使用 arraytofm 进行转换。

说明:由于 select 允许指定列名,且计算值会不定,且 select 不属于矩阵计算等高性能功能,因而设计上并未支持返回结果集为 fmarray。

f1 := fmarray[[1, 2], [0, 4], [1, 2], [5, 6]];
t := select \ * from f1 end;

此时的 t 是一个 Array而 select 改成 Mselect 后结果是一个 Matrix。

其它功能与用法与 Array 一致。

Insert 对 fmarray 的支持

允许 Insert 将 fmarray 或者 array 类型插入到 fmarray 之后

插入后结果还是 fmarray 矩阵,以及其他维度的结构和原矩阵单元格数据类型不会进行改变。

当插入的数据列数少于原有 fmarray缺省的列设置为 0。

当插入的数据类型不同,会将数据转换到原 fmarray 的类型。如果类型不正确,例如送入字符串,会报错

当插入的维度少于原始维度,会填充插入数据的最后一个维度的数据到被插入的 fmarray 里

a := minit(2, 3, 1.0); insert into a array(9);

A 的结果是: fmarray1.0,1.0,1.0],[1.0,1.0,1.0],[9.0,9.0,9.0

Delete 和 update 对 fmarray 的支持

Delete 和 update 支持对 fmarray 的操作,操作后的结果还是一个 FMArray且不会改变原矩阵单元格的数据类型。

例如delete from f where [1]=4; 可以删除矩阵 f 中 1 列值为 4 的所在行;

update f set [0]=100 where [1]=4

end; 可以将 1 列中值为 4 的行的 0 列值更新为 100。

提示fmarray 无法像 array 一样自动通过 update 增加列,这是因为这种操作对于 fmarray 相比 array 而言更慢,采用 fmarray 应该规避这类操作,只将 fmarray 用于必要的地方。

Fmarray 为 CopyOnwrite 模式

即当访问 Fmarray 矩阵时,只是引用,并不会产生复制操作,所以不会增大运行内存,只有当对矩阵进行写入时才会产生数据的复制。

如下这些操作,都只是引用:

A := minit(100, 100, 0.0);
B := a[0:98];
C := a[0];
D := A;

其内存表现:

支持 FMArray 的公用函数

常用支持 Array 数字数组的函数,也支持 FMArray 矩阵。

重构函数

支持 reshape允许重新构造相同大小维度不同的矩阵

注意:在重构时元素的个数要与原个数保持一致

例如:

f1 := fmarray[[1, 2], [3, 4], [5, 5]];
t := Reshape(f1, 6);
return t;
// t 的值为 fmarray[1,2,3,4,5,5]; 即由一个 2\*3 的矩阵变成了一个 6 行的一维矩阵。
排序

Sortarray,sorttablebyfield 支持对 fmarray 的排序

Sortarray 支持一维的 fmarray

Sorttablebyfield 支持二维的 fmarray

范例 01一维排序

f := fmarray[10, 2, -3, 8];
sortarray(f);
return f;

f 排序后的值为fmarray[-3,2,8,10]

范例 02二维排序

f1 := fmarray[[11, 12], [31, 4], [5, 5]];
SortTableByField(f1, 0, 1); // 按第一列进行升序排列
return f1;
All 与 Any

All、Any模式与 array 同,如果用于行列的 any 返回值为 fmarray

原有数字类型函数

原有支持 array 的基础函数,所有数字类型的函数均支持

例如 abs,roundto 等等。

暂时未对基础函数的返回类型是字符串的进行支持,因为 fmarray 目前并不支持字符串,亦未做自动 array 类型的实现。

例如 floattostr(mrand(3))会报错

原统计函数

统计函数,均支持 fmarray无论是单序列的函数双序列的

但如果多多维度的 fmarray 进行统计,本身返回不是一个标量的时候,我们并不返回相应的 fmarray 类型,而是返回 array 类型。

例如:

sum(minit(3,4,1.0))得到的数据是 array(3.0,3.0,3.0,3.0)

Sum(minit(3,4,1.0),1)对行统计得到的数据是 array(4.0,4.0,4.0)

这样设计为了兼容统计类函数的特殊返回值等设计,因为 fmarray 本身只支持数字类型

不支持的要点提示

1 不支持 Reindex,ReindexCopy 对 Fmarray 进行结构的操作。

无法对 fmarray 进行 reindex 操作,因为 fmarray 固定结构的特殊性,对这种操作的支持将会获得极差的性能,违背 fmarray 的初衷。

2 Fmarray 支持 DeleteIndex ,但不支持 DeleteField

提示fmarray 的特殊结构导致删除列的操作相对 ARRAY 会极为缓慢,采用 fmarray 应该规避这类操作,只将 fmarray 用于必要的地方。

SQL 基础到 TS-SQL

SQL 基础到 TS-SQL 简介

SQL 的全称为 Structured Query Language亦即结构化查询语言顾名思义SQL 本身就是一门计算机语言。目前,绝大多数的数据库系统的查询是基于 SQL 的SQL 语言在数据处理和查询方面具有独特的优势。目前市面上流行的 SQL 主要有 SQLSERVER 支持的 T-SQL(Transact-SQL)以及 Oracle 支持的 PL/SQL(Procedural Language/SQL),目前绝大多数的 SQL 都是基于 SQL-92 规范为蓝本建立起来SQL-92 规范也就是 1992 年由多家数据库厂商一起建立的一个 SQL 的规范。但需要注意的是SQL 有规范但是却没有标准,每一家数据库厂商自己均提供自己的 SQL 实现,而且都有自己的 SQL 特性,所以也在很多方面互不兼容,兼容是为了移植,而不兼容处才体现出各自之优越。

SQL 对数据的处理除了查询以外,还支持数据删除,数据更新,数据插入等功能。

TSL 语言内置集成了类 SQL 语法。为什么说是类 SQL 语法呢?首先 SQL 作为一门独立的语言,和 TSL 语言的语法在相当多的地方有冲突,不可能对 SQL 语法不做改动就融入 TSL 语言,其次 TSL 语言的类 SQL 语法是在吸收 SQL 语法的精华的同时,还增加许多自身特色,例如支持时间序列分析,同时支持数据库以及 TSL 的内存表和天软数据仓库。此外TSL 的 SQL 语法还支持对按照规范编写的对象进行查询,修改,插入和删除等工作。

无独有偶,微软的 Visual Studio 2008 里的 C#也引入了类 SQL 语法,微软称之为 LINQ Language INtegrated Query亦即语言集成查询。在我们的 TSL 中,类 SQL 语法我们称之为 TS-SQL(TinySoft SQL)。

与前面所学的相比可能这个章节所讲述的内容会更易懂并且功能也更强大而且有时候会给人一个误解SQL 无所不能。事实上,由于 SQL 是一门语言,所以 SQL 确实也是无所不能的,但是 SQL 有其局限性,在适合于 SQL 来实现的时候,无论运算效率还是简洁度而言都无以伦比,但在不适合于 SQL 来实现的时候,如果强行使用 SQL 语法来实现,可能既晦涩难懂,运行效率也非常低下。有许多程序员,已经沦为了数据库的奴隶,基本上最熟悉的就是 SQL 和数据库,在过分依赖 SQL 的时候,同时也给了自己的思想套上了一把枷锁,脱离了 SQL 就什么都不会了,所以笔者认为,无论 SQL 有多么强大,至少也需要知道自己如何去实现 SQL 实现的功能,可 SQL亦可不。在许多情况下尤其应该自己尝试实现去替代和超越适合于 SQL 的功能,不仅仅对于学习大有裨益,在今后的开发道路上,也会更为宽广,我们会尝试在后面做一些类似的尝试。

此外SQL 主要适合处理的是以行为单位的数据,而矩阵计算等数学运算功能是不适合的,当然利用 SQL 进行组合亦可以实现许多类似的功能,作为探索,这值得嘉许。在开发的学习过程中,尤其鼓励使用一个不适合的东西去实现一个难以实现的东西,因为这往往可以锻炼人的异向思维能力。

事实上TSL 语言本身内置了矩阵的计算的功能,在其他章节也会提到,而且矩阵计算事实上是可以以行、列、单元格为单位的,所以矩阵计算的支撑部分甚至于不亚于整套 SQL 的语法体系。而在面对越来越高级的语法,面对越来越强大的功能的时候,往往一两行可以解决许多问题,读者应冷静去思考其实现的途径,因为这些实现也是通过一行行最简单的代码来完成的。

SQL 语言本身就可以是一门相当复杂的课程,笔者希望可以抛弃掉许多专业的术语尽量简化理解,尝试用 TS-SQL 一步步带读者进入 SQL 的大门,并让读者了解到 SQL 和 TS-SQL 之间的差异以及 TS-SQL 和其他 SQL 相比的过人之处。但由于 SQL 本身的复杂性,本书的篇幅所限,不能一一展开,因此我们还是推荐对 SQL 很不熟悉的读者在阅读本章节之后可以再阅读专门的 SQL 语言相关的书籍。

TS-SQL 语法

SELECT 查询语句

TSL 语言支持 SELECT 语句,该语法类似于 SQL 中的 SELECT 语句但是又有许多特色和差异。TSL 语言的 SELECT 语句既可以查询存贮在天软数据仓库中的基本面信息数据也可以查询天软数据仓库中的交易定周期数据以及交易明细数据。除此以外SELECT 语句还可以用来直接查询内存数组,以及对 SQL 的结果集进行后查询处理,并且,还支持对按照规范编写的对象进行查询。

与 SQL 不同TSL 的 SELECT 查询语法由 SELECT 开始END 结束。Select 中可以使用任何 TSL 函数,默认返回为一个二维数组。

与 SQL 不同TSL 对字段的访问必需采用[字段名]的模式来表达,[]内可以是任何返回为数字或字符串的表达式,例如字段 close 用["close"]访问。

与 SQL 不同TSL 的 SELECT 查询不支持 TOP N但是支持更加灵活的 DRANGE(begn to endn)以及 M Of N 的模式来取指定的结果范围。

与 SQL 不同,在使用 JOIN 进行多表连接时,需要使用[1].[字段名]模式来访问指定的表列,而非“表名.字段”的模式。

与 SQL 不同,由于 TSL 支持(作为注释符号,因此 SQL 中的 count(),CheckSum()的模式在 TSL 的 SELECT 语句中用添加在(和之间的空格符号如countof( _ )、countof()、CheckSumOf( _ )替代。

与 SQL 不同TSL 的聚集函数为了和其他的函数分别开来,基本上在 SQL 的聚集函数的基础上添加 of例如 count 对应为 countofavg 对应于 avgof,sum 对应于 sumof 等等。

具体语法如下:

From 子句[SelectOpt(Options)] [distinct] [drange( <begntoendn | M Of N>)]

<select_list>  <from 子句> [where 子句] [group by 子句] [order by 子句] end;

为了方便使用和节省内存加快速度TSL 语言还支持 SSELECT,MSELECT 和 VSELECT 三个关键字做 SELECT 查询,当使用 MSELECT 查询的时候,返回的结果类型为 Matrix 类型,当使用 VSELECT 的时候,直接可返回数据的值,当使用 SSELECT 的时候,返回结果为一个一维数组。

VSelect

VSelect 返回单个值,主要用于如 sumof、countof 这类统计中,其他功能与 Select 同,只是 Select 返回的结果一般为二维数组。

当需要返回某个字段的最大值的时候,采用 (select maxof( ["F1"] ) from table end)[0]["Expr1"] 来描述太复杂,而采用 VSelect maxof(["F1"] from table end 就可以直接返回最大值。

如:

t := zeros(10, "A");
t[:, "A"] := 1 -  > 10;
v1 := select sumof(["A"]) from t end;
v2 := vselect sumof(["A"]) from t end;

其中v1 的结果为array(("Expr1":55.0)),要得到 55 这个数值需要进一步v1[0,"Expr1"]

v2 的结果为55

SSelect

SSelect 返回一个一维数组,例如只返回其中一列而且要一个一维向量,其它与 Select 一样,只是 Select 返回为一个二维数组。

SSelect 可用于对返回结果进行降维处理,用法如 sselect [“A”] from t end;

与 Select 的对比:

t := zeros(10, array("A", "B"));
t[:, "A"] := 1 -  > 10;
v1 := select ["A"] from t end;
v2 := sselect ["A"] from t end;

v1 与 v2 结果对比:

MSelect

MSelect 与 Select 几乎完全一样,只是 Select 返回结果是 Array 类型,而 MSelect 返回的结果内容为 Matrix而 Matrix 类型是一个在内存中紧缩存贮的二维表结构,可以最大限度的节省内存空间。

用法同 select

t := zeros(10, array("A", "B"));
t[:, "A"] := 1 -  > 10;
return Mselect * from t end;
Select 子句
语法

SELECT [SELECTOPT(Options)] [DISTINCT][DRANGE(<begn to endn | M Of N>)]< select_list >

< select_list > ::=

{* | StartFieldIndex To EndFieldIndex |{ column_name | Expression}[ [ AS ] column_alias ]}[ ,...n ]

参数

####### SELECTOPT(Options)

指定 SELECT 的选项,控制 SELECT 的行为, Options 为以下数值的组合(以下数值的_XOR 或者相加)

下表中二进制位从 0 开始,可通过 2^N2 的 N 次方)形式获取该位设置的值。

同时设置多模式时,可通过相加方式得到,比如设置 selectopt 的值为 2^6+2^8则表示在使用 MovingFirst 模式的同时设置 refof 越界时返回 nil 而非 0可参考下面示例 03

SelectOpt 以位掩码表示可选行为,常见选项:

  • SELECT_OPT_MULTIAGGSAMENAMEbit 4, value 16聚集字段名保持与字段一致。
  • SELECT_OPT_AGGSAMENAMEbit 5, value 32SUMOF(["abc"]) 返回字段名仍为 abc
  • SELECT_OPT_N_LEAD_CONDbit 6, value 64MovingFirst 模式。
  • SELECT_OPT_DUP_FIELD_POS_TO_LASTbit 7, value 128重复字段以最后一次为准。
  • SELECT_OPT_REFOF_EMPTY_NILbit 8, value 256refof 越界返回 nil
  • SELECT_OPT_ORDERBY_REFOFbit 14, value 16384ORDER BY 后以排序结果集为基准。
  • SELECT_OPT_NO_FAST_MOVINGbit 16, value 65536禁用移动计算的快速聚集算法productof 等)。
  • SELECT_OPT_FAST_MOVINGbit 17, value 131072显式启用移动计算的快速聚集算法。
  • SELECT_OPT_NO_MOVING_FIRSTbit 18, value 262144显式关闭 MovingFirst 模式。

其余选项与完整列表请参考函数库与 TS-SQL 相关文档。

注:

1、关于 MovingFirst 模式与禁用移动计算时的快速计算模式位设置互相矛盾时

由于指定功能是与指定功能否是通过不同值进行的设置,因此在设置时可能同时存在,即出现位互相矛盾的情况时,则为 MovingFirst 为真的模式。

即若既设定 16 位也设定了第 17 位,或者设定了第 6 位也设定了 18 位则依旧按照旧有真假模式MovingFirst 为真。

范例:

示例 01将表格行列下标更改为增长的自然数字下标数组

t := Zeros(4, array('A', 'B', 'C', 'D'));
return select selectopt(8) * from t end;

示例 02对数组每列进行累加累加后列名与原列名保持一致

t := `array('A': 0 -> 5, 'B': 10 -> 15, 'C': 20 -> 25);
return select selectopt(16) sumof(*, 1, 6) from t end;

示例 03对数组进行分组后其它所有列求和

即除分组标识列后,其它所有列进行累加,累加后列名与原列名保持一致

实现:对数据 t 按第 0 列的值进行分组后,其它列分别相加求和

t := array(
("股票1", 100, 300),
("股票2", 1000, 200),
("股票3", 10000, 14),
("股票2", 100, 200),
("股票3", 10, 140),
("股票4", 100, 15));
return select selectopt(16) sumof( * ), [0] from t group by [0] order by [0] end;

注:上述实现中 selectopt(16) sumof( * )是在分组后,对所有列进行求和,由于 0 列不需要求和,因此取原始该列的值进行最后覆盖,保留原始值,达到目的。

示例 04多功能模式展示设置 selectopt 的值为 2^6+2^8则表示在使用 MovingFirst 模式的同时设置 refof 越界时返回 nil 而非 0。

t := Integer(Rand(20, "v") * 100);
return select
selectopt(2 ^ 6 + 2 ^ 8)
* ,
sumof(["v"], ["v"] > 30, 2) as"sumV",
refof(["v"], 5, ["v"] > 30) as"ref5"
from t end;

####### DISTINCT

指定在结果集中只能显示唯一行。为了 DISTINCT 关键字的用途,空值被认为相等。不指定 DISTINCT 则指定在结果集中可以显示重复行。

####### DRANGEbegn TO endn

指定只从查询结果集中输出从 begn 到 endn 之间的行。当 begn 到 endn 是 0 或者正整数的时候,位置偏移从第一条开始算,第一条位置为 0当为负数的时候偏移从最后一条开始算最后一条的偏移为-1倒数第二条为-2以此类推。

如果查询包含 ORDER BY 子句,将输出由 ORDER BY 子句排序的从 begn 到 endn 之间的行。如果查询没有 ORDER BY 子句,行的顺序将由数据的存贮原有次序决定。

例子:

A := array();
for i := 0 to 999 do A[i]["ABCD"] := i;
return select Drange(10 to 99) * from A end;

返回序号为 10 到 99 的数据。

如果要返回的数据在后边的 10 条,那么

return select Drange(-10 to - 1) \ * from A end;

也就是说 DRANGE 里的范围为负数表示如下:

-1 为最后一条数据,-10 是倒数第十条数据。

####### DRANGE(M OF N)

指定只从查询结果集中输出从 (M-1)/N _ 记录总条数到 M/N _ 记录总条数 -1 之间的行。例如 DRange(5 Of 100)的意思是返回内容 100 等分的第 5 个等分DRange(1 of 2)则返回前一半的内容。

如果查询包含 ORDER BY 子句,将输出由 ORDER BY 子句排序的 N 等分的第 M 等分的行。如果查询没有 ORDER BY 子句,行的顺序将由数据的存贮原有次序决定。

例子:

return select Drange(1 of 10) \ * from A end;

这就是返回 10 等分中的第一等分部分。

####### < select_list >

为结果集选择的列。选择列表是以逗号分隔的一系列表达式。

*:指定在 FROM 子句内返回所有列。列按 FROM 子句所指定的内容返回,并按它们在指定的内容中的顺序返回。

StartFieldIndex To EndFieldIndex指定返回第几列到第几列。

例如一个结果集有字段名为 "f1" "f2" "f3"

1 to 2就表示列"f2"和"f3"

[column_name]

如果返回的是指定的列名,则采用在列名字符串或者列整数下标表达式加[]来表达。

Expression

是[列名表达式]、常量、函数以及由运算符连接的[列名表达式]、常量和函数的任意组合,或者是子查询。

column_alias

是查询结果集内替换列名的可选名。例如,可以为名为 quantity 的列指定别名,如"Quantity"或"Quantity to Date"或"Qty",列名也可以是数字。

别名还可用于为表达式结果指定名称,例如:

select AvgOf(["close"]) as"Average close"

FROM MarketTable datekey encodedate(2002,1,1) to encodedate(2002,12,31) of

"SZ000001"end;

column_alias_str 不可用于 ORDER BY 子句WHERE、GROUP BY 或 HAVING 子句。

From 子句
语法
FROM { <table_source> } [ , ...n ]

<table_source> : :=
    <Expression | THISGROUP | INFOTABLE 基本面表 ID>
  | { <MARKETTABLE | TRADETABLE>
      [DateKey BegTime TO EndTime]
      [of <StockID | StockIDArray>]
    }
  | { <SQLTABLE | HUGESQLTABLE [KEEPNULL]> <SQL_Str> <of SQL_Alias> }
  | <joined_table>
  | <table_source> CROSS JOIN <table_source>

<joined_table> : := <table_source> <join_type> <table_source> <ONWithExp>
<join_type> : := [{ LEFT | RIGHT | FULL }] JOIN
<ONWithExp> : := ON <search_condition>
            | with (Expression[,...n] ON Expression[,...n])
参数

####### Expression

二维数组表达式,从内存表中查询。

####### Thisgroup

在上一个分组中的子结果集。

参见THISGROUP

####### infotable id

如果从基本面数据表里查询,则用 infotable 表 ID。

详细使用可参考:

FAQINFOTABLE

####### markettable

提取时间序列数据表格的关键字,如取天软指定周期的行情数据等。

例如从分钟线,日线,周,月,季,年线中查询,则用 markettable,具体周期由系统参数确定。

具体用法可参考FAQMARKETTABLE

####### tradetable

如果从交易明细取数据,则用 tradetable当当时周期为交易明细的时候用 MarketTable 等同于 TradeTable。

具体用法可参考FAQTRADETABLE

####### DATEKEY begtime TO endtime

从 markettable 和 tradetable 取数据的时候,如果需要对时间进行约束的话,用 datekey 指定开始到截止的时间。

####### OF <StockID | StockIDArray>

从 InfoTable、markettable 和 tradetable 取数据的时候,必需指定查询的证券代码或者证券代码列表。

####### sqltable|hugesqltable[KeepNull]<sql_str> of <Sql_Alias>

从 SQL 数据源里取数据的时候sql_str 是 SQL 的字符串sql_alias 是 SQL 执行依赖的别名。

当数据集合特别庞大的时候,采用 hugesqltable 可以节省内存,但是速度可能会比 sqltable 慢很多。

有关数据库别名请参考ExecSQL

######## KeepNULL

当有 KeepNull 标识的时候,返回的结果集中如果为 NULL 值,则为 NIL 值,否则结果用默认的字段类型值,例如字符串类型字段的默认值为""。

JOIN 语法

JOIN 用于对多表进行连接,可以是 JOINLEFT JOINRIGHT JOINFULL JOINCROSS JOIN。JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN 支持 ON 或者 WITH 条件约束。

####### JOIN

Join 运算符返回满足第一个(顶端)输入与第二个(底端)输入联接的每一行。

####### LEFT JOIN

Left Join 返回每个满足第一个(顶端)输入与第二个(底端)输入的联接的行。它还返回任何在第二个输入中没有匹配行的第一个输入中的行。第二个输入中的非匹配行作为空值(NIL)返回。

####### RIGHT JOIN

Right Join 运算符返回满足第二个(底端)输入与第一个(顶端)输入中的每个匹配行联接的每一行。它还返回第二个输入中在第一输入中没有匹配行的任何行,即与 (NIL) 联接。

####### FULL JOIN

Full Join 运算符从第一个(顶端)输入中与第二个(底端)输入相联接的行中返回每个满足条件的行。它还可以从下面的输入返回行:

在第二个输入中没有匹配项的第一个输入。

在第一个输入中没有匹配项的第二个输入。

不包含匹配值的输入将作为空值返回。

####### CROSS JOIN

Cross Join 运算符将第一个(顶端)输入中的每行与第二个(底端)输入中的每行联接在一起。

Cross Join 不允许带 On 或者 With 条件Cross Join 也可以用“,”(逗号)来替代。

####### ON 条件

Join,Left Join,Right Join,Full Join 允许添加 On 的条件,用法范例:

select \ * from a join b on [1].["ID"] = [2].["ID"] and [1].["Type"] = [2].["Type"]
end;

ON 的用法和 SQL 规范类似,但 On 条件不做优化,如果要进行优化加速,建议采用 With On 条件。

####### WITH ON 条件

Join,Left Join,Right Join,Full Join 允许添加 With On 的条件,用法范例:

select \ * from a join b with([1].["ID"], [1].["Type"] ON [2].["ID"], [2].["Type"])
end;

WITH ON 是 TSL 语言类 SQL 的自有的扩展特性With On 是为了解决 Join 的效率问题ON 的两旁是两组表达式每组表达式的表达式个数应该完全相同with on 会利用两组表达式进行预运算,并建立快速索引,解决效率问题,一般来说 With On 的计算复杂度和 N+M 是一个量级,而 ON 条件的计算复杂度和 N*M 是一个量级(假定左右输入的数据集合的记录数分别为 N 和 M在 N 和 M 比较大的时候,性能的差异可以体现得非常明显,达到几个数量级的差异。

但是,由于 WITH ON 有预运算和索引,目前还不支持超大数据结果集之间的运算,在那种情况下,仍然只能使用 ON 条件。

WHERE 子句

语法

[WHERE ]

含义

WHERE 指定查询结果集的条件

范例

select \ * from SampleTable where ["字段"] > 10 end;
GROUP BY 子句

语法

[GROUP BY <Expression[....,n]> [HAVING ]]

含义

GROUP BY 子句包含以下子句:

一个或多个自由聚合的表达式。通参见:字段的表达与返回

1、常是对分组列的引用,多个列或多个表达式用逗号分隔。

2、通常情况下HAVING 子句与 GROUP BY 子句一起使用。

常用方式如:

t := array(
("证券":"SH600028", "行业":"采矿业", "涨幅(%)":-1.01),
("证券":"SH600030", "行业":"金融业", "涨幅(%)":0.07),
("证券":"SH601166", "行业":"金融业", "涨幅(%)":-1.6),
("证券":"SH601211", "行业":"金融业", "涨幅(%)":0.29),
("证券":"SH601225", "行业":"采矿业", "涨幅(%)":-0.49),
("证券":"SH601658", "行业":"金融业", "涨幅(%)":-1.62),
("证券":"SH601668", "行业":"建筑业", "涨幅(%)":1.16));
return select ["行业"], maxof(["涨幅(%)"]) as"最大涨幅"from t
group by ["行业"]
end;

返回每个行业个股最高涨幅,结果:

只要表达式中不包括聚合函数,就可通过该表达式分组。

例如,对 A 股股价进行聚类,返回聚类中股票个数大于 10 的聚类,得到每个聚类的股票个数以及平均价格:

return select GetStockPriceType() as"价格分类",
AvgOf(["close"]) as"平均价格", COUNTOF() as"个数"
from MarketTable
DateKey today() to today()
of GetBK("深证A股;上证A股")
group by GetStockPriceType() having COUNTOF() > 10
end;

GetStockPriceType 函数的定义如下:

function GetStockPriceType();
begin
    return
    ifthen(["close"] > 15, "高价股", ifthen(["close"] > 5, "中价股", "低价股"));
end;
THISGROUP

如果要查询分组的子结果集,可以使用子 select 查询语句对 thisgroup 进行子查询。

ThisGroup 不是一个数据,仅仅只是子数据集,必需用 SELECT 的 FROM 子句来访问

例如有列”证券”,”行业”,”涨幅(%)”的二维数组

t := array(
("证券":"SH600028", "行业":"采矿业", "涨幅(%)":-1.01),
("证券":"SH600030", "行业":"金融业", "涨幅(%)":0.07),
("证券":"SH601166", "行业":"金融业", "涨幅(%)":-1.6),
("证券":"SH601211", "行业":"金融业", "涨幅(%)":0.29),
("证券":"SH601225", "行业":"采矿业", "涨幅(%)":-0.49),
("证券":"SH601658", "行业":"金融业", "涨幅(%)":-1.62),
("证券":"SH601668", "行业":"建筑业", "涨幅(%)":1.16));
return select ["行业"], maxof(["涨幅(%)"]) as"最大涨幅"from t group by ["行业"] end;

这种写法熟悉 SQL 的都可以很了解,但是如何访问在[“涨幅(%)”]的最大值发生时候的[“证券”]的值呢?

ThisGroup 可以解决这个问题

return select ["行业"], mzf := maxof(["涨幅(%)"]) as"最大涨幅",
vselect ["证券"] from thisgroup where ["涨幅(%)"] = mzf end
as"最大涨幅券"
from t group by ["行业"] end;

当然用户也可以使用 select * from ThisGroup end 来得到分组的完全值

ORDER BY 子句

语法

[ORDER BY <Expression[desc|asc]>[,....n]]

含义

指定在 SELECT 语句返回的列中所使用的排序次序。

参数

Expression

排序的表达式,支持多个表达式排序,每个表达式之间用逗号分割。每个表达式之后允许用 DESC 或者 ASC 来标识排序的方向。

desc

逆序方法排序

asc

正序方法排序

字段的表达与返回

在 SQL 语句中,可以直接用字段名访问字段的值,在 TSL 中,必须用[字段名字符串表达式]来访问,例如一个字段名为 close访问其则为["close"]。

并且TSL 语言中的字段表达式中的字段名字符串是大小写相关的。

如果使用了 JOIN 导致一个 SQL 语句中有多表时,则用[TableSequence].[字段名]来访问,例如:[1].["close"]代码第一个表的 close 字段,如果有多表,并且不指定表序号,则返回的内容会从第一个表开始逐步查找。

例如

S := ”abcd”;

[S]表示字段 abcd,也可以采用[“abcd”],是一样的含义。

select [S] from Table end; // 表示返回列abcd而不是列S

同样[0]表示数字字段 0;

A := Rand(1000, array(“ABCD”));
B := select * from A where [“ABCD”] > 0.5 order by [“ABCD”] end;
return B;

再如:

select [“aaa”] as “aaa100” from a end;

返回的内容的字段名可以用 AS 更名,不带 AS 则使用系统的缺省名,如果返回的本身仅仅只是一个[]则为原始的字段名否则使用”Expr1”,”Expr2”…来命令

如果再 SELECT 中希望临时进行计算又不希望将临时计算的结果返回到结果集中,可以使用 AS NIL 的模式,可参考Select as nil 语法

行、行下标、行序号与行校验
ThisRow

返回在表里的当前行的数据值。

当 SELECT 对一维数组进行查询的时候THISROW 就很重要,因为没有列可以用来返回结果。

当有多个表进行 Join 的时候,调用 ThisRow 应该用 ThisRow(TableIndex)来指定特定的表。

ThisRowIndex

返回在表里的当前行的行下标值。

对于数组、矩阵、SQL 表而言,该索引是绝对的位置,对于其他类型而言,是一个相对的位置,例如对于 TRADETABLE 而言,是该行在当天的明细序列编号。

在采用 ORDER BY 排序的时候THISROWINDEX 可以返回行原来的位置。

当有多个表 Join 的时候,调用 ThisRowIndex 应该用 ThisRowIndex(TableIndex)来指定特定的表。

ThisOrder

在有 order by 的查询中 ThisOrder 可以获得行的自然排序序号

ChecksumOf
  • ThisRow
  • ThisRowIndex
  • ThisOrder
  • ChecksumOf
聚集函数

为了防止和 TSL 其他函数产生冲突,聚集函数一般都以 OF 结尾,例如平均值是 AVGOF求和是 SumOf。

TSL 支持的 SQL 语法的聚集函数有如下几个特点:

1、支持条件聚集即统计满足条件的记录一般为第一个可选参数如统计某列值大于 0.5 的项的总和

t := Rand(10, array("A", "B"));
return select
sumof(["A"], ["A"] > 0.5)
from t end;

返回结果是一个一行一列的数值。

2、支持时间序列化进行 N 日移动统计,即统计每行前 N 行内满足条件的记录,(一般为第二个可选参数),如计算每行某列的最近 N 日的移动平均

t := Rand(10, array("A", "B"));
return select * ,
AvgOF(["A"], true, 3)
as"3日平均"from t end;

返回结果是在原数组上增加["3 日平均"]列,展示如下:

3、条件与移动的优先顺序可控默认为先条件再移动若需要先移动再条件(MovingFirst 模式),可通过两种方式实现:

一种是通过指定参数(一般为第三个可选参数)为真,第二种是通过 select selectopt(64)进行指定,如:

t := `array("A":(1, 5, 4, 6, 8, 2, 9, 9, 5, 3));
t1 := select * ,
Sumof(["A"], ["A"] > 4, 3)
as"sumA1",
Sumof(["A"], ["A"] > 4, 3, 1)
as"sumA2"from t end;
t2 := select
selectopt(64)
* ,
Sumof(["A"], ["A"] > 4, 3)
as"sumA1"from t end;

其中 t1 结果是默认情况下,不指定与指定 MovingFirst 模式的对照,展示如下:(sumA1 为默认方式结果sumA2 为指定 MovingFirst 模式的结果)

t2 的结果是通过 select selectopt(64)进行指定默认方式转为 MovingFirst 模式,使["sumA1"]结果变更为["sumA2"]

4、可以利用 REFOF 访问前几条的数据,如:

t := `array("A":(1, 5, 4, 6, 8, 2, 9, 9, 5, 3));
return select * ,
refof(["A"], 1)
as"上一个A值"from t end;

5、支持先排序后聚集默认情况下是先聚集后排序如果希望 refof 或者移动聚集函数使用排序后的序列而非结果集原始序列,可以在 TS-SQL 里使用 SelectOpt(16384),具体参见:SELECTOPT(Options)。

6、 TSL 还支持多字段聚集返回

TSL还支持多字段聚集返回例如AvgOf ( * )则一次返回每个字段聚集的结果利用多字段聚集的时候同样支持条件聚集用AggValue可以返回当前聚集的字段的值。例如AvgOf( *,AggValue>10)则表示求每个字段大于10的平均数。当然也可以用Avgof( *,AggValue>10,20) 来求符合的移动平均。

多字段聚集返回时可通过 SelectOpt(16)指定列名与聚集列的列名保持一致。

多字段聚集返回还支持部分多字段聚集返回,利用 AvgOf(0 to 2)就可以分别返回 0,1,2 字段的平均数。

如:

t := Rand(10, array("A", "B", "C", "D"));
t1 := select
Maxof( * )
from t end; // 返回每列中最大值
t2 := select
selectopt(16) Maxof( *
) from t end; // 返回每列中最大值,且列名与聚集列的列名保持一致
t3 := select
Maxof( * , AggValue < 0.5 )
from t end; // 统计每列中小于0.5的项的最大值
t4 := select
Maxof( 0 to 2, AggValue < 0.5 )
from t end; // 统计前3列中小于0.5的项的最大值

7、支持时序计算时增加缓存加速默认不进行缓存加速一般为第四个可选参数具体可参考SQL 时间序列统计缓存标志与性能加速

多字段聚集

表达式可以用 * ,也可以用 StartFieldIndex To EndFieldIndex 如 0 to 2 的模式。

*表示每个字段进行聚集0 to 2 表示第一个到第三个字段求聚集。SelectOpt 设置为 16 可以使得聚集的结果字段名和原始字段名一致。

例如SumOf( * )可以对每个字段求和

SumOf(0 to 2)可以返回前三个字段的求和(返回三个求和值)

默认的情况下SumOf( * )和 SumOf(N1 to N2)返回的结果会按照正常模式以 ExprN 作为字段返回,例如"Expr1","Expr2"…,如果设置了 SelectOpt(16)则可以以原始的字段名返回。例如:

a := array(("abc":1, "bcd":2), ("abc":2, "bcd":3));
b := select sumof( * ) from a end; // b的结果为array(("Expr1":3,"Expr2":5));
c := select selectopt(16) sumof( * ) from a end; // c的结果为array(("abc":3,"bcd":5));

多字段聚集的时候,如果需要参与运算,用 AggValue 可以访问到当前运算的字段值,例如 SumOf( *,AggValue>10)

COUNTOF

范例

范例 01

Table1 := array(('A':1, 'B':2, 'C':7),
('A':nil, 'B':3, 'C':12),
('A':4, 'B':20, 'C':34));
return VSelect COUNTOF( * ) from Table1 end; // 返回组中项目的数量包括NULL和副本
// 返回结果为3

范例 02

Table1 := array(('A':1, 'B':2, 'C':7),
('A':nil, 'B':3, 'C':12),
('A':4, 'B':20, 'C':34));
return VSelect COUNTOF( ['A'] ) from Table1 end; // 返回非空值的数量
// 返回结果为2
REFOF

范例

范例 01

Table1 := array(('A':5, 'B':1, 'C':34),
('A':6, 'B':2, 'C':34),
('A':3, 'B':4, 'C':34),
('A':2, 'B':2, 'C':34),
('A':7, 'B':8, 'C':34),
('A':-1, 'B':59, 'C':34),
('A':1, 'B':18, 'C':34),
('A':9, 'B':1, 'C':34),
('A':2, 'B':0, 'C':34),
('A':4, 'B':18, 'C':34));
return select ["A"], REFOF(["A"], 2, ['A'] > 2) as'refA'from Table1 end;
COUNTIFOF

范例

范例 01

Table1 := array(('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return VSelect COUNTIFOF( ['A'] > 4 ) from Table1 end; // 返回[A]>4的项目的数量
// 返回结果为4

范例 02

Table1 := array(('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return select COUNTIFOF(['A'] > 4 , 2) from Table1 end;
AVGOF

范例

范例 01

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect AVGOF( DISTINCT ['A'] ) from Table1 end; // 返回6.

范例 02

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return select AVGOF( ['A'], ['A'] > 4, 2 ) from Table1 end;
EMAOF

范例

范例 01

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return select emaof(['A'], 1, 2) from Table1 end;
SMAOF

范例

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return select smaof(['A'], 2, 1, 3) from Table1 end;
HARMEANOF

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect HARMEANOF( DISTINCT ['A'] ) from Table1 end;
// 返回结果5.83333333333333
GEOMEANOF

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect GEOMEANOF ( DISTINCT ['A'] ) from Table1 end;
// 返回结果5.91607978309962
SUMOF

范例

范例 01

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect SUMOF( DISTINCT ['A'] ) from Table1 end; // 返回结果12.

范例 02

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34) );
return select SUMOF( ['A'], ['A'] > 4, 2 ) from Table1 end;
MINOF

范例

范例 01

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect MINOF( ['A'], ['A'] > 6 ) from Table1 end; // 返回7

范例 02缓存串范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect MINOF( ['A'], 1, nil, 1, '<MAXMIN=minA/>') from Table1 end;//返回5
MAXOF

范例

范例 01

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect MAXOF( ['A'], ['A'] > 6 ) from Table1 end; // 返回9

范例 02缓存串范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect MAXOF( ['A'], 1, nil, 1, '<MAXMIN=MAXA/>') from Table1 end;//返回9
RefMinOf

范例

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':2, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return select minof(['A'], 1, nil, 1, '<MAXMIN=minA/>'),
minof(['B'], 1, nil, 1, '<MAXMIN=minB/>'),
refminof(['B'], 'minA') , refminof(['A'], 'minB') from Table1 end;
RefMaxOf

范例

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':2, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return select maxof(['A'], 1, nil, 1, '<MAXMIN=maxA/>'),
maxof(['B'], 1, nil, 1, '<MAXMIN=maxB/>'),
refmaxof(['B'], 'maxA') , refmaxof(['A'], 'maxB') from Table1 end;
MODEOF

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect modeOF( ['A']) from Table1 end; // 返回5
MEDIANOF

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect MEDIANOF ( ['A']) from Table1 end; // 返回6
STDEVOF

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect STDEVOF( ['A']) from Table1 end;
// 返回结果1.91485421551268
STDEVPOF

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect STDEVPOF( ['A']) from Table1 end;
// 返回结果1.6583123951777
VarOf

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect VarOf ( ['A']) from Table1 end; // 返回结果3.66666666666667
VarpOf

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect VarpOf ( ['A']) from Table1 end; // 返回结果2.75
TotalVarOf

算法

其中X 为样本数据,μ 为总体均值。范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect TotalVarOf ( ['A']) from Table1 end; // 返回结果11
AvedevOF

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect AvedevOF ( ['A']) from Table1 end; // 返回结果1.5
DEVSQOF

算法

其中X 为样本数据,μ 为总体均值。范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect DEVSQOF ( ['A']) from Table1 end; // 返回结果11
NORMOF

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect NORMOF ( ['A']) from Table1 end; // 返回结果13.4164078649987
SKEWOF

算法

其中s 是样本标准差范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect SKEWOF ( ['A']) from Table1 end; // 返回结果0.854563038327971
SKEW2OF

算法

其中,σ 为总体标准差范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect SKEW2OF ( ['A']) from Table1 end; // 返回结果0.493382200218159
KURTOSISOF

算法

其中S 为样本的标准差。范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect KURTOSISOF ( ['A']) from Table1 end; // 返回结果-1.28925619834711
KURTOSIS2OF

算法

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect KURTOSIS2OF ( ['A']) from Table1 end; // 返回结果1.62809917355372
Largeof

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect largeof( ['A'], 2) from Table1 end; // 返回结果7
Smallof

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect Smallof ( ['A'], 2) from Table1 end; // 返回结果5
Percentileof

算法

对序列从小到大排序,数据长度为 n

1 求(n-1)* K记整数部分为 i小数部分为 j

2 所求结果1j序列第(i 1)个数+ j序列第(i+2)个数范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect Percentileof( ['A'], 1 / 3) from Table1 end; // 返回结果5
PercentRankOf

算法

1对数组进行排序

2计算指定数值 X 所处的排序后的位置比例。即小于 X 的值的个数/(总个数-1)

3X 值不存在数据集中则使用线性插值法得到;范例

范例 1一维数组中

x := array(10, 6, 2, 1, 9);
return vselect PercentRankOf(thisrow, 2) from x end; // 结果是0.25

范例 2二维数组中忽略空值

Table1 := array(
('A':2, 'B':3, 'C':7),
('A':10, 'B':1, 'C':5),
('B':1, 'C':5),
('A':6, 'B':8, 'C':4) );
return VSelect PercentRankOf( ['A'] , 6) from Table1 end; // 结果是0.5

注意PercentRankOf 可只用于数字列。空值将被忽略。

该模型只适用于计算指定值在序列中的排名,不适用于批量计算每个值的排名百分位,大量排名计算时可参考:

FAQQ计算序列中每个值的排位百分点的效率问题

QuartileOf

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect QuartileOf ( ['A'], 0) from Table1 end; // 返回结果5
RankOf

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect RankOf ( ['A'], 7) from Table1 end; // 返回结果2
TrimMeanOf

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return VSelect TrimMeanOf ( ['A'], 0.2) from Table1 end; // 返回结果6.5
FrequencyOf

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return vSelect FrequencyOf ( ['A'], array(6)) from Table1 end;
ProductOf

范例

Table1 := array(
('A':5, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return vSelect ProductOf ( ['A']) from Table1 end; // 结果1575
AGGVALUE

一般用于多字段聚集中,返回当前参与聚集的列的值。用法如 SUM( *,AggValue>10)。

示例:

t := Rand(10, array("A", "B", "C", "D"));
t1 := select selectopt(16) Maxof( * , AggValue < 0.5 ) from t end; // 统计每列中小于0.5的项的最大值
t2 := select selectopt(16) Maxof( 0 to 2, AggValue < 0.5 ) from t end; // 统计前3列中小于0.5的项的最大值

t1 与 t2 的结果展示:

其中SelectOpt(16)使指定列名与聚集列的列名保持一致

CHECKSUM_AGGOF

范例

Table1 := array(
('A':6, 'B':20, 'C':34),
('A':5, 'B':20, 'C':34),
('A':9, 'B':2, 'C':34),
('A':2, 'B':20, 'C':34),
('A':7, 'B':18, 'C':34));
return vSelect CHECKSUM_AGGOf(['C']) as'CHECKSUM_AGGOf'from Table1 end;
// 返回1849359852
AGGOF

范例

Table1 := array(
('A':6, 'B':20, 'C':1),
('A':5, 'B':20, 'C':2),
('A':9, 'B':2, 'C':3),
('A':2, 'B':20, 'C':4),
('A':7, 'B':18, 'C':5));
return vSelect aggof('AggSumSample', ['C']) from Table1 end; // 返回结果15
// 自定义的AggSumSample聚集函数。
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;

####### 聚集扩展函数的定义规范

function AggFunctionName(Flag:Integer;Value:[Boolean|Any]):[Boolean|Any];

说明

flag 的值表示调用该函数时的状态。

0聚集的初始化

value 为真则是 DISTINCT否则为所有。

返回为真表示成功,为假则失败。

1行数据

value 为当前行的表达式执行的值。

返回为真表示成功,为假则失败。

2聚集结束

返回聚集函数的执行结果。

范例:求和的聚集函数,其中利用系统参数来缓存数据。

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;
双序列统计聚集函数

####### Correlof

算法

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的相关系数

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect correlof(["价格"], ["大盘"])from data end;

####### Covof

算法

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的协方差

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect covof(["价格"], ["大盘"])from data end;

####### Slopeof

算法 or

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的回归斜率

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect Slopeof(["价格"], ["大盘"])from data end;

####### Interceptof

算法

其中b 为回归斜率

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的回归截距

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect Interceptof(["价格"], ["大盘"])from data end;

####### Rsqof

算法

其中r 即为相关系数

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的乘积矩相关系数的平方

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect RSQof(["价格"], ["大盘"])from data end;

####### Steyxof

算法

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的相对标准偏差

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect steyxof(["价格"], ["大盘"])from data end;

####### Slopeandinterceptof

算法

备注:移动条件统计时,满足条件长度的数据必须大于等于 2 范例

万科 A 在 2018/10/1~2018/10/30 日线收盘与大盘的回归斜率和截距

begt := 20181001T;
Endt := 20181030T;
Setsysparam(Pn_Stock(), "SZ000002");
dateArr := markettradedayQK(begt, Endt);
data := select thisrow as"日期",
Spec(Specdate(close(), thisrow), "SZ000002") as"价格",
SPec(Specdate(Close(), thisrow), "SH000001") as"大盘"
from dateArr
end;
return vselect slopeandinterceptof(["价格"], ["大盘"])from data end;
SQL 时间序列统计缓存标志与性能加速

技术时间序列统计相关聚集函数会自动进行数据缓存以进行加速,例如:

EMAOf(["close"],true,30)在执行的时候,每一步计算均会利用前一个计算的缓存。这样效率可以得到大大的提高,但是这个时候产生了一个问题,例如:

EMAOf(EMAOf(["close"],true,N),true,30),在这个外围 EMAOf 的计算中,由于内层 EMAOf 的参数 N 是不定的,所以外层 EMAOf 的缓存无法知道按照什么规则进行缓存,如果直接缓存的话,有可能在 N 的不同参数的调用时得到错误的结果(这种情况往往出现在将这些计算封装在一个函数中,在 SELECT 中来调用)。这个时候,我们通过给每个时间序列统计聚集函数增加了一个可选的参数,即一个缓存的字符串标志。这个字符串标志作用域仅仅用于当前调用的时间序列统计聚集函数,和其他调用的标志定义无关(也就是说可以和其他调用的标志定义重复也可以)。

这样说起来很拗口,我们可以来一个例子来说明:

function DEAOF(Field, Short, Long);
begin
    DEA := EMAOf(EMAOf([Field], true, Short, false, ""$Field) - EMAOf([Field], true, Long, false, ""$Field), true, M, false, ""$Field$" "$Short$" "$Long);
    return DEA;
end;

这个上述代码是使用 SQL 聚集函数实现 MACD 的定义中的一段代码,我们可以看到,对于 DEA 而言,由于 EMAOF 的内还有两个 EMA 的计算,分别和 SHORT 参数和 LONG 参数有关,我们可以将 SHORT 参数和 LONG 参数组成一个字符串来作为外层 EMAOF 的缓存标志串,这样,当具有缓存的时候,当 SHORT 和 LONG 进行了改变,我们不会利用其他参数组存贮的缓存数据来进行 EMAOF 的计算,也就是说我们只会用 SHORT 和 LONG 相同的计算缓存,这样就避免了缓存错误的问题。此外,我们在这里由于每个计算都和 FIELD 相关,所以每个的缓存标识串中均加入了 Field。这样我们就可以在 Select 中调用:

select DEAOF("close", 12, 30), DEAOF("open", 12, 30) from

…….而不会因为缓存错误而导致的结果问题。

如果我们没有进行函数封装,在绝大多数情况下,我们也可以忽略掉这个聚集函数的缓存标识参数。

INSERT 语句

将新行添加到数据库表或者内存表/矩阵中。

语法

INSERT [INTO]

<Expression | {<SQLTABLE | HUGESQLTABLE><SQL_Str>}>

[InsertFields([,…,FieldExpN])]

{valueExpression | {Values([,..,ValueExpN])}}

参数

[INTO]

一个可选的关键字,可以将它用在 INSERT 和目标表之间。

Expression

二维数组或者矩阵表达式,要插入的内存表。

<sqltable|hugesqltable><sql_str> of <Sql_Alias>

往 SQL 数据源里插入数据的时候sql_str 是 SQL 的字符串sql_alias 是 SQL 执行依赖的别名。

当数据集合特别庞大的时候,采用 hugesqltable 可以节省内存,但是速度可能会比 sqltable 慢很多。

有关数据库别名请参考ExecSQL

InsertFields

可选关键字,如果要指定 Values 关键字里的内容相应的字段,则采用 InsertFields([,…,FieldExpN])的模式。

FieldExp

在 InsertFields 关键字里的字段表达式,采用【字段名表达式】的模式,例如字段 ABCD 用["ABCD"]来表达。

ValueExpression

如果要批量插入数据,例如将一个 Select 的结果集插入一个表,或者直接将一个内存表的数据插入数据表,使用一个结果为数组的表达式。

也就是说既然可以使用类似于 SQL 语法的 Insert into AAA select _ from BBB end 的模式插入,也可以采用 Insert into AAA BBB 的模式。注意在这里 select _ from BBB end 返回的结果就是一个数组结果(通常是二维表结构)。

VALUES

引入要插入的数据值的列表。值的序列对应于 InsertFields如果已指定中或者表中的相应列位置。必须用圆括号将值列表括起来。

如果 VALUES 列表中的值与表中列的顺序不相同,那么必须使用 InsertFields 关键字明确地指定存储每个传入值的列。

UPDATE 语句

更新数据库表或者内存表/矩阵中的数据。

语法

UPDATE <Expression | {<SQLTABLE | HUGESQLTABLE><SQL_Str>}>

SET = ValueExp1[,…,FieldExpN = ValueExpN]

[WHERE WHEREEXP]

end

参数

Expression

二维数组或者矩阵表达式,要更新的内存表。

<sqltable|hugesqltable><sql_str> of <Sql_Alias>

往 SQL 数据源里更新数据的时候sql_str 是 SQL 的字符串sql_alias 是 SQL 执行依赖的别名。

当数据集合特别庞大的时候,采用 hugesqltable 可以节省内存,但是速度可能会比 sqltable 慢很多。

有关数据库别名请参考ExecSQL

SET

设置关键字

FieldExp

字段表达式,采用【字段名表达式】的模式,例如字段 ABCD 用["ABCD"]来表达。

ValueExp

对应于字段表达式的字段设置的值。

WHERE

条件更新的时候采用 WHERE 加上条件子句子,详细介绍请参照 SELECT 的 WHERE 子句。

DELETE 语句

删除数据库表或者内存表/矩阵中的数据。

语法

DELETE [DELETEOPT(Option)] FROM <Expression | {<SQLTABLE | HUGESQLTABLE><SQL_Str>}>

[WHERE WHEREEXP]

参数

DELETEOPT(Options)

指定 DELETEOPT 的选项,控制 DELETE 的行为

含义
0 默认值,删除时修订下标,较大的下标依次减一
1 不改变下标,例如删除下标 2 后,不将 3/4/5 改为 2/3/4

Expression

二维数组或者矩阵表达式,要更新的内存表。

<sqltable|hugesqltable><sql_str> of <Sql_Alias>

往 SQL 数据源里删除数据的时候sql_str 是 SQL 的字符串sql_alias 是 SQL 执行依赖的别名。

当数据集合特别庞大的时候,采用 hugesqltable 可以节省内存,但是速度可能会比 sqltable 慢很多。

有关数据库别名请参考ExecSQL

WHERE

条件删除的时候采用 WHERE 加上条件子句子,详细介绍请参照 SELECT 的 WHERE 子句。

数据库操作以及错误信息返回

例如:利用 INSERT 语法操作数据库返回的结果为真或者假,当为假的时候,到底错误内容是什么呢?系统提供了数据库错误信息返回的函数。

参见SQLErrorMsg,数据库访问函数

SQL 语法支持对象处理

TSL 的 SQL 语法支持对按照规范开发的对象进行处理,典型应用:如编写结构化文件的处理类使得可利用 SQL 进行查询。

以下是 SQL 语法识别的方法,不是所有的方法都必需实现,例如仅仅只查询就不需要实现编辑,删除等方法:

TSQLInsert

范例 范例中仅声明了使用到的方法

D := new selobj();
insert into D insertfields(["a"], ["b"], ["c"], ["d"]) values(1, 2, 3, 4);
return D.data(); // return D.FData;
type selobj = class
// ******数组变量和下标变量********
FData;
FIndex;
// ******数据处理方法**************
public
function create();
begin
    FData := array();
    FIndex := -1;
end;
function TSQLSetValue(Key, Value):Boolean;
begin
    FData[FIndex][Key] := Value;
    return true;
end;
function TSQLInsert():Boolean;
begin
    FIndex := length(FData);
    return true;
end;
function data();
begin
    return fdata;
end;
end;
TSQLSetValue

范例 见 TSQLInsert

TSQLBatchInsert

范例

范例 1新建对象 D插入 5 行 3 列随机数

D := new selobj();
insert into D rand(5, array('a', 'b', 'c'));
return D.data(); // return D.FData;
type selobj = class
// ******数组变量和下标变量********
FData;
FIndex;
// ******数据处理方法**************
public
function create();
begin
    FData := array();
    FIndex := -1;
end;
function TSQLBatchInsert(V);
begin
    FData& = V;
    return true;
end;
function data();
begin
    return fdata;
end;
end;

范例 2新建对象 D插入 1 行 4 列指定数据后,再插入 5 行 3 列随机数。

D := new selobj();
insert into D insertfields(["a"], ["b"], ["c"], ["d"]) values(1, 2, 3, 4);
insert into D rand(5, array('a', 'b', 'c'));
return D.data(); // return D.FData;
type selobj = class
// ******数组变量和下标变量********
FData;
FIndex;
// ******数据处理方法**************
public
function create();
begin
    FData := array();
    FIndex := -1;
end;
function TSQLBatchInsert(V);
begin
    FData& = V;
    return true;
end;
function TSQLSetValue(Key, Value):Boolean;
begin
    FData[FIndex][Key] := Value;
    return true;
end;
function TSQLInsert():Boolean;
begin
    FIndex := length(FData);
    return true;
end;
function data();
begin
    return fdata;
end;
end;
TSQLEdit
TSQLPost
TSQLFinal
TSQLFields
TSQLDelete

范例

插入数据后,对数据进行条件删除操作

D := new selobj();
insert into D insertfields(["a"], ["b"], ["c"], ["d"]) values('delete', 2, 3, 4);
insert into D rand(3, array('a', 'b', 'c'));
delete from D where ["a"] = 'delete';
return D.data(); // return D.FData;
type selobj = class
// ******数组变量和下标变量********
FData;
FIndex;
// ******数据处理方法**************
public
function TSQLBatchInsert(V);
begin
    FData& = V;
    return true;
end;
function create();
begin
    FData := array();
    FIndex := -1;
end;
function TSQLDataFirst(var Cursor:Integer):Boolean;
begin
    Cursor := 0;
    FIndex := 0;
    return length(FData) <> 0;
end;
function TSQLDataNext(var Cursor:Integer):Boolean;
begin
    Cursor := ++FIndex;
    return FIndex < length(FData);
end;
function TSQLEvalValue(Key;var Value):Boolean;
begin
    ret := (FIndex >= 0) and (FIndex < Length(FData));
    if ret then Value := FData[FIndex][Key];
    return ret;
end;
function TSQLSetValue(Key, Value):Boolean;
begin
    FData[FIndex][Key] := Value;
    return true;
end;
function TSQLSetCursor(Cursor:Integer):Boolean;
begin
    FIndex := Cursor;
    return true;
end;
function TSQLDelete(var Cursor:Integer):Boolean;
begin
    DeleteIndex(FData, FIndex);
    Cursor := FIndex;
    return true;
end;
function TSQLInsert():Boolean;
begin
    FIndex := length(FData);
    return true;
end;
function data();
begin
    return fdata;
end;
end;
TSQLEvalValue

范例 参考方法 TSQLDelete 下的使用范例

TSQLSetCursor

范例 参考方法 TSQLDelete 下的使用范例

TSQLDataFirst

范例 参考方法 TSQLDelete 下的使用范例

TSQLDataNext

范例 参考方法 TSQLDelete 下的使用范例

TSQLDataPrev
  • TSQLInsert
  • TSQLSetValue
  • TSQLBatchInsert
  • TSQLEdit
  • TSQLPost
  • TSQLFinal
  • TSQLFields
  • TSQLDelete
  • TSQLEvalValue
  • TSQLSetCursor
  • TSQLDataFirst
  • TSQLDataNext
  • TSQLDataPrev
  • TSQLSetDataKey
  • TSQLThisRow
  • TSQLSetThisRow
  • TSQLThisRowIndex
  • TSQLGetCurrentDate
TSQLSetDataKey

说明:设置 KEY 值。

TSQLThisRow
  • TSQLInsert
  • TSQLSetValue
  • TSQLBatchInsert
  • TSQLEdit
  • TSQLPost
  • TSQLFinal
  • TSQLFields
  • TSQLDelete
  • TSQLEvalValue
  • TSQLSetCursor
  • TSQLDataFirst
  • TSQLDataNext
  • TSQLDataPrev
  • TSQLSetDataKey
  • TSQLThisRow
  • TSQLSetThisRow
  • TSQLThisRowIndex
  • TSQLGetCurrentDate
TSQLSetThisRow
  • TSQLInsert
  • TSQLSetValue
  • TSQLBatchInsert
  • TSQLEdit
  • TSQLPost
  • TSQLFinal
  • TSQLFields
  • TSQLDelete
  • TSQLEvalValue
  • TSQLSetCursor
  • TSQLDataFirst
  • TSQLDataNext
  • TSQLDataPrev
  • TSQLSetDataKey
  • TSQLThisRow
  • TSQLSetThisRow
  • TSQLThisRowIndex
  • TSQLGetCurrentDate
TSQLThisRowIndex
  • TSQLInsert
  • TSQLSetValue
  • TSQLBatchInsert
  • TSQLEdit
  • TSQLPost
  • TSQLFinal
  • TSQLFields
  • TSQLDelete
  • TSQLEvalValue
  • TSQLSetCursor
  • TSQLDataFirst
  • TSQLDataNext
  • TSQLDataPrev
  • TSQLSetDataKey
  • TSQLThisRow
  • TSQLSetThisRow
  • TSQLThisRowIndex
  • TSQLGetCurrentDate
TSQLGetCurrentDate

说明:获取当前日期。

TS-SQL 入门

select 查询语句

在 SQL 语言中,最最常用的就是 select 子句了select 子句提供以行为单位进行查找返回,排序,分组,聚集等等功能。

首先select 是以行为单位来筛选结果集的绝大多数情况下会根据列的内容进行计算并筛选select 可以选择原有的列,也可以生成计算后的新列。

其次select 可以对结果集进行排序,排序可以对多个列以及多个计算(一般都是利用列进行计算)进行逆序或者正序进行排序,并且绝大多数的 SQL 均实现了可以取部分结果集(例如前 N 行,从第 M 行到第 N 行等)。

再次select 支持分组,什么叫分组呢?分组就是以行为单位把某些列或者某些计算的结果,将这些列或者计算完全相同的组成分组,分组后的结果可以用聚集函数等进行各个分组的计算,例如每个分组的个数,分组后的某个字段的平均值等等。

select 支持聚集,聚集的含义可以理解为对结果集或者分组进行整体统计,例如对字段求和,求平均等等。

下边我们来看看 select 到底如何使用。

利用 select 返回列和进行列计算

假定我们有一个二维数组成绩表 ScoreList有语文成绩数学成绩英语成绩历史成绩地理成绩等等结果如下

学号姓名英语成绩语文成绩数学成绩 ....

01 张三 80 90 ..

02 李四 60 70 ..

03 王五 90 80 ..

04 赵六 50 90 ..

05 钱七 88 89 ..

假定我们要从这个成绩表中生成一个英语成绩表,只需要英语成绩和学号,姓名列,如果用矩阵计算的子矩阵,我们可以使用如下方法:

ScoreList[:,array("学号","姓名","英语成绩")]

但是如果在查询的过程中还需要返回计算的内容,那么这种直接取子矩阵的算符就无法支持了。例如,我们需要获得英语成绩以及文科平均成绩,这个时候 select 就起到作用了。

我们来看一下上边的需求使用 select 是如何实现的:

R1 := select \ * , RoundTo((["英语成绩"] + ["语文成绩"] + ["历史成绩"] + ["地理成绩"]) / 4, 2)
as"文科成绩"from ScoreList end;

R1 的结果集在原有结果集的基础上增加了文科成绩两列,我们可以看到 select 的使用的方法select 和 from 之间是所需要的返回内容列表,多个返回内容之间用逗号进行间隔,*代表原结果集的所有列from 后则跟着所需查询的结果集,最后 select 以 end 作为结束。而每个返回的内容可以使用 as 列名来标示返回的字段列也可以省略一旦省略select 会自动安排列名(如果返回内容是[]表达式,则列名则和原列名保持一致,否则列名会自动用"Expr1","Expr2"..替代As 后的列名可以是字符串,也可以是整数。

我们从例子中可以看到,返回的内容中支持计算,允许调用 TSL 支持的各类函数(在例子中我们调用了舍入函数 RoundTo而访问列值则是采用[]来访问的,在方括号中我们加入字段下标即可。(字段下标可以是常量,也可以是变量,对于整数下标的矩阵,我们用[0]就表示下标为 0 的列)。

Select as nil 语法

select 在指定列使用 as nil 语法则该列不返回。

例如:

return select ['stockid'], ['date'], ['close'] * ['vol'] as nil from markettable datekey 20180801T to 20180801T of'SH000001'end;
where 进行条件查询

假如我们有一个二维数组 EnglishScore结构如下

学号姓名英语成绩

01 张三 80

02 李四 60

03 王五 90

04 赵六 50

05 钱七 88

我们现在有一个需求,我们需要查找出成绩大于 85 分的人的情况,并存贮在数组 B 中。通过我们已经掌握的知识,我们知道,通过一个循环语句可以解决这个问题。

假定 EnglishScore 是一个二维数组并已经存贮了英语成绩表。

B := array();
Index := 0;
for i := 0 to length(EnglishScore) - 1 do
begin
    if EnglishScore [i]["英语成绩"] > 85 then
    begin
        B[Index] := EnglishScore [i]; // 将A的i行的内容赋给B的Index行
        Index++;
    end;
end;

这样我们得到了一个 B存贮的是英语成绩大于 85 的英语成绩信息。

我们来看另外一种写法:

B := select \ * from EnglishScore where ["英语成绩"] > 85 end;

这样看起来是不是很简洁呢我们是否发现这种写法更容易理解呢事实上SQL 语法比较接近自然语言,我们可以把以上语法理解为:查询所有的从 EnglishScore 中的英语成绩> 85 的记录。

select from 结果集之后,允许跟 where 以及一个查询条件,这个约定俗成称之为 Where 子句。

TS-SQL 使用数组下标访问字段;当字段名是字符串时需要写成 ["字段名"]。例如:["英语成绩"]

TS-SQL 的 select 语句以 end 结束,外层语句仍以分号 ; 结束。

Order By 对返回的结果集进行排序

假如我们除了需要查询出数据,并且还要对查询的结果的英语成绩从小到大进行排序,我们没办法再用简单的几行程序直接写出来了。因为排序牵涉到排序的算法,我们可能会封装一个排序的算法,假定叫 SortFunction我们用最简单的冒泡排序为例

function SortFunction(Arr, SortField);
begin
    for i := 0 to length(Arr) - 1 do for j := I to length(Arr) - 1 do
    begin
        if Arr[i][SortField] > Arr[j][SortField] then
        begin
            swap := Arr[i];
            Arr[i] := Arr[j];
            Arr[j] := swap;
        end;
    end;
end;

那么在对结果 B 我们可以使用 SortFunction(B,"英语成绩");从我们实现查询到排序,总共使用了 10 好几条语句。

我们再看下 SQL 的写法:

B := select \ * from EnglishScore where ["英语成绩"] > 85 order by ["英语成绩"] end;

也就是说SQL 允许在 where 子句之后(或者省略 where 子句返回所有),可以带 order by 排序的表达式。order by 默认是从小到大排序的事实上order by 允许对多个字段同时进行排序,并且支持从大到小排序。我们如果要对 ScoreList 的语文成绩正序排序,然后对英语成绩和语文成绩的比值进行逆序排列,我们可以如下写:

B := select \ * from ScoreList order by ["语文成绩"] asc, ["英语成绩"] / ["语文成绩"]
desc end;

也就是说 order by 后的排序表达式之后允许跟 asc 或者 desc 来描述排序的方向,缺省为正序(asc 可以省略),而 desc 则代表逆序,并且 order by 之后支持多个排序,多个排序表达式和排序方向之间使用逗号进行分割。

ThisOrder 获得排序位置问题
A := select \ * , ThisOrder as"Order"from A order by ["AAA"] end;

这样可以得到["AAA"]排序的排序位置,该排序位置从 1 开始,与 ThisRowIndex 不同的是:相同的值排序号相同,且不一定连续,例如出现了连续两个排名第一,就没有第二,直接到第三名,这样更加严谨和科学。

DRange 返回结果集中的一部分

只需要英语成绩前十名时,可先取结果集再截取,或直接使用 DRange。

b := select * from EnglishScore order by ["英语成绩"] desc end;
b := b[0:9];

TS-SQL 提供 DRange 关键字:

b := select DRange(0 to 9) * from EnglishScore order by ["英语成绩"] desc end;

DRange 的用法是 DRange(from_index to to_index),索引从 0 开始;可用负数表示倒数,例如 DRange(-10 to -1) 表示最后十条。

等分法示例(前 10%

b := select DRange(1 of 10) from EnglishScore order by ["英语成绩"] desc end;
SELECT 统计与分组、聚集函数

通过从上面我们看到的例子我们已经体会到了 SQL 的优越性,我们现在再来一个新的需求。假定我们现在要统计出班级的平均分。当然,我们同样可以采用循环来做:

SUM := 0;
for I := 0 to length(EnglishScore) - 1 do
begin
    SUM += EnglishScore[I];
end;
AVG := SUM / length(EnglishScore); // AVG 里存贮英语成绩没及格

同样的,我们也可以把这个功能封装起来:

function AvgFunction(A, Field);
begin
    SUM := 0;
    for I := 0 to length(A) - 1 do
    begin
        SUM := SUM + A[I][Field];
    end;
    AVG := SUM / length(A);
    return AVG;
end;

我们调用 AvgFunction(EnglishScore,"英语成绩")就可以得到平均值了。

在 SQL 里,这类需求的实现统称为聚集函数。

我们采用 select AvgOf(["英语成绩"]) from EnglishScore end;就可以完成上述功能。

当然我们需要统计 85 分以上的成绩的平均成绩的时候,使用 select AvgOf(["英语成绩"]) from EnglishScore where ["英语成绩"]>85 end 就可以了。

除了 AvgOf 求出平均数以外,还有例如 Sumof 可以求和Countof 可以求个数Maxof、MinOf 求最大最小值等等数十个聚集函数,可以解决常用的统计功能,用户还可以使用 TS-SQL 的 AggOf 来扩展聚集函数,关于聚集函数的扩展我们在稍后章节会另行讲述。

Group by 进行分组与 Having 进行分组后的结果筛选

如果我们现在的需求进行了一个变化,我们需要得到的是不及格的人数和平均分,中等 60-70 分的人数和平均分,良好 70-85 的人数和平均分85 以上优秀的平均分。

这样的需要我们可以采用多条 SELECT 语句来分别实现各个成绩段的统计。但是 SQL 同样提供了直接的方法。我们看以下的写法:

return select AvgOf(["英语成绩"]), CountOf( \ * ), groupfunc(["英语成绩"]) from
EnglishScore group by groupfunc(["英语成绩"]) end;
function groupfunc(score);
begin
    if score < 60 then return "不及格"
    else if Score < 70 then return "中等"
    else if Score < 85 then return "良好"
    else return "优秀";
end;

上述代码我们可以如此理解groupfunc 只是一个自己定义的函数,用途仅仅只是为了上面的分组需要,因为英语成绩表中并没有什么是优秀什么是及格的标准。

GroupFunc 把成绩分成了四档,而 group by 则把这四档进行分组Avgof 和 countof 对分组的内容进行聚集计算。如何理解呢Avgof 在没有 group by 的时候是对整个结果集进行处理,而有 group by 的时候是对分组后的每个子结果集进行运算处理。

上述代码的返回结果为:

如果我们需要的内容是返回个数>1 的。那么我们的语句为:

return select AvgOf(["英语成绩"]), CountOf( _ ), groupfunc(["英语成绩"]) from
EnglishScore group by groupfunc(["英语成绩"]) having CountOf( _ ) > 1 end;

运行结果如下:

Having 和 Where 是不是很类似呢?许多初学 SQL 的人会很容易混淆两者的差异,事实上 Having 可以用聚集函数作为条件,而 Where 是不行的Having 是先 group 分组再计算 having 条件,而 where 则是最先开始进行条件筛选。在 group by 之前是允许有 where 条件的。

Group By 支持对多个值进行分组

Group By 支持对多个值进行分组,假定 EnglishScore 中还包括一个字段,是学生的性别,我们要分别统计男女性别的成绩分布,我们可以这样写:

return select ["性别"], AvgOf(["英语成绩"]), CountOf( \ * ), groupfunc(["英语成绩"])
from EnglishScore group by ["性别"], groupfunc(["英语成绩"]) end;

毋庸多说Group By 支持多值的分组只要将多个用于分组的计算在 Group By 后用逗号分隔即可。

join 处理多表联接查询

往往我们有需求对多个结果集的数据通过一定规则整合成另外一个结果集。我们来看一个案例:

假定我们有结果集 A

学号姓名

01 张三

02 李四

03 王五

04 赵六

05 钱七

结果集 B

学号英语成绩

01 80

02 60

04 90

03 50

05 88

我们现在要合成一个同时具备学号,姓名,英语成绩的结果集,这类需求我们在 SQL 中称之为多表联接,通过 join 关键字来支持:

R := select [1].\ * , [2].["英语成绩"] from A join B on [1].["学号"] = [2].["学号"]
end;

R 的结果就是我们要的结果集,我们来理解下多表查询和单表查询的差异:

首先是访问字段的表达方式有变化:我们以[2].["英语成绩"]来访问 B 的英语成绩列,用[1].["学号"]来表示了 A 结果集的学号,[2].["学号"]来表示 B 结果集的学号,而[1].*则表示了 A 结果集的所有字段。这个规则是什么呢?当存在有多表的时候,我们采用[TableOrder].[TableField]的模式来访问表的字段,而 TableOrder 则是表(结果集)在 select 中出现的次序(该次序从 1 开始),如果要返回某个表所有的字段,则采用[TableOrder].*的模式。

注意:如果没有字段重名的问题,*可以表达所有字段,而["英语成绩"]也可以自动选择到包含有英语成绩的表(结果集),如果有字段重复,我们建议带[TableOrder].前缀指定表。

其次是 from 字句里引入了 join 和 on 关键字A join B表示 B 和 A 进行联接,而表与表之间进行联接的时候,必需具有联接的条件,这个条件在 on 关键字之后,上边的范例中 on [1].["学号"]=[2].["学号"]就是联接的条件。由于 select 是基于行的,所以 join 是基于行的联接,而 on 则是行联接的条件。范例的含义为:将 A 表中的学号和 B 中的学号相同的行联接起来,并返回 A 中所有列以及 B 中的英语成绩列。

TS-SQL 支持 JOINLeft Join,Right Join,Full Join,cross Join 等完整的 SQL 规范,访问多表用[1].表示第一张表, [2].表示第二张,以此类推

Cross Join 与 Join

在进行表联接的时候,我们可能需要的是一个笛卡尔积,这种情况下我们可以采用 cross join这样可以得到多结果集所有行的完整排列组合。cross join 不支持 on 语法,但是支持 where 子句。我们一样可以用 cross join 来替代 join 语句。

r := select [1] . * , [2].["英语成绩"] from A join B
on [1].["学号"] = [2].["学号"]
end;

可以用下面的语句替换:

r := select [1] . * , [2].["英语成绩"] from A cross join B
where [1].["学号"] = [2].["学号"]
end;

Cross join 还有另外一种写法,就是用逗号“,”替换 corss join

r := select [1] . * , [2].["英语成绩"] from A, B
where [1].["学号"] = [2].["学号"]
end;

在这里,我们有一个疑问,就是既然 cross join 可以替代掉 join 的功能,那么他们两者的差异是什么呢?

首先是语法差异join 必须有 on 子句,且 on 之后仍允许使用 where 子句二次过滤。例如:

r := select [1] . * , [2].["英语成绩"] from A join B
on [1].["学号"] = [2].["学号"]
where [1].["英语成绩"] > 80
end;

而 cross join 不支持 on 子句(可以有或没有 where 子句)。

其次在使用的用途上有差异。cross join 是笛卡尔积,而 join 的本意是根据某些条件进行联接。

再次在效率上Join 具有优化的写法,而 cross join 则无法提高效率。

With On 实现 Join On 的优化

With on 是 on 语法的增强。

一个 On 运算符在 JOIN 的时候的计算复杂度为两个运算的表的元素个数相乘,是一个 N*M 的复杂度。事实上,绝大多数 JOIN 是可以优化的。我们 TSL 里支持 WITH ON 语法,使得计算复杂度降低到 N+M 的关系

在联接的时候,如果 on 的条件是一个或者多个等式约束的与在这种时候Join 的本身可以被优化TS-SQL 为这种类型的优化提供了新的语法。则范例代码

R := select [1] . * , [2].["英语成绩"] from A join B
on [1].["学号"] = [2].["学号"]
end;

可以写成:

R := select [1] . * , [2].["英语成绩"] from A join B
with ([1].["学号"] on [2].["学号"])
end;

假如有多个等式约束,那么把 ON 左边以及右边的多个表达式用逗号分隔开来。

假定除了学号以外,还有一个班级号字段,联接的时候需要使用班级号和学号一起来作为联接的条件,上述的代码可以这么写:

R := select [1] . * , [2].["英语成绩"] from A join B
with ([1].["学号"], [1].["班级"] on [2].["学号"], [2].["班级"])
end;

通过上述范例with on 的语法定义为:

with(等式左表达式组 on 等式右表达式组)

左右表达式组中的多个表达式用逗号“,”分隔。

采用这种 with on 的语法可以把笛卡尔积的计算复杂度降低到线性复杂度(参与运算的两个表的大小之和)。

多表 Join(超过两个结果集的 Join)

如果有三个表,假定还有一个语文成绩表 C我们要合并学号英语成绩语文成绩如何做呢

我们看一个范例大家自己来理解:

R := select [1].\ * , [2].["英语成绩"], [3].["语文成绩"] from A join B on
[1].["学号"] = [2].["学号"] join C on [1].["学号"] = [3].["学号"] end;

我们可以认为 join 后的结果集形成了一个新的结果集,这个结果集还可以继续和其他的结果集继续进行联接,用这种方式可以实现几乎任意多张表的联接。

读者必须了解到join 的计算复杂度很大(使用 with on 可以降低),许多张表的 join 也会大大降低代码的可读性,所以在使用 join 带来的便利的时候,当遇到性能问题的时候,我们可以考虑利用数组的优势来解决性能问题。

例:

BC 结果集我们可以先构造出 B1 和 C1 的结果集B1 的结果集和 C1 的结果是以学号为下标值为分数的数组,然后对 A 结果集进行循环直接利用学号访问 B1C1 结果集的值即可。

Left Join,Right Join,Full Join

####### Left Join

Join 产生的结果集可以理解为笛卡尔积中符合条件的行的集合,在实际应用中,我们经常会遇到 Join 无法解决的联接问题。

// 假定上边的案例中的结果集 C 包括的不是语文成绩,而是俄语成绩。而 A 结果集中所包括的学号包括所有学生,而这些学生有的选择的外语是英语,有的选择的是俄语,而我们现在需要汇总一份成绩表。由于 B 结果集和 C 结果集中的学号都不完整,如果使用 R:=select

[1]._,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"]

end;则 R 结果集中将不会包括没有英语成绩的学号,而采用 R := select

[1]._,[2].["英语成绩"],[3].["俄语成绩"] from A join B on [1].["学号"]=[2].["学号"] join C on [1].["学号"]=[3].["学号"]

end;则 R 的结果会是一个空集, 因为有俄语成绩的人没有英语成绩,有英语成绩的人没有俄语成绩。这可能和我们预期的结果是不一致的,我们或许需要的结果集是这样的:包括所有学生,没有俄语成绩的俄语成绩保留为空,没有英语成绩的英语成绩保留为空。

如何解决这个问题呢?我们来看一段修正后的代码:

R := select [1].\ * , [2].["英语成绩"], [3].["俄语成绩"] from A Left join B on
[1].["学号"] = [2].["学号"] Left join C on [1].["学号"] = [3].["学号"] end;

在这里,我们采用了 Left Join 替换了 Join。什么是 Left Join 呢?与 Join 不同的是Left Join 是以左结果集为基准,如果右结果集缺乏满足和左结果集中的某行联接的条件的行,那么依旧保留一条左结果集中的行,而联接的右结果集假定为空(所有字段的值为 NIL

LEFT JOIN 的结果显然吻合我们的预期,这样的结果集中包括所有 A 结果集中的学生,而没有成绩的学生保留为空。

####### Right Join

Right Join 可以完全参照 Left Join唯一不同的是 Right Join 是以右结果集为基准的。

####### Full Join

Full Join 可以参照 Left Join 和 Right Join不同之处是结合了 Left Join 和 Right Join在在 Join 的结果集中增加了左右结果集中未能在 Join 结果集内的行,其处理规则和 Left Join 类同。

典型的应用:

// 如果以上的例子只有 BC 结果集,而没有 A 结果集B 结果集为英语成绩C 为俄语成绩,那么用 R:=select

["学号"],[1].["英语成绩"],[2].["俄语成绩"] from B full join C on [1].["学号"]=[2].["学号"]

end;就可以合并成一个整合的成绩表,除了同时修两门外语的人有两门成绩,否则只有一门成绩有内容,而另外一门成绩为空。

在这里,我们需要注意一点,即["学号"]的处理,由于结果中行对应的左右结果集均可能为空行,所以不能在返回中指定某个结果集的学号来返回,缺省使用字段而不带[TableOrder]可以自动在多个表中根据字段名获得一个非空的结果。

当然,我们也可以用如下代码来替代:

R := select [1].["学号"]?:[2].["学号"] as"学号", [1].["英语成绩"], [2].["俄语成绩"]
from B full join C on [1].["学号"] = [2].["学号"] end;

当参与计算的表更多的时候,显然缺省["学号"]的表达方式会显得更为简洁有效。

TS-SQL 的数据更新

我们经常会有数据更新的需求,有时候需要更正数据,有的时候需要删除数据,另一些时候则需要插入数据。SQL 中的 Update,Delete,Insert 就是为了这类需求而生的。

在这里,我们需要注意:数据更新是直接在原始的结果集的基础之上进行更新,而数据查询则是对原始结果集查询产生新的结果集。对于刚刚入门的菜鸟而言,这一点需要牢记。

Delete 删除结果中符合的行

SQL 提供了 Delete 来实现删除结果集中符合条件行,我们直接来看一个删除的语句:

Delete from A where ["学号"] = "01";

这个无需多加解释,删除 A 中符合 where 子句中条件的记录。在这里,有一个需要提醒,就是可以省略 where 子句

Delete From A;这同样也是正确的。

// 问题来了,假定我们对一个结果集设置为空值,直接使用 A:=array();就可以了,为什么还需要使用 Delete 语句呢?

其实Delete 不仅仅支持对结果集进行操作,在后边章节里我们还会提到 Delete 还可以支撑对实体数据库的表进行操作,在那种时候才具有现实意义。

Insert 插入结果集

SQL 提供了在结果集中添加数据的功能,这通过 Insert 语句来实现。

在 TS-SQL 中,除非操作的对象是数据库/支持 TS-SQL 的对象,在普通数据操作上基本上没有必要使用 Insert因为 Union 操作就可以起到一样的效果,如果需要对原结果集进行更改,采用&=操作符就可以实现和 Insert 可以完成的功能。但,对于 SQL 而言Insert 语法尤为重要,因为数据库的数据插入大多数都是使用 Insert 语法来完成的。

Insert 主要有两种类型,一种是一次插入一行,另外一种则是批量插入。

TS-SQL 的 Insert 语法和标准的 SQL 基本相同,也有相异的地方,例如 TS-SQL 中引入的 insertfields 关键字就是 TS-SQL 独特语法。

Insert 插入单行

我们先来看一个插入行的例子:

insert into a values("06", "路人甲");

我们先温习下之前的范例中的 A 结果集A 结果集是包括学号和姓名两列的结果集,上边的代码是否足够清晰呢?

Insert Into 后跟随结果集(需要插入的对象),在结果集之后用 values()values 之中则是所需要插入的按照列次序以逗号分隔的数据。

我们现在来假定我们有一个结果集 R这个结果集 R 具有四列,分别是学号,姓名,英语成绩,语文成绩。假定现在英语老师带来了路人甲的英语成绩,但结果集 R 上目前并未有路人甲的其他信息,现在我们希望将路人甲以及英语信息先行添加进结果集 R用 INSERT 语句我们该怎么办呢?

insert into a values("06", "路人甲", 80, nil);

我们当然可以使用上边的代码来实现,也就是我们让语文成绩先置为空。接下来问题又来了,假定我们的结果集 R 有 10 列,分别是学号和姓名外加英语成绩,语文成绩,物理成绩,化学成绩,数学成绩。。。。等等。

我们如果用上述的写法,我们需要记住每个列的位置,并且要给非需要的列以 nil 替代,这肯定不是一件让人心满意足的写法,这种时候,我们可以用 insert 的另外一种写法:

insert into a insertfields(["学号"],["姓名"],["英语成绩"])

values("06", "路人甲", 80);

这看起来是不是简洁并且难以出错呢?这个语法是我们在 values 之前先用 insertfields(FieldsList)的模式来指定所需要插入的字段FieldsList 则采用逗号分隔的[FieldName]的模式,而 values 里的 ValueList 则需要和 InsertFields 中的字段次序保持一致。

Insert 插入多行的批量插入

假定我们有 A1 结果集,这个结果集含有多个学号和姓名,我们需要加入到结果集 A 中。

A1 := array(("学号":"07", "姓名":"路人乙"), ("学号":"08", "姓名":"路人丙"));
Insert Into A A1;

对于 TSL 的结果集而言,上边的写法可以用 A&=A1;来替代,但是对于数据库的插入而言,则显然无法使用&=操作符号,这个时候就必需使用 Insert 语法了。

Insert 指定字段插入

可以用 insertfields([字段 1],[字段 2],...)指定字段列表,每个字段名用[]括起来.

如:

A := array((1, 2, 3), (2, 3, 4));
insert into a
insertfields([0], [1])
values(3, 4);

A 的结果为 array((1, 2, 3), (2, 3, 4), (3, 4));

例子中特意使用数字下标的字段,让大家清楚理解其工作原理。

如果只插入一行,则用 values 关键字在()中逐个将字段的内容用逗号分隔。

也支持指定字段进行批量插入,如:

A := array((1, 2, 3), (2, 3, 4));
B := array((3, 4), (4, 5));
Insert into A insertfields([0], [1]) B;

在批量插入的时候,我们插入的内容可以是一个二维数组

Insert 对一维数组的插入

一维数组利用 insertfields([thisrow]) values(v)来插入单个值。

由于默认假定 INSERT values 插入的都是二维结构,也就是插入的是一行多列的数据,因此对于一维数组直接 insert into a value(3)会得到预料外的结果,正确例子:

A := array(1, 2);
insert into A
insertfields([thisrow]) values(3)
;

结果为 array(1,2,3)

也支持批量插入,批量方式支持两种方式,如:

A := array(1, 2);
insert into A
array(3, 4, 5)
;

A 的结果就是 array(1,2,3,4,5)

A := array(1, 2);
insert into A
insertfields([thisrow]) array(3, 4, 5)
;

A 的结果与前面示例完全一致

Update 更新结果集

我们经常会有行记录的数据更新需求Update 就是为了这种需求而产生的。

例一:将列 0 设置成当前行下标,从 0 到 99

A := Rand(100,10);
Update A
set [0]=ThisRowIndex
end;
// 我们可以看到set后的字段与insert的insertfields类似需要将字段名放在[]来描述
// 例二:设置的值可以字段计算
[code]
A := Rand(100,10);
Update A
set [0]=[1]+[2]+[3]
end;

这样我们把第 0 列的值设置成 1,2,3 列之和

Update 更新结果集中符合条件的值

在 update 语句中,可以通过增加 where 语句对指定符合条件的行进行更新。

如:

a := array((1, 2), (2, 3), (-1, 4));
update a set [0] = 0, [1] =  - [1] where [0] < 0 end; // 把 0 列小于 0 的值更新成 0并把 1 列取反

结果为 array((1,2),(2,3),(1,-4))

我们继续沿用上边所用的结果集,假定我们发现学号为 03 的英语成绩应该修订为 79 分,我们可以用如下代码来处理:

update b set ["英语成绩"] = 79 where ["学号"] = "03"end;

看起来语法很简单Update 之后 set 之前是需要更新的结果集,在 set 之后用[FieldName]=Value 的模式将值更新给符合条件的行的 FieldName 字段,而条件则和 select 一样使用 where 子句。

不用多加解释,但还需要关注三个问题:如果更新不需要条件?如果一次更新多个字段?如果字段不存在?

第一个问题:不需要条件时可省略 where 子句。例如把“合法标示”统一设为 1

update r set ["合法标示"] = 1 end;

也可以按表达式更新所有行,例如将英语成绩开根号后乘以 10

update b set ["英语成绩"] = sqrt(["英语成绩"]) * 10 end;

第二个问题Update 支持多个字段的同时更新,例如:

update b set ["英语成绩"] = 79, ["语文成绩"] = 80 where ["学号"] = "03"end;

即允许存在多个[FieldName]=Value之间使用逗号","进行分隔。

第三个问题,如果 TS-SQL 更新的内容是内存的结果集(而非实体数据库),而需要更新字段不存在,会自动产生新列存贮更新的数据,上边的第二个问题的 B 结果集则自动增加了"英语成绩"列。

Update 对一维数组更新

对一维数组的更新可以用[ThisRow]替代列标,如将数据求绝对值的实现:

A := array(1, 2, 3, -4, -5);
Update A set [ThisRow] = abs(ThisRow) end;
Update 新增列

Update 对数组操作可以允许更新原数组不存在的列,这样操作的结果就是新增该列。

例如:新增列"c"为两列和,而"d"列为行号。

A := rand(10, array("a", "b"));
Update A set ["c"] = ["a"] + ["b"], ["d"] = ThisRowIndex end;

TS_SQL [@Field]语法

SQL 中[@Field]可以获得字段的数据类型。

例如:

t := select ['stockid'], [@'stockid'] , ['date'], [@'date'] as'字段stockid的类型', ['date'] from
markettable datekey 20180801T to 20180801T of'SH000001'end;
update t set ['A'] = [@'stockid'] end;
return t;

TS-SQL 进阶

TS-SQL 和一维数组

SQL 语言是为了更简便地处理二维表结构数据而出现的,我们在之前所有的 TS-SQL 应用范例中,所操作的数据亦均为二维结果集。对 TSL 语言中大量存在的一维数组TS-SQL 是否可以支持呢?答案是肯定的。

在这里我们引入了一个关键字ThisRow。

ThisRow 和一维数组

为什么不能使用标准的 SQL 语句访问一维数组呢?原因是很简单的,因为在标准 SQL 中,是通过字段来访问数据的,例如在 TS-SQL 中,使用["英语成绩"]来访问、操作表中的英语成绩。没有了列,这种访问就无法表达了。

在这种情况下TS-SQL 引入了关键字 ThisRow顾名思义ThisRow 可以访问当前行的数据而对于一维数组整行数据就是数组项的值。利用这种特性ThisRow 可以支撑 select,update,insert,delete 对一维数组的访问和处理。我们看一个范例:

R := array(1, 2, 3, 4, 5, 6);
R1 := select ThisRow from R where ThisRow > 5 end;
// 该 R1 的结果为array(("Expr1":6))
update R set thisrow = thisrow\ * 10 end;
// R 的结果为array(10,20,30,40,50,60)
insert into R insertfields(thisrow) values(70);
// R 的结果为array(10,20,30,40,50,60,70)
delete from R where thisrow < 50;
// R 的结果为array(50,60,70)
ThisRow 的其他用途

在处理二维数据结构的时候ThisRow 同样是有用的,我们使用 update 的时候,由于需要使用 set [FieldName]=Value 的模式当字段特别多的时候使用起来就很麻烦这个时候ThisRow 就可以起到不错的效果,我们是否还记得在本书最初关于二维数组就是一维的一维的论述呢?

假定 R 是包括许多门成绩的汇总表,我们已经有结果集 DATA 内容格式为 array("学号":"01","英语成绩":80,"语文成绩":90.....),这个 DATA 包括 R 结果集的每一列的信息,现在我们要将 DATA 的内容更新到 R 相应的学号。

当然,我们可以写成如下内容:

Update R set ["语文成绩"]=DATA["语文成绩"],["英语成绩"]=DATA["英语成绩"],.....

where ["学号"] = DATA["学号"] end;

当字段很多的时候,这是否太罗嗦了呢?下列写法是否会让你觉得简洁多了呢?

Update R set thisrow = DATA where ["学号"] = DATA["学号"] end;

事实上,除了用于 Update 以外,我们还可以可以做整行相同的判断,这可以用于 Delete,select 等 where 子句中:

假定有矩阵 R其中存在全 0 的行,我们需要删除掉这些行,怎么做呢?

我们可以这么写:

Delete from R where [0] = 0 and [1] = 0 and [2] = 0 .....;

我们还可以换一个很简洁的写法:

Delete From R where Sum(ThisRow*ThisRow)=0; //条件为每一个元素的平方和为 0

当然,甚至我们还可以直接用 TSL 所支持的相等判断符:

Delete From R where ThisRow=Zeros(MCols(R));//条件为行为一个全零数组。

用户可以自己去发掘出 ThisRow 的更多用途,作为程序开发人员而言,最重要的是学会自己思考。

多表联接中的 ThisRow

我们已经知道了 ThisRow 可以代表源结果集中的当前行,在进行多表联接的时候如何使用 ThisRow 呢?

在多表联接的时候ThisRow 默认访问的是第一个结果集的当前行,如果要指定结果集,则使用 ThisRow(TableOrder)的模式:

ThisRow(1)表示第一个结果集的当前行ThisRow(2)为第二个结果集的当前行,以此类推。

SSelect 返回一维数组的方法

默认情况下select 返回的内容总是二维的表结构无论结果集是否是一维数组。这就造成了一个问题当我们处理一维结果集的查询的时候返回的结果集依旧是一个二维的数组这往往和使用者的意图相悖。TS-SQL 提供了 sselect 来解决这个问题。

我们来看一段代码例子:

R := array(1, 2, 3, 4, 5, 6);
R1 := select ThisRow from R where ThisRow > 5 end;
// 该 R1 的结果为array(("Expr1":6))
R2 := sselect ThisRow from R where ThisRow > 5 end;
// 该 R2 的结果为array(6)

SSelect 的语法和 select 完全相同返回的结果不同之处在于SSelect 将返回的结果集按照逐行逐列的原则将所有元素组织成一个一维数组。

VSelect 返回单一值

和 SSelect 类似,有时候用户仅仅只需要用 select 返回一个值,例如:

R1 := select AvgOf( ["英语成绩"] ) from B end;

R1 返回的结果集是一个 1 行 1 列的二维结果集这在开发上使用起来比较麻烦。为了方便起见TS-SQL 引入关键字 VSelect。

R2 := VSelect SumOf( ["英语成绩"] ) from B end;

这个 R2 的结果就是英语成绩的平均值了VSelect 和 SSelect 类似VSelect 返回的值为结果集中的第一行第一列的内容。当用户使用聚集函数进行统计的时候,使用 VSelect 可以让代码更为简洁。

TS-SQL 和多重嵌套查询

在前边的章节中,我们学习了多表的联接,但是有时候我们还是需要使用到多重嵌套查询,举一个例子:

假定 B 还是英语成绩结果集D 则是上课出勤记录,我们要返回一个结果集,在 B 的基础上增加一列,是该学生对应的缺课记录。

我们假定 D 的结构为:

学号课程名课程时间缺课

01 英语 2012-3-1 No

01 语文 2012-3-1 Yes

01 英语 2012-3-2 No

02 .......................................

似乎我们可以如下写:

R := select \ * , select ["课程时间"], ["缺课"] from D where ["学号"] = ["学号"] and
["缺课"] = "Yes"end as"出勤记录"from B end;

这个时候,我们发现了一个问题,在字查询中,我们无法直接用["学号"]来标示 B 和 D 的学号字段,而这个时候,由于不是使用 Join所以也无法使用[1].["学号"]的模式来识别字段来源。这时,子查询不存在 Join 的第一个第二个结果集的概念,但存在上一级结果集的概念(例如相对于对 D 查询的子查询而言B 结果集是 D 结果集查询的上层结果集)。

为了解决这个上下级的关系TS-SQL 加入了 RefsOf 关键字。

RefsOf 访问上级结果集

RefsOf 的定义为RefsOf(Exp,UpLevel)

其中 Exp 是一个任意的计算表达式UpLevel 是上几级1 表示上一级2 表示上两级,依此类推。

RefsOf 的含义是,使用指定的上级结果集计算 EXP 表达式。可能这样说会很抽象,举几个例子:

例如RefsOf(["学号"],1)表示上级结果集的学号字段,假定存在一个函数 SQLTest其定义为

function SQLTest();
begin
    return ["学号"];
end;

RefsOf(SQLTest(),1)同样访问的是上级结果集的学号字段。

在这里,这样重复地说明只是为了告诉读者,在 RefsOf 中的第一个参数中的计算,无论是直接访问结果集(如访问字段,ThisRow 等等),还是间接地访问(例如通过函数),访问的结果集均是指定的上层结果集的内容,够拗口吧。

之前的例子我们可以修改成如下代码:

R := select * , select ["课程时间"], ["缺课"] from D where ["学号"] =
RefsOf(["学号"], 1)
and ["缺课"] = "Yes"end as"出勤记录"from B end;

实例展示:在表 t2 中增加 t1 表中对应行业个股涨幅大于行业涨幅的情况

t1 := array(
("证券":"SH600028", "行业":"采矿业", "涨幅(%)":-1.01),
("证券":"SH600030", "行业":"金融业", "涨幅(%)":0.07),
("证券":"SH601166", "行业":"金融业", "涨幅(%)":-1.6),
("证券":"SH601211", "行业":"金融业", "涨幅(%)":0.29),
("证券":"SH601225", "行业":"采矿业", "涨幅(%)":-0.49),
("证券":"SH601658", "行业":"金融业", "涨幅(%)":-1.62),
("证券":"SH601668", "行业":"建筑业", "涨幅(%)":1.16));
t2 := array(("行业":"采矿业", "行业涨幅(%)":-1.1),
("行业":"金融业", "行业涨幅(%)":-0.12),
("行业":"建筑业", "行业涨幅(%)":2.1));
return select * ,
select * from t1 where ["行业"] =
refsof(["行业"], 1)
and ["涨幅(%)"] >=
refsof(["行业涨幅(%)"], 1)
end as"行业成份"
from t2 end;

TS-SQL 访问分组后组内的内容

先来温习下之前我们说过的分组和聚集函数,我们假定有一个学校的体检结果表 R结构如下

学号姓名性别年龄身高

01 张三男 10 145

02 李四男 11 150

03 王五女 10 151

04............................................

我们如果要统计男女在各种年龄的平均身高,我们可以这么写:

R1 := select ["性别"], ["年龄"], AvgOf(["身高"]) from R group by ["性别"], ["年龄"]
end;

好了接下来的问题来了假使我们需要返回每个分组中身高的人呢要解决这样的问题我们便需要有方法访问到分组中的内容这个时候TS-SQL 引入了 ThisGroup 关键字。

ThisGroup 访问分组内的内容

接着上边的问题,我们来看使用 ThisGroup 后的代码:

R1 := select ["性别"], ["年龄"], AvgOf(["身高"]), select \ * from ThisGroup where

["身高"]=Refsof(MaxOf(["身高"]),1 ) end as "最高的学生" from R Group By

["性别"], ["年龄"] end;

在这个范例中,我们看到我们的子查询使用了 ThisGroupThisGroup 是一个特别的关键字,代表着上一层分组中的结果集。在这个范例中,我们还看到使用了 RefsOf(maxOf(["身高"]),1)来计算上一级分组中的最大身高。子查询 Select * from ThisGroup where ["身高"]=Refsof(MaxOf(["身高"]),1 ) end 的含义是从分组中查询身高和分组的最大值相等的学生,也就是找出该分组中最高的学生。

ThisGroup 不能被直接当数据使用

如果我们现在需要返回的是分组中的具体内容而不是身高最高的学生,许多读者会这么写:

R1 := select ["性别"], ["年龄"], AvgOf(["身高"]), ThisGroup as"最高的学生"from R
group by ["性别"], ["年龄"] end;

很可惜的是这样写连语法都无法通过。为什么呢ThisGroup 仅仅只是一个关键字来代表当前分组,而如果要取出分组中的内容,则必需使用 select换句话来说就是ThisGroup 只能被放在 select 的 from 子句中。ThisGroup 只是 select 查询的一个特殊源,而不是一个现成的结果集。

上边代码正确的写法如下:

R1 := select ["性别"], ["年龄"], AvgOf(["身高"]), select \ * from ThisGroup end as
"详细信息"from R group by ["性别"], ["年龄"] end;

ThisGroup 作为一个 from 子句所支持的特殊关键字而不是一个数据集,有很多方面的考量,最主要的原因则是因为这样实现在大多数应用下可以获得更好的效率,因为用户往往并不一定需要访问分组子集的全部内容。

RefMaxOf,RefMinOf 引用最大最小的行

支持 refmaxof,refminof 聚集函数来获取最大值与最小值对应行的其它列的值,需在使用了 maxof 或者 minof 之后使用。

使用范例:

a := array((1, 2), (4, 3), (2, 5));
return select maxof([0]),
refmaxof([1])
from a end; // 访问最大值所在行的[1]的值。
// 返回结果集为array(("Expr1":4.0,"Expr2":3))

当在一条 SQL 里使用了多个 MaxOf 或 MinOf 会导致 RefMaxOf 或 RefMinof 无法确切知道引用哪条记录。这个时候,就要配合缓存的标识符。

MaxOf、MinOf 的缓存标识串中,如果<MAXMIN=IdString/>格式,那么这个标志就是 IdString这样我们可以在 RefMaxOf、RefMinof 中来使用标志串,例如:

t := array((85, 84), (82, 86), (7, 4), (4, 45), (20, 54),
(8, 92), (87, 35), (7, 17), (12, 49), (11, 15));
return select MaxOf([0], true, nil, true, "<MAXMIN=id0/>") as"0列最大值",
MaxOf([1], true, nil, true, "<MAXMIN=id1/>") as"1列最大值",
MinOf([0], true, nil, true, "<MAXMIN=id3/>") as"0列最小值",
MinOf([1], true, nil, true, "<MAXMIN=id4/>") as"1列最小值",
refMaxOf([1], "id0") as"0列最大值对应的1例值",
refMaxOf([0], "id1") as"1列最大值对应的0例值",
refMinOf([1], "id3") as"0列最小值对应的1例值",
refMinOf([0], "id4") as"1列最小值对应的0例值"
from t end;

如果存在子查询,结合 RefsOf 使用RefMaxOf,RefMinOf 可以简单地解决以前很麻烦的需求

ThisRowIndex 在 Select 中返回原始行下标

在使用 select 进行数据查询的时候我们有时候不返回所有的列而在使用返回结果集的时候有时候又有访问源结果集其他列的需要这个时候假如我们知道所返回的结果集的行下标就可以方便地访问源结果集中的内容。TS-SQL 提供了 ThisRowIndex 关键字来获得源结果集的行下标。

一个从一维数组中获得值小于 0 的下标列表:

A := array(-1, 1, 2, 3, -2, 3);
B := SSelect ThisRowIndex from A where ThisRow < 0 end;

B 的结果为 array(0,4)

我们在这个例子中,为了方便起见,我们使用了一维数组来描述,上述的例子中我们得到了小于 0 的值-1、-2 在 A 数组中的行下标为 0 和 4。

多表联接中的 ThisRowIndex

我们已经知道了 ThisRowIndex 可以代表源结果集中的当前行下标,在进行多表联接的时候如何使用 ThisRowIndex 呢?

在多表联接的时候ThisRowIndex 默认访问的是第一个结果集的当前行下标,如果要指定结果集,则使用 ThisRowIndex(TableOrder)的模式ThisRowIndex(1)表示第一个结果集的当前行ThisRowIndex(2)为第二个结果集的当前行,以此类推。

TS-SQL 与时间序列处理支持

什么是时间序列数据?

我们来看这样一个数据,气象站每天都采集了气温,有最高气温,最低气温,平均气温等几个数据,这就是一个标准的时间序列数据范例,我们可以用这样的一个结构来描述这些数据(假定这个数据结果集为 R

日期最高气温最低气温平均气温

2012-04-01 25 18 20

2012-04-02 23 18 19

2012-04-03 28 20 23

2012-04-04 29 21 24

..........................................................................

时间序列数据分析的特殊需要

时间序列数据处理往往有着和其他数据处理不同的应用要求。我们回顾下 TS-SQL 对聚集函数的支持,如果我们需要求所有数据的最高气温、最低气温以及平均气温,使用 select avgof(["平均气温"]),MaxOf(["最高气温"]),MinOf(["最低气温"]) from R end 就可以了,但是我们在做天气研究的时候,往往我们需要的是每天的最近十日以来的最低气温、最高气温以及平均气温。

这种需求就是移动的序列化支持,另外一种

由于这个气温数据被序列化。

聚集函数以及时间序列支撑函数进行时间序列分析是 TS-SQL 的特色,但正是由于其特殊性,即便是许多 TSL 的老用户对如何利用 TS-SQL 处理时间序列并不熟悉。下边我们来进阶下 TS-SQL 时间序列处理的功能。

说到时间序列处理,事实上我们可以分割成两个部分,其中一个是时间序列化,另一个则是序列化分析处理,关于时间序列化而言,可以理解为数据整理的功能,例如,高频数据的秒、分钟,日间数据的日、周、月、年等数据的周期化,此外,序列化的同时还需要剔除掉非交易日的时间,这些内容大部分熟悉 TSL 语言的数据访问的用户大多非常熟悉,在这里我们反而不做重点介绍,详细可以查阅 TS-SQL 的 select SelectList from markettable datekey BegTime to EndTime of TargetCode/TargetCodeList 的相关用法。

我们在这里要着重在序列化分析处理上,这是数据分析的能力,例如在 markettable 中,数据已经被序列化了,或者用户存在一个被序列化好的结果集,我们如何对其进行分析处理呢?

REFOF时间序列化的第一种需求相对位置需求

在时间序列处理中,前后相关的位置的数据往往非常重要,例如,我们要访问 N 个交易日前或者 N 个交易日后的数据,也就是说,我们需要提取序列中相对于当前位置的数据。在标准的 SQL 中缺乏这种能力TS-SQL 中加入了 REFOF 关键字。

假定 R 结果集中包括有 close 列

那么 select ["close"]/refof(["close"],1) - 1 from R

end;就可以返回出每天的涨幅,这足够简单吧。

我们理解 refof 的功能是以向前推移当前序列到指定的行再计算第一个参数的表达式,第二个参数为推移的行数,如果第二个参数为负则朝后推移。

花费这么多的气力来描述,仅仅只是提醒读者,我们不用把 refof(["close"]+["open"],1)写成 refof(["close"],1)+refof(["open"],1),前者的访问效率接近后者的两倍。

了解序列的移动处理

我们可能经常需要使用移动 N 条的标准差,移动 N 条的平均数,移动 N 条的最大值,这类应用需求在标准 SQL 中的应用简直是一场噩梦。

很幸运的是TS-SQL 的所有聚集函数均支持移动处理,下面我们仅仅使用求平均的聚集函数,至于 TS-SQL 支持哪些聚集函数,用户可以 TSDN 找到答案。

对于标准 SQL 的平均数聚集的定义为Average ( [ DISTINCT ] Expression)

其含义我们不多说,对于不是特别了解 SQL 的用户而言,可能比较陌生的是其 Distinct 前缀,也就是说,允许去重之后求平均,如果不带 DISTINCT则是所有的值求平均。

TS-SQL 的平均数聚类的定义为:

AVGOF ( [ DISTINCT ] Expression [,BoolConditionExp[,N[,MovingFirst[,CacheId]]]] )

和 SQL 的 Average 相比TS-SQL 增加了四个可选参数,一个是一个条件表达式,另外一个则是 N我们在这里姑且先不理会增加的第一、三、四个参数对于第二个参数 N肯定是绝对多数人所希望看到的参数这就是移动序列分析所必备的。

我们来看一个典型的应用案例:

R := Rand(100, 1);
return select [0], AvgOf([0]) as 1, AvgOf([0], true, 10) as 2 from R end;

我们将结果集以图形来呈现:

得到的有三条线,一条是随机数线,一条是整体的平均值,是一条直线,另外一条蓝色线则是一个移动平均后的平滑线。

也就是说TS-SQL 的 Avgof 支持移动平均N 的参数是否很容易理解呢?

在现实应用中,我们在做移动的时候很可能需要有条件的移动,例如,一个高频交易明细序列,由于记录了所有的行情波动,包括买卖盘的波动,这样可能会出现有交易量为 0 的点。但是我们可能有一个需求,是要求最近 N 个有交易的点的价格的平均,这样在移动的时候我们就需要可以支持起条件移动。

这个时候TS-SQL 的聚集函数中增加的第一个参数就非常有意义了。

例如,我们在万科的明细里返回从昨日到当前的时间、价格、最近 10 个成交价的平均数,我们可以如此写:

select datetimetostr(["date"]), ["close"], avgof(["close"], ["vol"] > 0, 10) from
tradetable datekey now() - 1 to now() of"SZ000002"end;

为了更清晰地看到其运行的结果,我们不妨用一个简单的数据序列来替代。我们假定有结果集 R,R 为一维数组,内容如下 array(1,3, 5, 0, 2,0, 4,5,3)

R1 := sselect AvgOf( ThisRow, ThisRow > 0, 3) from R end;

这里对于还不是特别掌握 TS-SQL 的读者,我们要做一个特别的提示,由于一维数组没有列概念,所以没有办法用[列标]的模式去访问列的内容,而 ThisRow 代表的是当前行的内容对于一维数组而言ThisRow 存贮的就是当前下标对应的值(还有一个 ThisRowIndex 对应当前的下标),而 select 默认返回的结果集是一个二维结构,在处理一维数组的时候,往往希望返回的也是一维数组,用 sselect 而不是 select 则起到这个作用。

我们在平台上运行可以知道 R1 的结果为 array(1.00,2.00,3.00,3.00,3.333,3.333,3.667,3.667,4.00),前两个结果是在数据点不够的情况下,自动转换成了 1 个点的平均 1/1=1两个点的平均(1+3)/2=2第三个点为(1+3+5)/3=3第四个点则从 0 开始往前推移 3 个>0 的点进行平均,得到的是(1+3+5)/3=3第五个点为(2+5+3)/3=3.333,第六个点为(2+5+3)/3=3.333,第七个点为(4+2+5)/3=3.667,第八个点为(5+4+2)/3=3.667,第九个点为(3+5+4)/3=4。我们可以清晰地看到 TS-SQL 的条件移动平均是先满足条件,后计算数据的移动。

上述这种移动平均我们可以理解为最近的三个大于 0 的数的平均。

在许多时候,我们需要的带条件移动平均是最近三个中大于 0 的数的平均,现实中的典型应用是:移动的最近 10 个交易日中上涨时的平均成交量,对于这类的应用,上述条件移动平均就无法直接达到要求了,那么,我们是否具有折中的办法呢?其实,即便利用上述的条件移动平均也可以实现,只是需要对任务进行步骤分解,将其分解为:得到最近 10 个交易日中的上涨天数,利用上涨天数求移动平均,这里,我们需要使用到聚集函数 CountIfOf 来求天数,我们以万科为例,其中 BegDate,EndDate 为起止时间段:

select
N := CountIfOf(["close"] > refof(["close"], 1), 10), AvgOf(["vol"], ["close"] > refof(["close"], 1), N)
from markettable Datekey BegDate to EndDate of"SZ000002"end;

在这里,我们利用了在 select 计算的过程中使用变量 N 将 CountIfOf 返回的个数记录了下来,并将 N 作为了 AvgOf 的参数,看起来是否是很巧妙呢?但是,我们在运行的时候,会遇到一个问题,就是 N 可能为 0因为在 10 个交易日中连续下跌的可能性是存在的,而 0 日平均是不被允许的,为了解决这个问题,我们可以将代码修改如下:

select
N := CountIfOf(["close"] > refof(["close"], 1), 10), N?AvgOf(["vol"], ["close"] > refof(["close"], 1), N):0
from markettable Datekey BegDate to EndDate of"SZ000002"end;

利用:表达式,我们把 N 为 0 的特殊情况彻底解决了。

TS-SQL 对上述问题的直接支持办法

上述办法虽然可以解决问题,但是读者可能还是会觉得很麻烦,要是直接支持多好。事实上,目前的 TSL 语言本身就支持这种应用,我们还是用前边的 R 来做结果集,做一个移动的三个数据中大于 0 的数的平均。

R1 := sselect selectOpt(64) AvgOf( ThisRow, ThisRow > 0, 3) from R end;

先来看下结果是否正确,运行得到的结果是:

array(1.00,2.00,3.00,4.00,3.5,2.00,3.00,4.5,4.00),为了方便起见,我们把 R 的内容在这里重复一下,R 的内容为array(1,3, 5, 0, 2,0, 4,5,3),原先的先判断再移动的结果为 array(1.00,2.00,3.00,3.00,3.333,3.333,3.667,3.667,4.00)。

由于前三个点没有为 0 的情况,所以前三个数据的结果与原来是一样的,依次为 1,2,3第四个点是(3+5)/2=4,第五个点是(5+2)/2=3.5,第六个点是 2/1=2第七个点是(4+2)/2=3第八个点是(4+5)/2=4.5,第九个点是(4+5+3)/3=4。结果和我们验算的是一样的。

OK,这样是否比前一种解决方案更好呢?

假如有一种情况,我们既需要得到最近 3 个数据大于 0 的数的平均,又要最近 3 个数据中数据大于 0 的数的平均,我们可以怎么办呢?在 N 之后的参数可以解决这个需要,如:

R1 := sselect AvgOf( ThisRow, ThisRow > 0, 3, true), AvgOf( ThisRow, ThisRow > 0, 3, false)

from R

end;该参数的意义就是决定 N 到底是先移动还是先判定条件,如果为真,就先移动,则计算 N 个内符合条件的,位假表示先判定,则计算最近 N 个符合条件的。