《MongoDB分组统计》
《简化MongonDB的关联运算》
《玩转MongoDB运算》
MonagoDB属于NoSql中的基于分布式文件存储的文档型数据库,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,但是写起来并不简单。若能集算器SPL语言结合,处理起来就相对容易多了。
现在我们针对MongoDB在计算方面的问题进行讨论分析,通过集算器SPL语言加以改进,方便用户使用MongoDB。现从如下情况加以说明:
1. 单表内嵌数组结构的统计................................................. 1
2. 单表内嵌文档求和....................................................... 3
3. 分段分组结构:.......................................................... 5
4. 同构表合并............................................................. 6
5. 关联嵌套结构情况1...................................................... 8
6. 关联嵌套结构情况2..................................................... 10
7. 关联嵌套结构情况3..................................................... 11
8. 多字段分组统计........................................................ 14
9. 两表关联查询.......................................................... 16
10.多表关联查询.......................................................... 17
11.指定数组查找.......................................................... 19
12.关联表中的数组查找.................................................... 20
对嵌套数组结构中的数据统计处理。查询考试科目的平均分及每个学生的总成绩情况。
测试数据:
_id |
name |
sex |
Scroe |
1 |
Tom |
F |
[{"lesson":" Physics", "mark":60 }, |
2 |
Jerry |
M |
[{"lesson":" Physics", "mark":92 }, |
期待统计结果:
Physics |
76 |
|
Tom |
132 |
Chemical |
72 |
|
Jerry |
173 |
Math |
81 |
|
|
|
Mongodb脚本:
db.student.aggregate( [ {$group: { "_id": {"lesson":"$scroe.lesson"} , "qty":{"$avg": "$scroe.mark"} } } ] ) |
db.student.aggregate( [ {$group: { "_id": {"name" :"$name"} , "qty":{"$sum" : "$scroe.mark"} } } ] ) |
由于各科分数scroe是按课目、成绩记录的数组结构,统计前需要将它拆解,将每科成绩与学生对应,然后再实现分组计算。这需要熟悉unwind与group组合的应用。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"student.find()").fetch() |
|
3 |
=A2.conj(scroe).groups(lesson:LESSON;avg(mark):AVG) |
|
4 |
=A2.new(name:NAME,scroe.sum(mark):TOTAL) |
|
5 |
>A1.close() |
|
按课目统计的总分数
LESSON |
AVG |
Chemical |
72.0 |
Math |
81.0 |
Physics |
76.0 |
每个学生的总成绩
NAME |
TOTAL |
Tom |
132 |
Jerry |
173 |
脚本说明:
A1:连接mongo数据库。
A2:获取student表中的数据。
A3:将scroe数据合并成序表,再按课程分组,计算平均分。
A4:统计每个学生的成绩后返回列名为NAME、TOTAL的序表。new函数表示生成新序表。
A5:关闭数据库连接。
这个比较常用嵌套结构统计的例子许多人遭遇过、需要先拆解,主要是熟悉mongodb对嵌套数据结构的处理。
2. 单表内嵌文档求和
对内嵌文档中的数据求和处理, 下面要统计每条记录的income,output的数量和。
测试数据:
_id |
income |
output |
1 |
{"cpu":1000, "mem":500, "mouse":"100"} |
{"cpu":1000, "mem":600 ,"mouse":"120"} |
2 |
{"cpu":2000, "mem":1000, |
{"cpu":1500, "mem":300 } |
期待统计结果
_id |
income |
output |
1 |
1600 |
1720 |
2 |
3550 |
1800 |
Mongodb脚本:
var fields = [ "income", "output" ]; db.computer.aggregate([ { $project:{ "values":{ $filter:{ input:{ "$objectToArray":"$$ROOT" }, cond:{ $in:[ "$$this.k", fields ] } } } } }, { $unwind:"$values" }, { $project:{ key:"$values.k", values:{ "$sum":{ "$let":{ "vars":{ "item":{ "$objectToArray":"$values.v" } }, "in":"$$item.v" } } } } }, {$sort: {"_id":-1}}, { "$group": { "_id": "$_id", 'income':{ "$first": "$values" }, "output":{ "$last": "$values" } }}, ]); |
filter将income,output部分信息存放到数组中,用unwind拆解成记录,再累计各项值求和,按_id分组合并数据。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"computer.find()").fetch() |
|
3 |
=A2.new(_id:ID,income.array().sum():INCOME,output.array().sum():OUTPUT) |
|
4 |
>A1.close() |
|
统计结果
ID |
INCOME |
OUTPUT |
1 |
1600.0 |
1720.0 |
2 |
3550.0 |
1800.0 |
脚本说明:
A1:连接数据库
A2:获取computer表中的数据
A3:将income、output字段中的数据分别转换成序列求和,再与ID组合生成新序表
A4:关闭数据库连接。
获取子记录的字段值,然后求和,相对于mongo脚本简化了不少。这个内嵌文档与内嵌数组在组织结构上有点类似,不小心容易混淆,注意与上例中的scroe数组结构比较,写出的脚本有所不同。
3. 分段分组结构
统计各段内的记录数量。下面按销售量分段,统计各段内的数据量,数据如下:
_id |
NAME |
STATE |
SALES |
1 |
Ashley |
New York |
11000 |
2 |
Rachel |
Montana |
9000 |
3 |
Emily |
New York |
8800 |
4 |
Matthew |
Texas |
8000 |
5 |
Alexis |
Illinois |
14000 |
分段方法:0-3000;3000-5000;5000-7500;7500-10000;10000以上。
期望结果:
Segment |
number |
3 |
3 |
4 |
2 |
Mongo脚本
var a_count=0; var b_count=0; var c_count=0; var d_count=0; var e_count=0; db.sales.find({
}).forEach( function(myDoc) { if (myDoc.SALES <3000) { a_count += 1; } else if (myDoc.SALES <5000) { b_count += 1; } else if (myDoc.SALES <7500) { c_count += 1; } else if (myDoc.SALES <10000) { d_count += 1; } else { e_count += 1; } } );
print("a_count="+a_count) print("b_count="+b_count) print("c_count="+c_count) print("d_count="+d_count) print("e_count="+e_count) |
这个需求按条件分段分组,mongodb没有提供对应的api,实现起来有点繁琐,上面的程序是其中实现的一个例子参考,当然也可以写成其它实现形式。下面看看集算器脚本的实现。
SPL脚本:
|
A |
B |
1 |
[3000,5000,7500,10000,15000] |
|
2 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
3 |
=mongo_shell(A2,"sales.find()").fetch() |
|
4 |
=A3.groups(A1.pseg(int(~.SALES)):Segment;count(1): number) |
|
5 |
>A2.close() |
|
脚本说明:
A1:定义SALES分组区间。
A2:连接mongodb数据库。
A3:获取sales表中的数据。
A4:根据SALES区间分组统计员工数。其中函数pseg()表示返回成员在序列中的区段序号,int()表示转换成整数。
A5:关闭数据库连接。
pseg的使用让SPL脚本精简了不少。
4. 同构表合并
具有相同结构的多表数据合并。下面将两个员工表数据合并。
Emp1:
_id |
NAME |
STATE |
HIREDATE |
DEPT |
SALARY |
1 |
Ashley |
New York |
2008-03-16 |
Finance |
11000 |
2 |
Rachel |
Michigan |
2001-04-16 |
Sales |
9000 |
3 |
Emily |
New York |
2011-07-11 |
HR |
8800 |
4 |
Matthew |
Texas |
2003-03-06 |
R&D |
8000 |
5 |
Alexis |
Illinois |
2008-03-10 |
Sale |
14000 |
Emp2:
_id |
NAME |
STATE |
HIREDATE |
DEPT |
SALARY |
10 |
Jacob |
New York |
2009-03-14 |
Sales |
13000 |
12 |
Jessica |
Florida |
2011-04-19 |
Sales |
9500 |
13 |
Daniel |
New York |
2001-02-11 |
HR |
7800 |
14 |
Alyssa |
Montana |
2013-09-06 |
R&D |
8000 |
15 |
Hannah |
Florida |
2015-06-10 |
Sales |
12500 |
合并数据结果:
_id |
NAME |
STATE |
HIREDATE |
DEPT |
SALARY |
1 |
Ashley |
New York |
2008-03-16 |
Finance |
11000 |
2 |
Rachel |
Michigan |
2001-04-16 |
Sales |
9000 |
3 |
Emily |
New York |
2011-07-11 |
HR |
8800 |
4 |
Matthew |
Texas |
2003-03-06 |
R&D |
8000 |
5 |
Alexis |
Illinois |
2008-03-10 |
Sale |
14000 |
10 |
Jacob |
New York |
2009-03-14 |
Sales |
13000 |
12 |
Jessica |
Florida |
2011-04-19 |
Sales |
9500 |
13 |
Daniel |
New York |
2001-02-11 |
HR |
7800 |
14 |
Alyssa |
Montana |
2013-09-06 |
R&D |
8000 |
15 |
Hannah |
Florida |
2015-06-10 |
Sales |
12500 |
Mongo脚本:
db.emp1.aggregate([ { "$limit": 1 }, { "$facet": { "collection1": [ { "$limit": 1 }, { "$lookup": { "from": " emp1", "pipeline": [{ "$match": { } }], "as": "collection1" }} ], "collection2": [ { "$limit": 1 }, { "$lookup": { "from": "emp2", "pipeline": [{ "$match": { } }], "as": "collection2" }} ] }}, { "$project": { "data": { "$concatArrays": [ { "$arrayElemAt": ["$collection1.collection1", 0] }, { "$arrayElemAt": ["$collection2.collection2", 0] }, ] } }}, { "$unwind": "$data" }, { "$replaceRoot": { "newRoot": "$data" } } ]) |
通过facet将两表数据先存入各自的数组中,然后concatArrays将数组合并,unwind拆解子记录后,并将它呈现在最外层。SPL脚本实现则没有那么多“花样”。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"emp1.find()").fetch() |
|
3 |
=mongo_shell(A1,"emp2.find()").fetch() |
|
4 |
=A2|A3 |
|
5 |
>A1.close() |
|
脚本说明:
A1:连接mongodb数据库。
A2:获取emp1表中的数据。
A3:获取emp2表中的数据。
A4:合并两表数据。
A5:关闭数据库连接。
熟悉sql语句的mongo初学者面对数据合并的mongo脚本,估计首次遇到时有点“懵”,SPL脚本就显得自然易懂了。
5. 关联嵌套结构情况1
两个关联表,表A与表B中的内嵌文档信息关联,且返回的信息在内嵌文档中。表childsgroup字段childs是嵌套数组结构,需要合并的信息name在其下。
测试数据:
history:
_id |
id |
History |
child_id |
1 |
001 |
today worked |
ch001 |
2 |
002 |
Working |
ch004 |
3 |
003 |
now working |
ch009 |
childsgroup:
_id |
groupid |
name |
childs |
1 |
g001 |
group1 |
{"id":"ch001","info":{"name":"a"}}, {"id":"ch002","info":{"name":"b"}} |
2 |
g002 |
group1 |
{"id":"ch004","info":{"name":"c"}}, {"id":"ch009","info":{"name":"d"}} |
表History中的child_id与表childsgroup中的childs.id关联,希望得到下面结果:
{
"_id" : ObjectId("5bab2ae8ab2f1bdb4f434bc3"),
"id" : "001",
"history" : "today worked",
"child_id" : "ch001",
"childInfo" :
{
"name" : "a"
}
………………
}
Mongo脚本
db.history.aggregate([ {$lookup: { from: "childsgroup", let: { child_id: "$child_id" }, pipeline: [ { $match: { $expr: { $in: [ "$$child_id", "$childs.id" ] } } }, { $unwind: "$childs" }, { $match: { $expr: { $eq: [ "$childs.id", "$$child_id" ] } } }, { $replaceRoot: { newRoot: "$childs.info" } } ], as: "childInfo" }}, ]) |
这个脚本用了几个函数lookup、pipeline、match、unwind、replaceRoot处理,一般mongodb用户不容易写出这样复杂脚本;那我们再看看spl脚本的实现:
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"history.find()").fetch() |
|
3 |
=mongo_shell(A1,"childsgroup.find()").fetch() |
|
4 |
=A3.conj(childs) |
|
5 |
=A2.join(child_id,A4:id,info.name:name) |
|
6 |
>A1.close() |
|
关联查询结果:
_id |
id |
history |
child_id |
name |
1 |
001 |
today worked |
ch001 |
a |
2 |
002 |
working |
ch004 |
c |
3 |
003 |
now working |
ch009 |
d |
脚本说明:
A1:连接mongodb数据库。
A2:获取history表中的数据。
A3:获取childsgroup表中的数据。
A4:将childsgroup中的childs数据提取出来合并成序表。
A5:表history中的child_id与表childs中的id关联查询,追加name字段, 返回序表。
A6:关闭数据库连接。
相对mongodb脚本写法,SPL脚本的难度降低了不少,省去了熟悉有关mongo函数的用法,如何去组合处理数据等,节约了不少时间。
6. 关联嵌套结构情况2
两个关联表,表A与表B中的内嵌文档信息关联,将信息合并到内嵌文档中。表txtPost
字段comment是嵌套数组结构,需要把comment_content合并到其下。
txtComment:
_ID |
comment_no |
comment_content |
1 |
143 |
test test |
2 |
140 |
math |
txtPost
_ID |
post_no |
Comment |
1 |
48 |
[{"comment_no" : 143, "comment_group" : 1} ] |
2 |
47 |
[{"comment_no" : 140, "comment_group" : 2}, |
期望结果:
_ID |
post_no |
Comment |
1 |
48 |
[{"comment_no" : 143, "comment_group" : 1,"comment_content" : "test test"} ] |
2 |
47 |
[{"comment_no" : 140, "comment_group" : 2,"comment_content" : "math"}, |
Mongo脚本
db.getCollection("txtPost").aggregate([ { "$unwind": "$comment" }, { "$lookup": { "from": "txtComment", "localField": "comment.comment_no", "foreignField": "comment_no", "as": "comment.comment_content" }}, { "$unwind": "$comment.comment_content" }, { "$addFields": { "comment.comment_content": "$comment.comment_content.comment_content" }}, { "$group": { "_id": "$_id", 'post_no':{ "$first": "$post_no" }, "comment": { "$push": "$comment" } }},
]).pretty() |
表txtPost按comment拆解成记录,然后与表txtComment关联查询,将其结果放到数组中,再将数组拆解成记录,将comment_content值移到comment下,最后分组合并。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"txtPost.find()").fetch() |
|
3 |
=mongo_shell(A1,"txtComment.find()").fetch() |
|
4 |
=A2.conj(comment.derive(A2.post_no:pno)) |
|
5 |
=A4.join(comment_no,A3:comment_no,comment_content:Content) |
|
6 |
=A5.group(pno;~:comment) |
|
7 |
>A1.close() |
|
脚本说明:
A1:连接mongodb数据库。
A2:获取txtPost表中的数据。
A3:获取txtComment表中的数据。
A4:将序表A2下的comment与post_no组合成序表,其中post_no改名为pno。
A5:序表A4通过comment_no与序表A3关联,追加字段comment_content,将其改名为Content。
A6:按pno分组返回序表,~表示当前记录。
A7:关闭数据库连接。
7. 关联嵌套结构情况3
两个关联表,表A与表B中的内嵌文档信息关联,且返回的信息在记录上。表collection2字段product是嵌套数组结构,返回的信息是isCompleted等字段。
测试数据:
collection1:
{
_id: '5bc2e44a106342152cd83e97',
description:
{
status: 'Good',
machine: 'X'
},
order: 'A',
lot: '1'
};
collection2:
{
_id: '5bc2e44a106342152cd83e80',
isCompleted: false,
serialNo: '1',
batchNo: '2',
product: [ // note the subdocuments here
{ order: 'A', lot: '1' },
{ order: 'A', lot: '2' }
]
}
期待结果
{
_id: 5bc2e44a106342152cd83e97,
description:
{
status: 'Good',
machine: 'X',
},
order: 'A',
lot: '1' ,
isCompleted: false,
serialNo: '1',
batchNo: '2'
}
Mongo脚本
db.collection1.aggregate([{ $lookup: { from: "collection2", let: { order: "$order", lot: "$lot" }, pipeline: [{ $match: { $expr:{ $in: [ { order: "$$order", lot: "$$lot" }, "$product"] } } }], as: "isCompleted" } }, { $addFields: { "isCompleted": { $arrayElemAt: [ "$isCompleted", 0 ] } } }, { $addFields: { // add the required fields to the top level structure "isCompleted": "$isCompleted.isCompleted", "batchNo": "$isCompleted.batchNo" } }]) |
lookup两表关联查询,首个addFields获取isCompleted数组的第一个记录,后一个addFields转换成所需要的几个字段信息
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"collection1.find()").fetch() |
|
3 |
=mongo_shell(A1,"collection2.find()").fetch() |
|
4 |
=A3.conj(A2.select(order:A3.product.order,lot:A3.product.lot).derive(A3.serialNo:sno,A3.batchNo:bno)) |
|
5 |
>A1.close() |
|
脚本说明:
A1:连接mongodb数据库。
A2:获取collection1表中的数据。
A3:获取collection2表中的数据。
A4:根据条件order, lot从序表A2中查询记录,然后追加序表A3中的字段serialNo, batchNo,返回合并后的序表。
A5:关闭数据库连接。
实现从数据记录中的内嵌结构中筛选,将符合条件的数据合并成新序表。
8. 多字段分组统计
统计分类项下的总数及各子项数。下面统计按addr分类book数及其下不同的book数。
addr |
book |
address1 |
book1 |
address2 |
book1 |
address1 |
book5 |
address3 |
book9 |
address2 |
book5 |
address2 |
book1 |
address1 |
book1 |
address15 |
book1 |
address4 |
book3 |
address5 |
book1 |
address7 |
book11 |
address1 |
book1 |
期望结果:
_id |
Total |
books |
Count |
address1 |
4 |
book1 |
3 |
|
|
book5 |
1 |
address15 |
1 |
book1 |
1 |
address2 |
3 |
book1 |
2 |
|
|
book5 |
1 |
address3 |
1 |
book9 |
1 |
address4 |
1 |
book3 |
1 |
address5 |
1 |
book1 |
1 |
address7 |
1 |
book11 |
1 |
Mongo脚本
db.books.aggregate([ { "$group": { "_id": { "addr": "$addr", "book": "$book" }, "bookCount": { "$sum": 1 } }}, { "$group": { "_id": "$_id.addr", "books": { "$push": { "book": "$_id.book", "count": "$bookCount" }, }, "count": { "$sum": "$bookCount" } }}, { "$sort": { "count": -1 } }, { "$project": { "books": { "$slice": [ "$books", 2 ] }, "count": 1 }} ]).pretty() |
先按addr,book分组统计book数,再按addr分组统计book数,调整显示顺序
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"books.find()") |
|
3 |
=A2.groups(addr,book;count(book): Count) |
|
4 |
=A3.groups(addr;sum(Count):Total) |
|
5 |
=A3.join(addr,A4:addr,Total) |
|
6 |
>A1.close() |
|
计算结果:
Address |
book |
Count |
Total |
address1 |
book1 |
3 |
4 |
address1 |
book5 |
1 |
4 |
address15 |
book1 |
1 |
1 |
address2 |
book1 |
2 |
3 |
address2 |
book5 |
1 |
3 |
address3 |
book9 |
1 |
1 |
address4 |
book3 |
1 |
1 |
address5 |
book1 |
1 |
1 |
address7 |
book11 |
1 |
1 |
脚本说明:
A1:连接mongodb数据库。
A2:获取books表中的数据。
A3:按addr,book分组统计book数,
A4:再按addr分组统计book数。
A5:将A4中的Total按addr关联后合并到序表中.
A6:关闭数据库连接。
9. 两表关联查询
从关联表中选择所需要的字段组合成新表。
Collection1:
user1 |
user2 |
income |
1 |
2 |
0.56 |
1 |
3 |
0.26 |
collection2:
user1 |
user2 |
output |
1 |
2 |
0.3 |
1 |
3 |
0.4 |
2 |
3 |
0.5 |
期望结果:
user1 |
user2 |
income |
output |
1 |
2 |
0.56 |
0.3 |
1 |
3 |
0.26 |
0.4 |
Mongo脚本
db.c1.aggregate([ { "$lookup": { "from": "c2", "localField": "user1", "foreignField": "user1", "as": "collection2_doc" }}, { "$unwind": "$collection2_doc" }, { "$redact": { "$cond": [ { "$eq": [ "$user2", "$collection2_doc.user2" ] }, "$$KEEP", "$$PRUNE" ] }}, { "$project": { "user1": 1, "user2": 1, " income ": "$income", " output ": "$collection2_doc. output " }} ]).pretty() |
lookup两表进行关联查询,redact对记录根据条件进行遍历处理,project选择要显示的字段。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"c1.find()").fetch() |
|
3 |
=mongo_shell(A1,"c2.find()").fetch() |
|
4 |
=A2.join(user1:user2,A3:user1:user2,output) |
|
5 |
>A1.close() |
|
脚本说明:
A1:连接mongodb数据库。
A2:获取c1表中的数据。
A3:获取c2表中的数据。
A4:两表按字段user1,user2关联,追加序表A3中的output字段,返回序表。
A5:关闭数据库连接。
通过join把两个关联表不同的字段合并成新表。
10. 多表关联查询
多于两个表的关联查询,结合成一张大表。
Doc1:
_id |
firstName |
lastName |
U001 |
shubham |
verma |
Doc2:
_id |
userId |
address |
mob |
2 |
U001 |
Gurgaon |
9876543200 |
Doc3:
_id |
userId |
fbURLs |
twitterURLs |
3 |
U001 |
http://www.facebook.com |
http://www.twitter.com |
合并后的结果:
{
"_id" : ObjectId("5901a4c63541b7d5d3293766"),
"firstName" : "shubham",
"lastName" : "verma",
"address" : {
"address" : "Gurgaon"
},
"social" : {
"fbURLs" : "http://www.facebook.com",
"twitterURLs" : "http://www.twitter.com"
}
}
Mongo脚本
db.doc1.aggregate([ { $match: { _id: ObjectId("5901a4c63541b7d5d3293766") } }, { $lookup: { from: "doc2", localField: "_id", foreignField: "userId", as: "address" } }, { $unwind: "$address" }, { $project: { "address._id": 0, "address.userId": 0, "address.mob": 0 } }, { $lookup: { from: "doc3", localField: "_id", foreignField: "userId", as: "social" } }, { $unwind: "$social" },
{ $project: { "social._id": 0, "social.userId": 0 } } ]).pretty(); |
由于Mongodb数据结构原因,写法也多样化,展示也各不相同。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"doc1.find()").fetch() |
|
3 |
=mongo_shell(A1,"doc2.find()").fetch() |
|
4 |
=mongo_shell(A1,"doc3.find()").fetch() |
|
5 |
=A2.join(_id,A3:userId,address,mob) |
|
6 |
=A5.join(_id,A4:userId,fbURLs,twitterURLs) |
|
7 |
>A1.close() |
|
此脚本与上面例子类似,只是多了一个关联表,每次join就新增加字段,最后叠加构成一张大表。.
SPL脚本的简洁性、统一性就非常明显。
从指定的数组中查找符合条件的记录。所给的数组为:["Chemical", "Biology", "Math"]。
测试数据:
_id |
Name |
Lesson |
1 |
jacker |
[English, Chemical,Math, Physics] |
2 |
tom |
[Chinese, Chemical,Math, Biology] |
3 |
Mint |
[Chinese, History] |
期望结果:
_id |
Name |
Lesson |
1 |
Jacker |
[Chemical,Math] |
2 |
Tom |
[Chemical,Math,Biology] |
Mongodb脚本
var field = ["Chemical", "Biology", "Math"] db.student.aggregate([ { "$project": { "name":1, "lessons": { "$filter": { "input": "$lesson", "cond": { "$in": [ "$$this", field ] } } }, }}, { "$project": {"name":1,"lessons":1,"sizeOflesson": {"$size": "$lessons"} }}, { $match: { "sizeOflesson":{ $gt: 0}}} ]) |
查询选修课包含["Chemical", "Biology", "Math"]的同学。
SPL脚本:
|
A |
B |
1 |
[Chemical, Biology, Math] |
|
2 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
3 |
=mongo_shell(A2,"student.find()").fetch() |
|
4 |
=A3.select(lesson^A1!=[]) |
|
5 |
=A4.new(name, ~.lesson^A1) |
|
6 |
>A2.close() |
|
脚本说明:
A1:定义查询条件科目数组。
A2:连接mongodb数据库。
A3:获取student表中的数据。
A4:查询存在数组中的科目记录。
A5:生成字段为name, lesson的新序表,其中符合条件的值存放在字段lesson中
A6:关闭数据库连接。
集算器对给定数组中查询记录的实现更简明易懂。
11. 关联表中的数组查找
从关联表记录数据组中查找符合条件的记录, 用给定的字段组合成新表。
测试数据:
users:
_id |
Name |
workouts |
1000 |
xxx |
[2,4,6] |
1002 |
yyy |
[1,3,5] |
workouts:
_id |
Date |
Book |
1 |
1/1/2001 |
Othello |
2 |
2/2/2001 |
A Midsummer Night's Dream |
3 |
3/3/2001 |
The Old Man and the Sea |
4 |
4/4/2001 |
GULLIVER’S TRAVELS |
5 |
5/5/2001 |
Pickwick Papers |
6 |
6/6/2001 |
The Red and the Black |
期望结果:
Name |
_id |
Date |
Book |
xxx |
2 |
2/2/2001 |
A Midsummer Night's Dream |
xxx |
4 |
4/4/2001 |
GULLIVER’S TRAVELS |
xxx |
6 |
6/6/2001 |
The Red and the Black |
yyy |
1 |
1/1/2001 |
Othello |
yyy |
3 |
3/3/2001 |
The Old Man and the Sea |
yyy |
5 |
5/5/2001 |
Pickwick Papers |
Mongo脚本
db.users.aggregate([ { "$lookup": { "from" : "workouts", "localField" : "workouts", "foreignField" : "_id", "as" : "workoutDocumentsArray" }}, { $project: { _id:0,workouts:0} } , { "$unwind": "$workoutDocumentsArray" },
{ "$replaceRoot": { "newRoot": { $mergeObjects: [ "$$ROOT", "$workoutDocumentsArray" ] } } }, { $project: { workoutDocumentsArray: 0} } ]).pretty() |
把关联表users,workouts查询结果放到数组中,再将数组拆解,提升子记录的位置,去掉不需要的字段。
SPL脚本:
|
A |
B |
1 |
=mongo_open("mongodb://127.0.0.1:27017/raqdb") |
|
2 |
=mongo_shell(A1,"users.find()").fetch() |
|
3 |
=mongo_shell(A1,"workouts.find()").fetch() |
|
4 |
=A2.conj(A3.select(A2.workouts^~.array(_id)!=[]).derive(A2.name)) |
|
5 |
>A1.close() |
|
脚本说明:
A1:连接mongodb数据库。
A2:获取users表中的数据。
A3:获取workouts表中的数据。
A4:查询序表A3的_id值存在于序表A2中workouts数组的记录, 并追加name字段。
返回合并的序表。
A5:关闭数据库连接。
由于需要获取序列的交集不为空为条件,故将_id转换成序列。
Mongo存储的数据结构相对关联数据库更复杂、更灵活,其提供的查询语言也非常强、能适应不同的情况,需要了解函数也不少,函数之间的结合更是变化无穷,因此要掌握并熟悉应用它并非易事。集算器的离散性、易用性恰好能弥补Mongo这方面的不足,它降低了mongo学习成本及使用mongo操作的复杂度、难度,让mongo的功能得到更充分的展现,同时也希望mongo越来越受到广大爱好者的青睐。
参考文献:
《MongoDB分组统计》
《简化MongonDB的关联运算》
《玩转MongoDB运算》