🎨 style(markdown): format all markdown files with prettier
- 使用 prettier 格式化所有 markdown 文件 - prose-wrap: always, print-width: 80 - 保持代码块和表格格式 - 提升可读性和一致性 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,7 +140,9 @@ TSL 语言中,以数组(矩阵)作为集合运算的基础,而数组也
|
||||
|
||||
注:In 是以最小的元素进行判断。
|
||||
|
||||
例如 1 in array(1,2,2)为真,1 in array(0,2)为假,需要注意的是,in 操作符号允许后边的结果集为多维数组,例如 1 in array((1),(2))同样会返回为真。
|
||||
例如 1 in array(1,2,2)为真,1 in
|
||||
array(0,2)为假,需要注意的是,in 操作符号允许后边的结果集为多维数组,例如 1 in
|
||||
array((1),(2))同样会返回为真。
|
||||
|
||||
in 操作符同样支持子集判断,
|
||||
|
||||
@@ -304,7 +306,9 @@ A Minus B 的结果为:
|
||||
|
||||
因此,集合的对称差集是行的对称差集,而不是元素的对称差集,所以 Outersect 是减去重复行的并集。
|
||||
|
||||
Outersect 比较特殊,在其他语言很少提供这类功能,但是在许多应用中会应用到,A Outersect B 相当于(A Union2 B) Minus (A Intersect B),也相当于(A Minus B) Union2 (B Minus A)。
|
||||
Outersect 比较特殊,在其他语言很少提供这类功能,但是在许多应用中会应用到,A
|
||||
Outersect B 相当于(A Union2 B) Minus (A Intersect B),也相当于(A Minus B) Union2
|
||||
(B Minus A)。
|
||||
|
||||
语法:A Outersect B
|
||||
|
||||
@@ -348,7 +352,8 @@ A Outersect B 的结果为:
|
||||
|
||||
将二维数组转换为一维数组的方法最简单的是利用 sselect 语法,关于 SSelect 的使用见 ts-sql 专题。
|
||||
|
||||
假定我们有一个二维矩阵,我们用 R1:=sselect \* from R end;就可以把 R 转换为一个一维数组到结果集 R1 了。然后我们的运算可以基于转换后的结果集。
|
||||
假定我们有一个二维矩阵,我们用 R1:=sselect \* from R
|
||||
end;就可以把 R 转换为一个一维数组到结果集 R1 了。然后我们的运算可以基于转换后的结果集。
|
||||
|
||||
## 结果集过滤运算
|
||||
|
||||
@@ -1548,7 +1553,8 @@ 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)
|
||||
事实上,union 对一维数组同样有效,例如 array(1,2) union
|
||||
array(3,4)的结果为 array(1,2,3,4)
|
||||
|
||||
如果需要将一个一维数组加到一个二维数组的最后一行,怎么做呢?
|
||||
|
||||
@@ -1580,7 +1586,8 @@ 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))
|
||||
事实上,|对一维数组同样有效,例如 array(1,2) |
|
||||
array(3,4)的结果为 array((1,3),(2,4))
|
||||
|
||||
也支持直接将一个一维数组直接用|加到一个二维数组上。
|
||||
|
||||
@@ -3407,7 +3414,8 @@ F1 与 F2 的返回结果如下:
|
||||
|
||||
一般的数值函数都已实现对数组的支持,可以直接使用,比如 FloatN,既可以对数值 12.656 进行四舍五入,也能对数组 array(12.656,11.121)或 fmarray[12.256,11.121]进行四舍五入。但也可能存在部分函数在设计时没考虑到数组的情景,或用户自己在封装时没有考虑数组的情景,此时,则可借助::=运算来快速实现对数组的支持。
|
||||
|
||||
比如,RoundTo5 函数只能.5 处理一个实数,那我们想要处理矩阵中每个单元格中的数值则可通过 t::= RoundTo5(mcell)来快速实现,实现如下:
|
||||
比如,RoundTo5 函数只能.5 处理一个实数,那我们想要处理矩阵中每个单元格中的数值则可通过 t::=
|
||||
RoundTo5(mcell)来快速实现,实现如下:
|
||||
|
||||
```text
|
||||
F:=fmarray[6.3,5.6,3.1,4.2,5.25];
|
||||
@@ -3450,7 +3458,8 @@ 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 的新矩阵。
|
||||
A[array(2,5,6), array(0,2)]
|
||||
表示取 A 矩阵中行下标为 2、5、6,列下标为 0 和 2 的新矩阵。
|
||||
|
||||
以上几种方式可以混搭灵活使用,能满足各类子矩阵提取的需求。
|
||||
|
||||
@@ -3714,7 +3723,8 @@ t2:=fmarray[[3,4]];
|
||||
|
||||
则左表行数超过右表时,即 return t1|t2; 返回表现如下:右表少的地方用 0 补足
|
||||
|
||||
若右表行数超过左表时,即 return t2|t1;返回如下:是左表先自扩张同右表一致,而后进行拼接
|
||||
若右表行数超过左表时,即 return
|
||||
t2|t1;返回如下:是左表先自扩张同右表一致,而后进行拼接
|
||||
|
||||
同时,将上列中的|变更为:|效果也一样,在 FMArray 中,两者功能无区别。
|
||||
|
||||
@@ -3766,7 +3776,8 @@ Delete 和 update 支持对 fmarray 的操作,操作后的结果还是一个 F
|
||||
|
||||
例如:delete from f where [1]=4; 可以删除矩阵 f 中 1 列值为 4 的所在行;
|
||||
|
||||
update f set [0]=100 where [1]=4 end; 可以将 1 列中值为 4 的行的 0 列值更新为 100。
|
||||
update f set [0]=100 where [1]=4
|
||||
end; 可以将 1 列中值为 4 的行的 0 列值更新为 100。
|
||||
|
||||
提示:fmarray 无法像 array 一样自动通过 update 增加列,这是因为这种操作对于 fmarray 相比 array 而言更慢,采用 fmarray 应该规避这类操作,只将 fmarray 用于必要的地方。
|
||||
|
||||
@@ -3897,13 +3908,18 @@ Sum(minit(3,4,1.0),1)对行统计得到的数据是 array(4.0,4.0,4.0)
|
||||
|
||||
### 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 的全称为 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)。
|
||||
无独有偶,微软的 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 的功能,不仅仅对于学习大有裨益,在今后的开发道路上,也会更为宽广,我们会尝试在后面做一些类似的尝试。
|
||||
|
||||
@@ -3932,17 +3948,20 @@ TSL 语言支持 SELECT 语句,该语法类似于 SQL 中的 SELECT 语句,
|
||||
|
||||
与 SQL 不同,TSL 对字段的访问必需采用[字段名]的模式来表达,[]内可以是任何返回为数字或字符串的表达式,例如字段 close 用["close"]访问。
|
||||
|
||||
与 SQL 不同,TSL 的 SELECT 查询不支持 TOP N,但是支持更加灵活的 DRANGE(begn to endn)以及 M Of N 的模式来取指定的结果范围。
|
||||
与 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 中的 count(_),CheckSum(*)的模式在 TSL 的 SELECT 语句中用添加在(和*之间的空格符号如:countof(
|
||||
_ )、countof()、CheckSumOf( _ )替代。
|
||||
|
||||
与 SQL 不同,TSL 的聚集函数为了和其他的函数分别开来,基本上在 SQL 的聚集函数的基础上添加 of,例如 count 对应为 countof,avg 对应于 avgof,sum 对应于 sumof 等等。
|
||||
|
||||
具体语法如下:
|
||||
|
||||
From 子句[SelectOpt(Options)] [distinct] [drange( <begntoendn | M Of N>)] <select_list><From 子句> [WHERE 子句] [GROUP BY 子句] [ORDER BY 子句] end;
|
||||
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 的时候,返回结果为一个一维数组。
|
||||
|
||||
@@ -3964,7 +3983,9 @@ From 子句[SelectOpt(Options)] [distinct] [drange( <begntoendn | M Of N>)] <sel
|
||||
|
||||
VSelect 返回单个值,主要用于如 sumof、countof 这类统计中,其他功能与 Select 同,只是 Select 返回的结果一般为二维数组。
|
||||
|
||||
当需要返回某个字段的最大值的时候,采用(select maxof( ["F1"] ) from table end)[0]["Expr1"]来描述太复杂,而采用 VSelect maxof(["F1"] from table end 就可以直接返回最大值。
|
||||
当需要返回某个字段的最大值的时候,采用(select maxof( ["F1"] ) from table
|
||||
end)[0]["Expr1"]来描述太复杂,而采用 VSelect maxof(["F1"] from table
|
||||
end 就可以直接返回最大值。
|
||||
|
||||
如:
|
||||
|
||||
@@ -4025,11 +4046,13 @@ return Mselect * from t end;
|
||||
|
||||
###### 语法
|
||||
|
||||
SELECT [SELECTOPT(Options)] [DISTINCT][DRANGE(<begn to endn | M Of N>)]< select_list >
|
||||
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 ]
|
||||
{\* | StartFieldIndex To EndFieldIndex |{ [column_name] | Expression}[ [ AS ]
|
||||
column_alias ]}[ ,...n ]
|
||||
|
||||
###### 参数
|
||||
|
||||
@@ -4121,7 +4144,8 @@ return select selectopt(16) sumof( *,1,6)from t end;
|
||||
|
||||
返回结果:
|
||||
|
||||
注:上述实现中 selectopt(16) sumof( \* )是在分组后,对所有列进行求和,由于 0 列不需要求和,因此取原始该列的值进行最后覆盖,保留原始值,达到目的。
|
||||
注:上述实现中 selectopt(16) sumof( \*
|
||||
)是在分组后,对所有列进行求和,由于 0 列不需要求和,因此取原始该列的值进行最后覆盖,保留原始值,达到目的。
|
||||
|
||||
示例 04:多功能模式展示,设置 selectopt 的值为 2^6+2^8,则表示在使用 MovingFirst 模式的同时设置 refof 越界时返回 nil 而非 0。
|
||||
|
||||
@@ -4149,7 +4173,9 @@ selectopt(2^6+2^8)
|
||||
|
||||
指定只从查询结果集中输出从 begn 到 endn 之间的行。当 begn 到 endn 是 0 或者正整数的时候,位置偏移从第一条开始算,第一条位置为 0,当为负数的时候,偏移从最后一条开始算,最后一条的偏移为-1,倒数第二条为-2,以此类推。
|
||||
|
||||
如果查询包含 ORDER BY 子句,将输出由 ORDER BY 子句排序的从 begn 到 endn 之间的行。如果查询没有 ORDER BY 子句,行的顺序将由数据的存贮原有次序决定。
|
||||
如果查询包含 ORDER BY 子句,将输出由 ORDER
|
||||
BY 子句排序的从 begn 到 endn 之间的行。如果查询没有 ORDER
|
||||
BY 子句,行的顺序将由数据的存贮原有次序决定。
|
||||
|
||||
例子:
|
||||
|
||||
@@ -4173,9 +4199,14 @@ Return Select Drange(-10 to -1) \* from A end;
|
||||
|
||||
####### DRANGE(M OF N)
|
||||
|
||||
指定只从查询结果集中输出从 (M-1)/N _ 记录总条数到 M/N _ 记录总条数 -1 之间的行。例如 DRange(5 Of 100)的意思是返回内容 100 等分的第 5 个等分,DRange(1 of 2)则返回前一半的内容。
|
||||
指定只从查询结果集中输出从 (M-1)/N _ 记录总条数到 M/N _
|
||||
记录总条数 -1 之间的行。例如 DRange(5
|
||||
Of 100)的意思是返回内容 100 等分的第 5 个等分,DRange(1
|
||||
of 2)则返回前一半的内容。
|
||||
|
||||
如果查询包含 ORDER BY 子句,将输出由 ORDER BY 子句排序的 N 等分的第 M 等分的行。如果查询没有 ORDER BY 子句,行的顺序将由数据的存贮原有次序决定。
|
||||
如果查询包含 ORDER BY 子句,将输出由 ORDER
|
||||
BY 子句排序的 N 等分的第 M 等分的行。如果查询没有 ORDER
|
||||
BY 子句,行的顺序将由数据的存贮原有次序决定。
|
||||
|
||||
例子:
|
||||
|
||||
@@ -4205,13 +4236,15 @@ Expression
|
||||
|
||||
column_alias
|
||||
|
||||
是查询结果集内替换列名的可选名。例如,可以为名为 quantity 的列指定别名,如"Quantity"或"Quantity to Date"或"Qty",列名也可以是数字。
|
||||
是查询结果集内替换列名的可选名。例如,可以为名为 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;
|
||||
FROM MarketTable datekey encodedate(2002,1,1) to encodedate(2002,12,31) of
|
||||
"SZ000001" END;
|
||||
|
||||
column_alias_str 不可用于 ORDER BY 子句,WHERE、GROUP BY 或 HAVING 子句。
|
||||
|
||||
@@ -4243,7 +4276,8 @@ FROM { < table_source > } [ ,...n ]
|
||||
|
||||
| <joined_table>
|
||||
|
||||
< joined_table > ::=< table_source >< join_type >< table_source >< ONWithExp >| < table_source > CROSS JOIN < table_source >| < joined_table >
|
||||
< joined_table > ::=< table_source >< join_type >< table_source >< ONWithExp >|
|
||||
< table_source > CROSS JOIN < table_source >| < joined_table >
|
||||
|
||||
< join_type > ::=[{ LEFT | RIGHT | FULL } ]JOIN
|
||||
|
||||
@@ -4322,7 +4356,8 @@ FAQ:INFOTABLE
|
||||
|
||||
###### JOIN 语法
|
||||
|
||||
JOIN 用于对多表进行连接,可以是 JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN,CROSS JOIN。JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN 支持 ON 或者 WITH 条件约束。
|
||||
JOIN 用于对多表进行连接,可以是 JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN,CROSS
|
||||
JOIN。JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN 支持 ON 或者 WITH 条件约束。
|
||||
|
||||
####### 内容
|
||||
|
||||
@@ -4340,15 +4375,18 @@ Join 运算符返回满足第一个(顶端)输入与第二个(底端)输
|
||||
|
||||
####### LEFT JOIN
|
||||
|
||||
Left Join 返回每个满足第一个(顶端)输入与第二个(底端)输入的联接的行。它还返回任何在第二个输入中没有匹配行的第一个输入中的行。第二个输入中的非匹配行作为空值(NIL)返回。
|
||||
Left
|
||||
Join 返回每个满足第一个(顶端)输入与第二个(底端)输入的联接的行。它还返回任何在第二个输入中没有匹配行的第一个输入中的行。第二个输入中的非匹配行作为空值(NIL)返回。
|
||||
|
||||
####### RIGHT JOIN
|
||||
|
||||
Right Join 运算符返回满足第二个(底端)输入与第一个(顶端)输入中的每个匹配行联接的每一行。它还返回第二个输入中在第一输入中没有匹配行的任何行,即与 (NIL) 联接。
|
||||
Right
|
||||
Join 运算符返回满足第二个(底端)输入与第一个(顶端)输入中的每个匹配行联接的每一行。它还返回第二个输入中在第一输入中没有匹配行的任何行,即与 (NIL) 联接。
|
||||
|
||||
####### FULL JOIN
|
||||
|
||||
Full Join 运算符从第一个(顶端)输入中与第二个(底端)输入相联接的行中返回每个满足条件的行。它还可以从下面的输入返回行:
|
||||
Full
|
||||
Join 运算符从第一个(顶端)输入中与第二个(底端)输入相联接的行中返回每个满足条件的行。它还可以从下面的输入返回行:
|
||||
|
||||
在第二个输入中没有匹配项的第一个输入。
|
||||
|
||||
@@ -4358,7 +4396,8 @@ Full Join 运算符从第一个(顶端)输入中与第二个(底端)输
|
||||
|
||||
####### CROSS JOIN
|
||||
|
||||
Cross Join 运算符将第一个(顶端)输入中的每行与第二个(底端)输入中的每行联接在一起。
|
||||
Cross
|
||||
Join 运算符将第一个(顶端)输入中的每行与第二个(底端)输入中的每行联接在一起。
|
||||
|
||||
Cross Join 不允许带 On 或者 With 条件,Cross Join 也可以用“,”(逗号)来替代。
|
||||
|
||||
@@ -4366,19 +4405,26 @@ Cross Join 不允许带 On 或者 With 条件,Cross Join 也可以用“,”
|
||||
|
||||
Join,Left Join,Right Join,Full Join 允许添加 On 的条件,用法范例:
|
||||
|
||||
Select \* from a join b on [1].["ID"]=[2].["ID"] and [1].["Type"]=[2].["Type"] end;
|
||||
Select \* from a join b on [1].["ID"]=[2].["ID"] and [1].["Type"]=[2].["Type"]
|
||||
end;
|
||||
|
||||
ON 的用法和 SQL 规范类似,但 On 条件不做优化,如果要进行优化加速,建议采用 With On 条件。
|
||||
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;
|
||||
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 是 TSL 语言类 SQL 的自有的扩展特性,With
|
||||
On 是为了解决 Join 的效率问题,ON 的两旁是两组表达式,每组表达式的表达式个数应该完全相同,with
|
||||
on 会利用两组表达式进行预运算,并建立快速索引,解决效率问题,一般来说 With
|
||||
On 的计算复杂度和 N+M 是一个量级,而 ON 条件的计算复杂度和 N\*M 是一个量级(假定左右输入的数据集合的记录数分别为 N 和 M),在 N 和 M 比较大的时候,性能的差异可以体现得非常明显,达到几个数量级的差异。
|
||||
|
||||
但是,由于 WITH ON 有预运算和索引,目前还不支持超大数据结果集之间的运算,在那种情况下,仍然只能使用 ON 条件。
|
||||
但是,由于 WITH
|
||||
ON 有预运算和索引,目前还不支持超大数据结果集之间的运算,在那种情况下,仍然只能使用 ON 条件。
|
||||
|
||||
##### WHERE 子句
|
||||
|
||||
@@ -4582,7 +4628,8 @@ Select [“aaa”] as “aaa100” from a end;
|
||||
|
||||
返回的内容的字段名可以用 AS 更名,不带 AS 则使用系统的缺省名,如果返回的本身仅仅只是一个[],则为原始的字段名,否则使用”Expr1”,”Expr2”…来命令
|
||||
|
||||
如果再 SELECT 中希望临时进行计算又不希望将临时计算的结果返回到结果集中,可以使用 AS NIL 的模式,可参考:Select as nil 语法
|
||||
如果再 SELECT 中希望临时进行计算又不希望将临时计算的结果返回到结果集中,可以使用 AS
|
||||
NIL 的模式,可参考:Select as nil 语法
|
||||
|
||||
##### 行、行下标、行序号与行校验
|
||||
|
||||
@@ -4654,7 +4701,8 @@ AvgOF(["A"],true,3)
|
||||
|
||||
3、条件与移动的优先顺序可控,默认为先条件再移动,若需要先移动再条件(MovingFirst 模式),可通过两种方式实现:
|
||||
|
||||
一种是通过指定参数(一般为第三个可选参数)为真,第二种是通过 select selectopt(64)进行指定,如:
|
||||
一种是通过指定参数(一般为第三个可选参数)为真,第二种是通过 select
|
||||
selectopt(64)进行指定,如:
|
||||
|
||||
```text
|
||||
t:=`array("A":(1,5,4,6,8,2,9,9,5,3));
|
||||
@@ -4674,7 +4722,8 @@ Sumof(["A"],["A"]>4,3)
|
||||
|
||||
其中 t1 结果是默认情况下,不指定与指定 MovingFirst 模式的对照,展示如下:(sumA1 为默认方式结果,sumA2 为指定 MovingFirst 模式的结果)
|
||||
|
||||
t2 的结果是通过 select selectopt(64)进行指定默认方式转为 MovingFirst 模式,使["sumA1"]结果变更为["sumA2"]
|
||||
t2 的结果是通过 select
|
||||
selectopt(64)进行指定默认方式转为 MovingFirst 模式,使["sumA1"]结果变更为["sumA2"]
|
||||
|
||||
4、可以利用 REFOF 访问前几条的数据,如:
|
||||
|
||||
@@ -4694,7 +4743,8 @@ refof(["A"],1)
|
||||
|
||||
多字段聚集返回时可通过 SelectOpt(16)指定列名与聚集列的列名保持一致。
|
||||
|
||||
多字段聚集返回还支持部分多字段聚集返回,利用 AvgOf(0 to 2)就可以分别返回 0,1,2 字段的平均数。
|
||||
多字段聚集返回还支持部分多字段聚集返回,利用 AvgOf(0
|
||||
to 2)就可以分别返回 0,1,2 字段的平均数。
|
||||
|
||||
如:
|
||||
|
||||
@@ -4769,13 +4819,15 @@ Maxof( 0 to 2,AggValue<0.5 )
|
||||
|
||||
表达式可以用 \* ,也可以用 StartFieldIndex To EndFieldIndex 如 0 to 2 的模式。
|
||||
|
||||
\*表示每个字段进行聚集,0 to 2 表示第一个到第三个字段求聚集。SelectOpt 设置为 16 可以使得聚集的结果字段名和原始字段名一致。
|
||||
\*表示每个字段进行聚集,0 to
|
||||
2 表示第一个到第三个字段求聚集。SelectOpt 设置为 16 可以使得聚集的结果字段名和原始字段名一致。
|
||||
|
||||
例如,SumOf( \* )可以对每个字段求和
|
||||
|
||||
SumOf(0 to 2)可以返回前三个字段的求和(返回三个求和值)
|
||||
|
||||
默认的情况下,SumOf( \* )和 SumOf(N1 to N2)返回的结果会按照正常模式以 ExprN 作为字段返回,例如"Expr1","Expr2"…,如果设置了 SelectOpt(16)则可以以原始的字段名返回。例如:
|
||||
默认的情况下,SumOf( \* )和 SumOf(N1 to
|
||||
N2)返回的结果会按照正常模式以 ExprN 作为字段返回,例如"Expr1","Expr2"…,如果设置了 SelectOpt(16)则可以以原始的字段名返回。例如:
|
||||
|
||||
```text
|
||||
a:=array(("abc":1,"bcd":2), ("abc":2,"bcd":3));
|
||||
@@ -4785,7 +4837,8 @@ 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)
|
||||
多字段聚集的时候,如果需要参与运算,用 AggValue 可以访问到当前运算的字段值,例如 SumOf(
|
||||
\*,AggValue>10)
|
||||
|
||||
###### COUNTOF
|
||||
|
||||
@@ -6044,7 +6097,8 @@ 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 …….而不会因为缓存错误而导致的结果问题。
|
||||
Select DEAOF("close",12,30),DEAOF("open",12,30) from
|
||||
…….而不会因为缓存错误而导致的结果问题。
|
||||
|
||||
如果我们没有进行函数封装,在绝大多数情况下,我们也可以忽略掉这个聚集函数的缓存标识参数。
|
||||
|
||||
@@ -6092,7 +6146,9 @@ ValueExpression
|
||||
|
||||
如果要批量插入数据,例如将一个 Select 的结果集插入一个表,或者直接将一个内存表的数据插入数据表,使用一个结果为数组的表达式。
|
||||
|
||||
也就是说既然可以使用类似于 SQL 语法的 Insert into AAA select _ from BBB end 的模式插入,也可以采用 Insert into AAA BBB 的模式。注意在这里 select _ from BBB end 返回的结果就是一个数组结果(通常是二维表结构)。
|
||||
也就是说既然可以使用类似于 SQL 语法的 Insert into AAA select _ from BBB
|
||||
end 的模式插入,也可以采用 Insert into AAA BBB 的模式。注意在这里 select _ from
|
||||
BBB end 返回的结果就是一个数组结果(通常是二维表结构)。
|
||||
|
||||
VALUES
|
||||
|
||||
@@ -6150,7 +6206,8 @@ WHERE
|
||||
|
||||
语法
|
||||
|
||||
DELETE [DELETEOPT(Option)] FROM <Expression | {<SQLTABLE | HUGESQLTABLE><SQL_Str><OF SQL_Alias>}>
|
||||
DELETE [DELETEOPT(Option)] FROM <Expression | {<SQLTABLE |
|
||||
HUGESQLTABLE><SQL_Str><OF SQL_Alias>}>
|
||||
|
||||
[WHERE WHEREEXP]
|
||||
|
||||
@@ -6724,7 +6781,8 @@ ScoreList[:,array("学号","姓名","英语成绩")]
|
||||
|
||||
我们来看一下上边的需求使用 select 是如何实现的:
|
||||
|
||||
R1:=select \*,RoundTo((["英语成绩"]+["语文成绩"]+["历史成绩"]+["地理成绩"])/4,2) as "文科成绩" from ScoreList end;
|
||||
R1:=select \*,RoundTo((["英语成绩"]+["语文成绩"]+["历史成绩"]+["地理成绩"])/4,2)
|
||||
as "文科成绩" from ScoreList end;
|
||||
|
||||
R1 的结果集在原有结果集的基础上增加了文科成绩两列,我们可以看到 select 的使用的方法,select 和 from 之间是所需要的返回内容列表,多个返回内容之间用逗号进行间隔,\*代表原结果集的所有列,from 后则跟着所需查询的结果集,最后 select 以 end 作为结束。而每个返回的内容可以使用 as 列名来标示返回的字段列,也可以省略,一旦省略,select 会自动安排列名(如果返回内容是[]表达式,则列名则和原列名保持一致,否则列名会自动用"Expr1","Expr2"..替代),As 后的列名可以是字符串,也可以是整数。
|
||||
|
||||
@@ -6788,11 +6846,16 @@ End;
|
||||
|
||||
B:=Select \* from EnglishScore where ["英语成绩"]>85 end;
|
||||
|
||||
这样看起来是不是很简洁呢?我们是否发现这种写法更容易理解呢?事实上,SQL 语法比较接近自然语言,我们可以把以上语法理解为:查询所有的从 EnglishScore 中的英语成绩> 85 的记录。
|
||||
这样看起来是不是很简洁呢?我们是否发现这种写法更容易理解呢?事实上,SQL 语法比较接近自然语言,我们可以把以上语法理解为:查询所有的从 EnglishScore 中的英语成绩>
|
||||
85 的记录。
|
||||
|
||||
Select from 结果集之后,允许跟 where 以及一个查询条件,这个约定俗成称之为 Where 子句。
|
||||
Select
|
||||
from 结果集之后,允许跟 where 以及一个查询条件,这个约定俗成称之为 Where 子句。
|
||||
|
||||
注:TS-SQL 访问数据项是用[数组下标]的模式,而在真实的 SQL 中,假如有【英语成绩】这个字段,是不需要在【英语成绩】前后加引号的。例如在 SQL 中 abcd 或者[abcd]表示字段 abcd,而不需要用["abcd"],但是在 TSL 中却例外,因为在 TSL 语言中,abcd 表示变量,你可以采用给 abcd 赋值为"英语成绩",使用["abcd"]来表示["英语成绩"]。此外,SQL 语法的 SELECT 是不需要 END 为结束符的,SQL 语法的结束符和 TSL 类似,大多为分号“;”但如果遵循该规则,B:=Select _ from EnglishScore where ["英语成绩"]>85 end;写成 B:=Select _ from EnglishScore where ["英语成绩"]>85;;就很难看了,第一个;表示 select 的结束,第二个;表示:=语句的结束。
|
||||
注:TS-SQL 访问数据项是用[数组下标]的模式,而在真实的 SQL 中,假如有【英语成绩】这个字段,是不需要在【英语成绩】前后加引号的。例如在 SQL 中 abcd 或者[abcd]表示字段 abcd,而不需要用["abcd"],但是在 TSL 中却例外,因为在 TSL 语言中,abcd 表示变量,你可以采用给 abcd 赋值为"英语成绩",使用["abcd"]来表示["英语成绩"]。此外,SQL 语法的 SELECT 是不需要 END 为结束符的,SQL 语法的结束符和 TSL 类似,大多为分号“;”但如果遵循该规则,B:=Select
|
||||
_ from EnglishScore where ["英语成绩"]>85 end;写成 B:=Select _ from EnglishScore
|
||||
where
|
||||
["英语成绩"]>85;;就很难看了,第一个;表示 select 的结束,第二个;表示:=语句的结束。
|
||||
|
||||
##### Order By 对返回的结果集进行排序
|
||||
|
||||
@@ -6830,11 +6893,16 @@ End;
|
||||
|
||||
B:=Select \* from EnglishScore where ["英语成绩"]>85 order by ["英语成绩"] end;
|
||||
|
||||
也就是说,SQL 允许在 where 子句之后(或者省略 where 子句返回所有),可以带 order by 排序的表达式。order by 默认是从小到大排序的,事实上,order by 允许对多个字段同时进行排序,并且支持从大到小排序。我们如果要对 ScoreList 的语文成绩正序排序,然后对英语成绩和语文成绩的比值进行逆序排列,我们可以如下写:
|
||||
也就是说,SQL 允许在 where 子句之后(或者省略 where 子句返回所有),可以带 order
|
||||
by 排序的表达式。order by 默认是从小到大排序的,事实上,order
|
||||
by 允许对多个字段同时进行排序,并且支持从大到小排序。我们如果要对 ScoreList 的语文成绩正序排序,然后对英语成绩和语文成绩的比值进行逆序排列,我们可以如下写:
|
||||
|
||||
B:=select \* from ScoreList Order by ["语文成绩"] asc,["英语成绩"]/["语文成绩"] desc end;
|
||||
B:=select \* from ScoreList Order by ["语文成绩"] asc,["英语成绩"]/["语文成绩"]
|
||||
desc end;
|
||||
|
||||
也就是说 order by 后的排序表达式之后允许跟 asc 或者 desc 来描述排序的方向,缺省为正序(asc 可以省略),而 desc 则代表逆序,并且 order by 之后支持多个排序,多个排序表达式和排序方向之间使用逗号进行分割。
|
||||
也就是说 order
|
||||
by 后的排序表达式之后允许跟 asc 或者 desc 来描述排序的方向,缺省为正序(asc 可以省略),而 desc 则代表逆序,并且 order
|
||||
by 之后支持多个排序,多个排序表达式和排序方向之间使用逗号进行分割。
|
||||
|
||||
##### ThisOrder 获得排序位置问题
|
||||
|
||||
@@ -6844,7 +6912,9 @@ A:=Select \*,ThisOrder as "Order" from A order by ["AAA"] end;
|
||||
|
||||
##### DRange 返回结果集中的一部分
|
||||
|
||||
假如我们只需要英语成绩的前十名,我们就不需要将所有的结果集都返回,由于我们已经学习了矩阵的功能,所以我们可以使用 select \* from EnglishScore order by ["英语成绩"] desc end[0:9]来返回英语成绩的前十名,但是假使结果集巨大,我们完全没有必要返回所有的结果集,再取矩阵的子集,因为这样效率会低下,计算机做了许多无用的工作。
|
||||
假如我们只需要英语成绩的前十名,我们就不需要将所有的结果集都返回,由于我们已经学习了矩阵的功能,所以我们可以使用 select \*
|
||||
from EnglishScore order by ["英语成绩"] desc
|
||||
end[0:9]来返回英语成绩的前十名,但是假使结果集巨大,我们完全没有必要返回所有的结果集,再取矩阵的子集,因为这样效率会低下,计算机做了许多无用的工作。
|
||||
|
||||
TS-SQL 提供了 DRange 关键字来支持这种应用:
|
||||
|
||||
@@ -6852,13 +6922,17 @@ B:=select DRange(0 to 9) \* from EnglishScore order by ["英语成绩"] desc end
|
||||
|
||||
这样我们就可以返回英语成绩最高的前十名了。
|
||||
|
||||
DRange 在 select 后返回列表之前,DRange 的用法是 DRange(FromIndex to ToIndex),和数组的下标一样,索引号也是从 0 作为基点的的,如果我们要返回最后的 10 条,我们可以用负数来代表倒数,-1 表示最后一条,-2 表示倒数第二条,例如 DRange(-10 to -1)就代表了最后的十条。
|
||||
DRange 在 select 后返回列表之前,DRange 的用法是 DRange(FromIndex to
|
||||
ToIndex),和数组的下标一样,索引号也是从 0 作为基点的的,如果我们要返回最后的 10 条,我们可以用负数来代表倒数,-1 表示最后一条,-2 表示倒数第二条,例如 DRange(-10
|
||||
to -1)就代表了最后的十条。
|
||||
|
||||
DRange 还有另外一个用法,就是等分法,例如我们要把成绩分成十等分,我们返回第一等分的内容,也就是说英语成绩排位前 10%的内容,B:=select DRange(1 of 10) from EnglishScore order by ["英语成绩"] desc end;
|
||||
DRange 还有另外一个用法,就是等分法,例如我们要把成绩分成十等分,我们返回第一等分的内容,也就是说英语成绩排位前 10%的内容,B:=select
|
||||
DRange(1 of 10) from EnglishScore order by ["英语成绩"] desc end;
|
||||
|
||||
相比之前学过的,SQL 是否非常强大便捷和简单易懂呢?
|
||||
|
||||
在传统 SQL 中,是不存在 DRange 的,但是传统 SQL 支持一些类似的写法,例如在 SQL-SERVER 的 SQL 语言中,支持 Top N 来取前 N 条,在 Oracle 中,支持 rownum 访问结果集的序号。
|
||||
在传统 SQL 中,是不存在 DRange 的,但是传统 SQL 支持一些类似的写法,例如在 SQL-SERVER 的 SQL 语言中,支持 Top
|
||||
N 来取前 N 条,在 Oracle 中,支持 rownum 访问结果集的序号。
|
||||
|
||||
##### SELECT 统计与分组、聚集函数
|
||||
|
||||
@@ -6904,7 +6978,8 @@ End;
|
||||
|
||||
我们采用 select AvgOf(["英语成绩"]) from EnglishScore end;就可以完成上述功能。
|
||||
|
||||
当然我们需要统计 85 分以上的成绩的平均成绩的时候,使用 select AvgOf(["英语成绩"]) from EnglishScore where ["英语成绩"]>85 end 就可以了。
|
||||
当然我们需要统计 85 分以上的成绩的平均成绩的时候,使用 select
|
||||
AvgOf(["英语成绩"]) from EnglishScore where ["英语成绩"]>85 end 就可以了。
|
||||
|
||||
除了 AvgOf 求出平均数以外,还有例如 Sumof 可以求和,Countof 可以求个数,Maxof、MinOf 求最大最小值等等数十个聚集函数,可以解决常用的统计功能,用户还可以使用 TS-SQL 的 AggOf 来扩展聚集函数,关于聚集函数的扩展我们在稍后章节会另行讲述。
|
||||
|
||||
@@ -6919,7 +6994,8 @@ End;
|
||||
|
||||
这样的需要我们可以采用多条 SELECT 语句来分别实现各个成绩段的统计。但是 SQL 同样提供了直接的方法。我们看以下的写法:
|
||||
|
||||
Return SELECT AvgOf(["英语成绩"]),CountOf( \* ),groupfunc(["英语成绩"]) from EnglishScore group by groupfunc(["英语成绩"]) end;
|
||||
Return SELECT AvgOf(["英语成绩"]),CountOf( \* ),groupfunc(["英语成绩"]) from
|
||||
EnglishScore group by groupfunc(["英语成绩"]) end;
|
||||
|
||||
Function groupfunc(score);
|
||||
|
||||
@@ -6943,25 +7019,33 @@ End;
|
||||
|
||||
上述代码我们可以如此理解:groupfunc 只是一个自己定义的函数,用途仅仅只是为了上面的分组需要,因为英语成绩表中并没有什么是优秀什么是及格的标准。
|
||||
|
||||
GroupFunc 把成绩分成了四档,而 group by 则把这四档进行分组,Avgof 和 countof 对分组的内容进行聚集计算。如何理解呢?Avgof 在没有 group by 的时候是对整个结果集进行处理,而有 group by 的时候是对分组后的每个子结果集进行运算处理。
|
||||
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;
|
||||
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 条件的。
|
||||
Having 和 Where 是不是很类似呢?许多初学 SQL 的人会很容易混淆两者的差异,事实上 Having 可以用聚集函数作为条件,而 Where 是不行的,Having 是先 group 分组再计算 having 条件,而 where 则是最先开始进行条件筛选。在 group
|
||||
by 之前是允许有 where 条件的。
|
||||
|
||||
###### Group By 支持对多个值进行分组
|
||||
|
||||
Group By 支持对多个值进行分组,假定 EnglishScore 中还包括一个字段,是学生的性别,我们要分别统计男女性别的成绩分布,我们可以这样写:
|
||||
Group
|
||||
By 支持对多个值进行分组,假定 EnglishScore 中还包括一个字段,是学生的性别,我们要分别统计男女性别的成绩分布,我们可以这样写:
|
||||
|
||||
Return SELECT ["性别"],AvgOf(["英语成绩"]),CountOf( \* ),groupfunc(["英语成绩"]) from EnglishScore group by ["性别"],groupfunc(["英语成绩"]) end;
|
||||
Return SELECT ["性别"],AvgOf(["英语成绩"]),CountOf( \* ),groupfunc(["英语成绩"])
|
||||
from EnglishScore group by ["性别"],groupfunc(["英语成绩"]) end;
|
||||
|
||||
毋庸多说,Group By 支持多值的分组只要将多个用于分组的计算在 Group By 后用逗号分隔即可。
|
||||
毋庸多说,Group By 支持多值的分组只要将多个用于分组的计算在 Group
|
||||
By 后用逗号分隔即可。
|
||||
|
||||
##### join 处理多表联接查询
|
||||
|
||||
@@ -6997,7 +7081,8 @@ Return SELECT ["性别"],AvgOf(["英语成绩"]),CountOf( \* ),groupfunc(["英
|
||||
|
||||
我们现在要合成一个同时具备学号,姓名,英语成绩的结果集,这类需求我们在 SQL 中称之为多表联接,通过 join 关键字来支持:
|
||||
|
||||
R:=select [1].\*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"] end;
|
||||
R:=select [1].\*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"]
|
||||
end;
|
||||
|
||||
R 的结果就是我们要的结果集,我们来理解下多表查询和单表查询的差异:
|
||||
|
||||
@@ -7005,9 +7090,12 @@ R 的结果就是我们要的结果集,我们来理解下多表查询和单表
|
||||
|
||||
注意:如果没有字段重名的问题,\*可以表达所有字段,而["英语成绩"]也可以自动选择到包含有英语成绩的表(结果集),如果有字段重复,我们建议带[TableOrder].前缀指定表。
|
||||
|
||||
其次是 from 字句里引入了 join 和 on 关键字,A join B,表示 B 和 A 进行联接,而表与表之间进行联接的时候,必需具有联接的条件,这个条件在 on 关键字之后,上边的范例中 on [1].["学号"]=[2].["学号"]就是联接的条件。由于 select 是基于行的,所以 join 是基于行的联接,而 on 则是行联接的条件。范例的含义为:将 A 表中的学号和 B 中的学号相同的行联接起来,并返回 A 中所有列以及 B 中的英语成绩列。
|
||||
其次是 from 字句里引入了 join 和 on 关键字,A join
|
||||
B,表示 B 和 A 进行联接,而表与表之间进行联接的时候,必需具有联接的条件,这个条件在 on 关键字之后,上边的范例中 on
|
||||
[1].["学号"]=[2].["学号"]就是联接的条件。由于 select 是基于行的,所以 join 是基于行的联接,而 on 则是行联接的条件。范例的含义为:将 A 表中的学号和 B 中的学号相同的行联接起来,并返回 A 中所有列以及 B 中的英语成绩列。
|
||||
|
||||
TS-SQL 支持 JOIN,Left Join,Right Join,Full Join,cross Join 等完整的 SQL 规范,访问多表用[1].表示第一张表, [2].表示第二张,以此类推
|
||||
TS-SQL 支持 JOIN,Left Join,Right Join,Full Join,cross
|
||||
Join 等完整的 SQL 规范,访问多表用[1].表示第一张表, [2].表示第二张,以此类推
|
||||
|
||||
###### 内容
|
||||
|
||||
@@ -7018,23 +7106,34 @@ TS-SQL 支持 JOIN,Left Join,Right Join,Full Join,cross Join 等完整的 SQL
|
||||
|
||||
###### Cross Join 与 Join
|
||||
|
||||
在进行表联接的时候,我们可能需要的是一个笛卡尔积,这种情况下我们可以采用 cross join,这样可以得到多结果集所有行的完整排列组合。cross join 不支持 on 语法,但是支持 where 子句。我们一样可以用 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 join B on [1].["学号"]=[2].["学号"]
|
||||
end;
|
||||
|
||||
可以用下面的语句替换:
|
||||
|
||||
R:=select [1].\*,[2].["英语成绩"] from A cross join B where [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 的功能,那么他们两者的差异是什么呢?
|
||||
在这里,我们有一个疑问,就是既然 cross
|
||||
join 可以替代掉 join 的功能,那么他们两者的差异是什么呢?
|
||||
|
||||
首先,是在语法上的差异,join 必需有 on 子句,on 子句之后还允许使用 where 子句二次查询,例如,我们可以用 R:=select [1].\*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"] where [1].["英语成绩"]>80 end;来在联接的结果集的基础之上过滤掉英语成绩不大于 80 的。而 cross join 不支持 on 子句(可以有或者没有 where 子句)。
|
||||
首先,是在语法上的差异,join 必需有 on 子句,on 子句之后还允许使用 where 子句二次查询,例如,我们可以用 R:=select
|
||||
[1].\*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"] where
|
||||
[1].["英语成绩"]>80
|
||||
end;来在联接的结果集的基础之上过滤掉英语成绩不大于 80 的。而 cross
|
||||
join 不支持 on 子句(可以有或者没有 where 子句)。
|
||||
|
||||
其次,在使用的用途上,有差异。cross join 是笛卡尔积,而 join 的本意是根据某些条件进行联接。
|
||||
其次,在使用的用途上,有差异。cross
|
||||
join 是笛卡尔积,而 join 的本意是根据某些条件进行联接。
|
||||
|
||||
再次,在效率上,Join 具有优化的写法,而 cross join 则无法提高效率。
|
||||
|
||||
@@ -7042,7 +7141,8 @@ R:=select [1].\*,[2].["英语成绩"] from A,B where [1].["学号"]=[2].["学号
|
||||
|
||||
With on 是 on 语法的增强。
|
||||
|
||||
一个 On 运算符在 JOIN 的时候的计算复杂度为两个运算的表的元素个数相乘,是一个 N\*M 的复杂度。事实上,绝大多数 JOIN 是可以优化的。我们 TSL 里支持 WITH ON 语法,使得计算复杂度降低到 N+M 的关系
|
||||
一个 On 运算符在 JOIN 的时候的计算复杂度为两个运算的表的元素个数相乘,是一个 N\*M 的复杂度。事实上,绝大多数 JOIN 是可以优化的。我们 TSL 里支持 WITH
|
||||
ON 语法,使得计算复杂度降低到 N+M 的关系
|
||||
|
||||
在联接的时候,如果 on 的条件是一个或者多个等式约束的与,在这种时候,Join 的本身可以被优化,TS-SQL 为这种类型的优化提供了新的语法。则范例代码
|
||||
|
||||
@@ -7076,7 +7176,8 @@ with(等式左表达式组 on 等式右表达式组)
|
||||
|
||||
左右表达式组中的多个表达式用逗号“,”分隔。
|
||||
|
||||
采用这种 with on 的语法可以把笛卡尔积的计算复杂度降低到线性复杂度(参与运算的两个表的大小之和)。
|
||||
采用这种 with
|
||||
on 的语法可以把笛卡尔积的计算复杂度降低到线性复杂度(参与运算的两个表的大小之和)。
|
||||
|
||||
###### 多表 Join(超过两个结果集的 Join)
|
||||
|
||||
@@ -7084,11 +7185,13 @@ with(等式左表达式组 on 等式右表达式组)
|
||||
|
||||
我们看一个范例大家自己来理解:
|
||||
|
||||
R:=select [1].\*,[2].["英语成绩"],[3].["语文成绩"] from A join B on [1].["学号"]=[2].["学号"] join C on [1].["学号"]=[3].["学号"] end;
|
||||
R:=select [1].\*,[2].["英语成绩"],[3].["语文成绩"] from A join B on
|
||||
[1].["学号"]=[2].["学号"] join C on [1].["学号"]=[3].["学号"] end;
|
||||
|
||||
我们可以认为 join 后的结果集形成了一个新的结果集,这个结果集还可以继续和其他的结果集继续进行联接,用这种方式可以实现几乎任意多张表的联接。
|
||||
|
||||
读者必须了解到,join 的计算复杂度很大(使用 with on 可以降低),许多张表的 join 也会大大降低代码的可读性,所以在使用 join 带来的便利的时候,当遇到性能问题的时候,我们可以考虑利用数组的优势来解决性能问题。
|
||||
读者必须了解到,join 的计算复杂度很大(使用 with
|
||||
on 可以降低),许多张表的 join 也会大大降低代码的可读性,所以在使用 join 带来的便利的时候,当遇到性能问题的时候,我们可以考虑利用数组的优势来解决性能问题。
|
||||
|
||||
例:
|
||||
|
||||
@@ -7106,33 +7209,49 @@ B,C 结果集我们可以先构造出 B1 和 C1 的结果集,B1 的结果集
|
||||
|
||||
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 的结果会是一个空集,因为有俄语成绩的人没有英语成绩,有英语成绩的人没有俄语成绩。这可能和我们预期的结果是不一致的,我们或许需要的结果集是这样的:包括所有学生,没有俄语成绩的俄语成绩保留为空,没有英语成绩的英语成绩保留为空。
|
||||
假定上边的案例中的结果集 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;
|
||||
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 替换了 Join。什么是 Left
|
||||
Join 呢?与 Join 不同的是,Left
|
||||
Join 是以左结果集为基准,如果右结果集缺乏满足和左结果集中的某行联接的条件的行,那么依旧保留一条左结果集中的行,而联接的右结果集假定为空(所有字段的值为 NIL)。
|
||||
|
||||
LEFT JOIN 的结果显然吻合我们的预期,这样的结果集中包括所有 A 结果集中的学生,而没有成绩的学生保留为空。
|
||||
LEFT
|
||||
JOIN 的结果显然吻合我们的预期,这样的结果集中包括所有 A 结果集中的学生,而没有成绩的学生保留为空。
|
||||
|
||||
####### Right Join
|
||||
|
||||
Right Join 可以完全参照 Left Join,唯一不同的是 Right Join 是以右结果集为基准的。
|
||||
Right Join 可以完全参照 Left Join,唯一不同的是 Right
|
||||
Join 是以右结果集为基准的。
|
||||
|
||||
####### Full Join
|
||||
|
||||
Full Join 可以参照 Left Join 和 Right Join,不同之处是结合了 Left Join 和 Right Join,在在 Join 的结果集中增加了左右结果集中未能在 Join 结果集内的行,其处理规则和 Left Join 类同。
|
||||
Full Join 可以参照 Left Join 和 Right Join,不同之处是结合了 Left Join 和 Right
|
||||
Join,在在 Join 的结果集中增加了左右结果集中未能在 Join 结果集内的行,其处理规则和 Left
|
||||
Join 类同。
|
||||
|
||||
典型的应用:
|
||||
|
||||
如果以上的例子只有 B,C 结果集,而没有 A 结果集,B 结果集为英语成绩,C 为俄语成绩,那么用 R:=select ["学号"],[1].["英语成绩"],[2].["俄语成绩"] from B full join C on [1].["学号"]=[2].["学号"] end;就可以合并成一个整合的成绩表,除了同时修两门外语的人有两门成绩,否则只有一门成绩有内容,而另外一门成绩为空。
|
||||
如果以上的例子只有 B,C 结果集,而没有 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;
|
||||
R:=select [1].["学号"]?:[2].["学号"] as "学号",[1].["英语成绩"],[2].["俄语成绩"]
|
||||
from B full join C on [1].["学号"]=[2].["学号"] end;
|
||||
|
||||
当参与计算的表更多的时候,显然缺省["学号"]的表达方式会显得更为简洁有效。
|
||||
|
||||
@@ -7187,7 +7306,8 @@ insert into a values("06","路人甲");
|
||||
|
||||
我们先温习下之前的范例中的 A 结果集,A 结果集是包括学号和姓名两列的结果集,上边的代码是否足够清晰呢?
|
||||
|
||||
Insert Into 后跟随结果集(需要插入的对象),在结果集之后用 values(),values 之中则是所需要插入的按照列次序以逗号分隔的数据。
|
||||
Insert
|
||||
Into 后跟随结果集(需要插入的对象),在结果集之后用 values(),values 之中则是所需要插入的按照列次序以逗号分隔的数据。
|
||||
|
||||
我们现在来假定我们有一个结果集 R,这个结果集 R 具有四列,分别是学号,姓名,英语成绩,语文成绩。假定现在英语老师带来了路人甲的英语成绩,但结果集 R 上目前并未有路人甲的其他信息,现在我们希望将路人甲以及英语信息先行添加进结果集 R,用 INSERT 语句我们该怎么办呢?
|
||||
|
||||
@@ -7197,7 +7317,8 @@ insert into a values("06","路人甲",80,nil);
|
||||
|
||||
我们如果用上述的写法,我们需要记住每个列的位置,并且要给非需要的列以 nil 替代,这肯定不是一件让人心满意足的写法,这种时候,我们可以用 insert 的另外一种写法:
|
||||
|
||||
insert into a insertfields(["学号"],["姓名"],["英语成绩"]) values("06","路人甲",80);
|
||||
insert into a insertfields(["学号"],["姓名"],["英语成绩"])
|
||||
values("06","路人甲",80);
|
||||
|
||||
这看起来是不是简洁并且难以出错呢?这个语法是我们在 values 之前先用 insertfields(FieldsList)的模式来指定所需要插入的字段,FieldsList 则采用逗号分隔的[FieldName]的模式,而 values 里的 ValueList 则需要和 InsertFields 中的字段次序保持一致。
|
||||
|
||||
@@ -7247,7 +7368,9 @@ Insert into A insertfields([0],[1]) B;
|
||||
|
||||
一维数组利用 insertfields([thisrow]) values(v)来插入单个值。
|
||||
|
||||
由于默认假定 INSERT values 插入的都是二维结构,也就是插入的是一行多列的数据,因此对于一维数组直接 insert into a value(3)会得到预料外的结果,正确例子:
|
||||
由于默认假定 INSERT
|
||||
values 插入的都是二维结构,也就是插入的是一行多列的数据,因此对于一维数组直接 insert
|
||||
into a value(3)会得到预料外的结果,正确例子:
|
||||
|
||||
```text
|
||||
A:=array(1,2);
|
||||
@@ -7339,7 +7462,11 @@ Update B set ["英语成绩"] = 79 where ["学号"] = "03" end;
|
||||
|
||||
答案:
|
||||
|
||||
第一个问题,更新不需要条件则可以省略 Where 子句,这听起来很简单,但是问题带来了,什么时候才不需要使用 Where 子句?不使用 Where 子句可以实现什么?当然,不需要使用 Where 子句是有需求的,一种典型的需求是假定我们有一个数据表,其中有一个数据合法的标示,我们一次性把合法标示设置成 1 标示合法,Update R set ["合法标示"] = 1 end;另一种典型需求则是对所有行按照某种规则进行更新,例如假定英语考试太难,导致不及格率太高,老师大发慈悲,决定把英语成绩进行一次调整,调整的方法是将原始成绩开根号再乘以十,这可以这样实现:Update B set ["英语成绩"]=sqrt(["英语成绩"])\*10 end;也就是说,我们在更新的时候,值可以引用原结果集进行计算。
|
||||
第一个问题,更新不需要条件则可以省略 Where 子句,这听起来很简单,但是问题带来了,什么时候才不需要使用 Where 子句?不使用 Where 子句可以实现什么?当然,不需要使用 Where 子句是有需求的,一种典型的需求是假定我们有一个数据表,其中有一个数据合法的标示,我们一次性把合法标示设置成 1 标示合法,Update
|
||||
R set ["合法标示"] = 1
|
||||
end;另一种典型需求则是对所有行按照某种规则进行更新,例如假定英语考试太难,导致不及格率太高,老师大发慈悲,决定把英语成绩进行一次调整,调整的方法是将原始成绩开根号再乘以十,这可以这样实现:Update
|
||||
B set ["英语成绩"]=sqrt(["英语成绩"])\*10
|
||||
end;也就是说,我们在更新的时候,值可以引用原结果集进行计算。
|
||||
|
||||
第二个问题,Update 支持多个字段的同时更新,例如:
|
||||
|
||||
@@ -7440,13 +7567,15 @@ delete from R where thisrow<50;
|
||||
|
||||
##### ThisRow 的其他用途
|
||||
|
||||
在处理二维数据结构的时候,ThisRow 同样是有用的,我们使用 update 的时候,由于需要使用 set [FieldName]=Value 的模式,当字段特别多的时候,使用起来就很麻烦,这个时候,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 ["语文成绩"]=DATA["语文成绩"],["英语成绩"]=DATA["英语成绩"],.....
|
||||
where ["学号"]=DATA["学号"] end;
|
||||
|
||||
当字段很多的时候,这是否太罗嗦了呢?下列写法是否会让你觉得简洁多了呢?
|
||||
|
||||
@@ -7528,7 +7657,8 @@ R2:=VSelect SumOf( ["英语成绩"] ) from B end;
|
||||
|
||||
似乎我们可以如下写:
|
||||
|
||||
R:=select \*,Select ["课程时间"],["缺课"] from D where ["学号"]=["学号"] and ["缺课"]="Yes" end as "出勤记录" from B end;
|
||||
R:=select \*,Select ["课程时间"],["缺课"] from D where ["学号"]=["学号"] and
|
||||
["缺课"]="Yes" end as "出勤记录" from B end;
|
||||
|
||||
这个时候,我们发现了一个问题,在字查询中,我们无法直接用["学号"]来标示 B 和 D 的学号字段,而这个时候,由于不是使用 Join,所以也无法使用[1].["学号"]的模式来识别字段来源。这时,子查询不存在 Join 的第一个第二个结果集的概念,但存在上一级结果集的概念(例如相对于对 D 查询的子查询而言,B 结果集是 D 结果集查询的上层结果集)。
|
||||
|
||||
@@ -7626,7 +7756,8 @@ refsof(["行业涨幅(%)"],1)
|
||||
|
||||
我们如果要统计男女在各种年龄的平均身高,我们可以这么写:
|
||||
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]) from R Group By ["性别"],["年龄"] end;
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]) from R Group By ["性别"],["年龄"]
|
||||
end;
|
||||
|
||||
好了,接下来的问题来了:假使我们需要返回每个分组中身高的人呢?要解决这样的问题,我们便需要有方法访问到分组中的内容,这个时候,TS-SQL 引入了 ThisGroup 关键字。
|
||||
|
||||
@@ -7638,9 +7769,13 @@ R1:=Select ["性别"],["年龄"],AvgOf(["身高"]) from R Group By ["性别"],["
|
||||
|
||||
接着上边的问题,我们来看使用 ThisGroup 后的代码:
|
||||
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]),Select \* from ThisGroup where ["身高"]=Refsof(MaxOf(["身高"]),1 ) end as "最高的学生" from R Group By ["性别"],["年龄"] end;
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]),Select \* from ThisGroup where
|
||||
["身高"]=Refsof(MaxOf(["身高"]),1 ) end as "最高的学生" from R Group By
|
||||
["性别"],["年龄"] end;
|
||||
|
||||
在这个范例中,我们看到我们的子查询使用了 ThisGroup,ThisGroup 是一个特别的关键字,代表着上一层分组中的结果集。在这个范例中,我们还看到使用了 RefsOf(maxOf(["身高"]),1)来计算上一级分组中的最大身高。子查询 Select \* from ThisGroup where ["身高"]=Refsof(MaxOf(["身高"]),1 ) end 的含义是从分组中查询身高和分组的最大值相等的学生,也就是找出该分组中最高的学生。
|
||||
在这个范例中,我们看到我们的子查询使用了 ThisGroup,ThisGroup 是一个特别的关键字,代表着上一层分组中的结果集。在这个范例中,我们还看到使用了 RefsOf(maxOf(["身高"]),1)来计算上一级分组中的最大身高。子查询 Select \*
|
||||
from ThisGroup where ["身高"]=Refsof(MaxOf(["身高"]),1 )
|
||||
end 的含义是从分组中查询身高和分组的最大值相等的学生,也就是找出该分组中最高的学生。
|
||||
|
||||
###### 内容
|
||||
|
||||
@@ -7650,13 +7785,15 @@ R1:=Select ["性别"],["年龄"],AvgOf(["身高"]),Select \* from ThisGroup wher
|
||||
|
||||
如果我们现在需要返回的是分组中的具体内容而不是身高最高的学生,许多读者会这么写:
|
||||
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]),ThisGroup as "最高的学生" from R Group By ["性别"],["年龄"] end;
|
||||
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;
|
||||
R1:=Select ["性别"],["年龄"],AvgOf(["身高"]),select \* from ThisGroup end as
|
||||
"详细信息" from R Group By ["性别"],["年龄"] end;
|
||||
|
||||
ThisGroup 作为一个 from 子句所支持的特殊关键字而不是一个数据集,有很多方面的考量,最主要的原因则是因为这样实现在大多数应用下可以获得更好的效率,因为用户往往并不一定需要访问分组子集的全部内容。
|
||||
|
||||
@@ -7757,7 +7894,9 @@ B 的结果为 array(0,4)
|
||||
|
||||
##### 时间序列数据分析的特殊需要
|
||||
|
||||
时间序列数据处理往往有着和其他数据处理不同的应用要求。我们回顾下 TS-SQL 对聚集函数的支持,如果我们需要求所有数据的最高气温、最低气温以及平均气温,使用 select avgof(["平均气温"]),MaxOf(["最高气温"]),MinOf(["最低气温"]) from R end 就可以了,但是我们在做天气研究的时候,往往我们需要的是每天的最近十日以来的最低气温、最高气温以及平均气温。
|
||||
时间序列数据处理往往有着和其他数据处理不同的应用要求。我们回顾下 TS-SQL 对聚集函数的支持,如果我们需要求所有数据的最高气温、最低气温以及平均气温,使用 select
|
||||
avgof(["平均气温"]),MaxOf(["最高气温"]),MinOf(["最低气温"]) from R
|
||||
end 就可以了,但是我们在做天气研究的时候,往往我们需要的是每天的最近十日以来的最低气温、最高气温以及平均气温。
|
||||
|
||||
这种需求就是移动的序列化支持,另外一种
|
||||
|
||||
@@ -7765,7 +7904,9 @@ B 的结果为 array(0,4)
|
||||
|
||||
聚集函数以及时间序列支撑函数进行时间序列分析是 TS-SQL 的特色,但正是由于其特殊性,即便是许多 TSL 的老用户对如何利用 TS-SQL 处理时间序列并不熟悉。下边我们来进阶下 TS-SQL 时间序列处理的功能。
|
||||
|
||||
说到时间序列处理,事实上我们可以分割成两个部分,其中一个是时间序列化,另一个则是序列化分析处理,关于时间序列化而言,可以理解为数据整理的功能,例如,高频数据的秒、分钟,日间数据的日、周、月、年等数据的周期化,此外,序列化的同时还需要剔除掉非交易日的时间,这些内容大部分熟悉 TSL 语言的数据访问的用户大多非常熟悉,在这里我们反而不做重点介绍,详细可以查阅 TS-SQL 的 select SelectList from markettable datekey BegTime to EndTime of TargetCode/TargetCodeList 的相关用法。
|
||||
说到时间序列处理,事实上我们可以分割成两个部分,其中一个是时间序列化,另一个则是序列化分析处理,关于时间序列化而言,可以理解为数据整理的功能,例如,高频数据的秒、分钟,日间数据的日、周、月、年等数据的周期化,此外,序列化的同时还需要剔除掉非交易日的时间,这些内容大部分熟悉 TSL 语言的数据访问的用户大多非常熟悉,在这里我们反而不做重点介绍,详细可以查阅 TS-SQL 的 select
|
||||
SelectList from markettable datekey BegTime to EndTime of
|
||||
TargetCode/TargetCodeList 的相关用法。
|
||||
|
||||
我们在这里要着重在序列化分析处理上,这是数据分析的能力,例如在 markettable 中,数据已经被序列化了,或者用户存在一个被序列化好的结果集,我们如何对其进行分析处理呢?
|
||||
|
||||
@@ -7775,7 +7916,8 @@ REFOF,时间序列化的第一种需求,相对位置需求:
|
||||
|
||||
假定 R 结果集中包括有 close 列
|
||||
|
||||
那么 select ["close"]/refof(["close"],1) - 1 from R end;就可以返回出每天的涨幅,这足够简单吧。
|
||||
那么 select ["close"]/refof(["close"],1) - 1 from R
|
||||
end;就可以返回出每天的涨幅,这足够简单吧。
|
||||
|
||||
我们理解 refof 的功能是以向前推移当前序列到指定的行再计算第一个参数的表达式,第二个参数为推移的行数,如果第二个参数为负则朝后推移。
|
||||
|
||||
@@ -7793,7 +7935,8 @@ REFOF,时间序列化的第一种需求,相对位置需求:
|
||||
|
||||
TS-SQL 的平均数聚类的定义为:
|
||||
|
||||
AVGOF ( [ DISTINCT ] Expression [,BoolConditionExp[,N[,MovingFirst[,CacheId]]]] )
|
||||
AVGOF ( [ DISTINCT ] Expression [,BoolConditionExp[,N[,MovingFirst[,CacheId]]]]
|
||||
)
|
||||
|
||||
和 SQL 的 Average 相比,TS-SQL 增加了四个可选参数,一个是一个条件表达式,另外一个则是 N,我们在这里姑且先不理会增加的第一、三、四个参数,对于第二个参数 N,肯定是绝对多数人所希望看到的参数,这就是移动序列分析所必备的。
|
||||
|
||||
@@ -7815,9 +7958,11 @@ Return select [0],AvgOf([0]) as 1,AvgOf([0],True,10) as 2 from R end;
|
||||
|
||||
例如,我们在万科的明细里返回从昨日到当前的时间、价格、最近 10 个成交价的平均数,我们可以如此写:
|
||||
|
||||
select datetimetostr(["date"]), ["close"], avgof(["close"], ["vol"]>0,10) from tradetable datekey now()-1 to now() of "SZ000002" end;
|
||||
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)
|
||||
为了更清晰地看到其运行的结果,我们不妨用一个简单的数据序列来替代。我们假定有结果集 R,R 为一维数组,内容如下 array(1,3,
|
||||
5, 0, 2,0, 4,5,3)
|
||||
|
||||
R1:=sselect AvgOf( ThisRow,ThisRow>0,3) from R end;
|
||||
|
||||
@@ -7829,11 +7974,15 @@ R1:=sselect AvgOf( ThisRow,ThisRow>0,3) from R end;
|
||||
|
||||
在许多时候,我们需要的带条件移动平均是最近三个中大于 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(["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;
|
||||
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 的特殊情况彻底解决了。
|
||||
|
||||
@@ -7845,7 +7994,9 @@ 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)。
|
||||
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。结果和我们验算的是一样的。
|
||||
|
||||
@@ -7853,4 +8004,6 @@ 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 个符合条件的。
|
||||
R1:=sselect AvgOf( ThisRow,ThisRow>0,3,true),AvgOf( ThisRow,ThisRow>0,3,false)
|
||||
from R
|
||||
end;该参数的意义就是决定 N 到底是先移动还是先判定条件,如果为真,就先移动,则计算 N 个内符合条件的,位假表示先判定,则计算最近 N 个符合条件的。
|
||||
|
||||
Reference in New Issue
Block a user