Reputation: 2498
I have a document that looks like:
{
"_id": ObjectId(),
"employees": [
{
"_id": ObjectId(),
"sharedBranches": [
ObjectId(),
ObjectId()
]
},
{
"_id": ObjectId()
}
]
}
I am trying to return the documents that contains my input ObjectId in the sharedBranches
field, and also filter the employees
array down so it only contains objects whose sharedBranches
contains my input ObjectId.
However, not every employee
object (i.e. the elem in the employees
array) contains the sharedBranches
field. My query is returning an error which I am pretty sure is due to the Nulls, but I can't figure out the syntax for $isNull
. here is my query. (note the branch_id is the input ObjectId I am searching on.
collection = client["collection"]["documents"]
pipeline = [
{
"$match": {
"employees.sharedBranches": {"$elemMatch": {"$eq": ObjectId(branch_id)}},
}
},
{
"$project": {
"employees": {
"$filter": {
"input": "$employees",
"as": "employees",
"cond": {"$in": [ObjectId(branch_id), {"$ifNull": ["$$employees.sharedBranches", []]}]}
}
}
}
}
]
This query returns the error:
OperationFailure: $in requires an array as a second argument, found: object, full error: {'ok': 0.0, 'code': 40081, 'errmsg': '$in requires an array as a second argument, found: object', 'operationTime': Timestamp(1639079887, 1)}
It seems that the $ifNull
stuff is not evaluating to an array. If I remove the $ifNull
stuff, and just try to use $in
on the array directly (so my $cond
looks like:
"cond": {"$in": [ObjectId(branch_id), "$$employees.sharedBranches"]}
,
I get this error:
OperationFailure: $in requires an array as a second argument, found: string, full error: {'ok': 0.0, 'code': 40081, 'errmsg': '$in requires an array as a second argument, found: string', 'operationTime': Timestamp(1639080588, 1)}
So I am at a loss of how to resolve this. Is my issue with the $ifNull
? Am I mistaken that it's needed at all?
Upvotes: 0
Views: 203
Reputation: 7568
I suspect some of your sharedBranches
fields are not arrays but strings with a single ID. Here is a little trick that sniffs for the $type
of such things and if the field is not an array (which includes if it is missing which will return missing
), it turns it into an array of one:
c = db.foo.aggregate([
{$project: {
employees: {$filter: {
input: "$employees",
as: "employees",
cond: {$in: [targetSharedBranchID, {$cond:
{if:{$ne:[{$type:'$$employees.sharedBranches'},"array"]},
then: ['$$employees.sharedBranches'], // ah HA! Create array of one on the fly.
// OK if missing; will create an empty array.
else: '$$employees.sharedBranches'
}} ] }
}}
}}
,{$match: {$expr: {$gt:[{$size:"$employees"},0]} }}
]);
Upvotes: 1