basic mongodb nested sets search, only want to retrieve single value - mongodb

I just started with document based datastores around 3-4 hours and I have a basic problem that I want to understand.
{
"_id": "5527e5ae06e55c02049bd114",
"owner": "John Doe",
"customers" : ["5527e3c806e55c01dad3a132", "5527e3c806e55c01dad3a133", "5527e3c806e55c01dad3a134"],
"location" : [
{
"address": "Wall St",
"location_id": "123123213",
"vendor" : [
{
"name" : "hello 123",
"price" : "3",
"serial" : "000000009730978e"
},
{
"name" : "hello abc",
"price" : "3.5",
"serial" : "0000000097308888"
}
]
},
{
"address" : "PCH 1",
"location_id": "987987",
"vendor" : [
{
"name" : "hello 456342",
"price" : "4",
"serial" : "000000009733452435"
},
{
"name" : "hello sdfsdg",
"price" : "4.5",
"serial" : "0000000095243532453"
}
]
}
]
}
So how can I find location.serial.price?
db.test.find_one( {"location.location_id" : "123123213" , "location.vendor.serial" : "000000009730978e" } )
would returns the entire object but I am just interested in location.serial.price where these conditions match.
Thanks a lot,
Ben

Usually you would use the positional-operator ($) to refer to array entries. But unfortunately this operator has a serious limitation: it does not work with nested arrays. So it does not help you in this case.
What you can do instead is use an aggregation pipeline which unwinds both arrays and then matches the serial.
db.test.aggregate([
// create a stream of location-documents
{ $unwind: "$location" },
// filter the stream by location-id
{ $match: { "location.id" : "123123213" },
// expand the remaining stream further to individual vendor-documents
{ $unwind: "$vendor" },
// filter the stream by serial
{ $match: { "location.vendor.serial": "000000009730978e" } }
]);
Keep in mind that aggregation can become quite slow. It also has a limitation of 16MB per aggregation step. You can avoid that limit with the allowDiskUse:true option, but that makes it even slower. So when you have lots of data and performance is a concern, you might want to reconsider your database schema.

Mongodb aggregation use here, below query will satisfied your criteria
db.collectionName.aggregate({
"$unwind": "$location"
},
{
"$match": {
"location.location_id": "123123213"
}
},
{
"$unwind": "$location.vendor"
},
{
"$match": {
"location.vendor.serial": "000000009730978e"
}
},
{
"$project": {
"serial": "$location.vendor.serial",
"price": "$location.vendor.price",
"_id": 0
}
}).pretty()

Related

How to join collections on nosqlbooster so that I can run $avg query on it?

I have two collections in my database with field names in the documents that are the same. I need to join these collections and then sum the values of the common field names and finally find the average as my output.
This is an example of a document in the first collection
{
"_id" : ObjectId("63074885ff3acbe0d63d7686"),
"year" : "2020",
"energy_products" : "Other Energy Products",
"sub_products" : "Other Energy Products",
"value_ktoe" : "70.4"
},
This is an example of a document in the second collection
{
"_id" : ObjectId("63074882ff3acbe0d63c391a"),
"year" : "2020",
"energy_products" : "Petroleum Products",
"sub_products" : "Other Petroleum Products",
"value_ktoe" : "10633.7"
},
So I need to join the collections and sum up all the values in the energy_products and the sub_products part and then find the average.
The output needs to look something like this
/* 1 */
{
"_id" : {
"energy_products" : "Petroleum Products"
},
"avg" : 18312.05625
},
/* 2 */
{
"_id" : {
"sub_products" : "Jet Fuel Kerosene"
},
"avg" : 4253.884375
},
Perform a $unionWith to "merge" the 2 collections. Perform a simple $group to get the $avg you need.
db.coll1.aggregate([
{
"$group": {
"_id": {
"energy_products": "$energy_products"
},
"avg": {
"$avg": {
"$toDouble": "$value_ktoe"
}
}
}
},
{
"$unionWith": {
"coll": "coll2",
"pipeline": [
{
"$group": {
"_id": {
"sub_products": "$sub_products"
},
"avg": {
"$avg": {
"$toDouble": "$value_ktoe"
}
}
}
}
]
}
}
])
Mongo Playground

group by field and count mongodb [duplicate]

I have following collection
{
"_id" : ObjectId("5b18d14cbc83fd271b6a157c"),
"status" : "pending",
"description" : "You have to complete the challenge...",
}
{
"_id" : ObjectId("5b18d31a27a37696ec8b5773"),
"status" : "completed",
"description" : "completed...",
}
{
"_id" : ObjectId("5b18d31a27a37696ec8b5775"),
"status" : "pending",
"description" : "pending...",
}
{
"_id" : ObjectId("5b18d31a27a37696ec8b5776"),
"status" : "inProgress",
"description" : "inProgress...",
}
I need to group by status and get all the keys dynamically which are in status
[
{
"completed": [
{
"_id": "5b18d31a27a37696ec8b5773",
"status": "completed",
"description": "completed..."
}
]
},
{
"pending": [
{
"_id": "5b18d14cbc83fd271b6a157c",
"status": "pending",
"description": "You have to complete the challenge..."
},
{
"_id": "5b18d31a27a37696ec8b5775",
"status": "pending",
"description": "pending..."
}
]
},
{
"inProgress": [
{
"_id": "5b18d31a27a37696ec8b5776",
"status": "inProgress",
"description": "inProgress..."
}
]
}
]
Not that I think it's a good idea and mostly because I don't see any "aggregation" here at all is that after "grouping" to add to an array you similarly $push all that content into array by the "status" grouping key and then convert into keys of a document in a $replaceRoot with $arrayToObject:
db.collection.aggregate([
{ "$group": {
"_id": "$status",
"data": { "$push": "$$ROOT" }
}},
{ "$group": {
"_id": null,
"data": {
"$push": {
"k": "$_id",
"v": "$data"
}
}
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
Returns:
{
"inProgress" : [
{
"_id" : ObjectId("5b18d31a27a37696ec8b5776"),
"status" : "inProgress",
"description" : "inProgress..."
}
],
"completed" : [
{
"_id" : ObjectId("5b18d31a27a37696ec8b5773"),
"status" : "completed",
"description" : "completed..."
}
],
"pending" : [
{
"_id" : ObjectId("5b18d14cbc83fd271b6a157c"),
"status" : "pending",
"description" : "You have to complete the challenge..."
},
{
"_id" : ObjectId("5b18d31a27a37696ec8b5775"),
"status" : "pending",
"description" : "pending..."
}
]
}
That might be okay IF you actually "aggregated" beforehand, but on any practically sized collection all that is doing is trying force the whole collection into a single document, and that's likely to break the BSON Limit of 16MB, so I just would not recommend even attempting this without "grouping" something else before this step.
Frankly, the same following code does the same thing, and without aggregation tricks and no BSON limit problem:
var obj = {};
// Using forEach as a premise for representing "any" cursor iteration form
db.collection.find().forEach(d => {
if (!obj.hasOwnProperty(d.status))
obj[d.status] = [];
obj[d.status].push(d);
})
printjson(obj);
Or a bit shorter:
var obj = {};
// Using forEach as a premise for representing "any" cursor iteration form
db.collection.find().forEach(d =>
obj[d.status] = [
...(obj.hasOwnProperty(d.status)) ? obj[d.status] : [],
d
]
)
printjson(obj);
Aggregations are used for "data reduction" and anything that is simply "reshaping results" without actually reducing the data returned from the server is usually better handled in client code anyway. You're still returning all data no matter what you do, and the client processing of the cursor has considerably less overhead. And NO restrictions.

Project multiple documents from a field's value using aggregation framework

Consider this document
{
"_id" : ObjectId("56d06614070b7f2b117b23db"),
"name" : "joe",
"value" : 3,
}
I need to get the following aggregation result :
[{
"name" : "joe",
},
{
"name" : "joe",
},
{
"name" : "joe",
}]
Do you have an idea on how to do that with aggregation framework ?
You can use $range:
db.collection.aggregate([
{ "$project": {
"_id": 0,
"name": {
"$map": {
"input": { "$range": [0,"$value"] },
"in": "$name"
}
}
}},
{ "$unwind": "$name" }
])
That essentially supplies [0,1,2] as in input array to $map, from which you can emit each value of "name" repeated. So the "$value" field in the document is being used as the upper bound or "length" of the array to produce.
Not really any other option if your MongoDB does not support that, other than you simply transform on the cursor instead.

how to sort array of objects by arbitrary list in mongo

I was looking for a way to sort array of object by an arbitrary list. Lets assume I have this array of objects.
[
{
"_id": "4JEEuhNIae",
"category": "grocery"
},
{
"_id": "4JW7miNITl",
"category": "food"
},
{
"_id": "4Je4kmrrbZ",
"category": "coffee"
},
{
"_id": "4JgAh3N86x",
"category": "coffee"
}
]
This is the array that I would like to use as sorting criteria. Records with foodshould come first, then coffeeand grocery.
['food','coffee','grocery']
Result should be:
[
{
"_id": "4JW7miNITl",
"category": "food"
},
{
"_id": "4Je4kmrrbZ",
"category": "coffee"
},
{
"_id": "4JgAh3N86x",
"category": "coffee"
},
{
"_id": "4JEEuhNIae",
"category": "grocery"
},
]
How can I do this type of sorting on mongodb by using mongoose? I really don't want to make any operations on the code after fetching data.
You could try running a custom comparator function with the native JavaScript sort() method on the array returned from the cursor.toArray() method:
var order = ["food", "coffee", "grocery"];
var docs = db.collection.find().toArray().sort(function(a, b) {
return order.indexOf(a.category) - order.indexOf(b.category);
});
printjson(docs);
Sample Output
[
{
"_id" : "4JW7miNITl",
"category" : "food"
},
{
"_id" : "4Je4kmrrbZ",
"category" : "coffee"
},
{
"_id" : "4JgAh3N86x",
"category" : "coffee"
},
{
"_id" : "4JEEuhNIae",
"category" : "grocery"
}
]
With the new MongoDB 3.4 version, you should be able to leverage the use of the native MongoDB operators $addFields and $indexOfArray in the aggregation framework.
The $addFields pipeline step allows you to $project new fields to existing documents without knowing all the other existing fields.
The $indexOfArray expression returns position of particular element in a given array.
So putting that altogether you could try the following aggregate operation (with MongoDB 3.4):
var order = ["food", "coffee", "grocery"],
projection = {
"$addFields" : {
"__order" : { "$indexOfArray" : [ order, "$category" ] }
}
},
sort = { "$sort" : { "__order" : 1 } };
db.collection.aggregate([ projection, sort]);

Can't include a field with $project operator in a MongoDB query

In my MongoDB database I have a collection called test that looks like this:
{
"_id" : ObjectId("5774f2807f93c094a6691506"),
"name" : "jack",
"city" : "LA",
"age" : 30.0,
"cars" : 0
}
{
"_id" : ObjectId("5774f2be7f93c094a6691507"),
"name" : "jack",
"city" : "LA",
"age" : 40.0,
"cars" : 0
}
{
"_id" : ObjectId("5774f2ed7f93c094a6691508"),
"name" : "peter",
"city" : "London",
"age" : 35.0,
"cars" : 1
}
I have made a query which groups the people by name and city and only displays the oldest element of each group. In addition it only displays the guys that have at least a car. The query looks like this:
db.getCollection('test').aggregate( [
{
"$match":{"cars":{$ne:0}}
},
{
"$group": { "_id": { name: "$name", city: "$city" }, "age":{$max:"$age"}}
}
,
{
"$project":{"age":1, "name":"$_id.name", "city":"$_id.city", "cars":true}
}
] )
After executing the above query I get the following result:
{
"_id" : {
"name" : "peter",
"city" : "London"
},
"age" : 35.0,
"name" : "peter",
"city" : "London"
}
It's correct because peter is the only guy that owns a car. The problem is that it doesn't display the "cars" field. As you can see in the query there is a $project operator and the "cars" field is set to true. So it should be displayed.
Does adding cars at the grouping stage help? I am assuming you need to count them.
"$group": {
"_id": { name: "$name", city: "$city" },
"age": { $max:"$age" }
"cars": { $sum:"$cars" }
}
The input of the project stage is the output of the grouping stage. In your original query, there was no cars field available in this input.
One solution could be to "$push" cars into an array while grouping the data.
db.getCollection('test').aggregate( [
{
"$match":{"cars":{$ne:0}}
},
{
"$group": { "_id": { name: "$name", city: "$city" },
cars : {$push : "$cars"}, "age":{$max:"$age"}}
}
,
{
"$project":{"age":1, "name":"$_id.name", "city":"$_id.city", "cars":true}
}
] )
It's not displayed because it's not created in the previous pipeline stage. To understand how the aggregation pipeline works, treat the the aggregation operation as you would with any database system.
The $group pipeline operator is similar to the SQL's GROUP BY clause. In SQL, you can't use GROUP BY unless we use any of the aggregation functions.
The same way, you have to use an aggregation function in MongoDB as well. In this instance, to generate a car field you would have to use the $first operator to return the top document fields in the group.
This works well when the documents getting into that $group pipeline step are ordered, hence the need for a $sort pipeline before the $group for ordering. You can then apply the $first operator to the ordered group to get the maximum (which is essentially the top document in the ordered group, with its corresponding car value).
A correct pipeline that returns the desired field would look like this:
db.test.aggregate([
{ "$match": { "cars": { "$ne": 0 } } },
{ "$sort": { "name": 1, "city": 1, "age": -1 } }
{
"$group": {
"_id": { "name": "$name", "city": "$city" },
"age": { "$first": "$age" } ,
"cars": { "$first": "$cars" }
}
},
{
"$project": {
"age": 1,
"cars": 1,
"_id": 0,
"name": "$_id.name",
"city": "$_id.city"
}
}
])