在 MongoDB 中搜索多个集合
Posted
技术标签:
【中文标题】在 MongoDB 中搜索多个集合【英文标题】:Search on multiple collections in MongoDB 【发布时间】:2013-12-02 03:23:33 【问题描述】:我知道 MongoDB 的理论和不支持连接的事实,我应该尽可能使用嵌入文档或非规范化,但这里是:
我有多个文档,例如:
用户,嵌入郊区,但也有:名字,姓氏 嵌入国家的郊区 嵌入 School 的孩子属于一个用户,但也有:名字,姓氏例子:
Users:
_id: 1, first_name: 'Bill', last_name: 'Gates', suburb: 1
_id: 2, first_name: 'Steve', last_name: 'Jobs', suburb: 3
Suburb:
_id: 1, name: 'Suburb A', state: 1
_id: 2, name: 'Suburb B', state: 1
_id: 3, name: 'Suburb C', state: 3
State:
_id: 1, name: 'LA'
_id: 3, name: 'NY'
Child:
_id: 1, _user_id: 1, first_name: 'Little Billy', last_name: 'Gates'
_id: 2, _user_id: 2, first_name: 'Little Stevie', last_name: 'Jobs'
我需要实现的搜索是:
用户和孩子的名字、姓氏 来自用户的状态我知道我必须执行多个查询才能完成它,但是如何实现呢?使用 mapReduce 还是聚合?
你能指出一个解决方案吗?
我尝试使用 mapReduce,但这并没有让我从用户那里获得包含 state_id 的文档,所以这就是我在这里提出它的原因。
【问题讨论】:
我不会这么说。聚合框架和 map-reduce 的目标是聚合/汇总单个集合内的数据。类似联接的操作应该由应用程序而不是数据库来处理。作为旁注 - 你不想“尽可能地去规范化”。 您能否编辑问题以包括您尝试解决问题的步骤?我不确定你卡在哪里了。 我并没有真正尝试过。当我看到 mapReduce 没有使用 state_id 填充我的用户文档时,我放弃了继续前进并寻求建议。 【参考方案1】:这个答案已经过时了。从 3.2 版开始,MongoDB 对使用 $lookup 聚合运算符的左外连接的支持有限
MongoDB 不执行跨越多个集合的查询 - 周期。当您需要连接来自多个集合的数据时,您必须在应用程序级别通过执行多个查询来完成。
-
查询集合 A
从结果中获取辅助键并将它们放入数组中
查询集合 B 将该数组作为 $in-operator 的值传递
在应用层以编程方式加入两个查询的结果
必须这样做应该是例外而不是常态。当您经常需要模拟这样的 JOIN 时,要么意味着您在设计数据库架构时仍然考虑过于关系化,要么您的数据根本不适合 MongoDB 的基于文档的存储概念。
【讨论】:
您好 Philipp,我完全理解您的逐步解释,但我想知道是否有任何方法可以直接在 MongoDB 中通过不同的 javascript 函数或相当于存储过程。 @AdrianIstrate 您可以使用 server-sided javascript 在 MongoDB 数据库服务器上完成所有这些操作。但是you shouldn't do that. 我可以使用像 rockmongo 这样的 GUI 组织多个查询吗? 我认为如果数据与 youtube 上 10gen 的视频中的多对多关系,则必须进行 2 步查询:youtube.com/watch?v=PIWVFUtBV1Q 就我而言,我们的数据根本不适合基于文档的存储。但是无模式设计的易用性以及扩展和分发等操作的易用性以及后台索引等实用程序使我们选择了 mongodb。因此,即使我们通过多个查询流血,我也无法将同一个文档写 2 亿次,而不仅仅是写一个 ID 作为辅助键【参考方案2】:如果您采用非规范化的模式设计方法,您会发现 MongoDB 更容易理解。也就是说,您希望按照请求的客户端应用程序理解它们的方式来构造您的文档。本质上,您将文档建模为应用程序处理的domain objects。当您以这种方式对数据建模时,连接变得不那么重要了。考虑一下我如何将您的数据非规范化到一个集合中:
_id: 1,
first_name: 'Bill',
last_name: 'Gates',
suburb: 'Suburb A',
state: 'LA',
child : [ 3 ]
_id: 2,
first_name: 'Steve',
last_name: 'Jobs',
suburb: 'Suburb C',
state 'NY',
child: [ 4 ]
_id: 3,
first_name: 'Little Billy',
last_name: 'Gates',
suburb: 'Suburb A',
state: 'LA',
parent : [ 1 ]
_id: 4,
first_name: 'Little Stevie',
last_name: 'Jobs'
suburb: 'Suburb C',
state 'NY',
parent: [ 2 ]
第一个优点是这个模式更容易查询。此外,地址字段的更新现在与单个 Person 实体一致,因为这些字段嵌入在单个文档中。还注意到父母和孩子之间的双向关系吗?这使得这个集合不仅仅是个人的集合。父子关系意味着这个集合也是一个social graph。以下是一些在思考schema design in MongoDB 时可能对您有所帮助的资源。
【讨论】:
【参考方案3】:所以现在可以在 mongodb 中加入,您可以在此处使用 $lookup
和 $facet
聚合来实现此目的,这可能是在多个集合中查找的最佳方法
db.collection.aggregate([
"$limit": 1 ,
"$facet":
"c1": [
"$lookup":
"from": Users.collection.name,
"pipeline": [
"$match": "first_name": "your_search_data"
],
"as": "collection1"
],
"c2": [
"$lookup":
"from": State.collection.name,
"pipeline": [
"$match": "name": "your_search_data"
],
"as": "collection2"
],
"c3": [
"$lookup":
"from": State.collection.name,
"pipeline": [
"$match": "name": "your_search_data"
],
"as": "collection3"
]
,
"$project":
"data":
"$concatArrays": [ "$c1", "$c2", "$c3" ]
,
"$unwind": "$data" ,
"$replaceRoot": "newRoot": "$data"
])
【讨论】:
【参考方案4】:这是一个 JavaScript 函数,它将返回一个包含所有符合指定条件的记录的数组,搜索当前数据库中的所有集合:
function searchAll(query,fields,sort)
var all = db.getCollectionNames();
var results = [];
for (var i in all)
var coll = all[i];
if (coll == "system.indexes") continue;
db[coll].find(query,fields).sort(sort).forEach(
function (rec) results.push(rec); );
return results;
在 Mongo shell 中,您可以复制/粘贴函数,然后像这样调用它:
> var recs = searchAll( filename: $regex:'.pdf$' , moddate:1,filename:1,_id:0, filename:1 ) > 推荐
【讨论】:
【参考方案5】:基于@brian-moquin 等人,我制作了一组函数,通过简单的关键字搜索具有整个键(字段)的整个集合。
这是我的要点; https://gist.github.com/fkiller/005dc8a07eaa3321110b3e5753dda71b
为了更详细,我首先做了一个收集所有键的函数。
function keys(collectionName)
mr = db.runCommand(
'mapreduce': collectionName,
'map': function ()
for (var key in this) emit(key, null);
,
'reduce': function (key, stuff) return null; ,
'out': 'my_collection' + '_keys'
);
return db[mr.result].distinct('_id');
然后再从keys数组生成$or
查询。
function createOR(fieldNames, keyword)
var query = [];
fieldNames.forEach(function (item)
var temp = ;
temp[item] = $regex: '.*' + keyword + '.*' ;
query.push(temp);
);
if (query.length == 0) return false;
return $or: query ;
下面是一个搜索单个集合的函数。
function findany(collection, keyword)
var query = createOR(keys(collection.getName()));
if (query)
return collection.findOne(query, keyword);
else
return false;
而且,最后是每个集合的搜索功能。
function searchAll(keyword)
var all = db.getCollectionNames();
var results = [];
all.forEach(function (collectionName)
print(collectionName);
if (db[collectionName]) results.push(findany(db[collectionName], keyword));
);
return results;
你可以在Mongo控制台中简单的加载所有函数,然后执行searchAll('any keyword')
【讨论】:
【参考方案6】:您可以使用 MongoDB 驱动程序的 $mergeObjects 来实现此目的 例子 使用以下文件创建收款单:
db.orders.insert([
"_id" : 1, "item" : "abc", "price" : 12, "ordered" : 2 ,
"_id" : 2, "item" : "jkl", "price" : 20, "ordered" : 1
])
使用以下文档创建另一个集合项:
db.items.insert([
"_id" : 1, "item" : "abc", description: "product 1", "instock" : 120 ,
"_id" : 2, "item" : "def", description: "product 2", "instock" : 80 ,
"_id" : 3, "item" : "jkl", description: "product 3", "instock" : 60
])
下面的操作首先使用$lookup阶段通过item字段连接两个集合,然后使用$replaceRoot中的$mergeObjects从item和orders中合并连接的文档:
db.orders.aggregate([
$lookup:
from: "items",
localField: "item", // field in the orders collection
foreignField: "item", // field in the items collection
as: "fromItems"
,
$replaceRoot: newRoot: $mergeObjects: [ $arrayElemAt: [ "$fromItems", 0 ] , "$$ROOT" ]
,
$project: fromItems: 0
])
该操作返回以下文档:
"_id" : 1, "item" : "abc", "description" : "product 1", "instock" : 120, "price" : 12, "ordered" : 2
"_id" : 2, "item" : "jkl", "description" : "product 3", "instock" : 60, "price" : 20, "ordered" : 1
此技术合并对象并返回结果
【讨论】:
【参考方案7】:Minime 解决方案有效,但需要修复: var query = createOR(keys(collection.getName())); 需要在此处添加关键字作为 createOR 调用的第二个参数。
【讨论】:
以上是关于在 MongoDB 中搜索多个集合的主要内容,如果未能解决你的问题,请参考以下文章
MongoDB:用于搜索性能的嵌套值与单独的集合 - 数据库模式设计