【发布时间】:2017-05-24 16:07:03
【问题描述】:
我正在对 mongo 集合执行聚合查询。在管道的特定阶段,如果某个表达式导致 false,我想从结果中删除一个子文档。
这是我希望删除子文档时的查询。
db.getCollection('[module].[virtualwarehouses].supplies').aggregate([
{
$match: {
$or: [
{ artNr: "ART01ds" },
{ GTIN: "GTIN0001" }
]
}
},
{
$lookup: {
from: '[module].[virtualwarehouses].warehouses',
localField: 'wId',
foreignField: '_id',
as: 'warehouse'
}
},
{
$unwind: '$warehouse'
},
{
$lookup: {
from: '[module].[virtualwarehouses].warehouses',
localField: 'warehouse._id',
foreignField: 'cIds',
as: 'warehouseP'
}
},
{
$unwind: {
path: '$warehouseP',
preserveNullAndEmptyArrays: true
}
},
{
$match: {
$and: [
{'warehouse.isDel' : false},
{$or: [
{ 'warehouseP.isDel' : false },
{ 'warehouseP' : { $exists: false } }
]},
{$or: [
{ 'warehouse.subs' : { $elemMatch: { sKey: "localhost" } } },
{ 'warehouseP.subs' : { $elemMatch: { sKey: "localhost" } } }
]}
]
}
}
])
------------ RESULT ---------------
{
"_id" : ObjectId("5922eae4f576274033147127"),
"GTIN" : "GTIN0001",
"status" : 0,
"stock" : 2,
"wId" : ObjectId("5922e378c4352e2b3ccc7b65"),
"warehouse" : {
"_id" : ObjectId("5922e378c4352e2b3ccc7b65"),
"name" : "Warehouse 2",
"pId" : "Test Company",
"type" : 0,
"source" : 0,
"cIds" : [],
"isDel" : false,
"isEnabled" : true,
"srcSettings" : {
"dataSource" : 0,
"ftpUrl" : "ftps.test.com",
"ftpDir" : "\\\\serv-s1\\importer",
"ftpFile" : "test.csv",
"dropImport" : true
},
"subs" : [
{
"sKey" : "localhost",
"order" : 500000
}
]
},
"warehouseP" : {
"_id" : ObjectId("5922e441de7c2c0eaca93e9b"),
"name" : "Warehouse Combo",
"pId" : "Test Company",
"type" : 1,
"source" : 0,
"cIds" : [
ObjectId("5922e263c4352e2b3ccc7b64"),
ObjectId("5922e378c4352e2b3ccc7b65"),
ObjectId("5923f49ef5762740331fadd5")
],
"isDel" : false,
"isEnabled" : true,
"srcSettings" : null,
"subs" : [
{
"sKey" : "fakeSubscriber",
"order" : 500000
}
]
}
}
在此查询中,我在集合中查找与文章/GTIN 编号匹配的用品。我做了 2 次查找以获取此可用文章所属的仓库。 (仓库显然可以包含很多物资,因此我们选择单独收集物资,否则会超出文件限制)
我之所以进行 2 次查找是因为在我们的数据模型中,一个仓库可以是多个仓库的组合,所以我需要检查包含这篇文章的仓库是否属于某个仓库组。
现在这就是我的问题出现的地方。人们可以订阅仓库,并且应该只从这些订阅的商店中检索库存信息。订阅存储在 subs 字段中。 (查看上面代码字段中的查询结果)
在上面的查询中,如果订阅字段不包含某个订阅者,我希望删除子文档仓库P(父仓库)。在这种情况下,localhost。
到目前为止我尝试过的是:
---- attempt 1, does nothing, always true
{
$project: {
warehouseP: {
$cond: {
if: {'warehouseP' : { "subs": { sKey: "localhost" } } },
then: "$warehouseP",
else: null
}
}
}
}
---- attempt 2, results in
---- errmsg: FieldPath field names may not contain '.'."
{
$project: {
warehouseP: {
$cond: {
if: { 'warehouseP.subs' : { $elemMatch: { sKey: "localhost" } } },
then: "$warehouseP",
else: null
}
}
}
}
---- attempt 3, results in:
---- errmsg: Unrecognized expression '$elemMatch'
{
$project: {
warehouseP: {
$cond: {
if: { 'warehouseP' : { 'subs' : { $elemMatch: { sKey: "localhost" } } } },
then: "$warehouseP",
else: null
}
}
}
}
所以,总结一下。在上面查询的输出中,我希望删除字段warehouseP(将其设置为null),因为它的subs 字段不包含localhost。 (如果包含供应的仓库没有父级,则此字段可能已经为空)我已经尝试了上述方法,但没有任何工作。
编辑,澄清情况。
我有 2 个系列,一个带仓库,一个带耗材。 Data examples 2 个集合。
supplies 集合包含一些简单的对象,其中包含有关文章的一些信息。它还包含对仓库的 ObjectId 引用。
仓库集合包含具有关联数据的仓库。这里的关键是仓库可以是“虚拟的”,这意味着它们只是一组其他仓库。如果是这种情况,他们在字段 cIds(子 ID)中有一个 ObjectId 数组。否则,仓库是一个真正的仓库,它可以在其他集合中有关联的库存。第二个重要元素是 subs 字段。在此字段中,我存储有关谁订阅了此仓库的数据。这里的想法是人们可以订阅某些仓库(谁能订阅和不能订阅哪个仓库的逻辑在这里不相关)。应该只能检索他们订阅的仓库中的物品供应信息。
为了方便起见,我希望有一个查询,如果我提供商品编号/GTIN 和订户密钥,则返回供应信息。如果适用,信息应按“虚拟”仓库分组。我的意思是,如果有人订阅了“虚拟”仓库,他应该会收到如下数据:
---- Warehouses that do not have a parent, all end up in this array
{
"_id" : null,
"artNr" : "ART01",
"GTIN" : null,
"n" : null,
"p" : null,
"source" : null,
"cIds" : null,
"warehouses" : [
{
"_id" : ObjectId("5922e576f576274033145a3f"),
"n" : "Supplier Warehouse 1",
"p" : "Bosch",
"status" : 0,
"stock" : 5
}
]
}
---- Warehouses that DO have a parent, should be grouped under a document for every 'virtual' (parent) warehouse
{
"_id" : ObjectId("5922e441de7c2c0eaca93e9b"),
"artNr" : "ART01",
"GTIN" : null,
"n" : "Warehouse Combo",
"p" : "D Soft",
"source" : 0,
"cIds" : [
ObjectId("5922e263c4352e2b3ccc7b64"),
ObjectId("5922e378c4352e2b3ccc7b65"),
ObjectId("5923f49ef5762740331fadd5")
],
"warehouses" : [
{
"_id" : ObjectId("5922e263c4352e2b3ccc7b64"),
"n" : "Warehouse 1",
"p" : "D Soft",
"status" : 0,
"stock" : 5
},
{
"_id" : ObjectId("5922e378c4352e2b3ccc7b65"),
"n" : "Warehouse 2",
"p" : "D Soft",
"status" : 0,
"stock" : 5
}
]
}
{
*** potentially many other 'virtual' warehouses ***
}
我在上面发布的查询实现了这一点,但在 1 种情况下出错: 如果有人订阅了属于某个组的仓库,则始终显示该组信息。即使订阅者没有订阅那个“虚拟”仓库。
如果我在哪里使用上面的数据作为示例,如果有人说 localhost 订阅了仓库 1,但没有订阅仓库组合,他仍然会收到这样的数据:
{
"_id" : ObjectId("5922e441de7c2c0eaca93e9b"),
"artNr" : "ART01",
"GTIN" : null,
"n" : "Warehouse Combo",
"p" : "D Soft",
"source" : 0,
"cIds" : [
ObjectId("5922e263c4352e2b3ccc7b64"),
ObjectId("5922e378c4352e2b3ccc7b65"),
ObjectId("5923f49ef5762740331fadd5")
],
"warehouses" : [
{
"_id" : ObjectId("5922e263c4352e2b3ccc7b64"),
"n" : "Warehouse 1",
"p" : "D Soft",
"status" : 0,
"stock" : 5
}
]
}
但是我想在数组中为没有父仓库的数据提供数据,因为该人没有订阅父仓库,并且应该无法接收该数据,如下所示:
{
"_id" : null,
"artNr" : "ART01",
"GTIN" : null,
"n" : null,
"p" : null,
"source" : null,
"cIds" : null,
"warehouses" : [
{
"_id" : ObjectId("5922e263c4352e2b3ccc7b64"),
"n" : "Warehouse 1",
"p" : "D Soft",
"status" : 0,
"stock" : 5
}
]
}
我现在拥有的完整查询产生了上面的示例(当然最后一个除外)是这样的(与问题顶部的查询相同,带有额外的 $group):
db.getCollection('[module].[virtualwarehouses].supplies').aggregate([
{
$match: {
$or: [
{ artNr: "ART01" },
{ GTIN: "GTIN001" }
]
}
},
{
$lookup: {
from: '[module].[virtualwarehouses].warehouses',
localField: 'wId',
foreignField: '_id',
as: 'warehouse'
}
},
{
$unwind: '$warehouse'
},
{
$lookup: {
from: '[module].[virtualwarehouses].warehouses',
localField: 'warehouse._id',
foreignField: 'cIds',
as: 'warehouseP'
}
},
{
$unwind: {
path: '$warehouseP',
preserveNullAndEmptyArrays: true
}
},
{
$match: {
$and: [
{'warehouse.isDel' : false},
{$or: [
{ 'warehouseP.isDel' : false },
{ 'warehouseP' : { $exists: false } }
]},
{$or: [
{ 'warehouse.subs' : { $elemMatch: { sKey: "D Soft" } } },
{ 'warehouseP.subs' : { $elemMatch: { sKey: "D Soft" } } }
]}
]
}
},
{
$group: {
_id: '$warehouseP._id',
artNr: { $first: '$artNr' },
GTIN: { $first : '$GTIN' },
n: { $first: '$warehouseP.name' },
p: { $first: '$warehouseP.pId' },
source: { $first: '$warehouseP.source' },
cIds: { $first: '$warehouseP.cIds' },
warehouses: {
$addToSet: {
_id: '$warehouse._id',
n : '$warehouse.name',
p : '$warehouse.pId',
status: '$status',
stock: '$stock',
etaStock: '$etaStock'
}
}
}
}
])
也许这个查询的整个方法是错误的,我可以更轻松地完成吗?我对 mongoDB 没有太多经验。提前致谢。
【问题讨论】:
-
如果不提供“小”样本数据集并至少显示您对预期结果的近似值,实际上很难说在这里做什么是“正确”的事情。这将大大有助于真正将问题摆在其他人面前,让他们可以推测出解决方案。因此,它有助于让其他人清楚地了解自己。没有数据,我们不能真正说您正在做的任何过程是最好的事情。
-
另请注意您自己的声明“否则将超出文档限制”,这也适用于
$lookup,因为它是 BSON 规范的基本限制。如果您不可能引用文档中的所有数据并因此将其放入其他集合中,那么$lookup所做的就是“尝试将所有这些数据填充回文档中”。因此,大概会有几种措施来实际“过滤”返回的结果,以免在任何时候打破这个限制。这是一个问题需要清晰的例子。 -
@NeilLunn 感谢您对这个问题感兴趣。我一直计划在 codereview 上问一个问题,问我所做的是否是最好的方法。我计划在那里提供对我的数据模型的更详细描述。但是,由于其中的要求是代码功能齐全,并且仍然存在我的查询没有给出我想要的结果的情况,我不想冒险在那里发布并被否决。感谢您的理解。
-
@NeilLunn 对于
$lookup:一个仓库可以容纳相当大量的物资。但在此查询中,我将耗材输出限制为特定商品编号/GTIN 的输出。所以,在我看来,我的输出会低得多(如果同时传入现有的 artNr 和 GTIN,每个仓库只有 2 个可能的供应品)我认为$lookup不会导致超出文件限制。还是我弄错了? -
如果您添加尽可能多的解释并按照我已经在您的问题中提到的内容,那将是最好的。然后,完成后,您可以发表评论以通知已添加信息。就像我说的那样,有样本很好,以便可视化它是如何组合在一起的。
标签: mongodb aggregation-framework