这个数据库似乎比较流行,但其操作方法 Q 又与 SQL 截然不同。虽然中文互联网有教程,但居然是收费的。虽然可以访问官方直接查询其文档,但他们这个文档写的也非常糟糕,读着头疼。因此,我将自己整理过程的部分片段分享出来,希望能给读者节约时间。
参考地址先列出:
关于 table 概念基本介绍:https://code.kx.com/q4m3/8_Tables/#8411-extracting-column-data
关于枚举 enumeration:https://code.kx.com/q4m3/7_Transforming_Data/#75-enumerations
与枚举相关的操作符:https://code.kx.com/q/ref/enum-extend/
关于 sym file:https://code.kx.com/q/wp/symfiles/#symbols-vs-strings
关于表的持久化:https://code.kx.com/q4m3/14_Introduction_to_Kdb%2B/#1412-foreign-keys-and-link-columns
关于 linking column:https://code.kx.com/q/kb/linking-columns/
分割线下是正文
1 Kdb
1.1 建表与相关语法
1.1.1 字典与表
一般来说,讨论表首先要认识建表语句。但在Kdb+中,讨论表table前需要先了解什么是字典dictionary。
字典应该看作一种值到域的映射,逻辑上与一个KV结构相同,但物理上存储为键值对列表。定义时使用叹号连接键值,发音为bang。出于历史原因,字典本身没有唯一性约束,但访问数据时只有第一个写入的键值对能被访问。
可以使用````u# ```来强调具有唯一约束的字典键,通过实现哈希结构提高效率
字典中的元组之间是有顺序关系的。
可以创建空字典,可以使用$指定键值类型。
(`symbol$())!`float$()
访问字典的语法,类似于访问列表,都需要使用中括号[],括号中的参数是键。
1.1.2 建表语法
(译者注:如果认为字典是一维结构,通过键可以访问值,那么表可以看作一个“二维”结构,即需要列名与行号才能访问值,如果仅提供列名或行号,则只能访问到一个列表)
表可以看作某种字典的“翻转(flip)”,用表的内容描述这个字典就是:键是列名,值是该列所有值。而翻转主要体现在查询过程中。前面提到,表是二维结构,访问值需要提供两个参数。对于翻转前的字典,通过将第一个参数声明为列名,可以获取该列所有值,而对于表,则需要将第二个值声明为列名以获取同样结果。获取表的过程中,第一个参数确定了行号。即:
t[<row_num>;<col_name>]
d[`name;] // 转置前的字典获取列所有值,分号后是第二个参数,此处为空
t[;`name] // 翻转后的表,获取结果同上
建表语法中,中括号[]用于声明主键。需要注意的是,如果使用如下语句建表,列名是不需要强调symbol类型的。
t: ([] *c1*:*L1*; ...; *cn*:*Ln*)
Kdb+会自作聪明地、将具有同样键名的单元素字典列表转化为表。直观地讲,就是认为如下内容会是表。如下内容中,本身将t定义为列表,但列表的每个元素都是一个字典,每个字典都只有一个元素且键名相同,因此t被转化为表。
t: (`name`iq!(`Dent;98); `name`iq!(`Beeblebrox;42))
1.1.3 主键与外键
为简便,将keyed table称为主键表。Kdb+将主键认为是与原表相关的另一个表,两个表通过元组顺序的位置关联。基本定义语法例子参考如下。实际文档中还提供了一种“更基础”的做法,是将两个表通过“!”链接起来,过于复杂,此处略去。
kt:([eid:1001 1002 1003] name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
定义主键还有一个更简单方式,即使用xkey关键字。如果对于表t1本身已有列kcol,则定义其为主键的语法如下。如果取消其主键,则使用()定义其主键。
t1: `kcol xkey t1
这里的主键是有约束能力的,重复主键值的元组无法写入表。但要说明的是,可能由于Kdb+“过于宽松”的完整性约束,向一个主键表写入重复主键值非常容易,例如可以定义另一个表kt2是kt无主键的情况,向kt2写入数据不受主键约束,且数据可反映在kt中。
kt2: () xkey kt
对于主键表,可以直接将主键列值作为唯一参数,通过中括号访问。注意,主键表和表属于不同的类型,表的访问方法(2个参数)并不能用于访问主键表。
设置外键重载了$符号,如下:
<table>:([]<col>:<ref_col>$())
trade: ([] sym:`financials$()) // 具体例子
注意到,设置外键的语法中,实际是把被引用表(列)作为一种“数据类型”,让引用列存储数据类型的索引。
1.2 写数据与查数据
建表、写与查才能验证我们真成功实现了预期。
写数据命令在文档中的表达也非常奇怪。以下两种方法都能写入数据,但文档中却是放在同一行,容易误解为一个命令中包含两个部分。其中,x必须是symbol类型,y是符合表结构的列。
x insert y
insert[x;y]
查数据相对简单,直接使用变量名后接中括号,中括号内使用查询参数即可。
1.3 吐槽
作为一个DSL,Q对操作符实现了重载,这可能是导致该语言难以理解应用的原因之一。例如在下述代码中,操作符“?”就有两种含义,分别是以生成指定数量的随机数组,以及查找数组位置。
q)u:`g`aapl`msft`ibm
q)v:1000000?u
q)k:u?v
这门语言的文档中开口闭口都要“After a bit of Zen”,但真正的Zen是蕴藏在符号之下的,而不是通过一些看似极简实而晦涩的语法体现。
1.4 一些基础语法速记
如果进一步阅读原文文档,会发现有些操作符的用法非常违背直觉,这里简单列两个。
在某些函数调用中,需要用变量名的symbol格式,而有些地方可以直接用变量名。下文用<ref_x>
表达需要用反引号的symbol格式,<x>
表达不需要。
// 给列表添加元素:
<lst>,:<elem>
// 给定两个列表,一个不重复列,一个目标列,求枚举,即索引编码:
<ref_unique_lst>$<src_lst>
// 给定两个列表,对源列表去重并将结果补充到不重复列表中:
<ref_unique_lst>?<src_lst>
声明链接列linking column重载了操作符!
,如下:
update parent:`t!id?101 101 102 102 from `t
parent是新增的链接列,t是被链接的表,也是新增列的表,id是链接目标。理解这一点,本质要理解这个表达式:id?101 101 102 102
。该表达式就是从按照列举字面量(101 101 102 102),从id列表中取得索引。而前面的\`t!
则是将这个索引列表与t联系起来,如果没有这一步,那么索引结果就不能用来访问t中的列与值。例如,上述增加的列可以略作修改如下,此时产生的列nparent就不能能问表t,但从结果值上讲与parent列是一样的。
update nparent:`id?101 101 102 102 from `t
类似地,建表时,直接指定某列为某个表的索引:
t2:([] c3:`sym?`a`b`a`c; c4: 1 2 3 4.)
其中,sym可以是存在或不存在的列表,该语句让t2表的c3列实际存储了写出的symbol类型字面量在列表sym中的索引。注意,在这里,问号?
虽然被重载,但实际表达了查找与补充(extend)的含义,即查找字面量在目标函数中的索引。