skip to Main Content

Below is my document. I want to compare the objects in array SKUPromo and get only those objects which have overlapping dates.

{
  "_id": "2531090",
  "SKUPromo": [
    {
      "_id": "1",
      "startDateTime": {
        "$date": "2023-01-05T06:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-02-07T05:59:59.000Z"
      },
      "promotionID": "6262",
      "skuId": "2531090"
    },
    {
      "_id": "2",
      "startDateTime": {
        "$date": "2023-06-25T05:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-07-16T04:59:59.000Z"
      },
      "promotionID": "10146",
      "skuId": "2531090"
    },
    {
      "_id": "3",
      "startDateTime": {
        "$date": "2023-06-25T05:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-07-02T04:59:59.000Z"
      },
      "promotionID": "8749",
      "skuId": "2531090"
    },
    {
      "_id": "4",
      "startDateTime": {
        "$date": "2023-06-04T05:00:00.000Z"
      }
      "endDateTime": {
        "$date": "2023-06-11T04:59:59.000Z"
      },
      "promotionID": "10024",
      "skuId": "2531090"
    },
    {
      "_id": "5",
      "startDateTime": {
        "$date": "2023-01-01T06:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-01-07T05:59:59.000Z"
      },
      "promotionID": "6262",
      "skuId": "2531090"
    }
  ]
}

The output should have below data,

{
  "_id": "2531090",
  "SKUPromo": [
    {
      "_id": "1",
      "startDateTime": {
        "$date": "2023-01-05T06:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-02-07T05:59:59.000Z"
      },
      "promotionID": "9345",
      "skuId": "2531090"
    },
    {
      "_id": "2",
      "startDateTime": {
        "$date": "2023-06-25T05:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-07-16T04:59:59.000Z"
      },
      "promotionID": "10146",
      "skuId": "2531090"
    },
    {
      "_id": "3",
      "startDateTime": {
        "$date": "2023-06-25T05:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-07-02T04:59:59.000Z"
      },
      "promotionID": "8749",
      "skuId": "2531090"
    },
    {
      "_id": "5",
      "startDateTime": {
        "$date": "2023-01-01T06:00:00.000Z"
      },
      "endDateTime": {
        "$date": "2023-01-07T05:59:59.000Z"
      },
      "promotionID": "6262",
      "skuId": "2531090"
    }
  ]
}

2

Answers


  1. This may not be exactly what the OP is looking for but the challenge of iterating over a list and examining each element in the context of the rest of the list is a good one. Here’s a potential solution:

    
    //  The end date of one object should not be between another's start and end date – 
    
    c = db.foo.aggregate([
      {
        $project: {
        SKUPromo: {
            /*
              This is the pseudocode in regular javascript:
              len = SKUPromo.length;
              for(n = 0; n < len; n++) {
                targ = SKUPromo[n]
                for(m = 0; m < len; m++) {
                  if(n == m) {  continue }
                  if(targ.enddate < SKUPromo[m].enddate and targ.enddate > SKUPromo[m].startdate
                        idmap[targ._id].append(m);
                  }
                }
              }
            */
    
            // Iterate over SKUPromo and call each one 'targ':
            $map: {input: "$SKUPromo", as: "targ", in: {
    
            // Now run a reduce over SKUPromo to assess against 'targ'.
            // The $mergeObjects is there to add the overlaps field
            // to the existing 'targ'.  In pipeline logic we cannot say:
            //    targ['overlaps'] = some_list
            // instead we must say:
            //    $mergeObjects: [ '$$targ', {overlaps: some_list}]
            //
            
                  $mergeObjects: ["$$targ",
                    {overlaps: {
                      $reduce: {input: "$SKUPromo",
                        initialValue: [],
                        in: {
                          $cond: [
                            { // IF
                              $and: [
                                { $ne: ["$$targ", "$$this"] },
                                { $lt: ["$$targ.endDateTime", "$$this.endDateTime"] },
                                { $gt: ["$$targ.endDateTime", "$$this.startDateTime"] }
                              ]
                            },
                            { $concatArrays: ["$$value", ["$$this._id"]] }, // THEN
                            "$$value" // ELSE
                          ]
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
        // Only keep things with anything in the overlaps array:
        ,{$project: {
          overlaps: {
            $filter: {
              input: "$SKUPromo",
              as: "targ",
              cond: { $ne: ["$$targ.overlaps", []] }
            }
          }
        }
      }
    
    ]);
    

    This yields:

    {
      _id: '2531090',
      overlaps: [
        {
          _id: '3',
          startDateTime: ISODate("2023-06-25T05:00:00.000Z"),
          endDateTime: ISODate("2023-07-02T04:59:59.000Z"),
          promotionID: '8749',
          skuId: '2531090',
          overlaps: [
            '2'
          ]
        },
        {
          _id: '5',
          startDateTime: ISODate("2023-01-01T06:00:00.000Z"),
          endDateTime: ISODate("2023-01-07T05:59:59.000Z"),
          promotionID: '6262',
          skuId: '2531090',
          overlaps: [
            '1'
          ]
        }
      ]
    }
    
    Login or Signup to reply.
  2. Here’s one way you could do it using "$reduce". Comments are in the aggregation pipeline.

    db.collection.aggregate([
      {
        "$set": {
          // rewrite SKUPromo
          "SKUPromo": {
            "$reduce": {
              "input": "$SKUPromo",
              // start with empty array
              // consider each element in turn
              "initialValue": [],
              "in": {
                "$let": {
                  "vars": {
                    "thisSKUP": "$$this"
                  },
                  "in": {
                    "$cond": [
                      { // does thisSKUP have any overlap
                        // with any other array element
                        "$reduce": {
                          "input": "$SKUPromo",
                          "initialValue": false,
                          "in": {
                            "$or": [
                              "$$value",
                              {
                                "$and": [
                                  { // don't consider overlap with self
                                    "$ne": ["$$this._id", "$$thisSKUP._id"]
                                  },
                                  // 2 conditions of overlap
                                  {
                                    "$lte": ["$$thisSKUP.startDateTime", "$$this.endDateTime"]
                                  },
                                  {
                                    "$gte": ["$$thisSKUP.endDateTime", "$$this.startDateTime"]
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      },
                      { // overlap exists, add element
                        "$concatArrays": ["$$value", ["$$this"]]
                      },
                      // no overlap, just keep current array
                      "$$value"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    ])
    

    Try it on mongoplayground.net.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search