MongoDB distinct query against an array of objects - mongodb

If I have a schema similar to the following:
{
"_id" : "12345",
"_class" : "Refrigerator",
"items" : {
"APPLE" : {
"id" : 123,
"name" : "APPLE"
},
"BANANA" : {
"id" : 234,
"name" : "BANANA"
},
"STRAWBERRY" : {
"id" : 345,
"name" : "STRAWBERRY"
}
},
},
{
"_id" : "23456",
"_class" : "Pantry",
"items" : {
"CEREAL" : {
"id" : 456,
"name" : "CEREAL"
}
},
}
I want to get a list of distinct items where the _class=Refrigerator. The result should be ["APPLE","BANANA","STRAWBERRY"].
How can I do this? Thank you!

You can utilise aggregation operator $objectToArray (SERVER-23310) to turn keys into values. It should be able to group 'unknown' or 'dynamic' fields. This operator is available in MongoDB v3.4.4+
Given your example documents, you can use below aggregation pipeline
db.collection.aggregate([
{$match:{"class":"Refrigerator"}},
{$project:{"items":{$objectToArray:"$items"}}},
{$unwind:"$items"},
{$group:{_id:"$_id", result:{$addToSet:"$items.k"}}}
])
See also the manual for: $unwind, $group, and $addToSet
If applicable for your use case, I would recommend to re-consider your data schema for easier access to the information that you're after. MongoDB has a flexible schema, you should use this for your application benefits.

Related

Mongo Query for 2 values in array list in a document field

I have a document in a collection that has a field called "myList", it has list items and I need to be able to query collection documents that have field "status" of "good" and "doneBy" with a value of "system" in the "myList" field:
[collection].myList
[
{
"location" : "3826487.pdf",
"status" : "good",
"time" : ISODate("2017-06-27T20:03:46.512Z"),
"reportIdx" : 0,
"doneBy" : "System"
},
{
"location" : "rt-0.pdf",
"status" : "bad",
"time" : ISODate("2017-06-28T16:24:16.559Z"),
"reportIdx" : 0,
"doneBy" : "System"
}
]
It should return all documents that have a list item qualified by the first one in this list. Even though the second list item has "bad", it should return this collection doc with "myList" having these 2 list items.
I figured out that a search for one of the fields would be this but how to do both , I'm not sure of the syntax.
db.getCollection('[collection]').find({myList : { $elemMatch : { "status" : "good" }}})
I believe I found it:
db.getCollection('[collection]')find({ myList:
{ $all: [
{$elemMatch : { "status" : "good" }},
{$elemMatch : {"doneBy" : "System"}}
]
} })

Mongodb accessing documents

I've the following db:
{ "_id" : 1, "results" : [ { "product" : "abc", "score" : 10 }, { "product" : "xyz", "score" : 5 } ] }
{ "_id" : 2, "results" : [ { "product" : "abc", "score" : 8 }, { "product" : "xyz", "score" : 7 } ] }
{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "xyz", "score" : 8 } ] }
I want to show the first score of each _id, i tried the following:
db.students.find({},{"results.$":1})
But it doesn't seem to work, any advice?
You can take advantage of aggregation pipeline to solve this.
Use $project in conjunction with $arrayElemAt to point to appropriate node index in the array.
So, to extract the documents of the first score, have written below query.
db.students.aggregate([ {$project: { scoredoc:{$arrayElemAt:["$results",0]}} } ]);
In case if you just wish to have scores excluding product, use $results.score as shown below.
db.students.aggregate([ {$project: { scoredoc:{$arrayElemAt:["$results.score",0]}} } ]);
Here scoredoc object will have all documents of first score element.
Hope this helps!
According to above mentioned description please try executing following query in MongoDB shell
db.students.find(
{results:
{$elemMatch:{score:{$exists:true}}}}, {'results.$.score':1}
)
According to MongoDB documentation
The positional $ operator limits the contents of an from the
query results to contain only the first element matching the query
document.
Hence in above mentioned query positional $ operator is used in projection section to retrieve first score of each document.

MongoDb Pivot on key value

I am new bee in MongoDB, I have collection with key value pairs like below..
input collection
{"restaurantid" : NumberInt("1"),
"Properties" : [
{ "Key" : "A", "Value" : NumberInt("25") },
{ "Key" : "B", "Value" : "StringValue" },
{ "Key" : "C", "Value" : ISODate("2017-02-09") }
] }
I am looking result set as
Output Collection
{ "restaurantid" : NumberInt("1"),
"A" : NumberInt("25"),
"B" : "StringValue",
"C" : ISODate("2017-02-09")
}
How do I get it without hardcoding "A", "B", "C" in the aggregation pipeline. My key value pairs are going to get bigger and is variable for given id
how do I get it without hardcoding “A”, “B”, “C” in the aggregation pipeline. My key value pairs are going to get bigger and is variable for given id
You can utilise new aggregation operator $arrayToObject (SERVER-18794) to pivot MongoDB keys. This operator currently is available in MongoDB v3.4.4+
For example, you can restructure your schema:
{
"restaurantid" : NumberInt("1"),
"Properties" : [
{ "k" : "A", "v" : NumberInt("25") },
{ "k" : "B", "v" : "StringValue" },
{ "k" : "C", "v" : ISODate("2017-02-09") }
]
}
Then you can utilise example aggregation pipeline below:
db.collection.aggregate(
[
{$project:{"tmp":{$arrayToObject:"$Properties"}, "restaurantid":"$resturantid"}},
{$addFields:{"tmp.restaurantid":"$restaurantid"}},
{$replaceRoot:{newRoot:"$tmp"}}
]);
See also $replaceRoot and $addFields. Depending on your use case, you could also take advantage of MongoDB flexible schema and reconsider your document model.

Find field inside an array using $elemMatch

I have a invoice collection, in which I want find the document with a specified book's id.
db.invoice.find({"sold": {$elemMatch: {"book":{$elemMatch:{"_id":"574e68e5ac9fbac82489b689"}}}}})
I tried this but it didn't work
{
"_id" : ObjectId("575e9bf5576533313ac9d993"),
"sold" : [
{
"book" : {
"_id" : "574e68e5ac9fbac82489b689",
"price" : 100,
},
"quantity" : 10,
"total_price" : 1000
}
],
"date" : "13-06-2016"
}
You do not need the $elemMatch query operator here because you only specify only a single query condition.
db.invoice.find( { 'sold.book._id': '574e68e5ac9fbac82489b689' } )
This is mentioned in the documentation:
If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch
https://docs.mongodb.com/manual/reference/operator/query/elemMatch/#op._S_elemMatch
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
mongo> db.invoice.find({"sold": {$elemMatch: {"book._id":"574e68e5ac9fbac82489b689"}}}).pretty()
{
"_id" : ObjectId("575e9bf5576533313ac9d993"),
"sold" : [
{
"book" : {
"_id" : "574e68e5ac9fbac82489b689",
"price" : 100
},
"quantity" : 10,
"total_price" : 1000
}
],
"date" : "13-06-2016"
}

Reactive Mongo Extensions: How to use $push with $each using query DSL in reactive mongo extensions

I want to push list of documents in mongodb document array field. In mongodb we can use $push with $each operator, but how we can using in reactive mongo extensions. example as below:
This is my doucment
{
"_id" : ObjectId("56cadf56c3ad8cb30223613a"),
"locations" : [
{
"_id" : ObjectId("56cadf56c3ad8cb30223613f"),
"fieldType" : "List",
"locationType" : "DEFAULT",
"createdOn" : ISODate("2016-02-22T15:43:42.929Z"),
"value" : "Default",
"active" : true
}
]
}
list of documents want to push in "locations" attribute of above document.
[
{
"_id" : ObjectId("56cae036c3ad8cb20a23614b"),
"locationType" : "COMPANY",
"createdOn" : ISODate("2016-02-22T15:47:26.168Z"),
"value" : "India",
"active" : true
},
{
"_id" : ObjectId("56cae036c3ad8cb20a23614b"),
"locationType" : "COMPANY",
"createdOn" : ISODate("2016-02-22T15:47:26.168Z"),
"value" : "USA",
"active" : true
}
]
val updateOp = Json.obj("$push"-> Json.obj("locations" -> Json.obj("$each" -> Json.toJson(locations))))
collection.update(Json.obj("_id" -> myID), updateOp)
should do it for you given that 'locations' is a sequence of 'Location' which is a case class with proper json Reads and Writes. It will be pretty much the same using BSON.
BTW, maybe you want to use $addToSet here instead.