find missing elements from the passed array to mongodb qyery - mongodb

for example
animals = ['cat','mat','rat'];
collection contains only 'cat' and 'mat'
I want the query to return 'rat' which is not there in collection..
collection contains
[
{
_id:objectid,
animal:'cat'
},
{
_id:objectid,
animal:'mat'
}
]
db.collection.find({'animal':{$nin:animals}})
(or)
db.collection.find({'animal':{$nin:['cat','mat','rat']}})

EDIT:
One option is:
Use $facet to $group all existing values to a set. using $facet allows to continue even if the db is empty, as #leoll2 mentioned.
$project with $cond to handle both cases: with or without data.
Find the set difference
db.collection.aggregate([
{$facet: {data: [{$group: {_id: 0, animals: {$addToSet: "$animal"}}}]}},
{$project: {
data: {
$cond: [{$gt: [{$size: "$data"}, 0]}, {$first: "$data"}, {animals: []}]
}
}},
{$project: {data: "$data.animals"}},
{$project: {_id: 0, missing: {$setDifference: [animals, "$data"]}}}
])
See how it works on the playground example - with data or playground example - without data

Related

Sort and assign the order to query in mongodb

I'd like to sort a collection, then add a virtual property to the result which is their numerical order in which the results where displayed as.
So for example, we have a collection called calls, and we'd like to ascertain the current call queue priority as a number so it can be synced to our CRM via reverse ETL.
We have to do this inside of the query itself because we don't have an intermediary step where we can introduce any logic to determine this logic.
So my current query is
db.getCollection('callqueues').aggregate([
{
$match: {
'invalidated': false,
'assigned_agent': null
}
},
{ $sort: {
score: -1, _id: -1
} },
{
$addFields: {
order: "<NEW ORDER PROPERTY HERE>",
}
},
])
So I was wondering how would I insert as a virtual property their order, where the first element after the sort should be 1, second 2, etc
One option (since mongoDB version 5.0) is to use $setWindowFields for this:
db.collection.aggregate([
{$match: {invalidated: false, assigned_agent: null}},
{$setWindowFields: {
sortBy: {score: -1, _id: -1},
output: {
order: {
$sum: 1,
window: {documents: ["unbounded", "current"]}
}
}
}}
])
See how it works on the playground example
EDIT: If your mongoDB version is earlier than 5.0, you can use a less efficient query, involving $group and $unwind:
db.collection.aggregate([
{$match: {invalidated: false, assigned_agent: null}},
{$sort: {score: -1, _id: -1}},
{$group: {_id: 0, data: {$push: "$$ROOT"}}},
{$unwind: {path: "$data", includeArrayIndex: "order"}},
{$replaceRoot: {newRoot: {$mergeObjects: ["$data", {order: {$add: ["$order", 1]}}]}}}
])
See how it works on the playground example < 5.0

How to iterate through a set to get field value in MongoDB

Can somebody tell me please if is possible to iterate through a set to create a field value for key in mongodb result. If I have $facet state in pipeline like:
'missing': [{'$group': {'_id': '$foo', 'woo': {'$addToSet': '$wwo'}}},
{'$project': {'missing_woo': {'$setDifference': [woo_set, '$woo']}}
I would like to get result where code value will be the key like
{'missing_woo': 'missing_woo1'}, {'missing_woo': 'missing_woo2'},... {'missing_woo': 'missing_wooN'}
so that I can iterate through the set generated at $project and to create field values
You can simply use $unwind:
db.collection.aggregate([
{
$facet: {
missing: [
{$group: {_id: "$foo", woo: {$addToSet: "$wwo"}}},
{$project: {_id: 0, missing_woo:
{$setDifference: [
[
"woo1",
"woo2",
"wooN",
"missing_woo1",
"missing_woo2",
"missing_wooN"
],
"$woo"
]
}
}
},
{$unwind: "$missing_woo"}
]
}
}
])
See how it works on the playground example

Cannot sort arrays using unwind or reduce mongodb aggregation pipline

I am having a problem sorting an array in a mongodb aggregation pipeline. I couldn't find an answer in stackoverflow so am posting my problem
I have some data in mongodb which has this structure
{
testOne:{ scores:[1, 5, 8]},
testTwo:{scores:[3, 4, 5]},
testThree:{scores:[9,0,1]},
........
testFifty:{scores:[0,8,1]}
}
Basically a series of many tests and the scores from those tests (this is a simplified example to illustrate the problem).
After a few hours banging my head against a wall I found I could only unwind two arrays, any more and no data was returned.
So I couldn't unwind all my arrays and group to get arrays of each test scores from the db
db.collection.aggregate([
{
{$unwind: '$testOne.scores'},
{$unwind: '$testTwo.scores'},
{$unwind: '$testThree.scores'},
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"},
concatTestTwo: { $push: "$testTwo.scores"},
concatTestThree: { $push: "$testThree.scores"}
}
}
To my frustration , even if I just did one unwind the resulting array did not sort properly see below:
db.collection.aggregate([
{
{$unwind: '$testOne.scores'},
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"}
}
{$sort:{concatTestOne: 1}},
}
The result was [1,5,8,3,4,5,....,9,0,1]. No sorting
So to get all the tests scores I used reduce to flatten the nested arrays resulting from the grouping stage with no 'unwinding' e.g.:
db.collection.aggregate([
{
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"}
},
{$addFields:{
testOneScores: {$reduce: {
input: "$concatTestOne",
initialValue: [ ],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
Once again the resulting arrays do not sort. Can anyone tell me what I am doing wrong. The arrays are large (length approx 3500) is it just mongoDB aggregation doesn't handle large arrays ?
Many thanks for any comments I have spent a lot of time trying to sort my arrays and noting works so far
In my understanding, this is your needed query, otherwise please update your sample output.
db.col.aggregate([
{$unwind: '$testOne.scores'},
{$unwind: '$testTwo.scores'},
{$unwind: '$testThree.scores'},
{"$sort" : {"testOne.scores" : -1,"testTwo.scores" : -1,"testThree.scores" : -1}},
{$group:{
_id: {},
concatTestOne: { $addToSet: "$testOne.scores"},
concatTestTwo: { $addToSet: "$testTwo.scores"},
concatTestThree: { $addToSet: "$testThree.scores"}
}
}
])

How to batch query by an array in mongodb?

I have a table Thread:
{
userId: String
messageId: String
}
Now I have an array of userIds, I need to query 20 messageIds for each of them, I can do it with a loop:
const messageIds = {}
for (const userId of userIds) {
const results = await Thread.find({ userId }).sort({ _id: -1 }).limit(20).exec()
messageIds[userId] = results.map(result => result.messageId)
}
But of course this doesn't perform well. Is there a better solution?
The problem with your approach is that you are issuing multiple separate queries to MongoDB.
The simplest workaround to this is using the $push and $slice approach. But this has the problem that the intermediate step would creating an array of huge size.
Another way could be to use $facet as part of aggregation query.
So you need a $facet step in the aggregation like -
[
{$facet: {
'userId1': [
{$match: {userId: 'userId1'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
'userId2': [
{$match: {userId: 'userId2'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
.... (for each userId in array)
}}
]
You can easily just generate this query by iterating over the list of users and adding keys for each user.
So you end up with an object where key is the userId and the value is the array of messages (obj[userId].msg)
You can use aggregation to group threads by userId, and return the top 20:
db.threads.aggregate([
{$match: {userId:{$in: userIds}}},
{$sort: {_id: -1}},
{$group: {_id: "$userId", threads: {$push: "$$ROOT"}}},
{$project: {_id:0, userId:"$_id", threads: {$slice:["$threads", 20]}}}
])

How to aggregate queries in mongodb

I have a document collection that look like the following:
{
name : "tester"
, activity: [
{
gear: "glasses"
where: "outside"
}
, {
gear: "hat"
, where: "inside"
}
, {
gear: "glasses"
, where: "car"
}
]
}
How do I query the collection to return only documents with multiple activities that contain the value of "gear":"glasses"?
Thanks!
I think it's possible to do without aggregation framework, if you need full document filtered by your condition:
db.collection.find({
"activity": {$elemMatch: {gear:"glasses"}},
"activity.1" : {$exists: 1}
})
This is going to be ugly with aggregation framework, but it can be done:
db.collection.aggregate(
{$match: {"activity.gear": "glasses"}},
{$unwind: "$activity"},
{$group: {
_id: {_id: "$_id", name: "$name"},
_count: {$sum: {$cond: [{$eq: ["glasses", "$activity.gear"]}, 1, 0]}}
}},
{$match: {_count: {$gt: 1}}}
)
When analyzing the above query, I would recommend walking through step. Start with just the "$match", the the "$match" and "$unwind". And so one. You will see how each step works.
The response is not the full document. If you are looking for the full document, include a $project step that passes through a dummy activity, and reconstruct the full document on the output.
You can also try this:
db.collection.find( { activity: { $elemMatch: { gear: "glasses" } } )