How to query an array of documents in MongoDB - arrays

I have an array of objects like this one:
{
"actions": [{
"help": {
"messages": [{}, {}]
}
}, {
"animals": [{
"sea": {
"messages": [{}, {}]
}
}, {
"land": {
"messages": [{}, {}]
}
}]
}]
}
I'm trying to get the messages array from each element. (Only the matched one)
I've tried something like:
db.getCollection('responses').find({"actions": "help"})
And
db.getCollection('responses').find({"actions.animals": "sea"})
With no luck as I'm still getting an empty array.
If this isn't possible, I'm open for alternatives.
I've been looking for similar questions like this one: Mongo db - Querying nested array and objects but in that question they're looking for a specific element inside the "messages" object (in my case) for example. Same as in this other question: Query for a field in an object in array with Mongo? where they're using $elementMatch and I don't think it fits my needs.
I thought that in the Mongo Tutorial: Query array of documents might help, but again, they have a similar structure like mine but they have a property for the help element for example, something like:
{
"actions": [{
"action": "help",
"messages": [{}, {}]
}, {
"action": "animals",
"classifications": [{
"classification": "sea",
"messages": [{}, {}]
}, {
"classification": "land",
"messages": [{}, {}]
}]
}]
}
Is this the only way to make it to work? Or can I still maintain my original structure?
Edit
After trying #Graciano's answer I'm getting the following:
db.getCollection('responses').aggregate([{
$project: { "messages": "$actions.animals.sea.messages" }
}])
Result:
/* 1 */
{
"_id" : ObjectId("5ac42e65734d1d5beda1f99b"),
"messages" : [
[
[
{
"type" : 0,
"speech" : "There are a lot of sea animals, to name a few: "
},
{
"type" : 0,
"speech" : "Whale\nFishes\nSharks"
}
]
]
]
}
Now the error to solve is that it must be a single array, not an array of arrays of arrays of messages... how to solve this?

I just updated your query and now it will look like this:
db.collection.aggregate([
{$project: { "messages": "$actions.animals.sea.messages" }},
{$unwind: "$messages"},
{$unwind: "$messages"}
])
And the result will be:
{
"_id" : ObjectId("5ac5b80dd39d9355012f6af3"),
"messages" : [
{
"type" : 0,
"speech" : "There are a lot of sea animals, to name a few: "
},
{
"type" : 0,
"speech" : "Whale\nFishes\nSharks"
}
]
}
Now you will get only single array, all you need to do $unwind the arrays respectively.

if all you need are the messages you can use an aggregation and create an Array from the elements you want
db.collection.aggregate([
{$project: { items: "$actions."+parameter+".messages" },
{$unwind: "$messages"},
{$unwind: "$messages"}
}])

Related

Mongo DB find value in array of multiple nested arrays

I need to check if an ObjectId exists in a non nested array and in multiple nested arrays, I've managed to get very close using the aggregation framework, but got stuck in the very last step.
My documents have this structure:
{
"_id" : ObjectId("605ce5f063b1c2eb384c2b7f"),
"name" : "Test",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("6058e94c3994d04d2863962e")
],
"variations" : [
{
"varName" : "Var1",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
{
"varName" : "Var2",
"attrs" : [
ObjectId("60591791d4d41d0a6817d22a"),
ObjectId("60591791d4d41d0a6817d255"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
],
"storeId" : "9acdq9zgke49pw85"
}
Let´s say I need to check if this if this _id exists "6058e94c3994d04d28639616" in all arrays named attrs.
My aggregation query goes like this:
db.product.aggregate([
{
$match: {
storeId,
},
},
{
$project: {
_id: 0,
attrs: 1,
'variations.attrs': 1,
},
},
{
$project: {
attrs: 1,
vars: '$variations.attrs',
},
},
{
$unwind: '$vars',
},
{
$project: {
attr: {
$concatArrays: ['$vars', '$attrs'],
},
},
},
]);
which results in this:
[
{
attr: [
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
6058e94c3994d04d2863962e,
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f
]
},
{
attr: [
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
6058e94c3994d04d28639624,
6058e94c3994d04d28639627,
6058e94c3994d04d28639628,
6058e94c3994d04d2863963e
]
}
]
Assuming I have two products in my DB, I get this result. Each element in the outermost array is a different product.
The last bit, which is checking for this key "6058e94c3994d04d28639616", I could not find a way to do it with $group, since I dont have keys to group on.
Or with $match, adding this to the end of the aggregation:
{
$match: {
attr: "6058e94c3994d04d28639616",
},
},
But that results in an empty array. I know that $match does not query arrays like this, but could not find a way to do it with $in as well.
Is this too complicated of a Schema? I cannot have the original data embedded, since it is mutable and I would not be happy to change all products if something changed.
Will this be very expensive if I had like 10000 products?
Thanks in advance
You are trying to compare string 6058e94c3994d04d28639616 with ObjectId. Convert the string to ObjectId using $toObjectId operator when perform $match operation like this:
{
$match: {
$expr: {
$in: [{ $toObjectId: "6058e94c3994d04d28639616" }, "$attr"]
}
}
}

MongoDB array of objects

The problem I am facing is below:
I have a MongoDB document whose structure is as follows
"name": "XYZ",
"array":[
{
"value": "Alpha"
},
{
"value": "Beta"
},
{
"value": "Alpha"
},
]
and I have to count how many objects have value Alpha.
I have tried the following two queries but both only give me value 1.
db.current_database.find({array: {$elemMatch: {value: "Alpha"}}}).count()
db.current_database.find({'array.value': 'Alpha'}).count()
The find collection method returns documents, not fragments.
A few options to count occurrances of elements in an array:
Most languages provide a method to filter/reduce/count elements in an array, so this should be fairly straightforward on the client side.
The MongoDB aggregation framework provides $reduce, $filter, $size, $group, $unwind, and a few other operators that might be useful in this situation.
One possible solution using $reduce:
db.current_database.aggregate([
{$match: {"array.value": "Alpha"}},
{$addFields:{
count: {
$reduce: {
input: "$array",
initialValue: 0,
in: {
$cond: {
if: {$eq: ["$$this.value", "Alpha"]},
then: {$sum: ["$$value", 1]},
else: "$$value"
}
}
}
}
}}
])

MongoDB query to find document with duplicate value in array

tldr; I'm struggling to construct a query to
Make an aggregation to get a count of values on a certain key ("original_text_source"), which
Is in a sub-document that is in an array
Full description
I have embedded documents with arrays that are structured like this:
{
"_id" : ObjectId("0123456789"),
"type" : "some_object",
"relationships" : {
"x" : [ ObjectId("0123456789") ],
"y" : [ ObjectId("0123456789") ],
},
"properties" : [
{
"a" : "1"
},
{
"b" : "1"
},
{
"original_text_source" : "foo.txt"
},
]
}
The docs were created from exactly 10k text files, sorted in various folders. During inserting documents into the MongoDB (in batches) I messed up and moved a few files around, causing one file to be imported twice (my database has a count of exactly 10001 docs), but obviously I don't know which one it is. Since one of the "original_text_source" values has to have a count of 2, I was planning on just deleting one.
I read up on solutions with $elemMatch, but since my array element is a document, I'm not sure how to proceed. Maybe with mapReduce? But I can't transfer the logic to my doc structure.
I also could just create a new collection and reupload all, but in case I mess up again, I'd rather like to learn how to query for duplicates. It seems more elegant :-)
You can find duplicates with a simple aggregation like this:
db.collection.aggregate(
{ $group: { _id: "$properties.original_text_source", docIds: { $push: "$_id" }, docCount: { $sum: 1 } } },
{ $match: { "docCount": { $gt: 1 } } }
)
which gives you something like this:
{
"_id" : [
"foo.txt"
],
"docIds" : [
ObjectId("59d6323613940a78ba1d5ffa"),
ObjectId("59d6324213940a78ba1d5ffc")
],
"docCount" : 2.0
}
Run the following:
db.collection.aggregate([
{ $group: {
_id: { name: "$properties.original_text_source" },
idsForDuplicatedDocs: { $addToSet: "$_id" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} }
]);
Given a collection which contains two copies of the document you showed in your question, the above command will return:
{
"_id" : {
"name" : [
"foo.txt"
]
},
"idsForDuplicatedDocs" : [
ObjectId("59d631d2c26584cd8b7b3337"),
ObjectId("59d631cbc26584cd8b7b3333")
],
"count" : 2
}
Where ...
The attribute _id.name is the value of the duplicated properties.original_text_source
The attribute idsForDuplicatedDocs contains the _id values for each of the documents which have a duplicated properties.original_text_source
"reviewAndRating": [
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd9745e2d66162a6dd1f0ef",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7518",
"_id": "5bd974612d66162a6dd1f0f0",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd974622d66162a6dd1f0f1",
"rating": "5"
}
]

Only one element returned in array

I am trying to find elements from my MongoDB database with meteor.
I managed to filter and go through the structure of my array, but the result is a single element, and not all the elements matching the criteria.
Query :
var json = Tests1VerlIR.find({}, {fields: {entries: {$elemMatch: {'payload.id': {$eq: this.params._id}} } } }).fetch();
this.response.setHeader('Content-Type', 'application/json');
this.response.end(JSON.stringify(json));
Data Structure :
{"entries":
[{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"},
{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"}, {...}]}
Response :
[
{
"_id":
{
"_str": "576155d7a605348159cd1f1a"
},
"entries":
[
{
"method": "POST",
"source": "ex",
"path": "/ex",
"time": 1464615406900,
"payload":
{
"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"
},
"_id": "574c41ee578d01af3664cbaf"
}
]
}
]
You cannot return multiple elements of an array matching your criteria in any form of a basic .find() query. To match more than one element you need to use the .aggregate() method instead.
refer this link.
Tests1VerlIR.aggregate([
{ "$match": { "entries.payload.id": "2" } },
// Unwind the array to denormalize
{ "$unwind": "$entries" },
// Match specific array elements
{ "$match": { "entries.payload.id": "2" } },
// Group back to array form
{ "$group": {
"_id": "$_id",
"entries": { "$push": "$entries" }
}}
])
Solution :
var json = Tests1VerlIR.aggregate({"$unwind": "$entries"}, {$match: {'entries.payload.id': this.params._id} });

How can I provide multiple criteria for an attribute within an element of array in mongo query?

I have a collection with following documents:
{
"_id": 1,
"books": [
{
"id":"Sherlock Holmes",
"category":"Novel"
},
{
"id":"10 tips for cook",
"category":"Tips"
}
]
},
{
"_id": 2,
"books": [
{
"id":"10 tips for cook",
"category":"Tips"
}
]
},
{
"_id": 3,
"books": [
{
"id":"Sherlock Holmes",
"category":"Novel"
}
]
}
I want to query document contains both books with id "Sherlock Holmes" and "10 tips for cook", where its "_id" is 1.
I've tried with $in and $elemMatch but the results are those three. I only need one in this case.
Do you have any solutions?
Use the $and operator to search for the same field with multiple expression.
db.coll.find({
'$and': [
{'books.id': 'Sherlock Holmes'},
{'books.id': '10 tips for cook'}
]
})
Result:
{
"_id" : 1,
"books" : [
{
"id" : "Sherlock Holmes",
"category" : "Novel"
},
{
"id" : "10 tips for cook",
"category" : "Tips"
}
]
}
Because _id is unique in a MongoDB collection, so you can just query
db.myCollection.find({_id:1})
And if you don't want the whole document to be returned, you can use projection
db.myCollection.find({_id:1},{_id:0, books:1})

Resources