Why mongodb finds this document ($ne: null for array) - mongodb

Data:
db.inventory.insertMany([
{ _id: 1, item: null },
{ _id: 2 },
{ _id: 3, item: 3 },
{ _id: 4 items: [1, 2, 3] },
{ _id: 5, items: [] }
])
Query 1:
db.inventory.find({ 'item': {$ne: null} })
Result 1:
{ _id: 3, item: 3 }
Query 2:
db.inventory.find({ 'items.0': {$ne: null} })
Result 2:
{ _id: 3, items: [1, 2, 3] },
{ _id: 4, items: [] }
Why mongoDB finds this document: { _id: 4, items: [] }?
Is this a bug?

Use $exists: true instead of $ne: null.
$ne docs explain:
$ne selects the documents where the value of the field is not equal to the specified value. This includes documents that do not contain the field.

null itself a datatype in mongodb. this is why at the 0 index of items
it is searching for a null value and not getting it. if you add null like
items: [null] and run the query again you will not see _id: 4
This is not a bug. this is how Mongo works.
In your first result, the key item in the object itself does not exist. To satisfy 'item': {$ne: null} the key itself has to be present in the object. This is how javascript works as well. Check the JS Code.
It also works with arrays. try: {'items.0': null} and {'items.0': {$ne: null}}, and you should see the difference.

Related

How to insert subdocument into array field only if there's no document with the same "key": "value" pair (MongoDB)?

I have a collection of documents that look like this:
{
"AAA": 1,
"BBB": [
{
"CCC": 1,
"DDD": [1,2,3]
}
]
}
How to insert a new subdocument ({"CCC": 1, "DDD": []}) into "BBB" array only if there's no object with {"CCC": 1} key pair?
You can actually do this in a couple of ways, the easiest would be to make the query 'fail' to match if the document has CCC: 1, like so:
db.collection.updateOne(
{
_id: docId,
'BBB.CCC': {
$ne: 1,
},
},
{
'$push': {
BBB: {
'CCC': 1,
'DDD': [],
},
},
},
);
Now if the document has a BBB.CCC value of 1 then the update will not find a document to update and nothing will be updated as you expect.
Mongo Playground

MongoDB Find values passed in that don't match

Currently stuck with an issue using MongoDB aggregation. I have a array of '_ids' that I need to check exist in a specific collection.
Example:
I have 3 records in 'Collection 1' with _id 1,2,3. I can find the matching values using:
$match: {
_id: {
$in: [1, 2, 3, 4]
}
}
However what I want to know is from the values I have passed in (1,2,3,4). Which ones don't match up to a record. (In this case _id 4 will not have a matching record)
So instead of returning records with _id 1, 2, 3. It needs to return the _id that doesn't exist. So in this example '_id: 4'
The query should also disregard any extra records in the collection. Example, if the collection held records with ID 1-10, and I passed in a query to determine if the _ids: 1, 7, 15 existed. The the value i'm expecting would be along the lines of ' _id: 15 doesn't exist
The first thought was to use to use $project within a aggregation to hold each _id that was passed in, and then attach each record in the collection. To the matching _id passed in. E.g:
Record 1:
{
_id: 1,
Collection1: [
record details: ...,
...
...
]
},
{
_id: 2,
Collection1: [] // This _id passed in, doesn't have a matching collection
}
However cant seem to get a working example in this instance. Any help would be appreciated!
If the input documents are:
{ _id: 1 },
{ _id: 2 },
{ _id: 5 },
{ _id: 10 }
And the array to match is:
var INPUT_ARRAY = [ 1, 7, 15 ]
The following aggregation:
db.test.aggregate( [
{
$match: {
_id: {
$in: INPUT_ARRAY
}
}
},
{
$group: {
_id: null,
matches: { $push: "$_id" }
}
},
{
$project: {
ids_not_exist: { $setDifference: [ INPUT_ARRAY, "$matches" ] },
_id: 0
}
}
] )
Returns:
{ "ids_not_exist" : [ 7, 15 ] }
Are you looking for $not ?
MDB Docs

Counting data per user with mongo aggregation framework

I have a collection, where each document contains user_ids as a property, which is an Array field. Example document(s) would be :
[{
_id: 'i3oi1u31o2yi12o3i1',
unique_prop: 33,
prop1: 'some string value',
prop2: 212,
user_ids: [1, 2, 3 ,4]
},
{
_id: 'i3oi1u88ffdfi12o3i1',
unique_prop: 34,
prop1: 'some string value',
prop2: 216,
user_ids: [2, 3 ,4]
},
{
_id: 'i3oi1u8834432ddsda12o3i1',
unique_prop: 35,
prop1: 'some string value',
prop2: 211,
user_ids: [2]
}]
My goal is to get number of documents per user, so sample output would be :
[
{user_id: 1, count: 1},
{user_id: 2, count: 3},
{user_id: 3, count: 2},
{user_id: 4, count: 2}
]
I've tried couple of things none of which worked, lastly I tried :
aggregate([
{ $group: {
_id: { unique_prop: "$unique_prop"},
users: { "$addToSet": "$user_ids" },
count: { "$sum": 1 }
}}
]
But it just returned the users per document. I m still trying to learn the any resource or advice would help.
You need to $unwind the "user_ids" array and in the $group stage count the number of time each "id" appears in the collection.
db.collection.aggregate([
{ "$unwind": "$user_ids" },
{ "$group": { "_id": "$user_ids", "count": {"$sum": 1 }}}
])
MongoDB aggregation performs computation on group of values from documents in a collection and return computed result through executing its stages in a pipeline.
According to above mentioned description please try executing following aggregate query in MongoDB shell.
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: "$user_ids"
},
// Stage 2
{
$group: {
_id:{user_id:'$user_ids'},
total:{$sum:1}
}
},
// Stage 3
{
$project: {
_id:0,
user_id:'$_id.user_id',
count:'$total'
}
},
]
);
In above aggregate query initially $unwind operator breaks an array field user_ids of each document into multiple documents for each element of array field and then it groups documents by value of user_ids field contained into each document and performs summation of documents for each value of user_ids field.

MongoDB aggregation framework approach to a multi-doc query

I am looking into the best way to organize filtering. I have the following document format:
{
_id: "info",
ids: ["id1", "id2", "id3"]
}
{
_id: "id1",
value: 5
}
{
_id: "id2",
value: 1
}
{
_id: "id3",
value: 5
}
I need to make the following query: get all documents by id from doc "info" and then filter them out by value 5. So, that result would be something like:
{
_id: "id1",
value: 5
}
{
_id: "id3",
value: 5
}
I suppose I need to do unwind on ids, but how do I then select all documents that match those values? Or maybe I should just use $in operator somehow to grab all documents and after that do filtering?
Any help is aprpeciated. Thanks.
If it is only MongoDB shell/script, I would do it like this:
db.ids.find({ _id: { $in: db.ids.findOne({ _id: "info" }).ids }, value: 5 })
You also have worse versions using:
or the eval command:
db.runCommand({
eval: function(value) {
var ids = db.ids.findOne({ _id: "info" }).ids;
return db.ids.find({ _id: { $in: ids }, value: value }).toArray();
},
args: [5]
})
or the $where operator (low performance because you execute one find for each candidate result with value 5):
db.ids.find({
value: 5,
$where: "db.ids.findOne({ _id: 'info', ids: this._id })"
})
But if you are trying to run the queries through a MongoDb driver, the story might be different.

MongoDB: match non-empty doc in array

I have a collection structured thusly:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
user: {user1: 0, user2: 7}
}
]
}
I need to find all documents that have at least one 'score' (element in score array) that has a certain value of 'bar' and a non-empty 'user' sub-document.
This is what I came up with (and it seemed like it should work):
db.col.find({score: {"$elemMatch": {bar:0, user: {"$not":{}} }}})
But, I get this error:
error: { "$err" : "$not cannot be empty", "code" : 13030 }
Any other way to do this?
Figured it out: { 'score.user': { "$gt": {} } } will match non-empty docs.
I'm not sure I quite understand your schema, but perhaps the most straight forward way would be to not have an "empty" value for score.user ?
Instead purposely not have that field in your document if it has no content?
Then your query could be something like ...
> db.test.find({ "score" : { "$elemMatch" : { bar : 0, "user" : {"$exists": true }}}})
i.e. looking for a value in score.bar that you want (0 in this case) checking for the mear existence ($exists, see docs) of score.user (and if it has a value, then it'll exist?)
editied: oops I missed the $elemMatch you had ...
You probably want to add an auxiliary array that keeps track of the users in the user document:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
users: ["user1", "user2"],
user: {user1: 0, user2: 7}
}
]
}
Then you can add new users atomically:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$set: {'score.$.user.user3': 10}, $addToSet: {'score.$.users': "user3"}})
Remove users:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$unset: {'score.$.user.user3': 1}, $pop: {'score.$.users': "user3"}})
Query scores:
> db.test.find({_id: 1, score: {$elemMatch: {bar: 0, users: {$not: {$size: 0}}}}})
If you know you'll only be adding non-existent users and removing existent users from the user document, you can simplify users to a counter instead of an array, but the above is more resilient.
Look at the $size operator for checking array sizes.
$group: {
_id: '$_id',
tasks: {
$addToSet: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.id', ''],
},
'',
],
},
then: '$$REMOVE',
else: {
id: '$tasks.id',
description: '$tasks.description',
assignee: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.assignee._id', ''],
},
'',
],
},
then: undefined,
else: {
id: '$tasks.assignee._id',
name: '$tasks.assignee.name',
},
},
},
},
},
},
},