У меня есть функция, которая дает мне несколько заказов на основе заданных параметров. Но параметр может быть пустым, в этом случае я хочу оставить $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
?
То, что вы хотите сделать, это построить весь конвейер в зависимости от предоставленных опций. Это всего лишь структура данных после.
Вы также неправильно тестируете "массив", и вы должны использовать 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
здесь. Это намного эффективнее, чем $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"
}
}
]
Если вам не нужно фильтровать, вы просто отказываетесь от дополнительной стадии конвейера.
$group
был из другого ответа на SO :) Я получаю ошибкуMongoError: Unrecognized pipeline stage name: '$addFields'
как я могу это решить?$project
, просто вам нужно явно назвать все поля, которые вы хотите явно, вместо того, чтобы просто «объединить». Так же, как вы делаете в$group
. Но вы должны действительно использовать$project
и$filter
в любой современной версии MongoDB, большей, чем v3. Который вы должны запустить, так как все, что меньше, больше официально не поддерживается.