kdb+ 32位版本开放下载了

Discussion in 'kdb+' started by itfin, Apr 14, 2008.

  1. Kdb+/Tow – Replay Module
    The basic aim of the replay module is to facilitate rapid replay of historical data (i.e. data in a
    kdb+ database residing on disk for example NYSE TAQ data). For the moment the module can
    only replay 1 days worth of data at a time, the reason for this is that symbols (ticker symbols
    that identify stocks) are not constant overtime and as such can and do change from day to day
    (although not very often). So if we try and replay multiple days of data for a particular stock
    and the stock symbol has changed within the timeframe of interest then the replay will
    obviously be wrong. We can try and get around this by using a master table with which we can
    build symbol chains but for now we will just look at the basic single day replay.
    The replay module works by getting record handles to the required data. By doing this it
    means that a query need only be performed once- we store the record-handle indices and then
    use these iteratively to 'pull' or 'push' the chunks of data.
    There are two options for configuring the replay server:
    1. We can 'pull' the data from the server which means we get the total number of time buckets
    for the date, table and symbols we are interested in (N, say), get our record handles and then
    call pull N times where each time it is called we are returned a table with the required data. In
    the case of replaying both trades and quotes we would get a list containing a segment of
    trades and a segment of quotes.
    or
    2. We can actually 'push' the data through the kdb+/tick application one chunk at a time-in
    this way we can mimic a feed and also have multiple different subscribers receiving the data
    doing whatever we wish. This method is very useful for testing strategies and also stress
    testing subscribers.
    In kdb+ we can do both of these at extremely high speeds- up to a million records a second.
    However, if using java/c as our engine for the strategies then we slow down a bit, so if
    possible the aim would be to try and write the strategies/clients in q and use these.
     
  2. zwz

    zwz

    难得的KDB中文资料:
    (来源:http://www.cnblogs.com/yyw84/)
    KDB数据库相对于其它关系型数据库最大的优点就是效率高,特别是对于那些基于列的数据的运算表现更加出色,但它也并不是十全十美的,例如它是一个基于内存的数据库,这也就是说它的效率很大的一个原因就是依赖于数据在内存中的高速传输,这可比硬盘要快得多了。今天我就对它的内存占用率做了一个测试,使用的是开发版本,也就是32位版本,而且运行时间只能是2小时,所以这也是这个测试最多能完成的时间,当前使用的测试是每插入1000行做一次内存使用情况跟所消耗时间的记录,最终结果如下:

    [​IMG]
    图中的蓝线代表的是数据库所占用的内存,红线代表的是数据库真正使用的内存,绿线表示每插入1000条数据所需要的时间。

    测试所使用的数据如下:
    [​IMG]

    从测试结果中我们可以看到数据库启动后会事先向操作系统申请60多M的内存,虽然这么多很多都是被闲置着的,但这么做的好处是当往里面添加数据的时候程序不需要再向操作系统申请内存,这节约了时间。测试发现只要数据量在50万行左右内存基本不会有增加,但如果还有新的数据添加进来,数据库又会一次性申请大量的内存,再接下去还是这样的操作,当数据达到350万行的时候,内存占用情况是400M左右。我想这还是一个可以接受的范围。再看下面的绿线,随着表中数据的不断增加,添加一行记录所需要的时间也在平滑地增长,这可不是什么好消息,不知道关系型数据库是不是也是这样的情况,这个还没有测试过。



    测试所使用的程序:

    1、KDB+数据库,测试所使用的是最新版本2.4;

    2、KdbTester 开发语言为C#,用于产生测试数据;

    3、KdbTestReporter 开发语言为JAVA,使用JFreeChart开源组件来生成报表。



    另外

    测试机器的配置如下:
    ====================================
    CPU: Intel Core 2 Duo E4300
    主板: 升技 AB9
    内存: 金士顿 DDR2 667 1G x2
    显卡: 影驰 GeForce 8600GT 魔灵
    硬盘: 酷鱼7200.10 SATAII 8M 250G
    电源: Tt 金刚450(W3009)
    机箱: 航嘉 H001



    操作系统是Vista sp1

    为了不让等待的时间太过于无聊,所以自始自终机器都是边跑测试边放电影。
     
  3. zwz

    zwz

    Q 语言初学者系列:(1)开门篇(来源:http://www.cnblogs.com/yyw84/)

    声明:本系列文章全部参考自官方教程,由于缺乏中文资料而且本人E文实在太菜,对于E文较好的朋友可以直接通过下面的链接访问官方网站提供的教程,
    欢迎大家一起学习讨论。
    hhttps://code.kx.com/trac/wiki/QforMortals2/contents
    用户名跟密码为anonymous


    历史背景:
    Q语言跟它的数据库kdb+是由同个作者Arthur Whitney开发的,发布于 2003年。Q语言天生地具体处理大数理量的能力,同时它也是kdb+数据库的查询语言,类似于SQL。

    KDB+介绍:
    KDB+ 是一种适合用于处理大数量的数据库服务器,和kdb一样被设计于用来高速地抓取、分析和存储数据。主要应用于金融软件的开发,区别于传统的关系型数据库,主要优势是挖掘快速发展的硬件的潜力,我想缺点是对硬件性能要求比较苛刻。
    刚刚接触KDB+数据库,google了半天没找到一丁点中文资料,加上英文太菜,或许对它的理解有误,不过暂时先这样定义,等有了更好的答案了再来Update.
    打开地址 http://www.kx.com/developers/software.php, 然后选择点击"Personal Developer kdb+ Software License Agreement"获取一个免费的32位版本,才100多K的东西。

    特点:
    ◇内存数据库(In Memory Database)
    把数据全部放置于内存中,好在现在内存是越来越白菜价了。
    ◇解释型语言(Interpreted)
    ◇有序列表(Ordered Lists)
    实现高速处理数据的关键,区别于传统数据库。
    ◇赋值顺序(Evaluation Order)
    ◇面向表编程(Table Oriented)
    放弃了经典的OO,取而代之的是一种更加复杂的思想,基于表得字典的编程。
    ◇面向列编程(Column Oriented)
    ◇强类型(Types):
    强类型,动态检测 (Q is a strongly typed, dynamically checked language)
    ◇空值(Null Values):
    每种类型都有自己的空值 (Types have separate null values. )


    开始:
    安装Kdb+ 数据库服务器,直接解压到C盘根目录下就OK了,如果希望放到其它目录下需要添加环境变量QHOME到q目录;
    进入到程序目录通过"q"命令启动一个q会话。
    [​IMG]


    变量:
    Q语言声明变量只需要使用一个变量名跟一个值,格式为“变量名:值”,解释器会根据值的类型给变量赋予相应的类型,需要注意的是赋值符号是":"而不像大多数语言所使用的"=".
    e.g.q)a:20
    变量的命名规范基本与匈牙利命名规范差不多,但有一点需要注意的是禁止使用下划线,就算是非使用不可的情况下也不应该把下划线放在最后一个字符

    可能使用一个斜杆(/)给程序添加注释,当然使用两杆(//)也是不会报错的
    程序的执行顺序是从右到左
    e.g. q)b:1+a:42
    q)b
    43
    今天的开门篇就介绍到这里,由于文中一些概念还没完全理解清楚,有很多表达不到位的地方经过后期的学习我会尽量补上。
    下一节将介绍Q语言的基本数据类型。
     
  4. zwz

    zwz

    Q 语言初学者系列:(2)基本数据类型 http://www.cnblogs.com/yyw84/archive/2008/07/10/1239623.html

    声明:本系列文章全部参考自官方教程,由于缺乏中文资料而且本人E文实在太菜,对于E文较好的朋友可以直接通过下面的链接访问官方网站提供的教程,
    欢迎大家一起学习讨论。
    hhttps://code.kx.com/trac/wiki/QforMortals2/contents
    用户名跟密码为anonymous

    Q语言跟C#、JAVA还有SQL基本数据类型的比较

    Q SQL Java C#
    boolean boolean Boolean Boolean
    byte byte Byte Byte
    short smallint Short Int16
    int int Integer Int32
    long bigint Long Int64
    real real Float Single
    float float Double Double
    char char(1) Character Char
    symbol varchar (String) (String)
    date date Date

    datetime datetime Timestamp !DateTime
    minute



    second



    time time Time !TimeSpan
    enumeration





    各种数据类型的信息

    type size char type num type notation null value
    boolean 1 b 1 1b 0b
    byte 1 x 4 0x26 0x00
    short 2 h 5 42h 0Nh
    int 4 i 6 42 0N
    long 8 j 7 42j 0Nj
    real 4 e 8 4.2e 0Ne
    float 8 f 9 4.2 0n
    char 1 c 10 "z" " "
    symbol * s 11 ‘zaphod `
    month 4 m 13 2006.07m 0Nm
    date 4 d 14 2006.07.21 0Nd
    datetime 4 z 15 2006.07.21T09:13:39 0Nz
    minute 4 u 17 23:59 0Nu
    second 4 v 18 23:59:59 0Nv
    time 4 t 19 09:01:02:042 0Nt
    enumeration

    * `u$v

    dictionary

    99 `a`b`c!!10 20 30

    table

    98 ([] c1:ab`c; c2:10 20 30)


    下面继续针对各种数据类型的讲解:
    1. 整数数据(Integer Data)
    整数类型跟其它编程语言的描述基本相同。

    int
    int占用4个字节的内存空间,存储了一个整型的数字。

    short
    short 占用两个字节的内存空间,只能存储一个较小的整数,以”h”结尾。

    e.g.
    q)b:-123h
    q)b
    -123h

    long
    long 占用了8个字节的内存空间,可以容纳较大的整数,以”f”结尾。

    e.g.
    q)c:1234567890j
    q)c
    1234567890j

    2. 浮点数据(Floating Point Data)
    支持科学记数法。

    float
    可以保证至少15位小数的的精度,以”f”结尾。相当于JAVA或C#里面的double

    real
    可以保证至少6位小数的的精度,以”e”结尾。相当于JAVA或C#里面的float

    3. 二进制数据(Binary Data)

    boolean
    当条件为真是,值为1b, 当条件为假时,值为0b

    e.g.
    q)1=1
    1b
    q)1=0
    0b

    byte

    4. 字符数据(Char Data)

    char
    表示一个字符使用的是引号。

    e.g.
    q)ch:”q”
    q)ch
    “q”

    还可以使用”\”做为转义字符,这跟C#和JAVA是一样的。

    q)ch:"\"" / double-quote
    q)ch / console also displays the escape "\""
    q)ch:"\\" / back-slash
    q)ch:"\n" / newline
    q)ch:"\r" / return
    q)ch:"\t" / horizontal tab

    5. symbol
    symbol是由”`”符号开头的一串字符,但symbol并不等同于字符串。
    那么如何才能让一个symbol包含空格等特殊符号呢?答案就是通过字符串,下面的例子并说明:

    e.g.
    q)`$"A symbol with `backtick"
    `A symbol with `backtick

    6. 时间类型

    date
    date类型的值的定义格式应该为yyyy.mm.dd,月份跟日期不够两位数需要在前面加0

    e.g.
    q)d:2008.07.03
    q)d
    2008.07.03

    还可以通过d.year, d.mm, d.dd 获取到年、月跟日期,d.month可以得到month的对象。

    time
    time 类型的值的定义格式应该为hh:mm:ss.uu,同样需要补0的地方还是要补上

    e.g.
    q)t:16:30.000
    q)t
    16:30.000

    可以通过t.hh, t.mm, t.ss 获取到小时、分钟跟秒,t.minute 跟 t.second 得取到minute跟second的对象。

    datetime
    datetime 的格式是由date跟time中间通过“T”隔开的标准格式

    e.g.
    q)dt:2006.07.04T09:04:59:000
    q)dt
    2006.07.04T09:04:59:000

    Datetime具备了date跟time所有的属性外还可以通过dt.date跟dt.time得到date跟time的对象。

    month
    month 的格式为yyyy.mm,而且需要在末尾追回一个字符“m”

    e.g.
    q) mon:2006.07m
    q)mon
    2006.07m

    minute
    minute 的格式为hh:mm

    second
    second 的格式为 hh:mm:ss

    7. 无穷大 跟 非数字 类型(Infinities and NaN)
    下表列出了各种类型的无穷大的表示法:

    Token Value
    0w Positive float infinity
    -0w Negative float infinity
    0W Positive int infinity
    -0W Negative int infinity
    0Wh Positive short infinity
    -0Wh Negative short infinity
    0Wj Positive long infinity
    -0Wj Negative long infinity
    0Wd Positive date infinity
    -0Wd Negative date infinity
    0Wt Positive time infinity
    -0Wt Negative time infinity
    0Wz Positive datetime infinity
    -0Wz Negative datetime infinity
    0n NaN, or not a number

    8. 空值类型(Null Values)
    下表列出了各种类型的空值表示法:

    type null
    boolean 0b
    byte 0x00
    short 0Nh
    int 0N
    long 0Nj
    real 0Ne
    float 0n
    char " "
    sym `
    month 0Nm
    date 0Nd
    datetime 0Nz
    minute 0Nu
    second 0Nv
    time 0Nt
     
  5. zwz

    zwz

  6. zwz

    zwz

    J/JDB vs q/KDB pros and cons
    I am learning q/KDB to use at work currently, but am more familiar
    with J. Does anybody know of a good essay which compares the
    performance and capabilities of q/KDB with J?

    The biggest differences I can see so far is the built-in KDB side and
    the built in use of dictionaries and tables in q - which is very
    useful. JDB may be trying to provide a similar functionality to KDB? I
    prefer J at the moment because it feels like it fits together better,
    but that may be because it is what I am used to using.

    Has anybody done a performance/functionality comparison between q/KDB
    and J/JDB? Is anybody using J to process tick data?


    Thanks,
    Matthew.
    ----------------------------------------------------------------------
    For information about J forums see http://www.jsoftware.com/forums.htm


    Matthew - as far as I understand it, JDB is _much_ newer than KDB. A
    disadvantage of this is that KDB almost certainly has better raw performance
    as they've been working on this problem for quite a while. In fact, someone
    like you who works with both systems would be in a better position to tell
    everyone here about relative performance.

    An advantage of JDB's newness is that the early users of it can influence
    its direction. Personally, I'm more interested in ease of use, stability,
    integration with J (and, potentially, other environments), and ability to
    handle large datasets. Performance, assuming it's adequate, would be less
    of a consideration for me than these other things.

    I'm very interested in trying out JDB's capabilities but have not yet done
    so. Please report your experiences as you become more familiar with it.

    Regards,

    Devon



    Yes, JDB is much newer than KDB, so it might not be as refined yet as
    KDB could be, and of course I will report back any feeedback I can
    give... but my question was aimed at anybody who might already have
    made some comparisons, who are actually using or have used, or tested,
    both systems. Especially anybody who works with tick data.

    For me it is important that it can handle large datasets without
    producing any limit errors or bottlenecks where KDB would not. I have
    no idea right now how big a dataset gets before KDB would struggle, I
    have only recently started to learn it.

    Anyway, I was wondering if there are any people out there who use
    Q/KDB and have tried or are using J (and possibly have tried out the
    new JDB) to see how they compare in terms of performance and
    capability.

    There are many things in q which are exactly the same as in J but go
    under a different name. I also note that J feels more self consistent
    and complete as a language. On the other hand, the dictionaries and
    keyed table support in q/KDB are extermely useful.

    It would be interesting to compile a list of how to "translate" q/KDB
    into J/JDB to acheive similar tasks, and to compare the performance
    and beauty of expression.


    Thanks,
    Matthew.
     
  7. 属何类?
     
  8. zwz

    zwz

    KDB+: an in-memory, column-based database.

    Q:Is kdb+ just an in-memory database?
    A:No. Kdb+ provides a full relational database management system with time-series analysis that handles data in memory as well as stored data on disk. For advanced applications such as backtesting of auto trading strategies or operational risk management, it is essential to be able to compare streaming data against history. You must be able to understand where the business has been in order judge and act upon real-time occurrences. Approaches that handle in-memory data alone or historical data alone can’t meet the needs of today’s real-time enterprise, where accurate comparison on the fly is becoming increasingly important. Approaches that try to combine a streaming or in-memory product from one vendor with a historical product from another can't deliver the performance necessary for real-time business, because they have to cope with two separate architectures. Excess overhead is unavoidable with multiple architectures.

    http://kx.com/developers/faq.php
     
  9. kdb

    kdb

    关于对性能测试的理解

    这个性能测试有一定的片面性。就这个case本身而言,其实insert的时间可以优化到常数O(0),不随表大小线性增长,详细信息请浏览在下对原文的回复:
    http://www.cnblogs.com/yyw84/archive/2008/07/20/1247212.html

     
  10. 学习完毕。
    佩服。
     
  11. Excellent!

    Merry Christmas!
     
  12. zwz

    zwz

    牛!
    圣诞快乐!
     
  13. kdb+是大师的天才之作,目前国内接触的人还不多,欢迎hewei兄多来交流指教!
    另外传贴code17Q笔记
    Q笔记 (1)
    Sat, 2008/04/19 - 06:50 — code17 @PAPL APL/J/K/Q notes
    Updated: 2008/09/22

    记点儿Q笔记,很零散,有空随时更新吧。

    基本使用
    获取
    前面说过了。比较有意思的是它的安装包结构,如下(以win32版本为例)

    $ tree -h
    .
    |-- [2.1K] README.txt
    |-- [ 16K] q.k
    |-- [4.1K] s.k
    |-- [ 821] sp.q
    |-- [ 150] trade.q
    `-- [4.0K] w32
    `-- [156K] q.exe

    1 directory, 6 files

    基本上就是一个可执行程序q.exe带上几个启动文件代码。各个平台的文件结构是一样的,diff一下那几个启动文件(纯文本)的内容也是一模一样,唯一不同的就是q.exe和它所在的子目录名(比如linux x86是l32)。另外注意了,传说中的kdb+/Q的体积!

    安装
    README里说是整个目录放到系统(个人)根目录下(w32: C:\q,linux: ~/q)。不过这不太符合我们一般的包管理原则,试了一下放到别处,果然报错——寻找启动文件q.k失败。

    办法:随便把q.exe扔到哪 (如果懒得写路径名的话就放到某个$PATH下),把剩余的几个文本文件放到某目录下,并设置环境变量$QHOME的值为这个目录即可。

    编辑
    Emacs mode
    Vim mode
    EmEditor, Colorer, Vedit syntax files
    解释器
    执行q.exe进入REPL。

    win32: 有history(上下键),但光标不能移动;没有^D之类的,必须用'\\'退出;用rlwrap或是在screen下执行立即退出——估计是自己实现了一些屏/行处理,和这些工具都有冲突
    linux: 没有history,光标可以四处移动编辑(但不知道有什么用);有^D,但没有^A/^E/^K这些;用rlwrap,配合良好。
    载入文件
    q语言源代码一般以 .q 结尾。在解释器中,我们可以用 \l filename.q 来载入一个源代码文件(这个代码本身也可以含有\l指令来载入其他文件),或者直接用 q.exe filename.q 在启动时载入这个源代码文件。

    传递参数
    当启动q解释器时,我们可以传递参数,而在解释器进程中,这些参数可以通过系统变量.z.x来访问,比如

    q.exe filename.q 42 forty 2.0
    q) .z.x 0 / "42"
    q) .z.x 1 / "forty"
    q) .z.x 2 / "2.0"
    帮助
    下载help.q,置于$QHOME下,然后加载即可使用

    q) \l help.q
    q) help`
    q) help `syscmd
    切换到K
    用\可以切换到K语法(Q前身)

    q)!7 / Q syntax
    '!
    q)\ / change to K syntax
    !7
    0 1 2 3 4 5 6
    \ / back to Q syntax
    q)k)!7 / inline K syntax with "k)", the same for Q (with "q)")
    0 1 2 3 4 5 6

    Q笔记 (2)
    Thu, 2008/04/24 - 06:38 — code17 @PAPL APL/J/K/Q
    Updated: 2008/04/23

    特征
    植根于APL和Lisp, functional,以ordered list为基本数据结构,SQL-like operators作为primitive function。

    type system: strongly typed, dynamically typed, implicit coercion, null value (with defaults)
    execution: interpreted, self-modifiable, right-to-left evaluation
    primitives: table oriented, column oriented
    基础
    注释
    q) 42 /This is a comment
    42
    赋值
    q) a:"k" / q assignment is like any other FP -- just a binding
    q) a
    "k"
    q) a:1+b:7*8+c:3*2
    q) c
    6 / the result tells:
    q) b / 1) an assignment is an expression with value, like in C
    98 / 2) right-to-left eval
    q) a / 3) rebinding
    99
    基本数据类型
    没什么特别,详见manual。有点印象的:

    char是用双引号而不是单引号,但里面只放一个字符,比如"a",
    Symbol
    概念类似于lisp里的。

    日期
    直接写 2008.03.21,但必须用0补齐。如果精确到月,则用m结尾,2003.05m,否则会与小数混淆。

    计数方便,从2000.01.01开始

    q) `int$2008.04m
    99
    q) `int$2003.01.02
    1097
    q) `int$1999.01.01
    -365
    时间
    和日期类似,格式hh:mm:ss.uuu或hh:mm:ss:uuu,24小时制,也是要补零,也可以计数

    q) `int$12:34:56.789
    45296789
    可以少一些后面的位,计数的含义是根据它的最小位来决定的

    q) `int$03:04
    184 / 多少分钟
    q) `int$03:04:05
    11045 / 多少秒
    q) `int$03:04:05:000
    11045000 / 多少毫秒
    日期时间
    就是将日期和时间合起来,中间用T间隔,比如2003.11.12T10:23:44:000

    q) `int$2003.11.12T10:23:44:000
    1411 / 计日
    q) `float$2003.11.12T10:23:44:000
    1411.43314815 / 计日、额外的时间用小数表示
    Dot notation:

    q) dt:2008.01.02T03:45:06.789
    q) d:dt.date
    q) d
    2008.01.02
    q) t:dt.time
    q) t
    03:45:06.789
    q) dt.year / or d.year
    2008
    q) dt.month / or d.month
    2008.01m
    q) dt.date / or d.date
    2008.01.02
    q) dt.time / or t.time
    03:45:06.789
    q) dt.minute / or t.minute
    03:45
    q)dt.second / or t.second
    03:45:06
    q) d.mm / or dt.mm
    1
    q) d.dd / or dt.dd
    2
    q) t.hh / or dt.hh
    3
    q) t.mm / no dt.mm, as already covered by the month operator
    45
    q) t.ss / or dt.ss
    6
    无穷与无定义
    为不同数值类型定义了一系列无穷值,比如除以0就会产生无穷。但基于无穷的算术却没有定义,因此对这些无穷值进行算术运算的结果实际上是对该无穷值的表示进行位操作的结果——个人认为这实在是很危险。具体参见手册。

    Null
    在q中,有些类型的null值是该类型里的一个普通值,比如bit/byte/char——这样你就不能区分某个值究竟是缺失了还是碰巧是这个default value;其他类型的null值则是该类型的一个特殊值,例如一般的数值类型和是日期时间类型——因此我们可以将缺失的情况认定出来。注意,这个特殊值依然是属于这个类型的,因此,无论是哪种情况,你都可以直接用它和这个类型里的任何值(包括null)进行各种compare/equal操作,而不是用IsNull之类的特别的函数来判定。

    Q笔记 (3)
    Sat, 2008/04/26 - 06:04 — code17 @PAPL APL/J/K/Q
    Updated: 2008/04/25

    列表
    概念上和Lisp的列表非常类似,但提供了大量基于索引的类似于数组的操作(index, slice, map, flip etc.)

    概念
    simple list: 全部由同类型的atom构成的列表
    general list: 其他情况:由不同类型的atom构成、由相同或不同类型的sub list构成、由atom和list混合构成
    simple list的内部实现更节约空间和高效。

    语法
    通用语法: 形如 (x; y; z),任何list可用,空表为( )
    便利语法: 仅simple list 适用,如下
    1 2 3 = (1; 2; 3)
    1.0 2.0 3.0 = 1 2 3f = (1.0; 2.0; 3.0)
    1 2 3h = (1h; 2h; 3h)
    011b = (0b; 1b; 1b)
    0x2001ff = (0x20; 0x01; 0xff)
    `Do`It`Yourself = (`Do; `It; `Yourself)
    "string" = ("s"; "t"; "r"; "i"; "n"; "g")
    即使是使用通用语法来书写simple list,系统依然可以识别它们并自动采用更高效的simple list内部实现。

    特殊:

    允许用便利语法书写int和float混杂的list,int会被自动转化为float
    允许用便利语法书写不同精度的时间值的list,结果自动按照第一个时间值的精度对其他时间值进行调整(舍入或添加)。除非最后一个时间值带有显式的type specifier,则按照specifier来确定最后的精度。
    null item
    ::是一个null item,它的类型和任何其他item不同,因此可以用于保证一个list是general list,e.g., l: 1 2 ::

    函数
    count 计数
    index
    索引(从0开始),e.g. 当l: 1 2 3,l[1] => 2。如果索引超出范围,则返回null值,单一类型的simple list返回该类型的null,混合型的general list返回0n(类型float的null)。索引为空或者null item时,返回整个list,e.g. l[] => 1 2 3,l[::] => 1 2 3。
    重赋值 e.g. l[1]:9,但必须使用同一类型的值替代,不会自动进行coersion。
    索引可以作用于sub list,e.g. 当l:1 2 (3 4) 5,l[2][1] = l[2;1] = 4。注意,这两种语法的语义是不同的,l[2][1]相当于连续两次的函数apply,相当于((l[2])[1]),而l[2;1]类似于多维数组的定位,并不是在所有情况下这两者都等价,比如重赋值的时候 l[2:1]:9是我们想要的语义,而l[2][1]:9则不是
    索引本身可以是一个list,结果是对应的元素以同样方式构成的list,符合conformability,e.g. l1:1 2 3 4 和 l2:"abcde",则有l1[3 1 2] = 4 2 3 和 l2[4 1 4 3] = "ebed",以及当 I:(1; (2; 1 3)) 时,l1 = (2; (3; 2 4)) 和 l2 = ("b"; ("c"; "bd"))。重赋值同理,当l1:(22; (33; 22 44)),则l1 = 1 22 33 44;当l2:"z",则l2 = "azzze"
    拷贝:重赋值不影响一个list的拷贝,e.g. 当l1:1 2 3且l2:l1,则l1[1]:9只改变l1而不是l2。
    并置:在没有歧义的情况下,index括号可以省略,e.g. l1 1 2 4 = l1 [1 2 4] 和 l2 I = l2
    join(,) 连接(cat)两个expr(list或atom),e.g. l1, l2 <> (l1; l2),输入的expr物理上并不作为输出expr的组成部分,因此改变输入expr里item的值,不影响生成的expr里的对应值
    enlist 用于产生单个元素的list e.g. count enlist 1 2 3 = 1。或者用join (),x 也可以。
    find(?) 返回item在列表中的位置,同样符合???conformability。e.g. "abcde" ? ("ab";"c") = (0 1; 2) (但 "abcde" ? ("a"; "bc") 返回类型错误!!!)
    矩形列表和矩阵
    其实就是将嵌套列表作为多维矩阵用,提供了一写便利语法,常常用l[ ; 2; ]这样的slicing语法。

    Q笔记 (4)
    Sun, 2008/04/27 - 06:16 — code17 @PAPL APL/J/K/Q
    Update: 2008/04/26

    操作符
    函数
    形如 f[x]、f[x;y]、f[x;y;z],list的index操作也用的同样语法,看来也可看作函数apply。
    原始函数:定义域与值域都是基本类型的函数

    操作符
    就是支持中序表示法的特殊的函数,这样的函数既支持中序表示(e.g. 1+2),也支持缺省的先序表示(e.g. +[1;2]),也支持混合表示(e.g. (1+)[2],类似于partial evaluation的概念)。在使用中序表示法时,操作符也被称作“动词”

    map原则
    原始函数和操作符的一大性质是:它们的定义域可以根据按位作用的方法扩展到一个列表——简言之,自动做map。比如

    neg 1 2 3 => -1 -2 -3
    2 * 4 5 6 => 8 10 12
    7 8 9 - 1 => 6 7 8
    1 2 3 + 4 5 6 => 5 7 9
    优先级
    Q里没有优先级的定义,完全按照从右向左(left of right)的顺序,如果需要不同的顺序,就使用括号。这是
    因为Q的表达式全是函数作用,所以先eval右边的参数,然后apply左边的函数来求值。f1 f2 f3 x的含义就是f1(f2(f3 x))。一个容易混淆的例子

    x:100
    x<42|x>98 => 0b
    (x<42)|x>98 => 1b
    符合 (~)
    简单说,就是structural equivalence,同type、同memory layout和同value的值被认为是相等的。典型用途:了解两个不同的语法是否表示同样的内容,debug

    比较 (=, not, >=, >, <=, <)
    比较是进行“数值”比较,即符号所对应的内部数值表示的比较。因此,不同类型的符号可以进行比较,e.g. "A"=65 和 3.2 > 2h均为真(1b)
    symbol类型与数值类型间不可进行比较,symbol之间的比较按其名称的字典顺序
    list和atom的比较以及list和list间的比较,按照map原则进行
    四则运算(+,-, *, %)
    四则运算用于数值类型,操作数总是被promote到int或更高(如果其中一个操作数比int更wide)
    除法用%而不是/,总是返回float
    也符合map原则
    max(|, or)和min(&, and)
    在Q里, |或or代表max,&或and代表min,适用于任何数值和字符类型,只是在用于bit(bool: 0b,1b)时,|恰好具有“或”的语义,&恰好具有“与”的语义。它们同样符合map原则。

    开方、指对数 (sqrt, exp, log, xexp, xlog)
    适用于任何数值和字符类型,总是返回float

    求余(mod)
    适用于任何数值类型,但被除数需要有等于或者高于(wider)除数的类型,结果和被除数同类型

    被除数是正数时,余数为被除数减去小于被除数的除数的最大整数倍
    被除数是负数时,变除数和被除数为其相反数,并求其余数的相反数
    符号(signum)
    适用于任何数值和字符类型,结果为int(-1,0,1)

    部数(reciprocal)
    适用于任何数值和字符类型,结果为float

    floor和cieling
    适用于int, float和char,结果为int或char
    floor x: 不大于x的最大int(char)
    cieling x: 不小于x的最小int(char)
    绝对值(abs)
    适用于任何数值类型,结果为对应类型

    日期和时间的运算
    日期(date)对应于一个int:即从2000.01.01开始的天数
    时间(time)对应于一个int:即该精度下相对于零点的差值,比如 03:00:00 => 3 * 60 * 60,03:00:00.000 = 3 * 60 * 60 * 1000
    日期时间(datetime)对应于一个float:即该时间点相对于2000.01.01T00:00:00.000之时间差换算成的天数
    剩余的就按照普通int/float的原则进行计算即可,有日期时间类型参与的运算(e.g. +,-,*)的结果往往保留为时间类型

    无穷和无定义
    很多细节问题,略。当运算里产生无穷或者是无定义的值时,会根据定义于这些特殊值上的运算原则继续进行,但其它们往往会propogate到整个运算。

    一个类型的无穷,往往与更高(wider)类型里的某个值有相同的内部表示,因此是相等的,e.g. 32767=0Wh。因此有的运算可能会promote一个类型的无穷到更高类型,e.g. 0W&0Wj => 2147483647j
    所有类型的null都相等
    nulls < -0w < -0Wj < -0W < -0Wh < 0Wh < 0W < 0Wj < 0w,普通数值出现在对应位置——以对应类型的无穷值为边界
    别名(alias)
    如前所述,a:b实际上是a对b的当前值作了一个硬拷贝,b在未来的变化不影响a。若希望定义a的值始终依赖于b,则应该使用alias,e.g. a::b 或 a::b+3,类似于symbolic link或是reactive variable的概念。

    recursive的alias会被自动检测,并不被允许
    查看当前的依赖关系,可以用.z.b
    Q笔记 (5)
    Wed, 2008/06/04 - 19:16 — code17 @PAPL APL/J/K/Q
    Update:2008/06/04

    函数
    语法
    就是某种lambda expression,定义{[p1;...;pn] e1; ...; en}。其中p1..pn是lambda变量,e1..en是表达式。

    e1 .. en 是一个从左向右evaluate的列表
    当给定输入,函数作用的结果e1;...;en等于c从左向右eval
    如果ex是空赋值表达式——即:ex,那么显式返回ex,不管后面的诸项
    如果ex是赋值表达式——级r:ex,那么本地绑定r为ex,然后eval后面的部分
    否则,返回eval最后一项的值
    有一种很有限的便捷语法——如果函数表达式没有变量部分,即{e1;...;en},则假定变量部分为[x]或[x;y]或[x;y;z]——根据函数体里出现的变量名称而定。比如{x+z}意味着{[x;y;z] x+z}。

    函数就是lambda表达式,所以自然支持匿名函数;函数就是value,所以自然是名词(noun)。

    ::是id函数。

    全局/本地变量
    和其他语言一样,本地变量遮盖全局变量,本地变量的scope只是在函数内部。以下例子有不同的语义

    b:6
    f:{b:7; x*b} /新的本地变量b在其scope内覆盖全局变量b
    f:{b::7; x*b} /在本地改变全局变量b的值
    修订
    x:10
    x+:3 /相当于C语言的x+=3
    x-:4 /相当于C语言的x-=4
    x%:2 /其他verb也可以
    唯一的条件是:因为是修改,所以前后的类型应该一致,否则发生类型错误。

    list修订

    L1:100 200 300 400
    L1[0 1 2]+:1 2 3 /L1为101 202 303 400
    L2:(1 2 3; 10 20 30)
    L2[;2]+:9 /L2为 (1 2 12; 10 20 39)
    映射
    就是partial evaluation,给定某些参数,获得一个变量元数更低的函数。cool syntax!

    diff:{[x;y] x-y}
    diffy:diff [42; ] /相当于 {[y] 42-y}
    diffx:diff [; 24] /相当于 {[x] x-24}
    diffc: diff [42] [6] /Curry!
    f:{x+y+z}
    f[;;3][1;][5] /多重映射,=9
    42- /verb映射,相当于-[42]
    -24 /右操作符不适用,因为有歧义,可以用 -[;24]
    List和function的对应关系
    前面已经提到过,List indexing 和 function application是对应的,至少在语法上

    f:{x*x}
    L:(0;1;4;9;16;25)
    (f 0) = (L 0)
    f[1] = L[1]
    (f 2 3) = (L 2 3)
    f[4 5] = L[4 5]
    多维数组indexing就对应于多参量函数的application,见以前的笔记(例子略)。

    但数组index的定义域是有限的,而函数往往不是。于是Q规定,定义域以外的indexing,返回结果是null,对于simple list,返回的是该list类型的null值,对于general list,返回的是0N.

    string 函数
    将任何值转变为对应的string表示,大概类似于F#的any_to_string

    副词
    对动词或函数附加某种语义。

    ' (each-both)
    当postfix于动词或函数f时,意味着对两个操作数进行item-wise的f运算(当两者都是list且等长),或map f运算(当其中一者是atom)。实际上大部分做用于atom的verb和函数已经有这样的能力。但对于,这样本身的平凡操作对象就是list并有基于list操作的明确语义的动词或函数,用'才可以提供item-wise操作的可能。

    each (monadic-each)
    和each-both类似,作用与单个操作数,而不是一对操作数。动机也是给那些本身已经是操作于list的动词和函数item-wise的语义。缺省的map语义固然强大,这里可以看出副作用也很明显。

    \: (each-left)
    和each-both对应,就是右操作符是单个atom

    /: (each-right)
    和each-both对应,就是左操作符是单个atom

    /:\: (cartesian product)
    这个不是primitive!是each-left和each-right的组合,很容易就可以产生catesian product,自己推好了。比如做两个向量的pair乘法,注意L1 */:\: L2 和 L1 *\:/: L2的区别。

    / (over)
    Aha, 这就是fold_left!

    \ (scan)
    而这就是FP里的scan_left!

    ': (each-previous)
    类似于prefix+map

    Verb: @ 和 .
    基本定义
    @是unarry apply,.是multivalent apply。

    f @ 1 2 /等于 f 1 2 或 f [1 2]
    L @ 1 2 /等于 L 1 2 或 L [1 2]
    f . 1 2 /等于 f [1;2]
    L . (),1 /等于 L [1;] ( "." 的参数必须是list)
    如果仅仅是为了语法,那么没有必要定义@和.。它们的主要作用是函数式修订,如下。

    函数式修订
    即不改变原输入参数的修订。

    @加二元函数形式
    @[L;I;f;y] / 语义为:index属于 I 的部分的值为 L f y (或 f[L;y]),其余部分为L里的原始值
    其实很容易理解,@本身是二元函数,取出L和I为参数得L,这个结果继续apply到后续参数,就得到这个结果。但这种特殊语法不是返回对L这个部分的计算结果,而是整个L,只是在I部分的index上的结果是被f update过了的。

    @加一元函数形式
    @[L;I;f] / 语义为:index属于 I 的部分的值为 f L (或 f[L]),其余部分为L里的原始值
    与上同理。

    .加二元函数形式
    .[L;I;f;y] / 语义为:index属于 I 的部分的值为 (L.I) f y (或 f[L.I ;y]),其余部分为L里的原始值
    .加一元函数形式
    .[L;I;f] / 语义为:index属于 I 的部分的值为 f L.I,其余部分为L里的原始值
    Q笔记 (6)
    Thu, 2008/06/05 - 18:45 — code17 @PAPL APL/J/K/Q
    Update: 2008/06/05

    类型操作
    类型函数
    每个atom的类型对应一个数值(short)、一个字符(char)和一个符号(symbol)表示,具体表略。用type函数可以取得这个数值:

    type 42 / -6h
    type `42 / -11h
    type "4" / -10
    type 2007.04.02 /-14h
    atom类型对应的数值是负的,而由该类型atom组成的simple list对应的数值是正的。

    type 42 43 44 / 6h
    type (`42;`and;`43) / 11h
    general list总是对应0;变量的类型由其所绑定的值决定。

    casting
    Q的casting($)是二元操作,左类型(以类型对应的数值、字符或符号来表示),右被操作数。

    6h$4.2 / 4
    "b"$4.2 / 1b
    `date$2004.04.02T04:02:24.042 / 2004.04.02
    c:10 20 30 40
    c[0 1 3]:(type c)$(1.1; 42j; 0x2a) / 1 42 30 42
    casting同样符合map原则(1|n, n|1, n|n)。

    parsing
    Q里的parsing实际上就是对string进行cast到目标类型的操作,只是左边的类型数值必须用大写的字符。

    "I"$"4267" / 4267
    "T"$"23:59:59.999" / 23:59:59.999
    "D" $ ("2007-04-24"; "12/25/2006"; "07/04/06") / 2007.04.24 2006.12.25 2006.07.04
    `$"sym" / 相当于"S"$"sym"
    distinct 与 find(?)
    v:3 1 2 2 1 3 1 1 2
    u:distinct v /3 1 2
    k:u?v / 0 1 2 2 1 0 1 1 2
    v ~ u k /1b
    u k结构等价于v,但当v里重复很多且单个item占用空间较大时,前者在时间和空间有更高的效率。这个例子说明了Q里面enumeration的动机。

    enumeration
    Q里enumeration用类似cast的语法:`u$v,获得v在u域上的表示——link to u + index。用cast语法很自然,因为这相当于把v往u域(而不是type u)上cast。

    u:`c`b`a
    v:`c`b`a`c`c`b`a`b`a`a`a`c
    ev:`u$v / `u$`c`b`a`c`c`b`a`b`a`a`a`c
    v = ev /1b
    v ~ ev /0b
    v ~ u ev / 1b
    得到的ev相当于link to u + index,所以有最后那个和v的等价关系。但ev和v不共享任何数据,所以更改其中任何一者不再影响另外一个。

    v[0]:`a / `a`b`a`c`c`b`a`b`a`a`a`c
    ev[0]:`b / `u$`b`b`a`c`c`b`a`b`a`a`a`c
    但ev是link到u的,所以改动u,ev的pretty print的结果会发生变化,但是实际上ev本身并没有变——还是到u的link和index,只是同样的index被应用到不同的base上自然结果不同而已。

    u / `c`a`b
    ev / `u$`b`b`a`c`c`b`a`b`a`a`a`c
    u[1 2]:u [2 1] / `c`b`a
    ev / `u$`a`a`b`c`c`a`b`a`b`b`b`c
    ev在大部分使用v的场合下均可对应使用,但在destructive append上则需要额外的考量。比如

    v,:`x / `a`b`a`c`c`b`a`b`a`a`a`c`x
    ev,:`x / 'cast错误
    很简单,以ev=u+index的表示,u中尚无x,如何计算对应的index呢?那么

    u,:`x
    ev,:`x
    即可。但这又带来另外一个问题,我们怎么知道u里是不是已经有了x呢?如果已经有了,那么append以后就有了两个x,带来语义上的混淆。先测试自然是一个方法,但Q特别提供另一个语法,强制cast to (enumerate on),就是说如果所cast (enumerate) 的域里没有某个被cast的symbol,那么域被改变增加这个新的symbol,这其实是一种特殊的enumeration,所以它的语法和enumeration很像,只是用?替代$

    u / `c`b`a`d
    `u?`a / `u$`a 返回结果是`a cast to (enumerate on) `u,但u不变
    `u?`x / `u$`x 返回结果是`x cast to (enumerate on) `u,但u已经变了,包含了`x,成为 `c`b`a`d`x
    v / `p`q`r
    `u?v / `u$v 此时u已成为`c`b`a`d`x`p`q`r
    那么,对ev进行append,可以

    `u?`x
    `ev,:`x
    获取一个enumeration所映射的值可以

    value ev / 或apply index (前面的方法): u ev
    每个域上的enumeration都各有一个新的type,从20h开始。因此,同一个value,在结构相等的域上enumerate,所获得的enumeration是不能match(~)的。

    u1:`c`b`a
    u2:`c`b`a
    v:`c`a`c`b`b`a
    type `u1$v / 24h
    type `u2$v / 25h
    (`u1$v) = (`u2$v) / 111111b
    (`u1$v) ~ (`u2$v) / 0b
    (u1 `u1$v) = (u2 `u2$v) / 111111b
    (u1 `u1$v) ~ (u2 `u2$v) / 1b
    Q笔记 (7)
    Sat, 2008/06/07 - 00:44 — code17 @PAPL
    Update: 2008/06/06

    字典
    字典是列表的泛化,列表以int列表为定义域,字典以任意列表为定义域,所以很多操作都是完全对应的。字典就是一般的hash表。

    语法要点
    L_key ! L_value
    类型:所有的字典类型值均为99h

    操作:count, key(cols), value 分别获取长度、定义域(列表)、值域(列表)

    顺序:字典的值与其中键值对的排列顺序是相关的,所以同样内容不同顺序的两个字典不互相match,尽管它们的查询操作总是返回相同的结果。

    键值:在一个字典里键应该是独一无二的,尽管Q并不强制这一点,如果有相同的多个键,那么查询操作返回最早出现的那个键所对应的值。

    查询:语法与list index和function application一样

    抽取:可以用key list#dict来获取一个sub dictionary

    操作
    修订:和list一样,用d:v来修订,唯一不同的是,字典可以通过这个语法来扩展(upsert)——如果这里的i原先不在d的定义域里的话,而列表则没有这个能力。

    (反向)查找:和list的?一样,list查找返回定义域里对应的index,是一个int;dictionary查找也返回定义域里的对应index,是一个key。若查找对象不存在,则返回定义域类型的null值(simple list)或0N(general list); 若字典里有多个相同的查找对象,则第一个对应的key为查找结果。

    删除:d _k 或kl _d,_左方的空格是必须的。删除是functional的,即返回一个删除后的字典,原字典不变。删除不存在的key,没有效果。_ = cut

    简单二元运算:字典和普通atom进行二元运算(比如加减),根据map原则,相当于将字典的values与这个atom进行运算,返回一个对应的新字典

    双字典二元运算:如果两字典的key域完全重合,那么就是对应的value进行运算,否则以null值来填补一方不存在的key-value pair。左操作符为主序,即以左边的字典为基础,对它们逐个从右边的字典里挑出对应pair来进行运算,最后在对右边字典里剩余的部分进行计算。另外,append(,)就是对pair进行upsert运算。

    column字典(表)
    以symbol为key,以list为value,我们就可以表示一个表了。所有的多维列表的语法均可以使用。

    scores:`name`iq!(`Dent`Beeblebrox`Prefect;42 98 126)
    scores [`name] / `Dent`Beeblebrox`Prefect,取name这一列
    scores [`name] [2] /`Prefect,取name这一列的第3行的值
    scores [`name;2] /`Prefect,同上
    scores . (`name; 2) /`Prefect, 同上
    scores.name 2 /`Prefect,特殊dot语法,注意没有`,同上
    scores [;2] /`name`iq!(`Prefect;126),相当于取一行!
    flip
    对column字典d进行flip,我们得到l,使得d[k] = d'[k],可以看出d'是一个list,其中每个item是和原来的d同结构的字典——一行。
    Q笔记 (8)
    Mon, 2008/09/08 - 18:53 — code17 @PAPL APL/J/K/Q notes
    Updated: 2008/09/08

    表(table)是 kdb+ 的基础。如前所述,表是用列字典(column dictionary)实现的,因此一列数据里的值的顺序是至关重要的,与普通的 SQL 数据库不同。优势是效率,比如处理时间序列的时候。

    任何表的类型值均为98h

    定义
    本质
    列字典,key 为 main dimension

    key -> value list (或 fun[key] -> value list)
    key -> index -> value (或 fun[key; index] -> value)
    表就是列字典的 flip, index 成为 main dimension:

    index -> key * value (或 fun[index] -> (key * value) dict)
    index -> key -> value (或 fun[index; key] -> value)
    定义
    用列字典的flip来定义当然可以,但表也有自己的syntax

    ([] C1:L1; ...; Cn:Ln)
    Cx: column名(symbol), Lx: 属于该column的value list

    源数据
    cols t / 取column名
    meta t / 取全部的meta信息,格式略
    record
    对应于普通数据库里的row

    t[1] / 取第一行,返回的是其实是一个 dict
    t[1;`name] / 取第一行, `name列的值
    value t[1] / 取第一行的"值",t[1]是个dict
    内部表示
    表的内部表示就是列字典加上一个flipped标记。每列里存储的数据是连续的,所以索引和单列操作非常高效,而行操作则涉及index各列中的对应数据,效率也不错。但删除一行的操作非常费时——要merge删除后的空档,使得各列依旧保持存储的性质。

    空表
    t: ([] name:(); iq:())
    Q是动态强类型的,这里name和id所对应的 value list 的类型相当于ML的weak type: _'a list,一旦被propogate,_'a 就被具体化为某一确定类型。如果想提前确定保证类型安全,可以用casting (相当于annotation).

    t:([] name:`symbol$(); iq:`int$())
    select
    参见后续 p-sql 章节,这里只是简单介绍。

    select ... from ... 实际上是 syntactic sugar,内部转换为基本的 function application.

    select from t / 选择所有列
    select col1,col2 from t / 选择某些列
    update iq:iq%100 from t / update 语法与 select 类似
    键表、值表和键值表
    对于普通 SQL 表里的主键(primary key)概念,Q 里的方法很简单:一个键表+一个值表,用字典对应两个表里的records(别忘了,一个record本身就是一个dict)。

    内部表示
    还是先用列字典来写表,更贴近根本

    q) kt:flip (enlist `eid)!enlist 1001 1002 1003 /键表
    q) vt:flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126) / 值表
    q) kvt:kt!vt /键值表,或者把kt/vt直接写一起也是一样
    kvt
    eid | name iq
    ----| --------------
    1001| Dent 98
    1002| Beeblebrox 42
    1003| Prefect 126
    q) kvt[`eid!1002] /这是dict lookup,`eid!1002 是键表里的一个record,求的是对应值表里的record
    name| `Beeblebrox
    iq | 42
    因此如果键表里有重复的值,lookup只能返回第一个,但用select可以返回全部。

    便捷语法
    前面的定义方法有点麻烦,便捷语法:还记得表的定义语法里的 [] 吗?把主键那一列(或多列)写进去就可以了。前面的定义等价于

    q) kt:([eid:1001 1002 1003] name:`Dent`Beeblebrox`Prefect; iq:98 42 126)
    q) kt[1002] /query 也有便捷语法
    name| `Beeblebrox
    iq | 42
    操作
    因为键值表本质上就是一个键表->值表的字典,而这个字典的定义域是一串键表的record,因此任何的字典操作均可在上面使用。比如 #, ? 等

    此外

    key kvt / 获取键表
    value kvt / 获取值表
    keys kvt / 获取键的column名
    cols kvt / 获取值得column名,前面用过
    转换
    通过指定键值的列(eid),来将一个普通表(t)简单地转换为键值表,用xkey: `eid xkey t
    通过指定键值的列为(),来讲一个键值表(kvt)简单地转换为普通表,用xkey:() xkey kvt
    多键值
    很简单,无论是创建还是查询,一个键(列)的地方写多个即可。

    通用lookup函数
    用三元函数txf查询键值表kvt的rl行(list),取它们的cl列。

    txf[kvt;rl;cl]
    外键和虚拟列
    Q的外键简单地定义为值在另一键值表(kvt)的主键上的enumeration,kvt的主键上的enumeration可以简单写为`kvt$xxx。给定前述例子中的kvt,kvt上的某个外键可以写作 `kvt$`eid!1001 或简写为 `kvt$1001

    这样,使用kvt外键的表可以轻松引用kvt的列为虚拟列,比如

    q) tdetails:([] eid:`kvt$1003 1001 1002 1001 1002 1001; sc:126 36 92 39 98 42) /定义eid为kvt上的外键
    q) select eid.name sc from tdetails / eid.name是虚拟列
    name sc
    --------------
    Prefect 126
    Beeblebrox 36
    Dent 92
    Beeblebrox 39
    Dent 98
    Beeblebrox 42
    (键)值表操作
    普通表和键值表均支持以下操作:first, last #, n#, -n# etc.

    find(?)
    t?r 对表t里的record作反向查找,但要注意,

    r里的键名可省
    根据map原则,r可以为一个或者多个record
    一个record一般有多个域,每个record自身必须是个list,即使只有一个域。
    纵向(row)join(,)
    纵向的join操作,链接两个域相同的表,record的键名不能省。对于普通表,这只是简单的append,重复的record不作任何处理;对于键值表,因为它本质上是一个字典,因此同一主键对应的record被覆盖。另一join操作(^)与(,)类似,唯一不同是,如果(^)的右面的键值表中的某些位置有missing value,那么那些位置将不会覆盖左边键值表中的对应位置的值。

    横向(column)join(,')
    又叫sideways join,和纵向join对应。

    当被join的两个表的rows不同时,作upsert.

    复杂数据列
    其实就是列数据的类型可以为复杂数据类形:比如list,一列里数据类型相同,但长度却可以不等,因此比普通的SQL更灵活。详见p-sql章节。

    属性标记
    标记一串数据(比如list, table)的性质,比如有序(sorted), 无重复(unique),可数(parted),分组(grouped)。核心思想是:

    目的是为了效率,某些操作在具有某些属性的数据串上可以加速
    手动标记,不是自动探测:根据是在数据量不是超大的情况下,没太大区别,所以需要的时候才用。
    标记会被check,强制标记某个数据串并不拥有的属性不会成功
    一旦标记,这个属性就附着在这个数据串上,每次数据串改动操作都会check这个属性是否还成立,如果成立则保持属性标记,否则丢失标记。
    sorted
    L:`s#1 2 2 4 8
    t:([]`s#t:04:02:42.001 04:02:42.003;v:101.05 100.95) / table里的colum也是个list,dict里的domain也是个list
    ts:`s#([]t:04:02:42.001 04:02:42.003;v:101.05 100.95) / 标记整个表,意味着第一列是 sorted
    kt:`s#([k:1 2 3 4] v:`d`c`b`a) / 标记一个键值表,意味着其键表各列是 sorted
    unique
    可以应用于 dict domain, column, 键值表的key column,但不能直接应用于 dict, table 或键值表本身。

    parted
    应用于可数类型,比如int或与int有对应关系的类型(比如时间)。

    这个属性在数据串操作时不能保持——计算从数学上讲结果数据串依然具有这一性质也是一样,估计是因为没有universal且effcient的办法判断。

    grouped
    没有其他性质的数据串均可申明为grouped,其实相当于对一串数据加上某种 SQL index,这样在取某一range的数据是可以比较快。
    Q笔记 (9)
    Mon, 2008/09/22 - 07:11 — code17 @PAPL APL/J/K/Q notes
    Updated: 2008/09/22

    q-sql
    Q提供了一些类似于SQL的语法,但本质上还是转化为作用于table的函数。前面讲过,Q 的 table 是基于 dictionary 的 flip,明白了这一点,对于后面SQL操作很容易理解。

    Insert
    例子:

    q) t:([] name:`Dent`Beeblebrox`Prefect; iq:42 98 126)
    q) insert[`t; (`Slartibartfast;156)] / insert a row
    ,3
    q) `t insert (`Prefect`Mickey;126 1024) /insert rows
    5 6
    q) insert[`t] (`name`iq!(`Slartibartfast;156)) / insert a record
    ,4
    q) `t insert ([] name:`Prefect`Mickey; iq:126 1024)) / insert table
    7 8
    很直观。值得注意的是,

    insert 同样是个 verb,所以支持 verb 的各种语法
    被插入的数据,可以是 a row/rows(column lists), a record/records(table),返回的是所插入的records对应的row number.
    键值表(或使用外键的表)的插入和普通表一样,只是如果键有冲突(或外键不存在)会报错
    select
    语法:

    select <p_s> <by p_b> from t_exp <where p_w>
    和普通SQL语法很类似,eval的顺序和要点:

    from t_exp (source)
    where p_w (filters)
    如有多个条件,"&" 表示并且,是atomic的;而"," 表示嵌套地filter,是successive的从左向右apply,所以从性能考虑,最selective的条件应该放在最左
    有的时候 test op 不是 atomic 或 uniform 的,需要用each,否则指的是对整个column测试
    by p_b (group_by)
    p_b成为输出table 的 key
    若多个record有同一p_b,这些 record 将被合并。如果 select 后跟随显式 column 参数,则这些被合并的 record 对应 column 将被合并为一个list;若select 后无显式 column 参数,则各 column 将取这些 record 中的最后一个在该column的值。
    p_b 可以是函数应用到某些 column 上的结果
    select p_s (selector)
    存在一个 implicit 的变量 i,对应于每个 record 在源表中的位置
    外键可以和普通column一样用
    p_s 可以是函数应用到某些 column 上的结果:有by字句时,这意味着将函数应用于每一行的对应column(往往是个list); 没有by字句时,这意味着将函数应用于整个column,如果需要分别应用于各行的对应column,则需要使用 each 操作。
    select[n] 选最前n个结果,select[-n] 选最后n个结果
    exec
    语法类似select,除了结果不是个table,而是 list (当结果仅含一个column) 或 dictionary (当结果包含多个 column)

    update
    语法:

    update <p_u> <by p_b> from t_exp <where p_w>
    这里有一个原则——在后面的其他修改类的SQL语句里也适用:假设t是一个table,字句中使用 from t 来表示作 functional update,产生一个新的table,旧的 table 不变;而用 from `t (i.e., reference by name) 来表示作 in-place update,也就是modification.

    update 甚至可以增加一个column,因此常用的方法是先用 by 作 group,然后对于 group 出来的 compond data (list) 做 aggregation,结果用 update 添加为新的一列。

    upsert
    upsert很简单,就是 update + insert,一个二元函数。当输入是键值表的时候, 相当于join;当输入是普通表的时候,相当于append。

    delete
    语法:

    delete <p_cols> from t_exp <where p_w>
    其中 p_cols 和 where p_w 子句同时只能有一个,显然,前者删除 columns,后者删除 rows。

    join
    q-sql 里大部分的 join 都是通过外键直接访问或者函数查询 implicitly 地进行的。此外,

    二元函数 lj 进行lookup join,比如 ts lj tl,对于 ts 中存在的各行, 将键值表tl 的对应行 join 起来,同名的列作upsert。
    二元函数 pj 进行 plus join,和 lj 工作方式类似,只是同名列将会叠加,缺少的 cell 将用 0值补齐。
    二元函数 uj 进行 union join,任一存在于 ts 或 tl 中的行都会被加入,同名的列被disjoint地合并(可重复),列上缺少的 cell 用 null值补齐。
    三元函数 aj 进行 asof join,aj[c1...cn; t1; t2] 对t1的任一record,查询t2里与之有公共column value c1...cn 的 record 并join,若无,则以null补齐。
    以上函数与外键访问的方式比起来算不得高效,但它不要求 ts 必须有 tl 的外键。

    其他函数
    xgroup: 二元函数,`p xgroup sp 将表 sp 按 column p 的值来 group,p相同的 records 被合并。
    ungroup:当一个table的某些records的column value里是由多个元素组成的list (往往由xgroup或select...by 产生),用ungroup可以产生一个新的table将这些records分别转换为多个对应的records,每个record在该column仅包含这个list里的一个元素。
    xasc, xdesc:排序二元函数。比如,p1p2 xasc t,对table t 按column p1 then p2 的升序排列
    xcol: 改名二元函数,比如 idname`val xcol t 将 table t 的前一二三个column重命名为id, name, val
    xcols: 改序二元函数,不改名,而是将对应的column按照给定的先后顺序排列。比如 idname`val xcols t 将 table t 的 列id, name 和 val 移到前三列。
    distict: 对一个有重复records 的table,生成一个去除重复的新 table。可与 select/exec 联用,select/exec distict c1 from ...
    fby: 常用于where子句,对某些column作aggregation

    (f_agg; exp_col) fby col
    (f_agg; exp_col) fby tbl
    对于record,先对col(多个column的话,用一个table表示) 作一下 group_by,然后用函数f_agg 对列表达式 exp_col (经过group,一般此column里的值为list) 进行aggregation。

    参数化查询
    任何的 q-sql 均可被视为普通函数的application,因此可以用它们来定义其他函数,实现参数化查询——例如被select的table、where子句条件,均可以作为函数参数传入。

    View
    SQL 的 view 在 q-sql 里就是一个 query 函数的 alias,这样它就可以随着被查询的 table 或其他参数的变化而自动变化了。

    Functional Form
    p-sql 语句只是语法糖,在内部总是被转换成为某种函数应用的形式(functional form),这种更根本的形式当然具有更大的灵活性,不过写起来也相对麻烦些。(暂略)

    Q笔记 (10)
    Mon, 2008/09/22 - 22:11 — code17 @PAPL APL/J/K/Q notes
    流程控制
    作为一个向量语言,Q 并不鼓励使用条件语句这类的流程控制,而建议使用各种数组操作的primitives (用 FP 角度来看—— combinators)。但 Q 也提供了一般意义上的条件语句,以备不时之需。

    条件求值
    语法:

    $[expr_cond; expr_true; expr_false]
    和 C 里的 bool ? expr_true : expr_false 一个意思,其中

    expr_cond 为 bool,其他类型的值按零/非零映射到 bool
    expr_true/expr_false 可以为单句,或由方括号括起来的丛句:[expr_1; expr_2; ...; expr_n]
    expr_false 子句如果是另一条件求值字句,$ 和 [] 可省。即 $[expr_cond1; expr_true1; expr_cond2; expr_true2; ...; expr_condn; expr_truen; expr_false] 表示 if expr_cond1 then expr_true1 else if expr_cond2 then expr_true2 else if expr_condn then expr_truen else expr_false
    向量条件求值
    语法:

    ?[vector_bool; expr_true; expr_false]
    其中 vector_bool 是一个 bool vector,而 expr_true, expr_false 是与之长度相同的两个同类型的 vector。结果是一个vector,对于 vector_bool 为真的位置,取 expr_true 对应位置的值,否则取 expr_false 对应位置的值。

    if
    语法

    if[expr_cond; expr1; expr2; ...; exprn]
    当 expr_cond 为真,从左向右 eval expr1, expr2 ... exprn

    do
    语法

    do[expr_count; expr1; ...; exprn]
    expr_count 必须为 int,对 expr1 ... exprn 从左向右 eval expr_count 遍。

    while
    语法

    while[expr_cond; expr1; ...; exprn]
    当expr_cond为真时,对expr1;...; exprn 从左向右 eval,然后重复直到 expr_cond 为假。

    return 和 signal
    有的时候我们需要提前终止一个丛句的evaluation,因为

    我们已经可以确定结果了,这时用return。具体语法是在丛句的任一子句中,绑定结果到一个空缺的名字即可。比如 f:{a:6; b:7; :a*b; c:8; c+x},f 0 结果是 42 而不是 8
    我们不知道结果,但希望提前异常终止。这时用signal,和return 语法类似,不用:而是用'
    try...with
    语法:

    @[f;a;expr_fail]
    类似于 try (f a) with _ -> expr_fail。其中 f 是一元函数作用于单个值 a。如果 f 是多元变量,作用于一个list,则用"."而不是"@"

    .[f_mul; l_args; expr_fail]
    debug
    Q 没debugger,基本上靠interactive interpreter 和 print。

    一个tip, 定义

    zs:{`d`P`L`G`D!(system"d"),v[1 2 3],enlist last v:value x}
    一元函数,如果一个函数应用被suspend的话,可以用来trace。

    Q笔记 (11)
    Tue, 2008/09/23 - 06:00 — code17 @PAPL APL/J/K/Q notes
    IO
    Q的I/O操作是通过handles来完成。

    handles 可以看作函数,作为值访问获取意味着读,传递参数给它意味着写。

    数据文件和handle
    任何Q里定义的实体均可直接输出(serialize)其内存表示到文件。文件handle的语法为

    `:[path]fname
    即以":"开头的一个symbol,path部分为可选,是当前OS可接受的一个路径即可(但无论是哪个OS统一用"/"为分隔符),fname是文件名。

    数据文件操作
    hcount <handle> 计算文件的大小(in bytes)
    hdel <handle> 删除文件
    <handle> set <value> 单步写入(自动打开、创建或覆盖、关闭文件,)
    get <handle> 单步读入(自动打开关闭文件,等同于 value <handle>)
    hopen <handle> 手工打开一个文件(如不存在自动创建),返回一个handle,而后可以通过直接pass参数的方式写入(追加形式)
    hclose <handle> 手工关闭hopen返回的handle
    .[<handle>;();:;<values>] 覆盖文件内容
    .[<handle>;();,;<values>] 追加文件内容
    数据文件读写:table
    table数据除了普通的读写方法:

    q) t:([] c1:101 102 103; c2:1.1 2.2 3.3)
    q) `:/q/data/t.dat set t
    还支持写入为splayed table

    q) `:/q/data/t/ set t
    将table t写为一整个目录/q/data/t,包括每列对应一个文件,和一个.d描述文件。读取也是类似。

    另外还支持save/load便捷操作(同时支持普通读写和splayed table读写):

    q) save `:path/tname / i.e., `:path/tname set tname
    q) load `:path/tname / i.e., tname: get `:path/tname
    q) save `:path/tname/ / i.e., `:path/tname/ set tname
    q) load `:path/tname/ / i.e., tname: get `:path/tname/
    给被save/set的文件名添加.txt,.csv,.xml后缀,表数据将被自动写为文本、CVS、XML文件的形式(但不支持写入compond data)。

    文本文件
    "0:" 操作符对应于 set,但是以文本方式写入。<handle> 0: <l_string> 将 string list l_string 写入handle,每个string一行
    read0 对应于 get,只是以文本方式读入
    以hopen打开的handle h,可以用 (neg h) 来以文本方式读取和写入
    二进制文件
    与文本文件类似

    "1:" 操作符对应于 set,但是以二进制方式写入。<handle> 1: <l_bit> 将 bits list l_bit 写入handle
    read1 对应于 get,只是以二进制方式读入
    以hopen打开的handle返回正值h,可以用来确定读入/写出二进制串
    Parsing records
    只要给出对应的规范,Q 可以以纯文本或二进制形式来parsing读入各种类型组合的record数据。各种类型对应的规范信息略。

    定长record读入的语法:

    (L_t;L_w) 0: <handle>
    (L_t;L_w) 1: <handle>
    其中"0:" (文本形式);"1:" (二进制形式),L_t 是各个 record field 的类型代号组成的 list,L_w 是与 L_t 等长的 int list,对应于各个field的size。结果是一个 list of list (column).

    <handle>的位置同样也可以放置<handle; offset; length>,用来读取文件的一部分。

    不定长record读入的语法:

    (L_t;D) 0: <handle>
    (L_t;D) 1: <handle>
    L_t和上面含义一样,而D是一个atomic delimeter,比如","。可以使用 enlist D 来告诉Q,第一个record是column的名字。

    save/load contexts
    当前整个运行环境(session),可以被保存以供后来使用。就是基本的文件读写,加上"`."的系统变量(handle)



    q) `:currentws set value `. / 存储当前环境所有绑定到 currentws 文件
    q) dc:get `:currentws / 将 currentws 里的环境读回来,绑定到dc
    q) `. set dc / 用dc来替换当前环境


    进程间通讯
    server启动

    q -p <num_port>
    client连接,对应于文件读写,也是handle。这里handle的语法是:

    `:[server]:port
    其中server可以为空,当client/server是同一主机,或主机名、ip地址、域名。

    client连接/关闭,与文件读写对应

    q) h:hopen <handle>
    q) hclose h
    远程RPC有两种形式:

    所有定义已在远程,这个最简单,只要以literal string的形式发送在远程执行的命令即可。发送命令即是向handle传递参数 h <string>。
    定义在本地,但希望在远程机器上执行,本质上是需要将函数和参数传递到远程后再执行。具体语法很简单 (f;arg_1;arg_2;...)。同样,发送命令即是向handle传递参数 h (f;arg_1;arg_2;...)
    以上是 synchronized 方式的RPC,本地等待远程回复然后才进行下一操作;Q 同时也支持 asynchronized 方式的远程调用,不等待远程回复立即返回,语法很简单,只是将 h 替换为 (neg h) 即可。

    一些预定义的handle
    暂略。

    Q笔记 (12)
    Tue, 2008/09/23 - 18:30 — code17 @PAPL APL/J/K/Q notes
    Workspace
    为了减少名字冲突,Q用context(i.e., namespace)来组织 workspace 中的各种定义。context简单定义为一个"."后面跟着一个identifier,比如

    . / 缺省context
    .aa
    .qk
    .zaphod
    而在一个context里定义的变量路径,就是将context名与变量名用"."连接。比如

    foorbar / 缺省context
    .aa.foobar
    .qk.foobar
    .zaphod.foobar
    用\d <context>来进入一个context,进入以后直接定义的variable(没有context prefix的),就属于这个context了。也可以直接在一个context里定义另一个context里的variable,只是需要使用完整的路径名。

    context是动态生成的,无论是变量定义或访问,都不需要预先定义context。

    一个context可以看作一个dictionary (name -> value),用value <context> 可以检查这个context里的定义,用正常的字典语法也可以访问其中变量的值。

    context没有层次结构(就一层),所以不要以为 ".aa" 是 "." 的下层结构,它们是并列的。".aa.bb.c" 是context ".aa" 里定义的变量 "bb.c" 而不是其他。如果你使用value来观测".aa.bb",似乎是一个dict of dict,会得出它是一个".aa" ".aa.bb" 的层次结构的结论,但这只是为了方便而造成假象,比如说你不能用\d 来进入".aa.bb"这样一个context。

    Scope: 每个context里,我们可以直接refer这个context里定义的variable,其他context里的variable必需要使用context路径前缀,包括缺省的context。但是,当定义函数的时候,缺省context里的variable可以在函数定义里直接引用。

    单个字母(大、小写)的context是系统预留的,不要使用。
     
  14. 支持Emacs模式。:cool:
     
  15. 这里(要用anonymous/anonymous登陆).:)
     
  16. 多谢itfin兄指点。收藏了。
    正好在学Emacs+ESS。很爽。
    以后再学新工具的时候,首选KDB。到时候还要请您多指点啊。:D
     
  17. 别客气,大家多交流:).