mongodbmongodb-queryaggregation-framework

Use array values as path to match documents in mongodb


Given a list of documents like:

[
  {
    _id: 1,
    field: {
      subfield1: {
        subsubfield1: [
          "value",
          "test"
        ]
      },
      subfield2: {
        subsufield2: [
          "value"
        ]
      }
    },
    paths: [
      "field.subfield1.subsubfield1",
      "field.subfield2.subsubfield2"
    ]
  },
  {
    _id: 2,
    field: {
      subfield1: {
        subsubfield1: [
          "value"
        ]
      },
      subfield2: {
        subsufield2: [
          "value",
          "test"
        ]
      }
    },
    paths: [
      "field.subfield2.subsubfield2"
    ]
  },
  {
    _id: 3,
    field: {
      subfield1: {
        subsubfield1: [
          "value"
        ]
      },
      subfield2: {
        subsufield2: [
          "value"
        ]
      }
    },
    paths: [
      "field.subfield1.subsubfield1"
    ]
  },
  {
    _id: 4,
    field: {
      subfield1: {
        subsubfield1: [
          "value"
        ]
      },
      subfield2: {
        subsufield2: [
          "value"
        ]
      }
    },
    paths: [
      "field.subfield1.subsubfield1"
      "field.subfield2.subsubfield2"
    ]
  }
]

in which paths field is an array with paths to document. I have to return documents that have test as a value in at least one of the paths specified in the paths array. For example, the documents with _id 1 and _id 2 would be returned, because at least one of the values in paths is a path in the document with value test. Documents with _id 3 and _id 4 are not returned since no element of paths is a path in the document that has value test.


Solution

  • EDIT: since OP updated the assumption that the field is always nested in 3 levels. This actually simplify the scenario, and thus the aggregation pipeline.

    1. use $objectToArray to parse the field object as an array of k-v tuple, named parsed
      • use $map to iterate through the parsed array
      • slightly wrangle the k to append the field. as prefix
    2. repeat the parsing
    3. $match only if any element in parsed array are inside the paths array and have value test
    db.collection.aggregate([
      {
        "$set": {
          "parsed": {
            "$map": {
              "input": {
                "$objectToArray": "$field"
              },
              "as": "kv",
              "in": {
                k: {
                  "$concat": [
                    "field",
                    ".",
                    "$$kv.k"
                  ]
                },
                v: "$$kv.v"
              }
            }
          }
        }
      },
      {
        "$set": {
          "parsed": {
            "$reduce": {
              "input": "$parsed",
              "initialValue": [],
              "in": {
                "$concatArrays": [
                  "$$value",
                  {
                    "$map": {
                      "input": {
                        "$objectToArray": "$$this.v"
                      },
                      "as": "kv",
                      "in": {
                        k: {
                          "$concat": [
                            "$$this.k",
                            ".",
                            "$$kv.k"
                          ]
                        },
                        v: "$$kv.v"
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      },
      {
        "$match": {
          "$expr": {
            "$anyElementTrue": {
              "$map": {
                "input": "$parsed",
                "as": "p",
                "in": {
                  //1. matching
                  "$and": [
                    // 1.1 matching path
                    {
                      "$in": [
                        "$$p.k",
                        "$paths"
                      ]
                    },
                    // 1.2 matching value "test"
                    {
                      "$in": [
                        "test",
                        "$$p.v"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    ])
    

    Mongo Playground