Search code examples
mongodbmongodb-queryaggregation-framework

$filter inside $reduce or inside $map from array without unwind


I need some help: I want to optimize this query to be faster , it need to filter by events.eventType:"log" all docs with server:"strong" , but without separate unwind & filter stages , maybe somehow inside the $reduce stage to add $filter.

example single document:

 {
 server: "strong",
 events: [
  {
    eventType: "log",
    createdAt: "2022-01-23T10:26:11.214Z",
    visitorInfo: {
      visitorId: "JohnID"
    }
  }

current aggregation query:

   db.collection.aggregate([
   {
    $match: {
    server: "strong"
    }
   },
   {
    $project: {
     total: {
      $reduce: {
      input: "$events",
      initialValue: {
        visitor: [],
        uniquevisitor: []
      },
      in: {
        visitor: {
          $concatArrays: [
            "$$value.visitor",
            [
              "$$this.visitorInfo.visitorId"
            ]
          ]
        },
        uniquevisitor: {
          $cond: [
            {
              $in: [
                "$$this.visitorInfo.visitorId",
                "$$value.uniquevisitor"
              ]
            },
            "$$value.uniquevisitor",
            {
              $concatArrays: [
                "$$value.uniquevisitor",
                [
                  "$$this.visitorInfo.visitorId"
                ]
              ]
            }
          ]
          }
        }
       }
      }
    }
    }
    ])

expected output , two lists with unique visitorId & list of all visitorId:

 [
{
"total": {
  "uniquevisitor": [
    "JohnID"
  ],
  "visitor": [
    "JohnID",
    "JohnID"
  ]
}

} ]

playground

In the example query no filter is added for events.eventType:"log" , how can this be implemented without $unwind?


Solution

  • I am not sure this approach is more optimized than yours but might be this will help,

    • $filter to iterate loop of events and filter by eventType
    • $let to declare a variable events and store the above filters result
    • return array of visitor by using dot notation $$events.visitorInfo.visitorId
    • return array of unique visitor uniquevisitor by using dot notation $$events.visitorInfo.visitorId and $setUnion operator
    db.collection.aggregate([
      { $match: { server: "strong" } },
      {
        $project: {
          total: {
            $let: {
              vars: {
                events: {
                  $filter: {
                    input: "$events",
                    cond: { $eq: ["$$this.eventType", "log"] }
                  }
                }
              },
              in: {
                visitor: "$$events.visitorInfo.visitorId",
                uniquevisitor: {
                  $setUnion: "$$events.visitorInfo.visitorId"
                }
              }
            }
          }
        }
      }
    ])
    

    Playground


    Or similar approach without $let and two $project stages,

    db.collection.aggregate([
      { $match: { server: "strong" } },
      {
        $project: {
          events: {
            $filter: {
              input: "$events",
              cond: { $eq: ["$$this.eventType", "log"] }
            }
          }
        }
      },
      {
        $project: {
          total: {
            visitor: "$events.visitorInfo.visitorId",
            uniquevisitor: {
              $setUnion: "$events.visitorInfo.visitorId"
            }
          }
        }
      }
    ])
    

    Playground