Вложенный уровень $filter с операторами $or, $gte, $lte

Я хочу фильтровать несколько вложенных документов в MONGO db

Пример JSON:

{ 
"_id" : ObjectId("5b5c3afbcc43cb5ed64b7a04"), 
"id" : NumberLong(15015060), 
"name" : "1801_Conf", 
"type" : NumberInt(2), 
"members" : [
    {
        "id" : NumberLong(15015061), 
        "name" : "1801_Conf-W--", 
        "sku" : "1801_new", 
        "type" : NumberInt(1), 
        "parent_id" : NumberLong(15015060), 
        "available_qty" : NumberInt(10), 
        "on_hand_qty" : NumberInt(10), 
        "outgoing_qty" : NumberInt(0), 
        "incoming_qty" : NumberInt(0), 
        "shortage_qty" : NumberInt(0), 
        "product_warehouses" : [
            {
                "warehouse_id" : NumberLong(3), 
                "available_qty" : NumberInt(10), 
                "outgoing_qty" : NumberInt(0), 
                "incoming_qty" : NumberInt(0)
            },
            {
                "warehouse_id" : NumberLong(4), 
                "available_qty" : NumberInt(600), 
                "outgoing_qty" : NumberInt(0), 
                "incoming_qty" : NumberInt(0)
            }
        ], 
        ]
    } 
] 

}

Ожидаемый результат: нужно отфильтровать только участников (не всех), у которых available_qty ‹ 50 и > 10 и member.product_warehouses.available_qty ‹ 50 и > 20.

Запрос:

db.products.aggregate([{
   "$match": {
       "tenant_id": XXX,
       "type" : 2
   }
}, {
   "$project": {
       "name": 1,
       "sku": 1,
       "members": {
           "$filter": {
               "input": "$members",
               "as": "member",
               "cond": {

                       "$and": 
                        [
                            {
                               "$gte": ["$$member.product_warehouses.available_qty", 10]
                            }, 
                            {
                               "$lte": ["$$member.available_qty", 50]
                            },
                            {
                               "product_warehouses": {
                               "$elemMatch" : {

                                   }
                               }
                            }
                        ]

               }
           }
       }
   }
}])

Ошибка: : { "ok" : 0, "errmsg" : "недопустимый оператор $elemMatch", "код": 15999 }: сбой агрегата


person Rahul Kumawat    schedule 30.07.2018    source источник


Ответы (4)


Сначала вам нужно $filter members и в то же время вы должны применить $filter в массив product_warehouses с циклом по отфильтрованному массиву members с использованием $map агрегация.

И наконец, вам нужно поставить cond с $and , $gt и оператор агрегации $eq, который аналогично вашему условию Ожидаемый результат.

db.collection.aggregate([
  { "$project": {
    "members": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$members",
            "as": "member",
            "in": {
              "$mergeObjects": [
                "$$member",
                { "product_warehouses": {
                  "$filter": {
                    "input": "$$member.product_warehouses",
                    "as": "product",
                    "cond": {
                      "$or": [
                        { "$lt": ["$$product.available_qty", 50] },
                        { "$gt": ["$$product.available_qty", 20] }
                      ]
                    }
                  }
                }}
              ]
            }
          }
        },
        "as": "member",
        "cond": {
          "$or": [
            { "$lte": [ "$$member.available_qty", 50 ] },
            { "$gte": [ "$$member.available_qty", 10 ] }
          ]
        }
      }
    }
  }},
  { "$match": { "members": { "$ne": [] } } }
])

Попробуйте

person Ashh    schedule 30.07.2018
comment
Мне не нужен результат (jsonobject), если ни один элемент не соответствует в $$member.product_warehouses, и если он совпадает, то я хочу, чтобы объект с полным массивом $$member.product_warehouses прямо сейчас возвращал только совпадающие элементы на складе, и я не могу использовать ввод: $$member.product_warehouses, потому что мне нужно поместить другие фильтры в другие поля, чтобы я мог только вводить: $$member - person Rahul Kumawat; 30.07.2018
comment
Хорошо, я поставил еще одно условие $match... посмотри - person Ashh; 30.07.2018
comment
но этот запрос возвращает только совпадающие элементы массива ($$member.product_warehouse) вместо этого я хочу, чтобы все элементы массива, если кто-то удовлетворяет нашим критериям - person Rahul Kumawat; 30.07.2018
comment
какой результат вы хотите получить из вышеприведенной коллекции, которую вы разместили? - person Ashh; 30.07.2018
comment
Я обновил вопрос, пожалуйста, проверьте, что я хочу member.product_warehouses (с обоими элементами), как если бы он соответствовал в настоящее время, он будет возвращать только соответствующий элемент - person Rahul Kumawat; 30.07.2018
comment
Обновлен ответ, пожалуйста, проверьте - person Ashh; 30.07.2018

В моем понимании, это ваш необходимый запрос.

db.col.aggregate([
    {"$unwind" : "$members"},
    {"$match" : {"$and" : [{"members.available_qty" : {"$lt" : 50}},{"members.available_qty" : {"$gt" : 10}}]}},
    {"$match" : {"$and" : [{"members.product_warehouses.available_qty" : {"$lt" : 50}},{"members.product_warehouses.available_qty" : {"$gt" : 20}}]}},
    {"$group" : {"_id" : "$_id",
        "type" : {"$first" : "$type"},
        "name" : {"$first" : "$name"},
        "id" : {"$first" : "$id"},
        "members" : {"$addToSet" : "$members"}
        }
    }
]);

or

db.lo.aggregate([
{"$unwind" : "$members"},
{"$match" : {
    "$and" : [
        {"members.available_qty" : {"$lt" : 50}},
        {"members.available_qty" : {"$gt" : 10}}
        ]
    }
},
{"$match" : { 
    "members.product_warehouses": { 
        "$elemMatch":{ 
            "available_qty": { "$lt": 50 }, 
            "available_qty": { "$gt": 20 }
        } 
    } 
}},
{"$group" : {"_id" : "$_id",
    "type" : {"$first" : "$type"},
    "name" : {"$first" : "$name"},
    "id" : {"$first" : "$id"},
    "members" : {"$addToSet" : "$members"}
    }
}

]);

Если какой-либо один поддокумент соответствует приведенному ниже условию members.product_warehouses.available_qty > 20 иmembers.product_warehouses.available_qty ‹ 50, он вернет весь массив product_warehouses, это похоже на $elemMatch< /сильный>

person Senthur Deva    schedule 30.07.2018

Попробуйте запрос ниже, он отлично работает и правильно протестирован

db.test.aggregate(

// Pipeline
[
    // Stage 1
    {
        $unwind: {
            path : "$members",
            includeArrayIndex : "arrayIndex", // optional
            preserveNullAndEmptyArrays : false // optional
        }
    },

    // Stage 2
    {
        $match: {
             $and: [ 

                   {"members.available_qty": {$gte:10}}, 
                   {"members.available_qty": {$lte:50}} 
                   ]
        }
    },

    // Stage 3
    {
        $project: {
            "_id" :1, 
            "id" :1, 
            "name" :1, 
            "type" :1, 
            "members" : {
                "id":1,
                "name" :1, 
                "sku" : 1, 
                "type" :1, 
                "parent_id" :1, 
                "available_qty" :1, 
                "on_hand_qty" :1, 
                "outgoing_qty" :1, 
                "incoming_qty" :1, 
                "shortage_qty" :1, 
                "product_warehouses": {
                            $filter: {
                                input: "$members.product_warehouses",
                                as: "item",
                                cond: {
                                  "$and": [
                                    { "$gte": [ "$$item.available_qty", 20] },
                                    { "$lte": [ "$$item.available_qty", 50 ] },
                                    ]
                                }
                            }
                        }

                },

        }
    },

    // Stage 4
    {
        $match: {
            "members.product_warehouses":{$ne: []}

        }
    },

    // Stage 5
    {
        $group: {
            _id:"$_id",
            "id" :{ $last: '$id' }, 
            "name" :{ $last: '$name' }, 
            "type" : { $last: '$type' }, 
            members: { $addToSet: "$members" }
        }
    },
],

);

person Mohit Kumar Bordia    schedule 30.07.2018
comment
Мне не нужен результат (jsonobject), если ни один элемент не соответствует в $$member.product_warehouses, и если он совпадает, то я хочу, чтобы объект с полным массивом $$member.product_warehouses прямо сейчас возвращал только совпадающие элементы на складе - person Rahul Kumawat; 30.07.2018

Вы можете использовать приведенный ниже запрос агрегации. Поскольку $elemMatch не является допустимым оператором на этапе $project, вы можете заменить эту часть на $filter, $size и $gt сравнение.

{"members":{
  "$filter":{
    "input":"$members",
    "cond":{
      "$and":[
        {"$gte":["$$this.available_qty",10]},
        {"$lte":["$$this.available_qty",50]},
        {"$gt":[
          {"$size":{
            "$filter":{
              "input":"$$this.product_warehouses",
              "cond":{
                "$and":[
                  {"$gte":["$$this.available_qty",20]},
                  {"$lte":["$$this.available_qty",50]}
                ]
              }
            }
          }},
          0
        ]}
      ]
    }
  }
}}
person s7vr    schedule 30.07.2018