前言
表是Q中的第一类实体,这意味着它们是存储在内存中的数据结构,就像列表或字典一样。Q的表本质上是字典的一个集合。因此,与关系数据库中的面向行的表相比,Q表是面向列的。此外,由于列表(list)是有序的,因此表的列也是,而SQL的行顺序是未定义的。Q表包含有序列列表的事实使得kdb +在存储,检索和操作顺序数据方面非常有效。这就是时间序列数据。
Kdb +处理Q表的统一环境中处理关系和时间序列数据。没有单独的数据定义语言,没有单独的存储过程语言,也不需要将内部表示映射到单独的表单以进行持久化。
一、表的定义
1. 我们可以通过两种方式来定义Q的表。
- 使用字典转置的形式
q)dc:`name`iq!(`Dent`Beeblebrox`Prefect;98 42 126) /定义一个字典
q)dc /查看所定义字典的内容
name| Dent Beeblebrox Prefect
iq | 98 42 126
q)dc[`iq;] /字典的查询
98 42 126
q)dc[;2] /字典的查询
name| `Prefect
iq | 126
q)dc[`iq; 2] /字典的查询
126
q)t:flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126) /通过将字典flip的形式来创建一个表
q)t /查看创建表的内容,通过对比,我们看出字典与表的输出展现形式的不同,字典是用竖线分隔key和value,表示用横线分隔column name和value
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)dc /查看字典的内容
name| Dent Beeblebrox Prefect
iq | 98 42 126
q)t[;`iq] /标的value值查询方式,正好与字典的相反
98 42 126
q)t[2;]
name| `Prefect
iq | 126
q)t[2;`iq]
126
q)type dc /查询字典的类型值,字典为99h
99h
q)type t /查询表的类型值,字典为98h
98h
t[integer;symbol name]:
· 它是一个二维数据结构,索引时第一个参数为整数索引值,第二个参数为符号列名。如:
q)t[2;`iq]
126
· 若只给第一个参数,即[integer],则检索表中的第integer行的数据,返回的结果为字典的形式。如:
q)t[2]
name| `Prefect
iq | 126
· 若只给第二个参数,即[;symbol name],则检索表中的symbol name列的数据,返回结果为列表。如:
q)t[;`iq]
98 42 126
· 若给定两个参数,t[integer;symbol name],则检索integer行,symbol name列的具体数据。如:
q)t[2;`iq]
126
· 表在逻辑上是字典中的列表。
· 转置字典的唯一效果是颠倒其索引的顺序; 没有改变数据结构。
- 使用标准定义语法
table:([] c 1:L 1 ; ...; c n:L n),通过这种方法定义表更加具有可读性。这里这里c i是表示列名的符号,L i是列值的对应列表。
q)t:([] name:`Dent`Beeblebrox`Prefect; iq:98 42 126) /利用标准形式定义表
q)t
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)t~flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126) /可以看出两种方式创建的表类型是一样的
1b
2. 表的元数据
我们可以通过meta关键字来查看标的数据信息(元数据)。
q)t:([] name:`Dent`Beeblebrox`Prefect; iq:98 42 126) /创建一个表t
q)t /显示表t的内容
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)meta t /通过meta关键字来查看表t的结构信息。
c | t f a
----| -----
name| s
iq | j
meta返回的结果当中,具有如下信息:
· 结果c包含表的列名称;
· 该列t包含一个type char的结果类型,表示该列的数据类型,如name列数据类型为s(symbol类型);
· 该列f包含任何外键或链接列;
· 该列a包含与所在列的所有属性(如排序(sorted)、去重(unique))。
我们也可以通过tables关键字来显示当前有哪些表在内存中。
q)tables `. /通过tables+`.操作可以查看当前内存中有哪些表存在
,`t
q)t2:([] c1:1 2 3; c2:(1 2; enlist 3; 4 5 6))
q)tables `.
`s#`t`t2
q)t1:([] c1:1 2 3 4 5;c2:`a`b`c`d`e)
q)tables `.
`t`t1`t2
二、空表
与其他数据库一样,很多时候我们一开始建立的表格都是一个空表,建立空表我们可以指定字段(列)的数据格式,也可以不指定,不指定的情况下由第一次给表添加数据的格式来决定。
q)t:([] name:(); iq:()) /创建一个未指定类型的空表
q)t1:([] name:`symbol$(); iq:`int$()) /创意一个指定类型的空表
q)t2:([] name:0#`; iq:0#0) /创意一个指定类型的空表,0#`表示指定name字段为symbol类型,0#0表示指定iq字段为int类型
q)t,:`zhagnsan,99.9 /为表t中添加数据,这里添加的name为symbol类型,iq字段为float类型,
/因此后面name字段只能添加symbol类型的数据,iq字段只能添加float类型的数据
q)t /查看表t中的数据
name iq
-------------
zhagnsan 99.9
q)t,:`lisi,98 /当我们为表t中添加的数据中的iq字段不为folat类型时会报错
'type
q)t,:`lisi,98.0 /添加成功
q)t
name iq
-------------
zhagnsan 99.9
lisi 98
q)t1,:`zhangsan,98.1 /表t1的iq字段指定的类型为int,因此添加float类型也会报错
q)t1,:`zhangsan,98 /成功添加对应类型的数据
q)t1
name iq
-----------
zhangsan 98
q)t2,:`wangwu,100
q)t2
name iq
----------
wangwu 100
三、表的主键与外键
与MySQL等数据库相似,很多表都可以有主键与外键,KDB+的表也支持主键与外键,而且使用上也更加的灵活。在其他数据库中,主键和外键的值一般是唯一的,但是KDB+中表的主键和外键的值可以不唯一,这是因为KDB+作为时序数据库,所有的值都是有顺序的,这也是它的一个特性。
1. 主键
创建含有主键的表的语法如下:
table_primary:([p 1:v 1 ; ...; p n:v n] c 1:L 1 ; ...; c n:L n)
这里[p 1:v 1 ; ...; p n:v n]为表table_primary的主键,同时主键可以是多个;
c 1:L 1 ; ...; c n:L n为表其他值。
q)kt:([eid:1001 1002 1003] name:`Dent`Beeblebrox`Prefect; iq:98 42 126) /创建还有一个主键的表kt。
q)kt /查看表还有主键kt,我们可以看到主键列与普通列之间有“|”分隔
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)ktempty:([eid:()] name:(); iq:()) 创建含有主键的空表方式一
q)ktempty
eid| name iq
---| -------
q)ktempty1:([eid:`int$()] `symbol$name:(); iq:`int$()) /创建含有主键的空表方式二
q)ktempty1
eid| name iq
---| -------
q)ktempty2:([eid:0#0] name:0#`; iq:0#0) /创建含有主键的空表方式三
q)ktempty2
eid| name iq
---| -------
我们也可以通过先创建一个普通表,后续增加主键,具体如下:
q)v:([] name:`Dent`Beeblebrox`Prefect; iq:98 42 126) /穿件一个普通表v
q)v
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)k:([] eid:1001 1002 1003) /创建一个普通表k
q)k
eid
----
1001
1002
1003
q)kt:k!v /将表k指定为表v的主键,利用!操作符
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)ktc:([lname:`Dent`Beeblebrox`Prefect; fname:`Arthur`Zaphod`Ford]; iq:98 42 126) /创建复合主键
q)ktc
lname fname | iq
-----------------| ---
Dent Arthur| 98
Beeblebrox Zaphod| 42
Prefect Ford | 126
q)ktc1:([lname:`symbol$();fname:`symbol$()] iq:`int$()) /创建含有复合主键的空表方式一
q)ktc1
lname fname| iq
-----------| --
q)ktc2:([lname:0#`;fname:0#`] iq:0#0) /创建含有复合主键的空表方式二
q)ktc2
lname fname| iq
-----------| --
2. 外键
在SQL中的外键是一个表中的列,其值是另一个表中主键列的成员。外键是用于在表之间建立关系的机制。
外键的一个重要特点是,RDBMS实施参照完整性,这意味着外键列的值要求是在相关主键列。在插入具有不存在的外键字段值的行之前,必须首先确保在相关表中存在具有该主键的行。
Q语言中的外键与我们前面讲的枚举的一个应用。
现在假设我们有张包含主键的旅行者的表kt(包含eid编号,名字和智商iq)和一张包含每个每位成员的一个重复智商测试表info_table(包含eid编号和每次测试成绩sc)
q)kt:([eid:1001 1002 1003] name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)info_table:([] eid:1003 1001 1002 1001 1002 1001; sc:126 36 92 39 98 42)
q)info_table
eid sc
--------
1003 126
1001 36
1002 92
1001 39
1002 98
1001 42
为了让智商测试表的每个eid编号都在旅行者的表kt中,我们可以将kt表的主键eid设置为智商测试表中的eid的一个外键,这样只有当向智商测试表中增加的eid编号在旅行者表的中的eid中包含此编号时才能添加成功,否则会报错。(为了方便对比,我们新建一个智商测试表info_table_foreign)
q)info_tables_foreign:([] eid:`kt$1003 1001 1002 1001 1002 1001; sc:126 36 92 39 98 42) /创建含有外键的表,
/这里eid:`kt$1003 1001 1002 1001 1002 1001;表示info_tables_foreign表的eid列参考表kt的主键eid列。
/`kt$value也是我们前面介绍的枚举的操作方法。
q)info_tables_foreign
eid sc
--------
1003 126
1001 36
1002 92
1001 39
1002 98
1001 42
q)meta info_tables_foreign /我们可以通过meta函数来查询表info_tables_foreign的一个结构信息,
/我们看到f栏中有kt,表示该表有个外键,并且对应的表是kt这张表
c | t f a
---| ------
eid| j kt
sc | j
q)info_tables_foreign,:11111,99 /这时我们往表中插入一条kt表的eid中不包含的11111编号,发现插入不成功。
'cast
q)info_tables_foreign,:1003,99 /这时我们往表中插入一条kt表的eid中包含的1003编号,插入成功。
q)info_tables_foreign
eid sc
--------
1003 126
1001 36
1002 92
1001 39
1002 98
1001 42
1003 99
q)info_tables,:11111,99 /我们往不好含外键的表中插入新的11111编号会成功,因为它是一张独立的表
q)info_tables
11111 99
3. 基本操作
我们可以通过key函数查询一个表中的主键;通过value函数查询除去主键的其他值;keys函数只返回主键的名称(不显示主键的值);cols函数查询一个表的所有列的名称,xkey函数来指定普通列转变为一个主键列或将一个主键列转为普通列;也可以通过!操作符来指定或者取消主键列;fkeys函数来返回外键的名称。具体操作方式如下:
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)key kt
eid
----
1001
1002
1003
q)value kt
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)keys kt
,`eid
q)cols kt
`eid`name`iq
q)t:([] eid:1001 1002 1003; name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
q)t
eid name iq
-------------------
1001 Dent 98
1002 Beeblebrox 42
1003 Prefect 126
q)`eid xkey t
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)`name xkey t
name | eid iq
----------| --------
Dent | 1001 98
Beeblebrox| 1002 42
Prefect | 1003 126
q)`eid`name xkey t
eid name | iq
---------------| ---
1001 Dent | 98
1002 Beeblebrox| 42
1003 Prefect | 126
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)() xkey kt
eid name iq
-------------------
1001 Dent 98
1002 Beeblebrox 42
1003 Prefect 126
q)t
eid name iq
-------------------
1001 Dent 98
1002 Beeblebrox 42
1003 Prefect 126
q)1!t
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)2!t
eid name | iq
---------------| ---
1001 Dent | 98
1002 Beeblebrox| 42
1003 Prefect | 126
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)0!kt
eid name iq
-------------------
1001 Dent 98
1002 Beeblebrox 42
1003 Prefect 126
q)info_tables_foreign
eid sc
--------
1003 126
1001 36
1002 92
1001 39
1002 98
1001 42
1003 99
q)meta info_tables_foreign
c | t f a
---| ------
eid| j kt
sc | j
q)fkeys info_tables_foreign
eid| kt
四、表的基本操作(虚拟列)
说到数据库,我们首先想到的就是增删改查,这里我们简单介绍一下表的基本操作,下一个主题我将会专门来分享一下表的详细的增删改查的操作。
1. 查询
- 1) count函数可以查询一个表有多少行数据;
- 2) select函数可以查询表中的具体数据;
- 3) 我们也可以用字典的查询方式来操作表,但是返回的结果是字典。
q)t:([] name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
q)t
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)count t /使用count函数可以查看表t中有多少行数据
3
q)t /表的第一种查询方式,适合小数据
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)t[0] /利用字典或者列表的索引方式,查询表t的第一行数据
name| `Dent
iq | 98
q)s:t[0]
q)s
name| `Dent
iq | 98
q)type s /可以看处利用字典这样的索引方式返回的结果是字典
99h
q)type t
98h
q)select from t /使用select查询,这里是查询所有类容,类似于前面的直接输入一个t返回的结果
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)select name from t /查询表t中name字段的所有内容
name
----------
Dent
Beeblebrox
Prefect
2. 增添
前面介绍多,给表中增加数据与字典中增加数据一样,使用,:操作符就可以快速增添新的数据。
q)t
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
q)t,:`wangwu,99 /通过,:操作符来给表t中增添数据,不报错就是添加成功
q)t
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
3. 删除
删除也是使用delete,与SQL相似。
q)t
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
q)delete name from t /删除表t中的name字段
iq
---
98
42
126
99
4. 更新
更新使用update,也是与SQL类似。
q)update iq:199 from t where name=`wangwu /这里我们想修改name是王五的iq为199,这里不能写成iq=99,Q语言的赋值都是:操作符
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 199
5. 合并
有时候我们可能会涉及将两个表进行合并,合并有“,”合并与“^” 合并,“,”
q)t,`name`iq!(`lisi;`123) /“,”合并与上面讲的,:增添很类似,至于不会进行数据类型检查,这里`123为symbol类型,而不是int类型也能添加成功
name iq
---------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
lisi `123
q)t,([] name:1#`w; iq:1#26) /这里1#`w的含义就是产生一个`w,2#`w就是产生两个`w`w
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
w 26
q)t,([] iq:1#42; name:`W) /列不对应,但是名称相同也会合并成功
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
W 42
q)t,([] iq:1#`w; name:`W)
name iq
--------------
Dent 98
Beeblebrox 42
Prefect 126
wangwu 99
W `w
q)kt:([eid:1001 1002 1003] name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
q)kt
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 126
q)kt,([eid:1003 1004] name:`Prefect`W; iq:150 26) /当合并时两边有相同的数据时,右边的会替换左右的元表中的数据
eid | name iq
----| --------------
1001| Dent 98
1002| Beeblebrox 42
1003| Prefect 150
1004| W 26
q)([k:`a`b`c] v:10 0N 30)^([k:`a`b`c] v:100 200 0N) /^合并以右边的为主,缺少项(0N)则会用左边表中的数据填补
k| v
-| ---
a| 100
b| 200
c| 30
q)([k:`a`b`c`x] v:10 0N 30 40)^([k:`a`b`c`y]; v:100 200 0N 0N)
k| v
-| ---
a| 100
b| 200
c| 30
x| 40
y| 0N
q)([] c1:`a`b`c),'([] c2:100 200 300)
c1 c2
------
a 100
b 200
c 300
q)([] c1:`a`b`c; c2:1 2 3),'([] c2:100 200 300)
c1 c2
------
a 100
b 200
c 300
q)([k:1 2 3] v1:10 20 30),'([k:3 4 5] v2:1000 2000 3000) /注意仔细理解这里的链接方式
k| v1 v2
-| -------
1| 10
2| 20
3| 30 1000
4| 2000
5| 3000
五、复杂的数据列
Q语言中的表还具有特殊性,前面我们介绍过列表(list)、字典(dictionary),字典是列表的特殊方式,而表(table)又是字典的特殊方式,因此表就有一种特殊的形式了,因此某个列或者某个value中的数值可以包含多个值,所以当前整个字段就变成了嵌套列表,当然我们还是推荐简单列表,这样的处理速度更快,这才是Q语言的本质。
下面举个例子,对于股票书籍,我们每天可能会记录一个开盘价和收盘价,则可以这样记录:
q)tm:([] date:2015.01.01 2015.01.08; stock_name:`Apple`IBM; Price_change:(38.92 67.34; 16.99 31.69))
q)tm
date stock_name Price_change
------------------------------------------------------
2015.01.01 Apple 38.92 67.34
2015.01.08 IBM 16.99 31.69
可能还有一些时候我们会记录一些更加复杂的数据,比如:
q)tm1:([] wk:2015.01.01 2015.01.08; rv:(38.92 67.34; 16.99 5.14 128.23 31.69))
q)tm1
wk rv
----------------------------------
2015.01.01 38.92 67.34
2015.01.08 16.99 5.14 128.23 31.69
这里我们的rv列就是一个复杂的数据列,我们对复杂的数据列也有特殊的操作方式,如:
q)select wk, srt:desc each rv, avgr:avg each rv, hi:max each rv from tm1
/desc each rv表示将rv字段的每个格子的值降序排列,由于rv字段不是简单列表,因此要用到each
/ avg each rv表示求rv字段每一个格子中的平均值
/ max each rv表示返回rv字段每一个格子中的最大值
wk srt avgr hi
-------------------------------------------------
2015.01.01 67.34 38.92 53.13 67.34
2015.01.08 128.23 31.69 16.99 5.14 45.5125 128.23
q)select wk, drp:neg 1_'deltas each desc each rv from tm1
/ neg 1_'deltas each desc each rv表示求rv字段的每个格子中的值的增量值,并降序排列
wk drp
---------------------------
2015.01.01 ,28.42
2015.01.08 96.54 14.7 11.85
六、表的属性与操作
我们可以给表中的一些字段添加一些特殊属性(应用到#的重载操作),非常类似于SQL中的约束条件,但是在Q语言中这写属性不是结构性的,而是描述性的,意思是添加了这些属性后,Q语言只会去检查是否满足这些属性,但是并不会去让解释器将添加了属性的字段强制变成这个属性。比如添加一个属性到某个字段字段是增序排列,如果不是增序排列,并不会将结果变成增序排列。具体可以参考下面的案例。
这里我们互主要介绍一下排序(s#),唯一(
u#),parted(p#),分组(
g#)和删除属性
q)`s#1 2 4 8 /给一个列表添加降序(`s)属性
`s#1 2 4 8
q)`s#2 1 3 4
's-fail
q)asc 2 1 8 4 / 这里我们可以看出`s属性与sac操作返回结果是一样的
`s#1 2 4 8
q)til 5
0 1 2 3 4
q)L:`s#1 2 3 4 5
q)L,:6
q)L
`s#1 2 3 4 5 6
q)L,:0 /当我们给一个有`s属性的列表添加一个会破坏该属性的值时,该列表的`s属性就会消失
q)L
1 2 3 4 5 6 0
q)t:([] ti:`s#00:00:00 00:00:01 00:00:03; v:98 98 100.) /我们给表中的某一个字段添加`s属性
q)t
ti v
------------
00:00:00 98
00:00:01 98
00:00:03 100
q)meta t /这时候我们就可以通过meta来查看表t的属性,a字段就出现了s的标识
c | t f a
--| -----
ti| v s
v | f
q)meta `s#([] ti:00:00:00 00:00:01 00:00:03; v:98 98 100.)
c | t f a
--| -----
ti| v p
v | f
q)meta `s#([k:1 2 3 4] v:`d`c`b`a)
c| t f a
-| -----
k| j s
v| s
q)`u#2 1 4 8 /`u的属性是让列表中的数据保持唯一性
`u#2 1 4 8
q)`u#2 1 4 8 2 /当该列表的每个数据不具有唯一性时就会报错
'u-fail
q)L:`u#2 1 4 8
q)L,:3
q)L
`u#2 1 4 8 3
q)L,:2 /当为列表中添加的数据列表中已经包含了的时候就会使该列表失去唯一的属性
q)L
2 1 4 8 3 2
q)`p#2 2 2 1 1 4 4 4 4 3 3
`p#2 2 2 1 1 4 4 4 4 3 3
q)`p#2 2 2 1 1 4 4 4 4 3 3 6 /`p属性就是检查后面的数据是否在前面出现过,如果出现则就会报错,
/但是可以连续出现,如222,222111都可以,但是不能2221112这样的
`p#2 2 2 1 1 4 4 4 4 3 3 6
q)`p#2 2 2 1 1 4 4 4 4 3 3 6 1
'u-fail
q))`p#2 2 2 1 1 4 4 4 4 3 3 6 2
'u-fail
q)`g#1 2 3 2 3 4 3 4 5 2 3 4 5 4 3 5 6
`g#1 2 3 2 3 4 3 4 5 2 3 4 5 4 3 5 6
q)L:`s#til 10
q)L
`s#0 1 2 3 4 5 6 7 8 9
q)`#L /通过`#操作就可以直接去掉属性
0 1 2 3 4 5 6 7 8 9