这篇文章翻译自 http://nathanmarz.com/blog/new-cascalog-features-outer-joins-combiners-sorting-and-more.html。如下进入正文。html
在 第一篇教程(译文)中,我讲了 Cascalog 的不少强大的功能:链接(join),累计(aggregates),子查询(subqueries),自定义操做(custom operations),等等。从几周前 Cascalog release 之后,我又加了不少新的特性,这些新特性使 Cascalog 更富有表现力、更强大,但丝绝不影响原有的简洁和灵活。java
跟第一篇教程同样,先经过如下命令把 playground 加载进来:bootstrap
lein compile-java && lein compile lein repl user=> (use 'cascalog.playground) (bootstrap)
咱们在第一篇教程中就已经看到,你能够用 Cascalog 把来自多个数据源的数据 join 在一块儿,只要在多个数据源中使用相同的变量名。例如,在 age 和 gender 数据源中,咱们运行这一段代码来获得每一个人的年龄和性别:oop
user=> (?<- (stdout) [?person ?age ?gender] (age ?person ?age) (gender ?person ?gender))
这实际上是一个 inner join,只有在两个数据集中都出现的人才会出如今结果中。咱们能够这样作 outer join :优化
user=> (?<- (stdout) [?person !!age !!gender] (age ?person !!age) (gender ?person !!gender))
这个查询的结果对于没有在 age 或 gender 数据集中没有出现的人,用空值替代不存在的值。翻译
Cascalog 的 outer join 由以 !!
开头的变量名触发,这些变量称为 “未决变量”(原文叫 "ungrounding variales"),包含未决变量的谓词叫作 “未决谓词”(原文叫 "unground predicate"),不包含未决变量的谓词称做 “已决谓词”(原文叫 "ground predicate")。对两个未决谓词作 join 是一个 full outer join,对一个已决谓词和一个未决谓词作 join 是一个 left join。code
这里有个 left join 的例子,获取 person 数据集中全部人的 follow 关系,若是没有 follow 关系则用空值代替:htm
user=> (?<- (stdout) [?person1 !!person2] (person ?person1) (follows ?person1 !!person2))
要拿到全部没有 follow 关系的人,能够这样blog
user=> (?<- (stdout) [?person] (person ?person) (follows ?person !!p2) (nil? !!p2))
(nil? !!p2)
谓语是在 join 以后执行的,这是 Cascalog 的 outer join 语义中很重要的一部分。排序
如今咱们要数一下每一个人 follow 人的数量,正常的 count
累计器不会生效,由于那会计算全部元组的数量,但不会区分 follows 元组是否为空。在这种状况下,咱们须要对空的 follow 元组计数为 0,对非空的计数为 1,Cascalog 的 !count
累计器能够作这件事:
user=> (?<- (stdout) [?person ?count] (person ?person) (follows ?person !!p2) (c/!count !!p2 :> ?count))
没有 follow 关系的人会被计数为 0。
一个未决变量只能在一个查询中出现一次,其余时候都跟正常变量同样。
<!--more-->
常规的累计器会把一个分组的全部元组都传到一台机器上去作运算,但有不少累计器如 count
,sum
,min
和 max
,能够被并行计算。例如,要计算总和,能够吧元组分红几个子集,计算各个子集的和,而后再把子集的和汇总来获得最后的结果,不少累计器均可以这样计算。
Cascalog 如今能够容许你定义 “并行累计”,这样能够尽量的在 map 阶段完成这些计算,而不用全拖到 reducer 里去完成。map 端的累计器叫作 “combiners”。Cascalog 甚至能够在运行多个并行累计的时候用到 combiners。下面的例子就是用了 combiners:
user=> (?<- (stdout) [?count ?sum] (integer ?n) (c/sum ?n :> ?sum) (c/count ?count))
Cascalog 会在可能的状况下自动插入 combiners,你不须要作任何事就能利用这个优化。
若是你想把经过 defaggregateop
或 defbufferop
定义的常规累计器用于并行累计,Cascalog 就不能自动用 combiners,全部的累计都会发生在 reducer 任务中。好比,下面这个用自定义累计器的查询就会把全部的累计放到 reducer 阶段:
user=> (defaggregateop product ([] 1) ([total val] (* total val)) ([total] [total])) user=> (?<- (stdout) [?prod ?count] (integer ?n) (product ?n :> ?prod) (c/count ?count))
并行累计器能够经过 defparallelagg
来定义。具体例子能够去 cascalog.ops
里去看。
在分组不多,好比计算全局数量的时候,你会看到这个特性在速度上会有很大的提高。
“隐式等式约束” 特性是指定等式约束的比较优雅的方法,这个特性仍是经过例子来说比较好。在 playground 里定义了一个 "integer" 的数据集,里面包含了个数字的集合,若是要的到全部平方后和自己相等的数字,能够这样搞:
user=> (?<- (stdout) [?n] (integer ?n) (* ?n ?n :> ?n))
Cascalog 检测到咱们尝试给 ?n 变量从新绑定值,会自动的过滤掉 *
谓词的输出和输入不相等的元组。
还有一些其余的状况你能够用这个特性。好比,要找到 "num-pair" 数据源里先后相等的两个数字对,能够这样:
user=> (?<- (stdout) [?n] (num-pair ?n ?n))
若是想拿到第二个数字是第一个数字两倍的数字对:
user=> (?<- (stdout) [?n1 ?n2] (num-pair ?n1 ?n2) (* 2 ?n1 :> ?n2))
这没什么可多说的,很直观。
默认状况下,累计器接收到的元组是任意顺序的,Cascalog 如今有 :sort
和 :reverse
谓词来控制元组到达累计器的顺序。举个例子,咱们能够这样找出一我的 follow 的年龄最小的人:
user=> (defbufferop first-tuple [tuples] (take 1 tuples)) user=> (?<- (stdout) [?person ?youngest] (follows ?person ?p2) (age ?p2 ?age) (:sort ?age) (first-tuple ?p2 :> ?youngest))
要找到 follow 的年纪最大的人,只要再加上 :reverse
谓词:
user=> (?<- (stdout) [?person ?youngest] (follows ?person ?p2) (age ?p2 ?age) (:sort ?age) (:reverse true) (first-tuple ?p2 :> ?youngest))
若是你的查询中没有累计器,Cascalog 默认会在 reduce 这步去掉全部重复的元组,你能够经过 :distinct
谓词来控制这个行为。比较下面两个查询:
user=> (?<- (stdout) [?a] (age _ ?a)) user=> (?<- (stdout) [?a] (age _ ?a) (:distinct false))
第二个查询的输出中会有重复。这个功能的用例是用一个子查询来对输入作一些预处理的时候。