How to retrieve just the array values only of a nested field of MongoDB document? [duplicate] - mongodb

This question already has answers here:
How to return just the nested documents of an array from all documents
(2 answers)
Closed 3 years ago.
I'm trying to deep query and retrieve specific fields from MongoDB, but unfortunately couldn't able to figure out the correct solution.
Document data:
[ {
"_id": 39127198,
"name": "Mike",
"details": {
"age": 25,
"vehicles":[
{"brand":"Chevrolet","model":"Silverado","plate":"AB11"},
{"brand":"Jeep","model":"Cherokee","plate":"CG678"}
]
}
}, {
"_id": 39127198,
"name": "Taylor",
"details": {
"age": 25,
"vehicles": [
{"brand":"GMC","model":"Sierra","plate":"748397"}
]
}
} ]
My requirement: Return "vehicles" array alone for a specific player. Let's say for user "Mike" in this case.
Here is what I tried;
collection.find( {"name":"Mike"} )
.project( {"details.vehicles" : 1, "_id": 0, "name": 0} )
.toArray(function(err, result) { ... } )
collection.aggregate([
{ $match: { "name":"Mike" } },
{ $project: {"details.vehicles" : 1, "_id": 0, "name": 0} }
]).toArray(function(err, result) { ... } )
Here is what I get for the above code:
[
{
"details": {
"vehicles": [
{"brand":"Chevrolet","model":"Silverado","plate":"AB11"},
{"brand":"Jeep","model":"Cherokee","plate":"CG678"}
]
}
}
]
Expected:
[
{"brand":"Chevrolet","model":"Silverado","plate":"AB11"},
{"brand":"Jeep","model":"Cherokee","plate":"CG678"}
]
I am using MongoClient. MongoDB shell version v4.2.1

You can use $unwind and $replaceRoot stages to achieve this :
db.collection.aggregate([
{
$match: {
"name": "Mike"
}
},
{
$unwind: "$details.vehicles"
},
{
$replaceRoot: {
newRoot: "$details.vehicles"
}
}
])
Will output exactly what you need.
Hope it helps

The query:
db.vehi.aggregate( [
{ $match: { "name":"Mike" } },
{ $project: { "vehicles": "$details.vehicles", "_id": 0 } }
] ).next().vehicles
The exact output:
[
{
"brand" : "Chevrolet",
"model" : "Silverado",
"plate" : "AB11"
},
{
"brand" : "Jeep",
"model" : "Cherokee",
"plate" : "CG678"
}
]
- OR -
This also gets the same result:
db.vehi.find(
{ "name" : "Mike" },
{ "details.vehicles" : 1, _id : 0 }
).next().details.vehicles

Related

mongodb update document from first element of array

Consider a collection client with the following documents:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
]
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
]
}
]
I am attempting to set a field (ownershipClientCode) as the first element of the clientIds array.
The result should be like that:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1",
"ownershipClientCode" : "clientClusterCode_1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
],
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2",
"ownershipClientCode" : "clientClusterCode_2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
],
}
]
I'm using this query but I can't get sub object from the first element in the array
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
]
}
}
}
])
This query add the all object but I want only the field (clientClusterCode).
Some thing like that
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
].clientClusterCode
}
}
}
])
I'm using mongodb 4.0.0
You're very close: https://mongoplayground.net/p/HY1Pj0P4z12
db.collection.aggregate([
{
$addFields: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
])
You can use the dot notation within the $arrayElemAt as well as when you defining the field name.
To directly set the field, do something like this (use aggregation in the update): https://mongoplayground.net/p/js-usEJSH_A
db.collection.update({},
[
{
$set: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
],
{
multi: true
})
Note: The second method to update needs to be an array, so that it functions as an pipeline.

MongoDB $lookup on array of objects

Categories
{
"_id" : ObjectId("61740086893f048528d166b9"),
"name": "Category1",
"tracks" : [
"61c65353565a2d9a1cd3020d",
"61c74518962dc3efb96c3438",
"61c74775703176a6f72df444"
]
}
Tracks
{
"_id" : ObjectId("61c65353565a2d9a1cd3020d"),
"name" : "Track1",
"categoryId" : ObjectId("61740086893f048528d166b9"),
"creatorId" : ObjectId("61c6478304e98ed63e8ee7d3"),
"thumbnailId" : ObjectId("61c65353565a2d9a1cd3020c"),
"plays" : [],
"media" : {
"type" : "wav",
"url" : ""
},
"status" : "approved",
"downloads" : [],
"uploadedDate" : 1640387411
}
Assuming that I have 5 categories and each category has many tracks ID, I wanna get N last tracks for each category so I used this code below
categories.aggregate([
{
$project: {
tracks: { $slice: ["$tracks", -2] },
},
},
]
And the response is
[
{
"_id": "61740086893f048528d166b9",
"tracks": [
"61c74518962dc3efb96c3438",
"61c74775703176a6f72df444"
]
},
{
"_id": "61740094893f048528d166c1",
"tracks": []
},
{
"_id": "617400a0893f048528d166cb",
"tracks": []
}
]
So far it's good, but the question is how can I replace each category's tracks from an array of IDs to an array of objects?
I tried $loopup but I probably didn't implement the localField correctly.
Expected result
[
{
"_id": "61740086893f048528d166b9",
"tracks": [
{
"_id": ObjectId("61c74518962dc3efb96c3438")
...
},
{
"_id": ObjectId("61c74775703176a6f72df444")
...
}
]
},
{
"_id": "61740094893f048528d166c1",
"tracks": []
},
{
"_id": "617400a0893f048528d166cb",
"tracks": []
}
]
***** UPDATE *****
I'm trying to replace the creatorId by createdBy which is an object of the users from the users collection
Users
{
"_id": ObjectId("61c6478304e98ed63e8ee7cb"),
"email": "USER888#gmail.com",
"username": "USER999",
"tracks": [
ObjectId("61c65353565a2d9a1cd3020d"),
],
}
The expected result should be
[
{
"_id": "61740086893f048528d166b9",
"tracks": [
{
"_id": ObjectId("61c74518962dc3efb96c3438"),
"createdBy": {
"_id": "userId"
...
},
...
},
{
"_id": ObjectId("61c74775703176a6f72df444"),
"createdBy": {
"_id": "userId"
...
}
...
}
]
},
{
"_id": "61740094893f048528d166c1",
"tracks": []
},
{
"_id": "617400a0893f048528d166cb",
"tracks": []
}
]
In addition to the solution below by ray, I added the code here https://mongoplayground.net/p/8AjmnL-vhtz
The createdBy is at the top level but not under every track
$lookup is the correct way for you to find the corresponding object in Tracks collection. Why your code does not work is that you are storing strings in tracks array in Categories collection; while the _id of Tracks collection is ObjectId. There will be no $lookup result as the datatypes do not match. What you can do is converting the strings to ObjectId by using $toObjectId in a $map, and then do the $lookup
db.categories.aggregate([
{
$project: {
tracks: {
$slice: [
"$tracks",
-2
]
}
}
},
{
$project: {
tracks: {
"$map": {
"input": "$tracks",
"as": "t",
"in": {
"$toObjectId": "$$t"
}
}
}
}
},
{
"$lookup": {
"from": "tracks",
let: {
t: "$tracks"
},
pipeline: [
{
$match: {
$expr: {
"$in": [
"$_id",
"$$t"
]
}
}
}
],
"as": "tracks"
}
}
])
Here is the Mongo playground for your reference.

MongoDB Aggregate - Group certain items in document array

I'm quite new on MongoDB
Having a document like:
"_id":0001
"Name": "John"
"Contacts": [
{
"Person" : [
{
"User" : {
"_id" : ObjectId("5836b916885383034437d230"),
"Name": "Name1",
"Age" : 25,
}
},
{
"User" : {
"_id" : ObjectId("2836b916885383034437d230"),
"Name": "Name2",
"Age" : 30,
}
},
{
"User" : {
"_id" : ObjectId("1835b916885383034437d230"),
"Name": "Name3",
"Age" : 31,
}
},
}
which is the best way to get an output with the information of the Contacts with age greater or equal than 30 years?
Output should like:
{_id: "John", "ContactName":"Name2", "Age":30 }
{_id: "John", "ContactName":"Name3", "Age":31 }
Is aggregation the best way to do it, or it can be done by using a simple "find" statement?
$match
$unwind
$unwind
$match
$project
db.collection.aggregate([
{
"$match": {
"Contacts.Person.User.Age": {
"$gte": 30
}
}
},
{
"$unwind": "$Contacts"
},
{
"$unwind": "$Contacts.Person"
},
{
"$match": {
"Contacts.Person.User.Age": {
"$gte": 30
}
}
},
{
"$project": {
"_id": "$Name",
"ContactName": "$Contacts.Person.User.Name",
"Age": "$Contacts.Person.User.Age"
}
}
])
mongoplayground

MongoDB select distinct and count

I have a product collection which looks like that:
products = [
{
"ref": "1",
"facets": [
{
"type":"category",
"val":"kitchen"
},
{
"type":"category",
"val":"bedroom"
},
{
"type":"material",
"val":"wood"
}
]
},
{
"ref": "2",
"facets": [
{
"type":"category",
"val":"kitchen"
},
{
"type":"category",
"val":"livingroom"
},
{
"type":"material",
"val":"plastic"
}
]
}
]
I would like to select and count the distinct categories and the number of products that have the category (Note that a product can have more than one category). Something like that:
[
{
"category": "kitchen",
"numberOfProducts": 2
},
{
"category": "bedroom",
"numberOfProducts": 1
},
{
"category": "livingroom",
"numberOfProducts": 1
}
]
And it would be better if I could get the same result for each different facet type, something like that:
[
{
"facetType": "category",
"distinctValues":
[
{
"val": "kitchen",
"numberOfProducts": 2
},
{
"val": "livingroom",
"numberOfProducts": 1
},
{
"val": "bedroom",
"numberOfProducts": 1
}
]
},
{
"facetType": "material",
"distinctValues":
[
{
"val": "wood",
"numberOfProducts": 1
},
{
"val": "plastic",
"numberOfProducts": 1
}
]
}
]
I am doing tests with distinct, aggregate and mapReduce. But can't achieve the results needed. Can anybody tell me the good way?
UPDATE:
With aggregate, this give me the different facet categories that a product have, but not the values nor the count of different values:
db.products.aggregate([
{$match:{'content.facets.type':'category'}},
{$group:{ _id: '$content.facets.type'} }
]).pretty();
The following aggregation pipeline will give you the desired result. In the first pipeline step, you need to do an $unwind operation on the facets array so that it's deconstructed to output a document for each element. After the $unwind stage is the first of the $group operations which groups the documents from the previous stream by category and type and calculates the number of products in each group using $sum. The next $group operation in the next pipeline stage then creates the array that holds the aggregated values by using $addToSet operator. The final pipeline stage is the $project operation which then transforms the document in the stream by modifying existing fields:
var pipeline = [
{ "$unwind": "$facets" },
{
"$group": {
"_id": {
"facetType": "$facets.type",
"value": "$facets.val"
},
"count": { "$sum": 1 }
}
},
{
"$group": {
"_id": "$_id.facetType",
"distinctValues": {
"$addToSet": {
"val": "$_id.value",
"numberOfProducts": "$count"
}
}
}
},
{
"$project": {
"_id": 0,
"facetType": "$_id",
"distinctValues": 1
}
}
];
db.product.aggregate(pipeline);
Output
/* 0 */
{
"result" : [
{
"distinctValues" : [
{
"val" : "kitchen",
"numberOfProducts" : 2
},
{
"val" : "bedroom",
"numberOfProducts" : 1
},
{
"val" : "livingroom",
"numberOfProducts" : 1
}
],
"facetType" : "category"
},
{
"distinctValues" : [
{
"val" : "wood",
"numberOfProducts" : 1
},
{
"val" : "plastic",
"numberOfProducts" : 1
}
],
"facetType" : "material"
}
],
"ok" : 1
}

MongoDb aggregate and group by two fields depending on values

I want to aggregate over a collection where a type is given. If the type is foo I want to group by the field author, if the type is bar I want to group by user.
All this should happen in one query.
Example Data:
{
"_id": 1,
"author": {
"someField": "abc",
},
"type": "foo"
}
{
"_id": 2,
"author": {
"someField": "abc",
},
"type": "foo"
}
{
"_id": 3,
"user": {
"someField": "abc",
},
"type": "bar"
}
This user field is only existing if the type is bar.
So basically something like that... tried to express it with an $or.
function () {
var results = db.vote.aggregate( [
{ $or: [ {
{ $match : { type : "foo" } },
{ $group : { _id : "$author", sumAuthor : {$sum : 1} } } },
{ { $match : { type : "bar" } },
{ $group : { _id : "$user", sumUser : {$sum : 1} } }
} ] }
] );
return results;
}
Does someone have a good solution for this?
I think it can be done by
db.c.aggregate([{
$group : {
_id : {
$cond : [{
$eq : [ "$type", "foo"]
}, "author", "user"]
},
sum : {
$sum : 1
}
}
}]);
The solution below can be cleaned up a bit...
For "bar" (note: for "foo", you have to change a bit)
db.vote.aggregate(
{
$project:{
user:{ $ifNull: ["$user", "notbar"]},
type:1
}
},
{
$group:{
_id:{_id:"$user.someField"},
sumUser:{$sum:1}
}
}
)
Also note: In you final answer, anything that is not of type "bar" will have an _id=null
What you want here is the $cond operator, which is a ternary operator returning a specific value where the condition is true or false.
db.vote.aggregate([
{ "$group": {
"_id": null,
"sumUser": {
"$sum": {
"$cond": [ { "$eq": [ "$type", "user" ] }, 1, 0 ]
}
},
"sumAuhtor": {
"$sum": {
"$cond": [ { "$eq": [ "$type", "auhtor" ] }, 1, 0 ]
}
}
}}
])
This basically tests the "type" of the current document and decides whether to pass either 1 or 0 to the $sum operation.
This also avoids errant grouping should the "user" and "author" fields contain the same values as they do in your example. The end result is a single document with the count of both types.