MongoDB Aggregation - конвейер поиска, не возвращающий никаких документов - PullRequest
0 голосов
/ 18 января 2020

Мне трудно заставить $lookup с pipeline работать в MongoDB Compass.

У меня есть следующие коллекции:

Игрушки

First Image - Toys

Данные

[
  {
    "_id": {
      "$oid": "5d233c3bb173a546386c59bb"
    },
    "type": "multiple",
    "tags": [
      ""
    ],
    "searchFields": [
      "Jungle Stampers - Two",
      ""
    ],
    "items": [
      {
        "$oid": "5d233c3cb173a546386c59bd"
      },
      {
        "$oid": "5d233c3cb173a546386c59be"
      },
      {
        "$oid": "5d233c3cb173a546386c59bf"
      },
      {
        "$oid": "5d233c3cb173a546386c59c0"
      },
      {
        "$oid": "5d233c3cb173a546386c59c1"
      },
      {
        "$oid": "5d233c3cb173a546386c59c2"
      },
      {
        "$oid": "5d233c3cb173a546386c59c3"
      },
      {
        "$oid": "5d233c3cb173a546386c59c4"
      }
    ],
    "name": "Jungle Stampers - Two",
    "description": "",
    "status": "active",
    "category": {
      "$oid": "5cfe727cac920000086b880e"
    },
    "subCategory": "Stamp Sets",
    "make": "",
    "defaultCharge": null,
    "defaultOverdue": null,
    "sizeCategory": {
      "$oid": "5d0cfde57561e107c88fbde3"
    },
    "ageFrom": {
      "$numberInt": "24"
    },
    "ageTo": {
      "$numberInt": "120"
    },
    "images": [
      {
        "_id": {
          "$oid": "5d233c3bb173a546386c59bc"
        },
        "id": {
          "$oid": "5d233c39b173a546386c59ba"
        },
        "url": "/toyimages/5d233c39b173a546386c59ba.jpg",
        "thumbUrl": "/toyimages/thumbs/tn_5d233c39b173a546386c59ba.jpg"
      }
    ],
    "__v": {
      "$numberInt": "2"
    }
  }
]

Кредиты

Second Image - Loans

Данные

[
  {
    "_id": {
      "$oid": "5e1f1661b712215978c746d9"
    },
    "tags": [],
    "member": {
      "$oid": "5e17495e4f81ab3f900dbb63"
    },
    "source": "admin portal - potter1@gmail.com",
    "items": [
      {
        "id": {
          "$oid": "5e1f160eb712215978c746d5"
        },
        "status": "new",
        "_id": {
          "$oid": "5e1f1661b712215978c746db"
        },
        "toy": {
          "$oid": "5d233c3bb173a546386c59bb"
        },
        "cost": {
          "$numberInt": "0"
        }
      },
      {
        "id": {
          "$oid": "5e1f160eb712215978c746d5"
        },
        "status": "new",
        "_id": {
          "$oid": "5e1f1661b712215978c746da"
        },
        "toy": {
          "$oid": "5d233b1ab173a546386c59b5"
        },
        "cost": {
          "$numberInt": "0"
        }
      }
    ],
    "dateEntered": {
      "$date": {
        "$numberLong": "1579095632870"
      }
    },
    "dateDue": {
      "$date": {
        "$numberLong": "1579651200000"
      }
    },
    "__v": {
      "$numberInt": "0"
    }
  }
]

Я пытаюсь вернуть список игрушек и связанных с ними кредитов, которые имеют статус «новый» или «вне».

Я могу использовать следующую совокупность $lookup для извлечения всех ссуд:

{
  from: 'loans',
  localField: '_id',
  foreignField: 'items.toy',
  as: 'loansSimple'
}

Однако я пытаюсь использовать конвейер для загрузки ссуд с двумя интересующими меня состояниями, но он всегда возвращает только ноль документы:

{
  from: 'loans',
  let: {
    'toyid': '$_id'
  },
  pipeline: [
    {
      $match: {
        $expr: {
          $and: [
            {$eq: ['$items.toy', '$$toyid']},
            {$eq: ['$items.status', 'new']} // changed from $in to $eq for simplicity
          ]
        }
      }
    }  
  ],
  as: 'loans'
}

Кажется, всегда возвращается 0 документов, однако я организую это:

Image Three - Results

Я допустил ошибку где-то?

Я использую MongoDB Atlas, v4.2.2, MongoDB Compass v 1.20.4

Ответы [ 2 ]

1 голос
/ 18 января 2020

$ expr получает выражения агрегации , в этот момент $$items.toy анализируется для каждого элемента в массиве, как и следовало ожидать (однако, если это произойдет, он все равно даст вам " плохие результаты, так как вы получите кредиты с нужным идентификатором игрушки и любым другим предметом со статусом new в массиве предметов).

Таким образом, у вас есть два варианта решения этой проблемы:

  1. Если вас не интересуют другие элементы в документе поиска, вы можете добавить этап $unwind в начале конвейера поиска следующим образом:
{
    from: 'loans',
    let: {
        'toyid': '$_id'
    },
    pipeline: [
        {
            $unwind: "$items"
        },
        {
            $match: {
                $expr: {
                    $and: [
                        {$eq: ['$items.toy', '$$toyid']},
                        {$eq: ['$items.status', 'new']} // changed from $in to $eq for simplicity
                    ]
                }
            }
        }
    ],
    as: 'loans'
}
Если вы заботитесь о них, просто выполните итерацию массива одним из возможных способов получения «правильного» соответствия, вот пример с использованием $ filter
    {
        from: 'loads',
        let: {
            'toyid': '$_id'
        },
        pipeline: [
            {
                $addFields: {
                    temp: {
                        $filter: {
                            input: "$items",
                            as: "item",
                            cond: {
                                $and: [
                                    {$eq: ["$$item.toy", "$$toyid"]},
                                    {$eq: ["$$item.status", "new"]}
                                ]
                            }
                        }


                    }
                }

            }, {$match: {"temp.0": {exists: true}}}
        ],
        as: 'loans'
    }
1 голос
/ 18 января 2020

Вы пытаетесь найти $$toyid во внутреннем массиве, но выражение оператора $eq не может разрешить его.

Лучшее решение: $let (возвращает отфильтрованный loans по критериям) + $filter (применяет фильтр к внутреннему массиву), оператор помогает нам получить желаемый результат.

db.toys.aggregate([
  {
    $lookup: {
      from: "loans",
      let: {
        "toyid": "$_id",
        "toystatus": "new"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $gt: [
                {
                  $size: {
                    $let: {
                      vars: {
                        item: {
                          $filter: {
                            input: "$items",
                            as: "tmp",
                            cond: {
                              $and: [
                                {
                                  $eq: [
                                    "$$tmp.toy",
                                    "$$toyid"
                                  ]
                                },
                                {
                                  $eq: [
                                    "$$tmp.status",
                                    "$$toystatus"
                                  ]
                                }
                              ]
                            }
                          }
                        }
                      },
                      in: "$$item"
                    }
                  }
                },
                0
              ]
            }
          }
        }
      ],
      as: "loans"
    }
  }
])

MongoPlayground

Альтернативное решение 1. Используйте $unwind, чтобы сгладить атрибут items. (Мы создаем дополнительное поле с именем tmp, в котором хранится значение items, выравниваем его с помощью оператора $unwind, сопоставляем, как вы делали, а затем исключаем из результата)

db.toys.aggregate([
  {
    $lookup: {
      from: "loans",
      let: {
        "toyid": "$_id"
      },
      pipeline: [
        {
          $addFields: {
            tmp: "$items"
          }
        },
        {
          $unwind: "$tmp"
        },
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$tmp.toy",
                    "$$toyid"
                  ]
                },
                {
                  $eq: [
                    "$tmp.status",
                    "new"
                  ]
                }
              ]
            }
          }
        },
        {
          $project: {
            tmp: 0
          }
        }
      ],
      as: "loans"
    }
  }
])

MongoPlayground

Альтернативное решение 2 . Мы используем $reduce для создания массива игрушки и с помощью оператора $in проверяем, существует ли toyid внутри этого массива.

db.toys.aggregate([
  {
    $lookup: {
      from: "loans",
      let: {
        "toyid": "$_id"
      },
      pipeline: [
        {
          $addFields: {
            toys: {
              $reduce: {
                input: "$items",
                initialValue: [],
                in: {
                  $concatArrays: [
                    "$$value",
                    [
                      "$$this.toy"
                    ]
                  ]
                }
              }
            }
          }
        },
        {
          $match: {
            $expr: {
              $in: [
                "$$toyid",
                "$toys"
              ]
            }
          }
        },
        {
          $project: {
            toys: 0
          }
        }
      ],
      as: "loans"
    }
  }
])
...