在本章中,将介绍kdb +,它是Kx提供的数据库。粗略地说,kdb +是当q表被持久化然后映射回内存以进行操作时会发生的事情。
一、内存中的表与序列化的表
只要有足够的物理内存来保存,就可以将表完全保留在内存中。从数据库的角度来看,这有一个问题:
内存中表是短暂的,意味着如果q进程死亡,所有的数据都将丢失。
一种解决方案是使用set或类似的机制将表序列化为持久存储。我们先回复一下之前已经介绍的关于内存中的表与序列化的表的相关知识。
1. 表与键表
简单回顾一下表的几种创建形式。
q)flip `s`v!(`a`b`c;100 200 300) /通过对字典的转置形式创建表
s v
-----
a 100
b 200
c 300
q)([] s:`a`b`c; v:100 200 300) /通过定义形式穿件表
s v
-----
a 100
b 200
c 300
q)([] s:`symbol$(); v:`int$()) /通过定义形式穿件一个空表
s v
---
q)t:([] s:`a`b; v:10 20) /简单创建表并赋值
q)([id:1001 1002 1003] s:`a`b`c; v:100 200 300) /创建一个键表
id | s v
----| -----
1001| a 100
1002| b 200
1003| c 300
q)([id:`int$()] s:`symbol$(); v:`int$()) /创建一个空的键表
id| s v
--| ---
q)meta t /查询表的相关结构信息
c| t f a
-| -----
s| s
v| j
2. 外键和连接列
q)kt:([id:1001 1002 1003] s:`a`b`c; v:100 200 300)
q)t:([]; id:`kt$1002 1001 1003 1001; q:100 101 102 103) /表t的有一个外键对应的是kt表的主键
q)kt
id | s v
----| -----
1001| a 100
1002| b 200
1003| c 300
q)t
id q
--------
1002 100
1001 101
1003 102
1001 103
q)meta t /查询表t的信息,f列表示外键列,会显示外键对应的表
c | t f a
--| ------
id| j kt
q | j
q)select id.v,q from t /外键的联接查询方式
v q
-------
200 100
100 101
300 102
100 103
链接列类似于外键,因为其条目是表中行的索引,但需要手动执行查找。链接列的优点是:
目标可以是表或键控表。
目标可以是包含链接列的表。
链接列可以展开或分区,而外键则不能。
下面是一个链接表的示例:
q)tk:([] id:1001 1002 100; s:`a`b`c; v:100 200 300)
q)t:([]; id:`tk!(exec id from tk)?1002 1001 1003 1001; q:100 101 102 103)
q)tk
id s v
----------
1001 a 100
1002 b 200
100 c 300
q)t
id q
------
1 100
0 101
3 102
0 103
q)meta t
c | t f a
--| ------
id| j tk
q | j
q)tree:([] id:0 1 2 3 4; pid:`tree!0N 0 0 1 1; v:100 200 300 400 500)
q)tree
id pid v
----------
0 100
1 0 200
2 0 300
3 1 400
4 1 500
q)select from tree where pid=0
id pid v
----------
1 0 200
2 0 300
3. 序列化的表
q)`:/data/t set ([] s:`a`b`c; v:100 200 300)
`:/data/t
q)\\
>q
q)t /重新打开会话后表t在内存中已经清除了
't
q)t:get `:/data/t /将本地序列化的表t映射到内存中
q)t
q)t
s v
-----
a 100
b 200
c 300
q)kt:([id:1001 1002 1003] s:`a`b`c; v:100 200 300)
q)tk:([] id:1001 1002 100; s:`a`b`c; v:100 200 300)
q)`:/data/kt set kt
`:/data/kt
q)`:/data/tk set tk
`:/data/tk
q)t1:([]; id:`kt$1002 1001 1003 1001; q:100 101 102 103)
q)t2:([]; id:`kt!(exec id from tk)?1002 1001 1003 1001; q:100 101 102 103)
q)`:/data/t1 set t1
`:/data/t1
q)`:/data/t2 set t2
`:/data/t1
q)\\
>q
q)t1:get `:/data/t1 /我们知道t1有一个关于表kt的外键,在先导入kt的情况下,我们是可以导入表t1的。
q)t1
id q
------
1 100
0 101
3 102
0 103
q)meta t1 /但是我们查询t1的结构信息的时候就会报错,因为表kt并没有自动映射进来
'..kt
q)kt:get `:/data/kt /此时需要我们导入表kt
q)meta t1 /查询表t1的结构信息成功
c | t f a
--| ------
id| j kt
q | j
q)tk:get `:/data/tk
q)kt:get `:/data/kt
q)t2:get `:/data/t2
4. 序列化的表的操作
使用序列化表或键表的机制是,在后台,Q操作将其加载到内存中并将其写回。除此之外,这意味着任何想要使用它的人都必须能够将其完全导入内存中。
q)`:/data/t set ([] s:`a`b`c; v:100 200 300) /将表序列化到本地
`:/data/t
q)select from `:/data/t where s in `a`c /通过给定指定路径来对序列化的表查询
s v
-----
a 100
c 300
q)`:/data/t upsert (`x;42) /通过给定指定路径来添加记录
`:/data/t
q).[`:/data/t;();,;([] s:`y`z; v:400 500)]
`:/data/t
q)select from `:/data/t
s v
-----
a 100
b 200
c 300
x 42
y 400
z 500
q)select from `:/data/t where s in `x`y`z
s v
-----
x 42
y 400
z 500
q)`:/data/kt set ([k:`a`b`c] v:10 20 30)
`:/data/kt
q)`:/data/kt upsert (`b;200)
`:/data/kt
q)`:/data/kt upsert (`x;42)
`:/data/kt
q)select from `:/data/kt
k| v
-| ---
a| 10
b| 200
c| 30
x| 42
5. 数据库
我们前面介绍的形式都是将数据序列化到本地后以单个文件来存储的形式,但是假如我们的数据量巨大的时候呢?我们是否应该对这样的数据进行分割处理?后面我们介绍的其实就是数据的分割序列化的思想。基于此我们可以从三个维度来对表进行分割处理,一是纵向分割,那就是扩展表;而是纵向+横向分割,那就是分区表;三是纵向+横向+分段,那就是分段表。
二、扩展表
在前面的介绍中,我们看到可以使用序列化来持久保存表。从数据库的角度来看,由于整个表被加载到内存中,因此对序列化表进行操作存在(至少)两个问题。
1)整个表必须适合每个用户机器的内存(可能我的电脑16G,别的电脑只有8G);
2)由于每次都重新加载整个表,对持久表的操作会很慢。
当表太大而无法作为单个实体放入内存时,我们可以将其组件保存到目录中。这被称为splaying table,其核心思想就是将表按照列字段来拆分。具体我们可以参看下图。
扩展解决了内存/重新载入的问题,因为splayed表被映射到内存中,我们按需加载列,然后在不再需要时释放内存。具有许多列的表特别受益于扩展序列化,因为大多数查询仅引用少数列,并且实际上只会加载那些列。
扩展表会将整个表序列化为一个文件夹,同时将表的每个字段都序列化为以其字段名称为命名的文件。
通常,表目录是从作为数据库根目录的向下创建的。
/root
/tablename <- 表的目录
.d <- 包含字段名称的文件
column1name <- 字段文件
column2name <- 字段文件
…
1. 创建扩展表
创建扩展表与普通序列化表的方式一样,只是在表名后面加了一个/号,这样就是将一个文件变成一个目录文件了。具体形式如下:
[`:/路径/表名/] set 表
q)t:([] v1:10 20 30; v2:1.1 2.2 3.3)
q)`:/db/t/ set t
`:/db/t/
此时我们可以看到我们对应的路径db文件夹下多了一个表t的文件夹,文件夹里多了几个文件。
.d文件保存了所有字段名称的信息,v1文件保存了v1字段的数据信息,v2字段保存了v2字段的数据信息。
q)\ls -a /db/t /我们可以通过ls命令来查看当前文件夹下的所有文件
,"."
".."
".d"
"v1"
"v2"
也可以upsert操作来使用文件句柄作为表名创建扩展表,如果当前表存在,则直接添加,如果不存在,则重新创建(此时upsert与set的意义就一样了)。
q)`:/db/t2/ upsert ([] v1:10 20 30; v2:1.1 2.2 3.3)
`:/db/t2/
我们也可以使用前面介绍的函数形式来创建扩展表。
q).[`:/db/t3/; (); ,; ([] v1:10 20 30; v2:1.1 2.2 3.3)]
`:/db/t3/
同时我们也可以使用.Q.en[`路径; table_name]来进行扩展序列化。使用.Q.en的方便之处就是对于表中含有symbol类型的数据也可以序列化,普通set序列化就会报type类型错误。
q)`:/db/t4/ set ([] s1:`a`b`c; v:10 20 30; s2:`x`y`z)
'type
q)`:/db/t4/ set .Q.en[`:/db; ([] s1:`a`b`c; v:10 20 30; s2:`x`y`z)]
`:/db/t4/
接下来我们对我们序列化后的几个文件进行验证一下。
q)\\
C:\Users\caoxi>q
q)get `:/db/t/.d /查看序列化后.d文件的内容
`v1`v2
q)get `:/db/t/v1 /查看序列化后v1文件的内容
10 20 30
q)get `:/db/t/v2 /查看序列化后v2文件的内容
1.1 2.2 3.3
q)\l /db/t /将表t导入到我们的内存中
`t
q)t
v1 v2
------
10 1.1
20 2.2
30 3.3
q)\l /db /将整个db目录下的序列化表导入到我们的内存中
q)t2
v1 v2
------
10 1.1
20 2.2
30 3.3
q)t3
v1 v2
------
10 1.1
20 2.2
30 3.3
Q对表的扩展也有一些限制:
1) 普通表可以扩展,但是键表不能扩展;
由于扩展是列向分割,而键表这些是有一些关联关系的,因此不适合。
2) 只能显示简单列表或复合列表的列。通过复合列表中,我们的意思是一致的类型的简单列表的列表;
在实践中并不是太有限制,因为数据通常以统一的格式出现,可以很容易地放入简单或复合列表中。顺便提一下,这种限制的原因是使用映射的通用列表比使用简单列表或复合列表要慢得多,特别是对于非常大的数据集。
3) 所有的symbol数据会被枚举到一个sym文件中。
2. 带symbol数据的扩展表创建
扩展(分区表)表中带有符号数据的列约定是所有表(比如/db目录下序列化好几个表)中的所有列符号数据会序列化到列表(list)sym上进行枚举,列表sym被序列化到根目录中。
q)`:/db/st/ set .Q.en[`:/db; ([] s1:`a`b`c; v:10 20 30; s2:`x`y`z)]
`:/db/st/
q)\ls /db
"sym"
,"t"
此时我们在db目录下再序列化一个表st1,也会将st1中的symbol类型的数据序列化到sym文件中。
q)\\
C:\Users\caoxi>q
q)\l /db
q)sym /我们可以看到sym文件中包含了st表和st1表中的所有symbol数据,
不会在序列化第二个表的时候覆盖个表的数据,
会将该目录下的所有表的symbol数据都序列化到该文件当中
`a`b`c`x`y`z`e`f`g
q)select from st
s1 v s2
--------
a 10 x
b 20 y
c 30 z
q)select from st1
s1 v s2
--------
a 10 x
e 20 e
f 30 g
还有一种情况,当我们只导入了db目录下的某一个表的时候,系统是不会自动也将sym文件也导入的。这时我们会发现导入的表的symbol数据会以在sym文件中的索引号来显示。当我们将sym文件导入到内存后,才会显示symbol数据。
q)\\
C:\Users\caoxi>q
q)\l /db/st
`st
q)st
s1 v s2
--------
0 10 3
1 20 4
2 30 5
q)\l /db
q)st
s1 v s2
--------
a 10 x
b 20 y
c 30 z
3. 嵌套数据的扩展表创建
对于表的字段中含有嵌套或者符合列表数据也可以进行序列化。
q)t:([] ci:(1 2 3; enlist 4; 5 6); cstr:("abc";enlist"d";"ef"))
q)meta t
c | t f a
----| -----
ci | J
cstr| C
q)`:/db/tcomp2/ set t
`:/db/tcomp2/
我们可以看到我们序列化后的文件夹里比之前多了几个#结尾的文件。#结尾的文件是包含原始列表的二进制数据,而非#文件是序列化的列表,
q)`:/db/tcomp3/ set ([] c:(1;1,1;`1))
`:/db/tcomp3/
q)meta ([] c:(1 2 3 ;1,1;`1))
c| t f a
-| -----
c| J
q)`:/db/tbad/ set ([] c:(1 2 3 ;1,1;`1))
`:/db/tbad/
q)`:/db/tstr/ set ([] c:("abc";enlist "d";"ef"))
`:/db/tstr/
将一个复合字段序列化为两个文件的目的是在将显示的表映射到内存时加快对它们的操作。当然,处理速度不会像简单列表的字段那么快,但对于大多数用途来说仍然很快。
在设计kdb +数据库时经常出现的一个问题是,是将文本数据存储为符号还是字符串。符号的优点是它们具有原子语义,并且一旦枚举它们就是封面下的整数,处理速度非常快。符号的主要问题是,如果将所有文本组成符号,则sym列表会变得庞大,枚举的优势也会消失。
相反,字符串不会使用一次性实例弄乱sym列表,并且速度相当快。
4. 扩展表的基本操作
我们可以在打开会话的时候就加载表或者进入会话之后使用\l命令来加载表。
C:\Users\caoxi>q /db/t
q)select from t
s1 v s2
--------
0 10 3
1 20 4
2 30 5
q)\l /db/t
`t
q)t
s1 v s2
--------
0 10 3
1 20 4
2 30 5
q)select from t
s1 v s2
--------
0 10 3
1 20 4
2 30 5
我们发现表t中的s1字段和s2字段中的symbol数据并没有显示出来,而是显示的一个数字,那是因为我们没有将sym文件加载到内存中,因此其只是显示的这些symbol数据在sym中的一个索引值,我们把sym文件加载到内存中后就会显示symbol数据。
q)sym: get `:/db/sym
q)t
s1 v s2
--------
a 10 x
b 20 y
c 30 z
下面是前面介绍的表的一些基本操作,就不详细介绍了。
q)\a
,`t
q)meta t
c | t f a
--| -----
s1| s
v | j
s2| s
q)cols t
`s1`v`s2
q)type t
98h
q)count t
3
q)t[0]
s1| `sym$`a
v | 10
s2| `sym$`x
q)t `s1
`sym$`a`b`c
q)select from t where s1=`c
s1 v s2
--------
c 30 z
5. 以扩展表的路径操作
我们也可以直接给表的路径来进行相应的操作。但是我们更新数据好是先将表映射到我们的内存中,然后再去跟新数据,如果只是查询,可以不映射到内存中。
q)select from `:/db/t
s1 v s2
--------
0 10 3
1 20 4
2 30 5
q)select from `:/db /查看db目录下有哪些表
st | +`s1`v`s2!(`sym!0 1 2;10 20 30;`sym!3 4 5)
st1 | +`s1`v`s2!(`sym!0 6 7;10 20 30;`sym!3 6 8)
sym | `a`b`c`x`y`z`e`f`g
t | +`s1`v`s2!(`sym!0 1 2;10 20 30;`sym!3 4 5)
tbad | +(,`c)!,(1 2 3;1 1;`1)
tcomp2 | +`ci`cstr!((1 2 3;,4;5 6);("abc";,"d";"ef"))
tcomp3 | +(,`c)!,(1;1 1;`1)
tsplay5| +`c1`c2!(`sym!0 1 2;10 20 30)
tstr | +(,`c)!,("abc";,"d";"ef")
由于嵌套列表的序列化机制不同,因此不加载sym文件,也会显示symbol数据类型的数据,但是普通symbol类型的字段就不会显示,这里的表t的symbol数据还是以sym文件的索引号来显示的。
q)select from `:/db/tcomp3
c
---
1
1 1
`1
q)select from `:/db/tcomp2
ci cstr
-----------
1 2 3 "abc"
,4 ,"d"
5 6 "ef"
q)exec v from `:/db/t
10 20 30
q)`v xdesc `:/db/t
`:/db/t
q)select from `:/db/t
s1 v s2
--------
2 30 5
1 20 4
0 10 3
q)@[`:/db/t;`s1;`p#]
`:/db/t
q)select from `:/db/t
s1 v s2
--------
2 30 5
1 20 4
0 10 3
q)\l /db
q)meta t
c | t f a
--| -----
s1| s p
v | j
s2| s
更新数据还是建议先将表映射到内存后再来更新。我们更新的只是内存中的表的数据,但是并没有更新磁盘中的数据,所以还需要重新序列化。
q)t
s1 v s2
--------
c 30 z
b 20 y
a 10 x
q)update v:300 from `t where s1=`c
`t
q)select from t
s1 v s2
---------
c 300 z
b 20 y
a 10 x
q)\l /db
q)select from t
s1 v s2
--------
c 30 z
b 20 y
a 10 x
而且我们使用下面的方式来更新也不行(可能还存在其他方法我没有发现)。
q)select from `:/db/t
s1 v s2
--------
2 30 5
1 20 4
0 10 3
q)update v:300 from `:/db/t where s1=2
'type
6. 扩展表的数据添加
我们使用upsert来直接将我们的数据添加到我们的磁盘文件中。
q)`:/db/t/ set .Q.en[`:/db;] ([] s1:`a`b`c; v:10 20 30; s2:`x`y`z)
`:/db/t/
q)`:/db/t/ upsert .Q.en[`:/db;] ([] s1:`d`e; v:40 50; s2:`u`v)
`:/db/t/
q)`:/db/t upsert .Q.en[`:/db;] enlist `s1`v`s2!(`f;60;`t)
`:/db/t
q)`:/db/t upsert .Q.en[`:/db;] flip `s1`v`s2!flip ((`g;70;`r);(`h;80;`s))
`:/db/t
q)select from `:/db/t
s1 v s2
--------
a 10 x
b 20 y
c 30 z
d 40 u
e 50 v
f 60 t
g 70 r
h 80 s
q)\l /db/t
`t
q)t
s1 v s2
--------
a 10 x
b 20 y
c 30 z
d 40 u
e 50 v
f 60 t
g 70 r
h 80 s
q)select from t
s1 v s2
--------
a 10 x
b 20 y
c 30 z
d 40 u
e 50 v
f 60 t
g 70 r
h 80 s
q)\\
q)\l /db/t
`t
q)t
s1 v s2
--------
0 10 3
1 20 4
2 30 5
6 40 8
7 50 9
10 60 11
12 70 14
13 80 15
q)\l /db
q)t
s1 v s2
--------
a 10 x
b 20 y
c 30 z
d 40 u
e 50 v
f 60 t
g 70 r
h 80 s
上面这种插入方法很多程序员肯定觉得繁琐,我们就可以写成标准的函数添加形式来添加数据。以这种方式我们就可以逐步来构建一个大型的扩展表。batch函数是添加函数,dayrecs是一个表。后面就是如何添加了。
q)batch:{[rt;tn;recs] hsym[`$rt,"/",tn,"/"] upsert .Q.en[hsym `$rt;] recs}
q)dayrecs:{([] dt:x; ti:asc 100?24:00:00; sym:100?`ibm`aapl; qty:100*1+100?1000)}
q)appday:batch["/db";"t";]
q)appday
{[rt;tn;recs] hsym[`$rt,"/",tn,"/"] upsert .Q.en[hsym `$rt;] recs}["/db";"t";]
q)appday dayrecs 2015.01.01
`:/db/t/
q)appday dayrecs 2015.01.02
`:/db/t/
q)appday dayrecs 2015.01.03
`:/db/t/
q)\l /db
q)t
dt ti sym qty
------------------------------
2015.01.01 00:00:11 aapl 90900
2015.01.01 00:07:32 ibm 36100
2015.01.01 00:32:28 ibm 52300
2015.01.01 00:49:36 aapl 25800
2015.01.01 01:39:48 ibm 85900
2015.01.01 01:41:33 aapl 58600
2015.01.01 01:45:53 ibm 9100
2015.01.01 03:42:20 aapl 68400
2015.01.01 03:42:43 aapl 9100
2015.01.01 03:48:19 aapl 87000
2015.01.01 03:54:22 ibm 46900
2015.01.01 04:04:41 ibm 96000
2015.01.01 04:27:03 ibm 22200
2015.01.01 04:28:50 ibm 69500
2015.01.01 04:46:43 aapl 93500
2015.01.01 04:47:08 ibm 86600
2015.01.01 04:54:24 ibm 34500
2015.01.01 04:58:16 ibm 99800
2015.01.01 05:21:02 ibm 31500
2015.01.01 05:23:47 ibm 58100
..
7. 扩展表的手动操作
前面介绍了如果表扩展表的一个基本操作,但是我们可能还有一种情况,那就是如果我们要删除某个字段或者新添加一个字段如何来操作呢?然没有内置操作来更新磁盘上的splayed表,但可以通过操作序列化文件来执行此类操作。
q)`:/db/t/ set ([] ti:09:30:00 09:31:00; p:101.5 33.5)
`:/db/t/
q)`:/db/t/p set .[get `:/db/t/p; where 09:31:00=get `:/db/t/ti; :;42.0] /通过反序列化然后再序列化的方式来更新表中的数据。
`:/db/t/p
q)\l /db/t
`t
q)t
ti p
--------------
09:30:00 101.5
09:31:00 42 /修改成功
q)`:/db/t/s set (count get `:/db/t/ti)#` /我们要新添加一个字段s
`:/db/t/s
q)`:/db/t/.d set get[`:/db/t/.d] union `s /这时还需要将字段s添加到.d文件当中
`:/db/t/.d
q)t
ti p
--------------
09:30:00 101.5
09:31:00 42
q)\l /db/t /重新载入后可以看到字段s添加成功
`t
q)t
ti p s
----------------
09:30:00 101.5
09:31:00 42
q)`:/db/t/.d set get[`:/db/t/.d] except `s /同样我们要删除一个字段也一样,在.d文件中删除字段s的记录就可以了。
然后再通过rm命令删除s的字段文件。
`:/db/t/.d
system "rm /db/t/s"
q)\l /db
q)t
ti p
--------------
09:30:00 101.5
09:31:00 42
8. 对sym文件的操作
有时可能需要执行涉及sym文件的操作。例如,可能希望将表从一个数据库移动到另一个数据库。或者可能会发现sym文件受到破坏。或者可能不明智地选择了具有许多不同的枚举域,并且希望将它们合并为一个。所有这些情况都可以通过对sym文件进行仔细操作来解决。
更新sym文件非常简单,但是得记得一定要先备份一个sym文件,尤其是一个目录下有多个表的存在。sym文件损坏可能几乎肯定使数据库无法使用。
下面首先展示如何将一个数据库中的表合并到另一个数据库,假设我们有根目录/db1和/db2,并具有扩展表t1和t2,在对应的目录/db1和/db2。
q)`:/db/db1/t1/ set .Q.en[`:/db/db1;] ([] s1:`a`b`c; v:10 20 30)
`:/db/db1/t1/
q)`:/db/db2/t2/ set .Q.en[`:/db/db2;] ([] s2:`c`d`e; v2:300 400 500)
`:/db/db2/t2/
q)get `:/db/db1/sym
`a`b`c
q)get `:/db/db2/sym
`c`d`e
现在假设我们要把t2表也移动到db1目录下,那我们就需要对sym文件进行合并。我们首先需要对t2的sym文件的枚举进行取消,然后到t1的sym文件中进行重新枚举。
q)symcols2:exec c from meta `:/db/db2/t2 where t="s" /将表t2中的symbol类型字段的名称找出来,这里要用exec,因为exec返回的是一个列表,不是一个表,然后将结果赋值给symcols2变量
q)symcols2
,`s2
q)meta `:/db/db2/t2
c | t f a
--| -----
s2| s
v2| j
q)t2noenum:@[select from `:/db/db2/t2;symcols2;value] /取消t2表的枚举,然后将t2表赋给中间表t2noenum
q)t2noenum
s2 v2
------
c 300
d 400
e 500
q)`:/db/db1/t2/ set .Q.en[`:/db/db1] t2noenum /将中间表t2noenum序列化到db1目录下
`:/db/db1/t2/
q)get `:/db/db1/sym /这时候我们发现db1目录下的sym文件已经有了t2表的symbol值的枚举了
`a`b`c`d`e
q)select from `:/db/db1/t2 /从db1目录下查询t2表可以查到了。
s2 v2
------
c 300
d 400
e 500
上面是官方给的一个将一个表已经序列化的表迁移到另外一个序列化的目录下的思路,但是我发现好像可以直接将t2表序列化到db1目录下,结果是一样的(现在还没有发现这样做有什么问题,欢迎指正)。
q)`:/db/db1/t1/ set .Q.en[`:/db/db1;] ([] s:`a`b`c; v:10 20 30)
`:/db/db1/t1/
q)`:/db/db2/t2/ set .Q.en[`:/db/db2;] ([] s:`c`d`e; v:300 400 500)
`:/db/db2/t2/
q)`:/db/db1/t2/ set .Q.en[`:/db/db1;] ([] s:`c`d`e; v:300 400 500) /直接将t2表序列化到db1目录下,后发现得到的结果是一样的
`:/db/db1/t2/
q)get `:/db/db1/sym
`a`b`c`d`e
q)\l /db/db1
q)t2
s v
-----
c 300
d 400
e 500
q)t1
s v
----
a 10
b 20
c 30
现在假设我们把某个字段为字符串,后不小心弄成了symbol类型给序列化到了sym文件当中,那我们就需要将这些本应该是字符串的symbol数据从sym文件中除去,但是不是说直接从列表中删除掉就可以了。还是比较复杂的。
q)`:/db/t1/ set .Q.en[`:/db;] ([] s:`a`b`c; v:10 20 30)
`:/db/t1/
q)`:/db/t2/ set .Q.en[`:/db;] ([] c:1 2 3; comment:`$("abc";"de";"fg"))
`:/db/t2/
q)get `:/db/sym /发现conment是一个注释字段,不应该序列化到sym文件当中
`a`b`c`abc`de`fg
这时我们就需要将数据库先载入到我们的内存中,然后来操作sym文件
q)\l /db
q)sym
`a`b`c`abc`de`fg
q)sym:sym except exec comment from t2 /先将表t2的枚举数据从sym文件中去除
q)reenum:{@[x;exec c from meta x where t="s";`sym?]} /写一个重新枚举的函数
q)`:/db/t2/ set reenum ([] s:`a`b`c; comment:("abc";"de";"fg")) /将comment字段改为字符串类型重新序列化
`:/db/t2/
q)ts:system["a"] except `t2 /system[“a”]相当于\a操作
q)unenum:{@[select from x; exec c from meta x where t="s";value]} /取消枚举函数
q){(hsym `$"/db/",string[x],"/") set reenum unenum x} each ts /利用函数将t1表先取消枚举,然后再重新枚举后序列化
,`:/db/t1/
q)`:/db/sym set sym /然后替换前面保存的sym文件到本地
`:/db/sym
q)\l /db
q)t1
s v
----
a 10
b 20
c 30
q)t2
s comment
---------
a "abc"
b "de"
c "fg"
q)get `:/db/sym /这时候我们就发现已经将序列化到sym中的无关数据除去了
`a`b`c
9. 带链接字段的扩展表
我们之前指出,对于含有主键的表是不能扩展的,因此不能在扩展表之间建立外键关系。但是我们可以扩展到链接列来建立关联关系,然后像使用外键一样来使用。前提必须自己完成创建索引的工作,就像内存中包含表的链接列一样。
首先我们假设我们要扩展的表还在内存中,没有被序列化到本地,那我们就可以先创建链接后再来序列化。
q)t1:([] c1:`c`b`a; c2: 10 20 30)
q)t1
c1 c2
-----
c 10
b 20
a 30
q)t2:([] c3:`a`b`a`c; c4: 1. 2. 3. 4.)
q)t2
c3 c4
-----
a 1
b 2
a 3
c 4
q)update t1lnk:`t1!t1[`c1]?t2[`c3] from `t2 /创建对应链接索引,t2表的c3字段的数据在t1表的c1字段中的一个索引
`t2
q)t1
c1 c2
-----
c 10
b 20
a 30
q)t2
c3 c4 t1lnk
-----------
a 1 2
b 2 1
a 3 2
c 4 0
q)select c3,t1lnk.c2,c4 from t2 /这样我们就可以通过链接来关联查询了
c3 c2 c4
--------
a 30 1
b 20 2
a 30 3
c 10 4
q)`:/db/t1/ set `.Q.en[`:/db;] t1
`:/db/t1/
q)`:/db/t2/ set `.Q.en[`:/db;] t2
`:/db/t2/
q)\\
C:\Users\caoxi>q
q)\l /db
q)select c3,t1lnk.c2,c4 from t2 /重新载入一样可以关联查询
c3 c2 c4
--------
a 30 1
b 20 2
a 30 3
c 10 4
第二种情况就是我们假设表已经被序列化到内存中,那可以先将数据库映射到内存中,或者直接给路径的形式来使用。我们还有一个附加步骤,即将链接列附加到.d文件中t2。
q)`:/db/t1/ set .Q.en[`:/db;] ([] c1:`c`b`a; c2: 10 20 30)
`:/db/t1/
q)`:/db/t2/ set .Q.en[`:/db;] ([] c3:`a`b`a`c; c4: 1. 2. 3. 4.)
`:/db/t2/
q)`:/db/t2/t1link set `t1!(get `:/db/t1/c1)?get `:/db/t2/c3
`:/db/t2/t1link
q).[`:/db/t2/.d;();,;`t1link] /这一步非常重要,一定要给.d文件添加上`t1link字段记录
`:/db/t2/.d
q)\l /db
q)select c3, t1link.c2,c4 from t2
c3 c2 c4
--------
a 30 1
b 20 2
a 30 3
c 10 4
10. 查询优势
正如我们在本节的介绍中所指出的那样,扩展表是一个内存上的一个节约,因为表被映射到内存中,并且实际上只需要加载需要用到的字段,而不是所有的字段都加载到内存中。由于一般的查询只需要少量字段,因此可以大大减少处理包含许多字段的表所需的内存量。
除了内存节约之外,如果很快再次引用相同的列,则可以直接使用在内存或存储控制器中缓存其数据。这也是节约了时间和内存。
在如何实现where短语方面,还有一个更微妙的优势。如前面表的查询所介绍的一样,每个where子语句都会生成一个布尔向量作为掩码,在后续的分组和聚合中将检查这些位置,所以当映射表时,这还可以显著减少加载到内存中的数据量。
来源 https://zhuanlan.zhihu.com/p/70965117