Search code examples
pythonmongodbfilterexpr

How to filter field in array in MongoDB


I have array:

{'_id': ObjectId('7262718c217dda3ad90ef151'), 'SomeText': "aaa", 'items': [{'status': 500, 'x': 1, 'y': None,  'price': 3}, { 'status': 500, 'x': 2, 'y': None,  'price': 4}]}
{'_id': ObjectId('7262718c217dda3ad90ef152'), 'SomeText': "bbb", 'items': [{'status': 300, 'x': 1, 'y': 2,  'price': 7}, { 'status': 500, 'x': 2, 'y': 2,  'price': 5}]}
{'_id': ObjectId('7262718c217dda3ad90ef153'), 'SomeText': "ccc", 'items': [{'status': 300, 'x': 1, 'y': 1,  'price': 8}]}

I need to find documents only those values where x not equal y and only field "SomeText", "х", "y"

Query:

fx = mycol.find({"$expr": {"$ne": ["$items.x", "$items.y"]}},
                   { "_id": 0, "SomeText": 1, "items.x": 1, "items.y": 1, }
                  ).limit(3)
for x in fx:
    print(x)

Returns document with ALL array items in shapes,but I'd like to get the document only with the array that contains x not equal y

{'SomeText': "aaa" 'items': [{'x': 1, 'y': None,}, {'x': 2, 'y': None,}]}
{'SomeText': "bbb", 'items': [{'x': 1, 'y': 2}, {'x': 2, 'y': 2]}

Query:

fx=mycol.aggregate([
    {"$match": {"$expr": {"$ne": ["$items.x", "$items.y"]}}},
    {"$project": {
        "items": {"$filter": {
            "input": '$items',
            "as": 'item',
            "cond":  {"$ne": ["$$item.x", "$$item.y"]}
        }},
        "_id": 0, "SomeText": 1
    }},
    {"$limit" : 5}
])

for x in fx:
    print(x)

Returns document with "status" and "price"

{'SomeText': "aaa", 'items': [{'status': 500, 'x': 1, 'y': None,  'price': 3}, { 'status': 500, 'x': 2, 'y': None,  'price': 4}]}
{'SomeText': "bbb", 'items': [{'status': 300, 'x': 1, 'y': 2,  'price': 7}]}

Can I filter element in array to get result?

{'SomeText': "aaa", 'items': [{'x': 1, 'y': None,}, {'x': 2, 'y': None,}]}
{'SomeText': "bbb", 'items': [{'x': 1, 'y': 2}]}

Solution

  • Just add a $map step to your current query:

    db.collection.aggregate([
      {
        $match: {$expr: {$ne: ["$items.x", "$items.y"]}}
      },
      {
        $project: {
          items: {
            $filter: {
              input: "$items",
              as: "item",
              cond: {$ne: ["$$item.x", "$$item.y"]}
            }
          },
          _id: 0,
          SomeText: 1
        }
      },
      {
        $set: {
          items: {
            $map: {
              input: "$items",
              as: "item",
              in: {x: "$$item.x", y: "$$item.y"}
            }
          }
        }
      },
      {
        $limit: 5
      }
    ])
    

    See how it works on the playground example