嵌套密钥中MongoDB的聚合不返回任何内容

时间:2016-09-19 19:53:33

标签: mongodb aggregation-framework

编辑:编辑此问题是为了更好地反映问题,这可能比提议的相关问题稍微复杂一点。)

假设我有这两个集合

产品

{
  _id: 'AAAA',
  components: [
    { type: 'foo', items: [
      { itemId: 'item1', qty: 2 },
      { itemId: 'item2', qty: 1 }
    ] },
    { type: 'bar', items: [
      { itemId: 'item3', qty: 8 }
    ] }
  ]
}

{
  _id: 'item1',
  name: 'Foo Item'
}
{
  _id: 'item2',
  name: 'Bar Item'
}
{
  _id: 'item3',
  name: 'Buz Item'
}

我执行此查询

db['products'].aggregate([
  { $lookup: { 
    from: 'items',
    localField: 'components.items.itemId',
    foreignField: '_id',
    as: 'componentItems'
  } }
]);

我明白了

{
  _id: 'AAAA',
  components: [
    { type: 'foo', items: [
      { itemId: 'item1', qty: 2 },
      { itemId: 'item2', qty: 1 }
    ] }
    { type: 'bar', items: [
      { itemId: 'item3', qty: 8 }
    ] }
  ],
  componentItems: [ ]
}

为什么聚合不读取本地字段值?如何在不丢失原始文档结构的情况下检索外部文档?

修改

我已阅读jira issue并看到了建议的答案,但我不知道这是如何适用的。这仅仅是一个数组,而是一个对象的值, inside 一个数组。我不知道如何解除这个问题,以及如何将其重新组合在一起而不会丢失文档结构。

编辑2

我遇到的问题是我不确定如何将结果组合在一起。有了这个查询:

db['products'].aggregate([
  { $unwind: '$components' },
  { $unwind: '$components.items' },
  { $lookup: {
    from: 'items',
    localField: 'components.items.itemId',
    foreignField: '_id',
    as: 'componentsItems'
  } }
]);

我得到

的“正确”结果
{ "_id" : "AAAA", "components" : { "type" : "foo", "items" : { "itemId" : "item1", "qty" : 2 } }, "componentsItems" : [ { "_id" : "item1", "name" : "Foo Item" } ] }
{ "_id" : "AAAA", "components" : { "type" : "foo", "items" : { "itemId" : "item2", "qty" : 1 } }, "componentsItems" : [ { "_id" : "item2", "name" : "Bar Item" } ] }
{ "_id" : "AAAA", "components" : { "type" : "bar", "items" : { "itemId" : "item3", "qty" : 8 } }, "componentsItems" : [ { "_id" : "item3", "name" : "Buz Item" } ] }

但是,虽然我可以放松components.items,但我似乎无法解决这个问题,因为$group抱怨

  

“无法使用组聚合字段名称'components.items',因为$ group的字段名称不能包含'。'”

db['products'].aggregate([
  { $unwind: '$components' },
  { $unwind: '$components.items' },
  { $lookup: {
    from: 'items',
    localField: 'components.items.itemId',
    foreignField: '_id',
    as: 'componentsItems'
  } },
  { "$group": {
    "components.type": "$components.type",
    "components.items": { $push: "$components.items" },
    "componentsItems": { $push: "$componentsItems" }
  } },
  { "$group": {
    "_id": "$_id",
    "components": { $push: "$components" },
    "componentsItems": { $push: "$componentsItems" }
  } }
]);

编辑3

到目前为止,此查询是我找到的最接近的查询,但components未按type分组。

db['products'].aggregate([
  { $unwind: '$components' },
  { $unwind: '$components.items' },
  { $lookup: {
    from: 'items',
    localField: 'components.items.itemId',
    foreignField: '_id',
    as: 'componentsItems'
  } },
  { $unwind: '$componentsItems' },
  { $group: {
    "_id": "$_id",
    "components": {
      $push: {
        "type": "$components.type",
        "items": "$components.items"
      }
    },
    "componentsItems": { $addToSet: "$componentsItems" }
  } }
]);

:我担心使用$unwind$group可能会影响应保留的components的顺序。 AFAIK,MongoDB在存储文档时保留数组顺序。我讨厌$lookup的尴尬打破这个功能。

1 个答案:

答案 0 :(得分:0)

这是我漫长而尴尬的解决方案:

db['products'].aggregate([
  // unwind all... because $lookup cannot work with multi-values
  { $unwind: '$components' },
  { $unwind: '$components.items' },

  // lookup... This is a 1:1 relationship but who cares, right?
  { $lookup: {
    from: 'items',
    localField: 'components.items.itemId',
    foreignField: '_id',
    as: 'componentsItems'
  } },

  // our 1:1 relationship is now an array, so this is required
  // before grouping, so we don't end up with array of arrays
  { $unwind: '$componentsItems' },

  // Group 1: put "components.items" in a temporary array
  //          and filter duplicates from "componentsItems"
  { $group: {
    "_id": {
      "i": "$_id",
      "t": "$components.type"
    },
    "items": {
      $push: "$components.items"
    },
    "componentsItems": { $addToSet: "$componentsItems" }
  } },

  // undo $push...
  { $unwind: "$componentsItems" },

  // Group 2: put everything back together
  { $group: {
    "_id": "$_id.i",
    "items": {
      $push: {
        "type": "$_id.t",
        "items": "$items"
      }
    },
    "componentsItems": { $push: "$componentsItems" }
  } }
]);

修改

更好的解决方案:

db['products'].aggregate([
  // Return document, added a collection of "itemId"
  { $project: {
    "_id": 1,
    "components": 1,
    "componentItemId": "$components.items.itemId"
  } },
  // Since there was two arrays, the field is an array of arrays...
  { $unwind: "$componentItemId" },
  { $unwind: "$componentItemId" },

  // make 1:1 lookup...
  { $lookup: {
    from: 'items',
    localField: 'componentItemId',
    foreignField: '_id',
    as: 'componentsItems'
  } },
  // ... extract the 1:1 reference...
  { $unwind: "$componentsItems" },

  // group back, ignoring the "componentItemId" field
  { $group: {
    "_id": "$_id",
    "components": { $first: "$components" },
    "componentItems": { $addToSet: "$componentsItems" }
  }}
]);

我不确定还有是否是更好的解决方案,我 关注性能,但这似乎是我能想到的唯一解决方案的。

缺点是文档不能是动态的,只要架构发生变化,就需要修改此查询。

更新

这似乎是MongoDB 3.3.4中的to be resolved(在撰写本答案时尚未发布)。