Условие совокупного слияния MongoDB

У меня есть коллекция ниже, и я использую агрегацию MongoDB для получения результата (где я объединяю все позиции заказа для заказа) и пытаюсь объединиться с другой коллекцией (order).

[
  {
    "order_line_item_id": 1,
    "order_id": 100,
    "products": [
      {
        "name": "Shoe",
        "hasDefect": "YES"
      },
      {
        "name": "Pant",
        "hasDefect": "NO"
      },
      {
        "name": "Shirt",
        "hasDefect": "NOT_SURE"
      }
    ]
  },
  {
    "order_line_item_id": 2,
    "order_id": 100,
    "products": [
      {
        "name": "Shoe",
        "hasDefect": "YES"
      },
      {
        "name": "Pant",
        "hasDefect": "YES"
      },
      {
        "name": "Shirt",
        "hasDefect": "YES"
      }
    ]
  },
  {
    "order_line_item_id": 3,
    "order_id": 101,
    "products": [
      {
        "name": "Shoe",
        "hasDefect": "YES"
      },
      {
        "name": "Pant",
        "hasDefect": "NO"
      },
      {
        "name": "Shirt",
        "hasDefect": "NOT_SURE"
      }
    ]
  }
]

Я использую приведенную ниже агрегацию для достижения результата

db.collection.aggregate([
  {
    $match: {
      order_id: {
        $eq: 100
      }
    }
  },
  {
    $unwind: "$products"
  },
  {
    $match: {
      "products.name": {
        $eq: "Pant"
      }
    }
  },
  {
    $group: {
      _id: {
        order_id: "$order_id",
        productName: "$products.name"
      },
      hasDefects: {
        $push: "$products.hasDefect"
      },
      products: {
        $push: "$products"
      }
    }
  },
  {
    $set: {
      hasDefect: {
        $switch: {
          branches: [
            {
              case: {
                $allElementsTrue: {
                  $map: {
                    input: "$hasDefects",
                    as: "d",
                    in: {
                      $eq: [
                        "$$d",
                        "YES"
                      ]
                    }
                  }
                }
              },
              then: "YES"
            },
            {
              case: {
                $in: [
                  "NO",
                  "$hasDefects"
                ]
              },
              then: "NO"
            },
            {
              case: {
                $in: [
                  "NOT_SURE",
                  "$hasDefects"
                ]
              },
              then: "NOT_SURE"
            }
          ],
          default: "<DEFAULT>"
        }
      }
    }
  },
  {
    $group: {
      _id: "$_id.order_id",
      order_id: {
        "$first": "$_id.order_id"
      },
      products: {
        $push: {
          name: "$_id.productName",
          hasDefect: "$hasDefect",
          lastModified: {
            $dateToString: {
              date: new Date(),
              timezone: "America/New_York"
            }
          }
        }
      }
    }
  },
  {
    $unset: [
      "_id"
    ]
  },
  { 
    $merge: { 
      into: "order_res", 
      on: [ "order_id" ], 
      whenMatched: "replace", 
      whenNotMatched: "insert" 
    }
  }
])

Результатом может быть новый документ или обновление одного из продуктов в документе order (как показано ниже).

[
  {    
    "order_id": 100,
    "products": [
      {
        "name": "Shoe",
        "hasDefect": "YES"
      },
    ]
  }
]   

Я использую этап агрегирования слиянием для вставки/обновления в другую коллекцию mongoDB. { $merge: { into: "order", on: "order_id", whenMatched: "replace", whenNotMatched: "insert" }}

Проблема в том, что это операция обновления, и в заказе есть предыдущие продукты, которые заменяют все существующие продукты на новые. Например, если в заказе есть указанный ниже документ, он должен обновить только товар «Обувь».

[
  {    
    "order_id": 100,
    "products": [
      {
        "name": "Shoe",
        "hasDefect": "NO"
      },
      {
        "name": "Pant",
        "hasDefect": "NO"
      },
      {
        "name": "Shirt",
        "hasDefect": "NOT_SURE"
      }
    ]
  }
]

второй блок кода — это массив обновлений? может ли он иметь несколько элементов. если да, то order_id одинаков для всех. произойдет ли обновление обоих документов с order_line_item_id 1 и 2

cmgchess 01.07.2024 16:25

@cmgchess Я также обновил пост. Да, массив обновлений может содержать несколько элементов и иметь один и тот же order_id.

Fadd 01.07.2024 20:56
Использование JavaScript и MongoDB
Использование JavaScript и MongoDB
Сегодня я собираюсь вкратце рассказать о прототипах в JavaScript, а также представить и объяснить вам работу с базой данных MongoDB.
0
2
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать $mergeObjects вместо $merge, чтобы создать новую коллекцию с нужными документами. Пожалуйста, дайте мне знать, если приведенный ниже запрос работает для вас:

[
  {
    $match: {
      order_id: 100
    }
  },
  {
    $unwind: "$products"
  },
  {
    $match: {
      "products.name": "Pant"
    }
  },
  {
    $group: {
      _id: {
        order_id: "$order_id",
        productName: "$products.name"
      },
      hasDefects: {
        $push: "$products.hasDefect"
      }
    }
  },
  {
    $set: {
      hasDefect: {
        $switch: {
          branches: [
            {
              case: {
                $allElementsTrue: {
                  $map: {
                    input: "$hasDefects",
                    as: "d",
                    in: {
                      $eq: ["$$d", "YES"]
                    }
                  }
                }
              },
              then: "YES"
            },
            {
              case: {
                $in: ["NO", "$hasDefects"]
              },
              then: "NO"
            },
            {
              case: {
                $in: ["NOT_SURE", "$hasDefects"]
              },
              then: "NOT_SURE"
            }
          ],
          default: "<DEFAULT>"
        }
      }
    }
  },
  {
    $project: {
      order_id: "$_id.order_id",
      productName: "$_id.productName",
      hasDefect: "$hasDefect",
      lastModified: {
        $dateToString: {
          date: new Date(),
          timezone: "America/New_York"
        }
      }
    }
  },
  {
    $merge: {
      into: "order",
      whenMatched: [
        {
          $addFields: {
            products: {
              $map: {
                input: "$$ROOT.products",
                as: "product",
                in: {
                  $cond: {
                    if: {
                      $eq: ["$$product.name", "$productName"]
                    },
                    then: {
                      $mergeObjects: [
                        "$$product",
                        { hasDefect: "$hasDefect" }
                      ]
                    },
                    else: "$$product"
                  }
                }
              }
            }
          }
        }
      ],
      whenNotMatched: "insert"
    }
  }
]

С наилучшими пожеланиями Асавари

В коллекции order также будет множество товаров. Вы заменили второй $group на $project. Что у меня не работает.

Fadd 01.07.2024 23:02
Ответ принят как подходящий

Ссылаясь на вопрос из моего предыдущего ответа,

Вы можете предоставить конвейер агрегации для WhenMatched.

Важным ключом является ссылка на поле (результат), возвращаемое из конвейера агрегации, вам необходимо использовать переменную $$new.

db.collection.aggregate([
  {
    $unwind: "$products"
  },
  {
    $group: {
      _id: {
        order_id: "$order_id",
        productName: "$products.name"
      },
      hasDefects: {
        $push: "$products.hasDefect"
      },
      products: {
        $push: "$products"
      }
    }
  },
  {
    $set: {
      hasDefect: {
        $switch: {
          branches: [
            {
              case: {
                $allElementsTrue: {
                  $map: {
                    input: "$hasDefects",
                    as: "d",
                    in: {
                      $eq: [
                        "$$d",
                        "YES"
                      ]
                    }
                  }
                }
              },
              then: "YES"
            },
            {
              case: {
                $in: [
                  "NO",
                  "$hasDefects"
                ]
              },
              then: "NO"
            },
            {
              case: {
                $in: [
                  "NOT_SURE",
                  "$hasDefects"
                ]
              },
              then: "NOT_SURE"
            }
          ],
          default: "<DEFAULT>"
        }
      }
    }
  },
  {
    $group: {
      _id: "$_id.order_id",
      products: {
        $push: {
          name: "$_id.productName",
          hasDefect: "$hasDefect"
        }
      }
    }
  },
  {
    $merge: {
      into: "order",
      whenMatched: [
        {
          $addFields: {
            products: {
              $let: {
                vars: {
                  newProducts: "$$new.products"
                },
                in: {
                  $concatArrays: [
                    {
                      $map: {
                        input: "$products",
                        as: "oldProduct",
                        in: {
                          $cond: {
                            if: {
                              $in: [
                                "$$oldProduct.name",
                                "$$newProducts.name"
                              ]
                            },
                            then: {
                              $first: {
                                $filter: {
                                  input: "$$newProducts",
                                  cond: {
                                    $eq: [
                                      "$$this.name",
                                      "$$oldProduct.name"
                                    ]
                                  }
                                }
                              }
                            },
                            else: "$$oldProduct"
                          }
                        }
                      }
                    },
                    {
                      $filter: {
                        input: "$$newProducts",
                        cond: {
                          $not: {
                            $in: [
                              "$$this.name",
                              "$products.name"
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      ],
      whenNotMatched: "insert"
    }
  }
])

Демо @ Mongo Playground

Я попробовал, но он не обновляет существующий продукт. он просто добавляет к массиву продуктов. Например, {"order_id": 100,"products": [{"name": "Shoe","hasDefect": "YES"}]} добавляется как еще один товар «Обувь» в коллекцию, хотя в коллекции есть товар «Обувь», и его следует только что обновить.

Fadd 02.07.2024 19:05

Можете ли вы воспроизвести свой сценарий с данными Mongo Playground? Демо, которое я добавил в ответ, не может воспроизвести ваш сценарий.

Yong Shun 03.07.2024 01:31

Моя вина, это действительно работает. У меня было неправильное имя одной из переменных, поэтому она не могла найти и всегда добавлялась. У меня есть один вопрос о том, как это работает: $in: [ "$$oldProduct.name", "$$newProducts.name"] Мне интересно, как "$$newProducts.name" преобразуется в массив?

Fadd 03.07.2024 01:44

Привет, MongoDB имеет возможность получить доступ к вложенному полю и вернуть его, поскольку newProducts — это массив, следовательно, с помощью $$newProducts.name он вернет все значения name в виде массива. Цель использования $in — проверить наличие $$oldProduct.name в массиве $$newProducts.name.

Yong Shun 03.07.2024 04:14

Другие вопросы по теме