mongoDB query to find the document in nested array - mongodb

[{
"username":"user1",
"products":[
{"productID":1,"itemCode":"CODE1"},
{"productID":2,"itemCode":"CODE1"},
{"productID":3,"itemCode":"CODE2"},
]
},
{
"username":"user2",
"products":[
{"productID":1,"itemCode":"CODE1"},
{"productID":2,"itemCode":"CODE2"},
]
}]
I want to find all the "productID" of "products" for "user1" such that "itemCode" for the product is "CODE1".
What query in mongoDB should be written to do so?

If you only need to match a single condition, then the dot notation is sufficient.
In Mongo shell:
db.col.find({"products.itemCode" : "CODE1", "username" : "user1"})
This will return all users with nested product objects having itemCode "CODE1".
Updated
Wasn't clear on your requirements at first but this should be it.
If you want each product as a separate entry, then you would need to use the aggregate framework. First split the entries in the array using $unwind, then use $match for your conditions.
db.col.aggregate(
{ $unwind: "$products" },
{ $match: { username: "user1", "products.itemCode": "CODE1" } }
);
response:
{ "_id" : ObjectId("57cdf9c0f7f7ecd0f7ef81b6"), "username" : "user1", "products" : { "productID" : 1, "itemCode" : "CODE1" } }
{ "_id" : ObjectId("57cdf9c0f7f7ecd0f7ef81b6"), "username" : "user1", "products" : { "productID" : 2, "itemCode" : "CODE1" } }

The answer to your question is
db.col.aggregate([
{ $unwind: "$products" },
{ $match: { username: "user1", "products.itemCode": CODE1 } },
{ $project: { _id: 0, "products.productID": 1 } }
]);
In my case didn't work without [ ] tags.

You need multiple filter for this like below which is nothing but AND condition (assuming your collection name is collection1)
db.collection1.find({"username":"user1", "products.itemCode" : "CODE1"})

Related

MongoDB Aggregation Project check if array contains

I have following document:
{
_id : 21353456,
username : "xy",
text : "asdf",
comments : [
{
username : "User1",
text : "hi",
},
{
username : "User2",
text : "hi1",
},
{
username : "User3",
text : "hi2",
},
{
username : "User4",
text : "hi3",
}
]
}
Now I want to get the username, text and comments with aggregation and project. In addition I also want a boolean if the comments array contains an username with "User1". I have this, but it doesn't work.
db.posttest.aggregate(
[
{
$project:
{
username: 1,
text: 1,
comments : 1,
hasComment: { $eq: [ "comments.$.username", "User1" ] },
_id: 0
}
}
]
)
To achieve this you would need to first unwind the comments, and then use a group with a little trick. If you want to omit the _id, then you would also need to do a simple project. Here is the full aggregation pipeline:
db.posttest.aggregate([
{ $unwind : "$comments" },
{ $group : {
_id : "$_id",
username : { $first : "$username" },
text : { $first : "$text" },
comments : { $push : "$comments" },
hasComments : { $max : { $eq : [ "$comments.username", "User1" ] } }
}},
{ $project : { _id : false } }
])
An explanation is following.
First, we need to get rid of an array (comments). To do this we unwind the record; it gives us four records:
{
"_id" : 21353456,
"username" : "xy",
"text" : "asdf",
"comments" : {
"username" : "User1",
"text" : "hi"
}
},
{
"_id" : 21353456,
"username" : "xy",
"text" : "asdf",
"comments" : {
"username" : "User2",
"text" : "hi1"
}
},
{
"_id" : 21353456,
"username" : "xy",
"text" : "asdf",
"comments" : {
"username" : "User3",
"text" : "hi2"
}
},
{
"_id" : 21353456,
"username" : "xy",
"text" : "asdf",
"comments" : {
"username" : "User4",
"text" : "hi3"
}
}
Now we can group all the records into one applying a function to each field. First, we need to give criteria, the 'group by' field (or set of fields). In our case, it is simply the id: _id: "$_id".
Then, for each field, we need to make a decision on how to include it into the resulting record. We have few fields: username, text, and comments. For each four records the username and text are the same, so we can easily pick any of them, namely $first or $last.
comments, however, are different. We want to preserve all of them so that we $push each one back.
The hasComments is a bit tricky here: we need to check if at least one comment.username contains the username. We can use $eq: [...] here, it will give us some array, e.g. [true, false, false, false] or [false, false, true, false]. We would need to pick which value goes into the resulting record. In this case, we can use neither $first nor $last. However, $max will give us an appropriate result.
I know this is an old question and also has an accepted answer, but there is an easiest way than using $unwind and $group, only using $project like this:
The trick here is compare the intersection beetween comments array and desired value (in this case User1. If the interesection is greater than 0 (i.e. exists the value) then the field existsUser will be true, otherwise false.
{
"$project": {
"comments": 1,
"existsUser": {
"$cond": {
"if": {
"$gt": [
{
"$size": {
"$setIntersection": [
"$comments.username",
[
"User1"
]
]
}
},
0
]
},
"then": true,
"else": false
}
}
}
}
Example here

Mongodb aggregate positional fields inside an array [duplicate]

I have a collection with the following data:
{
"_id" : ObjectId("5516d416d0c2323619ddbca8"),
"date" : "28/02/2015",
"driver" : "user1",
"passengers" : [
{
"user" : "user2",
"times" : 2
},
{
"user" : "user3",
"times" : 3
}
]
}
{
"_id" : ObjectId("5516d517d0c2323619ddbca9"),
"date" : "27/02/2015",
"driver" : "user2",
"passengers" : [
{
"user" : "user1",
"times" : 2
},
{
"user" : "user3",
"times" : 2
}
]
}
And I would like to perform aggregation so that I will know for a certain passenger, times it was with a certain driver, in my example it would be:
for user1: [{ driver: user2, times: 2}]
for user2: [{ driver: user1, times: 2}]
for user3: [{ driver: user1, times: 3}, {driver: user2, times:2}]
Im quite new with mongo and know how to perform easy aggregation with sum, but not when its inside arrays, and when my subject is itself in the array.
what is the appropriate way to perform this kind of aggregation, and in more specific, how I perform it in express.js based server?
To achieve your needs with aggregation framework, the first pipeline stage will be a $match operation on the passenger in question that matches the documents with the user in the passenger array, followed by the $unwind operation which deconstructs the passengers array from the input documents in the previous operation to output a document for each element. Another $match operation on the deconstructed array follows that further filters the previous document stream to allow only matching documents to pass unmodified into the next pipeline stage, which is projecting the required fields with the $project operator. So essentially your aggregation pipeline for user3 will be like:
db.collection.aggregate([
{
"$match": {
"passengers.user": "user3"
}
},
{
"$unwind": "$passengers"
},
{
"$match": {
"passengers.user": "user3"
}
},
{
"$project": {
"_id": 0,
"driver": "$driver",
"times": "$passengers.times"
}
}
])
Result:
/* 0 */
{
"result" : [
{
"driver" : "user1",
"times" : 3
},
{
"driver" : "user2",
"times" : 2
}
],
"ok" : 1
}
UPDATE:
For grouping duplicates on drivers with different dates, as you mentioned you can do a $group operation just before the last $project pipeline stage where you compute the total passengers times using the $sum operator:
db.collection.aggregate([
{
"$match": {
"passengers.user": "user3"
}
},
{
"$unwind": "$passengers"
},
{
"$match": {
"passengers.user": "user3"
}
},
{
"$group": {
"_id": "$driver",
"total": {
"$sum": "$passengers.times"
}
}
},
{
"$project": {
"_id": 0,
"driver": "$_id",
"total": 1
}
}
])
Result:
/* 0 */
{
"result" : [
{
"total" : 2,
"driver" : "user2"
},
{
"total" : 3,
"driver" : "user1"
}
],
"ok" : 1
}

combining distinct on projection in mongodb

Is there a query i can use on the following collection to get the result at the bottom?
Example:
{
"_id" : ObectId(xyz),
"name" : "Carl",
"something":"else"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny",
"something":"else"
},
{
"_id" : ObectId(bbb),
"name" : "Carl",
"something":"other"
}
I need a query to get this result:
{
"_id" : ObectId(xyz),
"name" : "Carl"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny"
},
A set of documents with no identical names. Its not important which _ids are kept.
You can use aggregation framework to get this shape, the query could look like this:
db.collection.aggregate(
[
{
$group:
{
_id: "$name",
id: { $first: "$_id" }
}
},
{
$project:{
_id:"$id",
name:"$_id"
}
}
]
)
As long as you don't need other fields this will be sufficient.
If you need to add other fields - please update document structure and expected result.
as you don't care about ids it can be simplified
db.collection.aggregate([{$group:{_id: "$name"}}])

In MongoDB how to find documents where property is object but not Array

I have collection users where each contains property gyms e.g.:
{
"_id" : ObjectId("aaaaa"),
"firstName" : "first",
"lastName" : "second",
"email" : "aaa#aaaa",
"password" : "aaaaa",
"gyms" : [
{
"name" : "aaaaaa",
"address" : "aaaaaaaaaaaaa"
}
]
}
However when I run db.users.aggregate(..) I get:
exception: Value at end of $unwind field path '$gyms' must be an Array, but is a Object
It seems that some of the users documents contain gym:{} or no gym at all rather than array. I need to find these documents, how would I do that?
EDIT:
Aggregate command I run:
db.users.aggregate({ $unwind: "$gyms" }, { $match: { "gyms.address": { $exists: true } } } )
Using {$type: "object"} gives you results containing documents with arrays too. So, to check for pure objects, just add an extra check to ensure that arrays are discarded, viz., obj.0.key: {$exists: false}.
In your case,
db.users.find({"gyms": {$type: "object"}, "gyms.0.name": {$exists: false}}).map(function(u) {
// modify u.gyms to your satisfaction
db.conversations.save(c);
})
Try this may be it also help you
db.users.aggregate([{
"$match": {
"$nor": [{
"gyms": {
"$exists": false
}
}, {
"gyms": {
"$size": 0
}
}, {
"gyms": {
"$size": 1
}
}]
}
}, {
"$unwind": "$gyms"
}]).pretty()
Try something like this:
> db.test.drop()
> db.test.insert({ "x" : ["an array"] })
> db.test.insert({ "x" : { "type" : "object" } })
> db.test.find({ "x" : { "$type" : 3 } })
{ "x" : { "type" : "object" } }

MongoDB query only the inner document

My mongodb collection looks like this:
{
"_id" : ObjectId("5333bf6b2988dc2230c9c924"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb1",
"content" : "mongo content1"
},
{
"title" : "replicaset1",
"content" : "replca content1"
}
]
}
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo content2"
},
{
"title" : "replicaset1",
"content" : "replca content1"
},
{
"title" : "mongodb2",
"content" : "mongo content3"
}
]
}
I want to query only notes that have the title "mongodb2" but do not want the complete document.
I am using the following query:
> db.test.find({ 'notes.title': 'mongodb2' }, {'notes.$': 1}).pretty()
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo bakwas2"
}
]
}
I was expecting it to return both notes that have title "mongodb2".
Does mongo return only the first document when we query for a document within a document ?
The positional $ operator can only return the first match index that it finds.
Using aggregate:
db.test.aggregate([
// Match only the valid documents to narrow down
{ "$match": { "notes.title": "mongodb2" } },
// Unwind the array
{ "$unwind": "$notes" },
// Filter just the array
{ "$match": { "notes.title": "mongodb2" } },
// Reform via group
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"notes": { "$push": "$notes" }
}}
])
So you can use this to "filter" specific documents from the array.
$ always refers to the first match, as does the $elemMatch projection operator.
I think you have three options:
separate the notes so each is a document of its own
accept sending more data over the network and filter client-side
use the aggregation pipeline ($match and $project)
I'd probably choose option 1, but you probably have a reason for your data model.