Return flattened array from each element in a nested array mongo with aggregation query - mongodb

I have a collection setup with documents that look like :
{
"_id" : ObjectId("5c786d9486c1140b1452d777"),
"code" : "TEST-123",
"owner" : "John",
"cars" : [
{
"carPlate" : "QPZ-756",
"carColor" : "blue"
},
{
"carPlate" : "REF-473",
"carColor" : "red"
}
],
}
I'm looking for an mongo aggregate query that grabs each carPlate and outputs the following for every document in the collection
{
"carPlate" : "QPZ-756",
"owner" : "John",
"code" : "TEST-123",
},
{
"carPlate" : "REF-473",
"owner" : "John",
"code" : "TEST-123",
},
I had a look at the $map operator, would this be a good place to start?

I would use $unwind to flatten the array followed by $mergeObjects to combine keys along with $replaceRoot to promote the merge documents to the top.
Something like
db.colname.aggregate([
{$unwind:"$cars"},
{$replaceRoot:{newRoot:{$mergeObjects:[{owner:"$owner"}, "$cars"]}}}
])

Related

Using MongoDB $arrayToObject using custom keys

Consider the following document that is output of an aggregation pipeline:
{ "_id" : 1, "results" : [
{ "status" : "HOLD", "footage" : 43.01, "pieces" : 1 },
{ "status" : "ACCEPTED", "footage" : 80.90, "pieces" : 2 },
{ "status" : "REJECTED", "footage" : 40.00, "pieces" : 1 }
]}
I am trying to create custom keys that concatenate the status with the footage and piece fields resulting in:
{ "_id" : 1, "results" :
{
"HOLD_footage": 43.01,
"HOLD_pieces":1,
"ACCEPTED_footage": 80.90,
"ACCEPTED_pieces": 2,
"REJECTED_footage":40.00,
"REJECTED_pieces":1
}
}
You can do following:
use $map to build the k-v tuples with key you want(i.e. HOLD_footage...)
use $concatArrays to group back the arrays of k-v tuples
use $objectToArray to get back the array form you expected
Here is the Mongo playground for your reference.

MongoDB get all embedded documents where condition is met

I did this in my mongodb:
db.teams.insert({name:"Alpha team",employees:[{name:"john"},{name:"david"}]});
db.teams.insert({name:"True team",employees:[{name:"oliver"},{name:"sam"}]});
db.teams.insert({name:"Blue team",employees:[{name:"jane"},{name:"raji"}]});
db.teams.find({"employees.name":/.*o.*/});
But what I got was:
{ "_id" : ObjectId("5ddf3ca83c182cc5354a15dd"), "name" : "Alpha team", "employees" : [ { "name" : "john" }, { "name" : "david" } ] }
{ "_id" : ObjectId("5ddf3ca93c182cc5354a15de"), "name" : "True team", "employees" : [ { "name" : "oliver" }, { "name" : "sam" } ] }
But what I really want is
[{"name":"john"},{"name":"oliver"}]
I'm having a hard time finding examples of this without using some kind of programmatic iterator/loop. Or examples I find return the parent document, which means I'd have to parse out the embedded array employees and do some kind of UNION statement?
Eg.
How to get embedded document in mongodb?
Retrieve only the queried element in an object array in MongoDB collection
Can someone point me in the right direction?
Please add projections to filter out the fields you don't need. Please refer the project link mongodb projections
Your find query should be constructed with the projection parameters like below:
db.teams.find({"employees.name":/.*o.*/}, {_id:0, "employees.name": 1});
This will return you:
[{"name":"john"},{"name":"oliver"}]
Can be solved with a simple aggregation pipeline.
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
])
EDIT:
OP Wants to skip the parent fields. Modified query:
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
{$project : {"name":"$employees.name",_id:0}}
])
Output:
{ "name" : "john" }
{ "name" : "oliver" }

Mongodb aggregate lookup return only one field of array

i have some collections for our project.
Casts collection contains movie casts
Contents collection contains movie contents
i want to run aggregate lookup for get information about movie casts with position type.
i removed collections details unnecessary fields.
Casts details:
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_name" : "",
"en_name" : "Ehsan",
"fa_bio" : "",
"en_bio" : ""
}
Contents details:
{
"_id" : ObjectId("5a6b8b734f1408137f79e2cc"),
"casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan2",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Director"
}
},
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan1",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Writers"
}
}
],
"status" : 0,
"created" : Timestamp(1516997542, 4),
"updated" : Timestamp(1516997542, 5)
}
when i run aggregate lookup with bellow query, in new generated lookup array only one casts contents If in accordance with above casts array value aggregate lookup should return two casts content with two type. in casts array value exists two type of casts, 1) writers and directors. but returned director casts content. _casts should contains two object not one object!
aggregate lookup query:
{$lookup:{from:"casts",localField:"casts._id",foreignField:"_id",as:"_casts"}}
result:
{
"_id" : ObjectId("5a6b8b734f1408137f79e2cc"),
"casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan2",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Director"
}
},
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan1",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Writers"
}
}
],
"_casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_name" : "",
"en_name" : "Ehsan",
"fa_bio" : "",
"en_bio" : ""
}
],
"status" : 0,
"created" : Timestamp(1516997542, 4),
"updated" : Timestamp(1516997542, 5)
}
EDIT-1
finally my problem is solved. i have only one problem with this query, this query doesn't show root document fields. finally solve this problem. finally query exists in EDIT-2.
query:
db.contents.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"casts.info"}},
{"$unwind":"$casts.info"},
{"$group":{"_id":"$_id", "casts":{"$push":"$casts"}}},
])
EDIT-2
db.contents.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"casts.info"}},
{"$unwind":"$casts.info"},
{$group:{"_id":"$_id", "data":{"$first":"$$ROOT"}, "casts":{"$push":"$casts"}}},
{$replaceRoot:{"newRoot":{"$mergeObjects":["$data",{"casts‌​":"$casts"}]}}},
{$project:{"casts":0}}
]).pretty()
This is expected behavior.
From the docs,
If your localField is an array, you may want to add an $unwind stage
to your pipeline. Otherwise, the equality condition between the
localField and foreignField is foreignField: { $in: [
localField.elem1, localField.elem2, ... ] }.
So to join each local field array element with foreign field element you have to $unwind the local array.
db.content.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"_casts"}}
])
Vendor Collection
Items Collection
db.items.aggregate([
{ $match:
{"item_id":{$eq:"I001"}}
},
{
$lookup:{
from:"vendor",
localField:"vendor_id",
foreignField:"vendor_id",
as:"vendor_details"
}
},
{
$unwind:"$vendor_details"
},
{
$project:{
"_id":0,
"vendor_id":0,
"vendor_details.vendor_company_description":0,
"vendor_details._id":0,
"vendor_details.country":0,
"vendor_details.city":0,
"vendor_details.website":0
}
}
]);
Output
Your Casts collection shows only 1 document. Your Contents collection, likewise, shows only 1 document.
This is 1 to 1 - not 1 to 2. Aggregate is working as designed.
The Contents document has 2 "casts." These 2 casts are sub-documents. Work with those as sub-documents, or re-design your collections. I don't like using sub-documents unless I know I will not need to use them as look-ups or join on them.
I would suggest you re-design your collection.
Your Contents collection (it makes me think of "Movies") could look like this:
_id
title
releaseDate
genre
etc.
You can create a MovieCasts collection like this:
_id
movieId (this is _id from Contents collection, above)
castId (this is _id from Casts collection, below)
Casts
_id
name
age
etc.

Using Mongo query to find an in array element

Have records in my db with such structure:
{
"_id" : "YA14163134",
"discount" : "",
"retail" : "115.0000",
"cost" : "",
"description" : "Caterpillar Mens Big Twist Analog Watch",
"stock_update" : "05",
"brand" : "Kronos",
"img_url" : "image2342000.jpg",
"UPC" : "4895053708012",
"stock" : [ [ "1611292138", "5" ], [ "1612032232", "4" ], [ "1612050918", "0" ] ]
}
and looking for query to get all records that have in "stock" "1612050918" value. That is update id.
Trying something like:
db.vlc.find({stock: {$elemMatch:{$all:["1612050918"]}}})
or
db.vlc.find({stock: { $in : ['1611292138']}})
or
db.vlc.find({stock: { $all : [[1611292138]]}})
with no result. It works only if I include in request second array element like here
db.vlc.find({stock: { $all : [['1611292138', '7']]}})
but that limit my request to all items from update with qnty 7 when I need with any qnty. Thank you in advance!
use this query:
{
"stock" : {
"$elemMatch" : {
"$elemMatch" : {
"$eq" : "1611292138"
}
}
}
}
Explanation:
The first $elemMatch allows you to scan all three arrays under stock
The nex $elemMatch allows you to scan the two elements in the sub-arrays
since $elemMatch requires a query object, the $eq notation is used for a literal match.
If you know that "1611292138" will always be the first element of the sub-array, your query becomes simpler:
{ "stock" : { "$elemMatch" : { "0" : "1611292138" } } }
Explanation:
Scan all arrays under stock
Look for "1611292138" in the first slot of each sub-array
Use nested $elemMatch as below :
db.vlc.find({stock: { "$elemMatch":{"$elemMatch":{"$all":["1612050918"]}}}})
Or
db.vlc.find({stock: {"$elemMatch":{ "$elemMatch":{"$in" : ["1612050918"]}}}})

Get specific object in array of array in MongoDB

I need get a specific object in array of array in MongoDB.
I need get only the task object = [_id = ObjectId("543429a2cb38b1d83c3ff2c2")].
My document (projects):
{
"_id" : ObjectId("543428c2cb38b1d83c3ff2bd"),
"name" : "new project",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"members" : [
ObjectId("5424ac37eb0ea85d4c921f8b")
],
"US" : [
{
"_id" : ObjectId("5434297fcb38b1d83c3ff2c0"),
"name" : "Test Story",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"tasks" : [
{
"_id" : ObjectId("54342987cb38b1d83c3ff2c1"),
"name" : "teste3",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
},
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
]
}
]
}
Result expected:
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
But i not getting it.
I still testing with no success:
db.projects.find({
"US.tasks._id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}, { "US.tasks.$" : 1 })
I tryed with $elemMatch too, but return nothing.
db.projects.find({
"US" : {
"tasks" : {
$elemMatch : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}
}
}
})
Can i get ONLY my result expected using find()? If not, what and how use?
Thanks!
You will need an aggregation for that:
db.projects.aggregate([{$unwind:"$US"},
{$unwind:"$US.tasks"},
{$match:{"US.tasks._id":ObjectId("543429a2cb38b1d83c3ff2c2")}},
{$project:{_id:0,"task":"$US.tasks"}}])
should return
{ task : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
Explanation:
$unwind creates a new (virtual) document for each array element
$match is the query part of your find
$project is similar as to project part in find i.e. it specifies the fields you want to get in the results
You might want to add a second $match before the $unwind if you know the document you are searching (look at performance metrics).
Edit: added a second $unwind since US is an array.
Don't know what you are doing (so realy can't tell and just sugesting) but you might want to examine if your schema (and mongodb) is ideal for your task because the document looks just like denormalized relational data probably a relational database would be better for you.