AlexZeDim
AlexZeDim

Reputation: 4352

MongoDB aggregation $group with mutually exclusive fields OR split / unwind documents

I have a collection with schema, with mutually exclusive fields:

{
  item_id: Number, //if this field is present in document
  a_item_id: Number, //then this both field doesn't exists and vice versa
  h_item_id: Number, //then this both field doesn't exists and vice versa
  quantity: Number,
  other_field: String
}

The problem:

I need to split documents via aggregate stage in such order, that every document which contains both a_item_id && h_item_id (twin) fields will become two separate documents (they should inherit their base fields)

Also, the point is, that I can't just $group by two fields at the same time, and then $unwind because, if this fields are presented in a document they always have different values between each other, like:

a_item_id: 2
h_item_id: 3

So, if item_id isn't present in document, then a_item_id and h_item_id are presented at the same time, always. AND they have different values between each other.

MongoPlayground example

Does anyone have any idea to split this twins and achieve such results? Like this:

  {
    "_id": ObjectId("5a934e000102030405000000"),
    "item_id": 1,
    "quantity": 1
  },
  {
    "_id": ObjectId("5a934e000102030405000001"),
    "a_item_id": 2,
    "quantity": 1
  },
  {
    "_id": ObjectId("5a934e000102030405000002"),
    "h_item_id": 3,
    "quantity": 1
  }

P.S. I understand that using this schema is a total failure, but I haven't project it. So I cann't re-build the collection in my own image.

UPDATED: What have I done already?

I am trying to solve my problem, by adding new array and push every value from every document to it, for later $unwind

  {
    $addFields: {
      items_array: [
        "$item_id",
        "$h_item_id",
        "$a_item_id"
      ]
    }
  }

Upvotes: 0

Views: 370

Answers (2)

prasad_
prasad_

Reputation: 14317

This aggregation uses another approach, using $facet stage:

db.collection.aggregate([
  { 
      $match: { a_item_id: { $exists: true },  h_item_id: { $exists: true } } 
  },
  { 
      $facet: {
          doc_a_item: [ 
               { $addFields: { h_item_id: "$$REMOVE" } },
          ],
          doc_h_item: [
               { $addFields: { a_item_id: "$$REMOVE" } }
          ]
       } 
  },
  { 
      $project: { doc: { $concatArrays: [ "$doc_a_item", "$doc_h_item" ] } } 
  },
  { 
      $unwind: "$doc" 
  },
  { 
      $replaceWith: "$doc" 
  }
] )

The output, as two documents:

{
        "_id" : ObjectId("5f1ed8090fdddd9a43c261e5"),
        "a_item_id" : 34,
        "quantity" : 50
}
{
        "_id" : ObjectId("5f1ed8090fdddd9a43c261e5"),
        "h_item_id" : 56,
        "quantity" : 50
}

The input document:

{
    _id : ObjectId("5f1ed8090fdddd9a43c261e5"), 
    a_item_id: 34, 
    h_item_id: 56, 
    quantity: 50
}

Upvotes: 1

AlexZeDim
AlexZeDim

Reputation: 4352

«Fine, I'll do it myself»

MongoPlayground example for future uses

Model.aggregate([
  {
    $match: {
      $or: [
        {
          a_item_id: 2,
          
        },
        {
          h_item_id: 3,
          
        },
        {
          item_id: 1,
          
        }
      ]
    }
  },
  {
    $addFields: {
      item_id: {
        $filter: {
          input: [
            "$item_id",
            "$h_item_id",
            "$a_item_id"
          ],
          as: "d",
          cond: {
            $ne: [
              "$$d",
              null
            ]
          }
        }
      }
    }
  },
  {
    $unset: [
      "a_item_id",
      "h_item_id",
      
    ]
  },
  {
    $unwind: {
      path: "$item_id",
      preserveNullAndEmptyArrays: true
    }
  },
  {
    $group: {
      _id: "$item_id"
    }
  }
])

At last group I could add:

  data: {
    $push: "$$ROOT"
  }

and then $unwind data field and receive the original documents.

Upvotes: 0

Related Questions