尝试使用MongoDB 3.2中引入的$lookup阶段
虽然有点麻烦,但由于工作需要,有很多人希望使用,我们决定亲自动手试试。
我们使用的是截至2015/11/13时最新的版本3.2.0-rc2。
我们在专门为此建立的Vagrant上的CentOS 7.0上运行,所以二进制文件的位置等都比较杂乱。
$ wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.2.0-rc2.tgz
$ tar -zxvf mongodb-linux-x86_64-rhel70-3.2.0-rc2.tgz
$ sudo mkdir -p /opt/mongodb
$ cp -R -n mongodb-linux-x86_64-rhel70-3.2.0-rc2 /opt/mongodb
$ echo PATH=/opt/mongodb/mongodb-linux-x86_64-rhel70-3.2.0-rc2/bin:$PATH
$ mongod --version
# 以下の様な感じで表示されれば OK!
# db version v3.2.0-rc2
# git version: 8a3acb42742182c5e314636041c2df368232bbc5
# OpenSSL version: OpenSSL 1.0.1e-fips 11 Feb 2013
# allocator: tcmalloc
# modules: none
# build environment:
# distmod: rhel70
# distarch: x86_64
# target_arch: x86_64
好的,让它启动吧。
$ mkdir -p ~/mongodb/data ~/mongodb/log
$ mongod --dbpath ~/mongodb/data --logpath ~/mongodb/log/mongod.log --fork
$ mongo # シェルが始まれば良い.
将样本分析所需的文件投入。这次的文件包括2015年日本中央职业棒球联盟规定的上场次数达标球员的打击成绩数据以及球员的个人资料数据。
数据已经放置在以下位置。
players.json:https://gist.github.com/Mura-Mi/a492ecce742d86c5e5bd
hitters_stats.json:https://gist.github.com/Mura-Mi/40ce800ec6f3f2372129
球员数据.json:https://gist.github.com/Mura-Mi/a492ecce742d86c5e5bd
击球手统计.json:https://gist.github.com/Mura-Mi/40ce800ec6f3f2372129
$ mongoimport --db central --collection players --file players.json # 選手データの投入
# 2015-11-13T16:12:35.839+0000 connected to: localhost
# 2015-11-13T16:12:35.864+0000 imported 24 documents
$
$ mongoimport --db central --collection stats --file hitters_stats.json
$
$ mongo localhost:27017 # MongoDB Shell を起動する.
我可以打一个查询来确认它是否正确安装。
use central
db.teams.find();
// 以下の様な感じで出力されれば OK
// { "_id" : ObjectId("56460d554936431a75165ab2"), "name" : "Yakult Swallows", "abbreviation" : "ヤ" }
// { "_id" : ObjectId("56460d554936431a75165ab3"), "name" : "Yomiuri Giants", "abbreviation" : "巨" }
// ...
db.teams.count()
// => 6
db.batters.find({}, {_id: 0, name: 1, team: 1})
// 以下のようになれば OK.
// { "name" : "川端慎吾", "team" : "ヤ" }
// { "name" : "山田哲人", "team" : "ヤ" }
// { "name" : "筒香嘉智", "team" : "D" }
// ...
db.batters.count()
// => 24
那么,尝试进行$lookup。首先,只需简单地进行查找,而无需进行进一步的汇总处理。
db.hitting_stats.aggregate(
[
{$lookup: {
from: 'players', // どのコレクションを結合するか
localField: 'name', // 集計対象のコレクション (hitting_stats) のどのフィールド?
foreignField: 'name', // 結合対象のコレクション (player) のどのフィールド?
as: 'profile' // 集計対象のコレクションのなんというフィールドをキーとして結合する?
}
}
]
);
这个结果可以举一个例子,例如一个文档会是这样的。
{
"_id" : ObjectId("564764654936431a75165af4"),
"name" : "川端慎吾",
"games" : 143,
"plate" : 632,
...(中略)...,
"profile" : [
{
"_id" : ObjectId("564761794936431a75165ac4"),
"name" : "川端慎吾",
"team" : "Ys",
"number" : 5,
"born_in" : "大阪府"
}
]
}
我明白了,player集合的文档已经嵌入到了profile字段中,但是与profile键相关联的值是一个数组。如果有多个符合条件的文档,那么所有这些文档都将包含在这个数组中。如果我们知道业务上应当是唯一的情况,那么在这之后插入$unwind阶段将数组分解成标量值就可以了吧?但是,如果实际上存储了不唯一的数据,那么看起来可能会变成一个难以察觉的错误…也许可以通过模式验证来进行控制吗?
经常听说有说“经过查找后,最终能够在MongoDB中进行JOIN操作!”实际上,实际操作是将文档与文档进行一对多关联,并将与字段相关联的键进行关联。此外,如果指定的字段不存在,则该字段的值将默认为null,并且如果两个null相匹配,则会进行JOIN操作,所以它似乎不像SQL的JOIN操作那样灵活。
那么,我们来统计每个选手原籍地的全垒打总数,并按照数量从多到少进行排列。
db.hitting_stats.aggregate(
[
{
$lookup: {
from: 'players',
localField: 'name',
foreignField: 'name',
as: 'profile'
}
},
{ $unwind: "$profile" },
{
$group: {
_id: "$profile.born_in",
"HR": {$sum: "$homerun"}
}
},
{
$sort: {
HR: -1
}
}
]
);
然后,就会得到这样的结果。
{ "_id" : [ "兵庫県" ], "HR" : 50 }
{ "_id" : [ "ベネズエラ" ], "HR" : 38 }
{ "_id" : [ "ドミニカ共和国" ], "HR" : 36 }
{ "_id" : [ "岩手県" ], "HR" : 26 }
{ "_id" : [ "和歌山県" ], "HR" : 24 }
{ "_id" : [ "大阪府" ], "HR" : 21 }
{ "_id" : [ "鹿児島県" ], "HR" : 20 }
{ "_id" : [ "千葉県" ], "HR" : 19 }
{ "_id" : [ "神奈川県" ], "HR" : 16 }
{ "_id" : [ "佐賀県" ], "HR" : 15 }
{ "_id" : [ "東京都" ], "HR" : 14 }
{ "_id" : [ "島根県" ], "HR" : 13 }
{ "_id" : [ "広島県" ], "HR" : 11 }
{ "_id" : [ "アメリカ合衆国" ], "HR" : 9 }
{ "_id" : [ "愛知県" ], "HR" : 6 }
{ "_id" : [ "福井県" ], "HR" : 2 }
选手数目不多,所以有点微妙…但是,山田哲人还是在兵库发挥着引领作用。外国人情况是来自中南美洲而不是北美洲。
因此,我试着使用$lookup进行了一番尝试。