Условно включить этапы агрегации

1

У меня есть функция, которая дает мне несколько заказов на основе заданных параметров. Но параметр может быть пустым, в этом случае я хочу оставить $match одиночку.

Это код, который у меня есть:

if(req.query.status && typeof(req.query.status) == 'array'){
        var match = {
            $in: req.query.status
        };
    }else if(req.query.status){
        var match = req.query.status;
    }else{
        //when empty find all statuses
        var match = null;
    }


 Order.aggregate(
    {
        $match: {
            'shop.nameSlug' : req.query.nameSlug,
        }
    },
    {
        $unwind: "$status"
    },
    {
        $match: {
            "status.status" : match
        }
    },
    {
        $group: {
            _id: "$_id",
            status: {
                $addToSet: "$status"
            },
            number: {
                $first: "$number"
            },
            date: {
                $first: "$date"
            },
            comment: {
                $first: "$comment"
            }
        }
    }
).exec(function(err, orders){
})

когда match равно null, нет возвращенных ордеров, потому что mongo ищет поля равными нулю. Как я могу удалить этот $match когда match == null?

Теги:
aggregation-framework

1 ответ

2
Лучший ответ

То, что вы хотите сделать, это построить весь конвейер в зависимости от предоставленных опций. Это всего лишь структура данных после.

Вы также неправильно тестируете "массив", и вы должны использовать instanceof потому что typeof фактически возвращает "object" а не "array".

Более того, вы действительно хотите, что условие в первом каскаде конвейера для оптимального выбора документов, а также, в дополнение к тому, добавляется после $unwind, где это необходимо:

var pipeline = [
  { $match: 
      Object.assign(
        { 'shop.nameSlug' : req.query.nameSlug },
        (req.query.status) 
          ? { "status.status": (req.query.status instanceof Array)
            ? { "$in": req.query.status } : req.query.status }
          : {}
      )
  },
  { $unwind: "$status" },
  ...(
    (req.query.status)
      ? [{ "$match": { 
          "status.status": (req.query.status instanceof Array)
           ? { "$in": req.query.status } : req.query.status
       }}]
      : []
    ),
    { $group: {
      _id: "$_id",
      status: { $addToSet: "$status" },
      number: { $first: "$number" },
      date: { $first: "$date" },
      comment: { $first: "$comment" }
    }}
];    


Order.aggregate(pipeline).exec(function(err, orders){

})

Учитывая объект req с чем-то присутствующим в status вы получаете:

// Example stucture
var req = {
  query: { 
   nameSlug: "Bill", 
   status: "A"
  },
};

// Pipeline output as:

[
    {
        "$match" : {
            "shop.nameSlug" : "Bill",
            "status.status" : "A"
        }
    },
    {
        "$unwind" : "$status"
    },
    {
        "$match" : {
            "status.status" : "A"
        }
    },
    {
        "$group" : {
            "_id" : "$_id",
            "status" : {
                "$addToSet" : "$status"
            },
            "number" : {
                "$first" : "$number"
            },
            "date" : {
                "$first" : "$date"
            },
            "comment" : {
                "$first" : "$comment"
            }
        }
    }
]

С массивом:

var req = {
  query: { 
   nameSlug: "Bill", 
   status: ["A","B"]
  },
};

// Pipeline output as:
[
    {
        "$match" : {
            "shop.nameSlug" : "Bill",
            "status.status" : {
                "$in" : [ 
                    "A", 
                    "B"
                ]
            }
        }
    },
    {
        "$unwind" : "$status"
    },
    {
        "$match" : {
            "status.status" : {
                "$in" : [ 
                    "A", 
                    "B"
                ]
            }
        }
    },
    {
        "$group" : {
            "_id" : "$_id",
            "status" : {
                "$addToSet" : "$status"
            },
            "number" : {
                "$first" : "$number"
            },
            "date" : {
                "$first" : "$date"
            },
            "comment" : {
                "$first" : "$comment"
            }
        }
    }
]

И ни с чем:

var req = {
  query: { 
   nameSlug: "Bill", 
   //status: ["A","B"]
  },
};

// Pipeline output as:
[
    {
        "$match" : {
            "shop.nameSlug" : "Bill"
        }
    },
    {
        "$unwind" : "$status"
    },
    {
        "$group" : {
            "_id" : "$_id",
            "status" : {
                "$addToSet" : "$status"
            },
            "number" : {
                "$first" : "$number"
            },
            "date" : {
                "$first" : "$date"
            },
            "comment" : {
                "$first" : "$comment"
            }
        }
    }
]

Таким образом, вы можете видеть, где части условно включены в зависимости от предоставленных значений.


Использование $ filter

Вы действительно должны использовать $filter здесь. Это намного эффективнее, чем $unwind и вы действительно ничего не группируете. Все, что вам действительно нужно, это фильтрованные массивы. Чтобы все вы условно добавляли:

var pipeline = [
  { $match: 
      Object.assign(
        { 'shop.nameSlug' : req.query.nameSlug },
        (req.query.status) 
          ? { "status.status": (req.query.status instanceof Array)
            ? { "$in": req.query.status } : req.query.status }
          : {}
      )
  },
  ...(
    (req.query.status)
      ? [{ "$addFields": { 
          "status": {
            "$filter": {
              "input": "$status",
              "cond": {
                [(req.query.status instanceof Array) ? "$in" : "$eq"]:
                  [ "$$this.status", req.query.status ]
              }
            }    
          }
       }}]
      : []
    )
];

Выбор существует между $in и $eq для сравнительного теста, в зависимости от того, что поставляется. Вы можете опционально обернуть все это в $setUnion если вы "действительно имеете в виду" использовать "набор" в результате. Но обычно это похоже на то, что вы просто хотите "фильтровать" значения из массива.

При одинаковых ожиданиях одного значения:

var req = {
  query: { 
   nameSlug: "Bill", 
   //status: ["A","B"]
   status: "A"
  },
};

/* 1 */
[
    {
        "$match" : {
            "shop.nameSlug" : "Bill",
            "status.status" : "A"
        }
    },
    {
        "$addFields" : {
            "status" : {
                "$filter" : {
                    "input" : "$status",
                    "cond" : {
                        "$eq" : [ 
                            "$$this.status", 
                            "A"
                        ]
                    }
                }
            }
        }
    }
]

Массив:

var req = {
  query: { 
   nameSlug: "Bill", 
   status: ["A","B"]
  },
};

/* 1 */
[
    {
        "$match" : {
            "shop.nameSlug" : "Bill",
            "status.status" : {
                "$in" : [ 
                    "A", 
                    "B"
                ]
            }
        }
    },
    {
        "$addFields" : {
            "status" : {
                "$filter" : {
                    "input" : "$status",
                    "cond" : {
                        "$in" : [ 
                            "$$this.status", 
                            [ 
                                "A", 
                                "B"
                            ]
                        ]
                    }
                }
            }
        }
    }
]

Или ничего:

var req = {
  query: { 
   nameSlug: "Bill", 
   //status: ["A","B"]
  },
};

/* 1 */
[
    {
        "$match" : {
            "shop.nameSlug" : "Bill"
        }
    }
]

Если вам не нужно фильтровать, вы просто отказываетесь от дополнительной стадии конвейера.

  • 0
    w0w. What.An.Answer! Briljant! Я еще не эксперт по mongo / mongoose, поэтому код $group был из другого ответа на SO :) Я получаю ошибку MongoError: Unrecognized pipeline stage name: '$addFields' как я могу это решить?
  • 0
    @NVO Вам понадобится MongoDB 3.4 для этой стадии конвейера. Но вместо этого вы можете просто использовать $project , просто вам нужно явно назвать все поля, которые вы хотите явно, вместо того, чтобы просто «объединить». Так же, как вы делаете в $group . Но вы должны действительно использовать $project и $filter в любой современной версии MongoDB, большей, чем v3. Который вы должны запустить, так как все, что меньше, больше официально не поддерживается.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню