MongoDB: Unable to find required records - mongodb

I am new to MongoDB. And I have following collections in my MongoDB. My Problem is I am not able to make a find command which gives required facilities based on provided ID or Name.
Find command which I have tried BUT NOT working :-
db.mgh_facilities.find({facilities: {$elemMatch: {name: "Foreign exchange assistance"}}}).
For me its returning all the records. Shouldn't it be just returning { "id" : "11", "name" : "Foreign exchange assistance" }
My Collections:-
{
"_id" : ObjectId("548acc28ae6ff1c0fd1d7470"),
"responseCode" : "true",
"facilities" : [
{
"id" : "11",
"name" : "Foreign exchange assistance"
},
{
"id" : "12",
"name" : "Assistance with luggage on request"
},
{
"id" : "13",
"name" : "24 hours power back-up"
},
{
"id" : "14",
"name" : "A/C Power Backup Available"
},
{
"id" : "15",
"name" : "swimming pool"
},
{
"id" : "35",
"name" : "shoe cleaning service"
},
{
"id" : "36",
"name" : "Smoke detectors"
},
{
"id" : "37",
"name" : "Fire Extinguishers in each room"
},
{
"id" : "38",
"name" : "Pest Control"
},
{
"id" : "39",
"name" : "Conference / Banquet Hall"
},
{
"id" : "45",
"name" : "Restaurant"
},
{
"id" : "53",
"name" : "Anti-slip ramps"
},
{
"id" : "56",
"name" : "Tea/ Coffee Maker in the Rooms"
},
{
"id" : "59",
"name" : "Wi Fi Internet"
}
]
}

The find command has found the document which contains the facility object with a name matching your search. It doesn't know that you only want to see the single subdocument responsible for that document matching your search criteria.
You can tell mongo that you want to see that element with the positional $ operator.
db.mgh_facilities.find({"facilities.name": "Foreign exchange assistance"}, {"facilities.$": true})

You can try this
db.mgh_facilities.aggregate([
{
$unwind:"$facilities"
},
{
$match:{"facilities.name": "Foreign exchange assistance"}
},{
$project:{
_id:0,
facilities:1
}
}
])
result:
{
"result" : [
{
"facilities" : {
"id" : "11",
"name" : "Foreign exchange assistance"
}
}
],
"ok" : 1
}

Related

how to make mongodb aggregate with $in, $or $and

Here is Query in mongodb , where i used to get information on the basis of status, verify_status and their category as well as their Location, their is only one location for the field but need to find in multiple set of location.
Update : I try to run this query in Robo3t , it return blank Result but same query i run in Studio3t it shows expected Result. is there any version issue.
{
"status":{
"$eq":"Active"
},
"verify_status":{
"$eq":"Yes"
},
"$or":[
{
"category":{
"$eq":{
"$oid": "5da0f71e15712b5f7723bb58"
}
}
},
{
"multi_car_status":{
"$eq":"ON"
},
"additional_category":{
"$eq": "5da0f71e15712b5f7723bb58"
}
}
],
"driver_location":{
"$in":[
"5dee787f15712b443f6a5e06",
"5dee787f15712b443f6a5e06"
]
}
}
When i removed the driver_location or "$or" its return resul.there are
result in the db but it return return 0 result. i can't get the result with the both the operators. below is the sample data.
{
"_id" : ObjectId("5ead0f5f15712b5df22d7e23"),
"driver_location" : "5dee787f15712b443f6a5e06",
"category" : ObjectId("5da0f71e15712b5f7723bb58"),
"image" : "7f8e1ddfbe862a7ac4386b2f41fe953f.jpg",
"driver_name" : "Ravi aa",
"email" : "######gmail.com",
"dail_code" : "+1",
"mobile_number" : "83748746",
"dob" : "10/05/2000",
"password" : "######",
"verify_status" : "Yes",
"status" : "Active",
"availability" : "Yes",
"mode" : "Available",
"gender" : "male",
"vehicle_type" : null,
"vehicle_maker" : "5da0f05115712b5fb36e3d02",
"vehicle_model" : "5db22e1215712b25a36b8a3c",
"loc" : {
"lon" : 77.35124777,
"lat" : 28.6543715
},
"address" : {
"address" : "delhi",
"county" : "United States",
"state" : "AR",
"city" : "delhi",
"postal_code" : "122001"
},
"multi_car_status" : "ON",
"additional_category" : [
"5da09ed315712b413333ca58",
"5da0f6fc15712b5c1956eed6",
"5da0f73b15712b5fb36e3d09"
],
}

MOngoDB query to groupby with different fields based on condition to return the latest record

I want to groupby based on condition where my target field can be sender.category or reciever.category based on condition that either of those field belongs to "cat1", and get the last record of each of on sender.id or reciever.id based on createdAt.
Sample Json1:
{
"code" : "34242342",
"name" : "name1",
"amount" : 200,
"sender" : {
"id" : "fsrfsr3242",
"name" : "name2",
"phone" : "12345678",
"category": "cat1"
},
"receiver" : {
"id" : "42342rewr",
"name" : "naem3",
"phone" : "5653679755",
"category": "cat2"
},
"message" : "",
"status" : "done",
"createdAt" : "2019-09-27T09:17:32.597Z"
}
Sample Json2:
{
"code" : "34242342",
"name" : "name1",
"amount" : 200,
"sender" : {
"id" : "fsrfsr3242",
"name" : "name2",
"phone" : "12345678",
"category": "cat3"
},
"receiver" : {
"id" : "42342rewr",
"name" : "naem3",
"phone" : "5653679755",
"category": "cat1"
},
"message" : "",
"status" : "done",
"createdAt" : "2019-09-27T09:17:32.597Z"
}
Query:
[{$match: {
$or: [{ "sender.category": 'cat1' }, {"receiver.category" : 'cat1'}]
}}, {$sort: {
"createdAt": 1
}}, {$group: {
_id: {sender :"$sender.id", reciever : "$receiver.id"},
lastrecord: {
$last: "$$ROOT"
}
}}]
I want to return only the last record, sender.id or receiver.id can have multiple records of which i only want to retrieve the last one. But my query is returning multiple records.
How to get only the last i.e latest one based on above conditions and createdAt?
Sample output:
{
"lastrecord" : {
"name" : "name1",
"amount" : 1000,
"sender" : {
"name" : "name2",
"phone" : "213232141",
"category" : "cat1"
},
"receiver" : {
"name" : "name",
"phone" : "321312412",
"category" : "cat2"
},
"status" : "done",
"createdAt" : "2019-11-25T17:00:17.226+06:30"
}
}
i want this for every sender.id or receiver.id

Should I choose mongo aggregation or should do it at application level service

I have the below collections.
Company:
{
"_id" : ObjectId("5a7848e8ca70273218e9d743"),
"name" : "Google",
"departments" : [
{
"name" : "IT",
"_id" : "1234567890"
},
{
"name" : "Sales",
"_id" : "1234567891"
}
]
}
User:
{
"_id" : ObjectId("5a784977ca70273218e9d759"),
"name" : "Sankarshan",
"company" : ObjectId("5a7848e8ca70273218e9d743"),
"department" : "1234567890"
}
topicCategories
{
"_id" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "topicCtegoryOne",
"order" : 1
}
topics
{
"_id" : ObjectId("5a784cc3ca70273218e9d7d3"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "TopicOne",
"order" : 1,
"label" : "oneTopic",
"color" : "red"
}
dimentions
{
"_id" : ObjectId("5a784fdcca70273218e9d869"),
"name" : "dimentionOne"
}
queries
{
"_id" : ObjectId("5a78519aca70273218e9d8d7"),
"topic" : ObjectId("5a784cc3ca70273218e9d7d3"),
"dimention" : ObjectId("5a784fdcca70273218e9d869"),
"order" : 1,
"label" : "queryLabelOne",
"statement" : "This is one question - Top1"
}
User_responses
{
"_id" : ObjectId("5a7859f5ca70273218e9da7d"),
"user" : ObjectId("5a784977ca70273218e9d759"),
"company" : ObjectId("5a7848e8ca70273218e9d743"),
"department" : "1234567890",
"response" : {
"question" : ObjectId("5a78519aca70273218e9d8d7"),
"topic" : ObjectId("5a784cc3ca70273218e9d7d3"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"dimention" : ObjectId("5a784fdcca70273218e9d869"),
"rating" : 5,
"color" : "red"
}
}
What is happening above
Each User belongs to a company.
Each Topic belongs to a Topic category.
Each Query belongs to a Topic
Each query has a dimention.
A user can answer a Query in terms of rating.
The user response to each question will be stored in User_responses.
What I want to achieve
An aggregated result of responses based on Topic category and Dimension
Expected aggregated result format
The query is based on one topic category.
All questions on each topic is aggregated based on Topic
Each Topic will have calculated average ratings based on dimension.
Response
{
"topicCategory": "topic category",
"topics": [
{
"topicLabel": "label of the topic one",
"dimentions": [
{
"name": "dimention name one",
"avgValue": 5.6
}
{
"name": "dimention name two",
"avgValue": 4.5
}
]
},
{
"topicLabel": "label of the topic two",
"dimentions": [
{
"name": "dimention name one",
"avgValue": 6.6
}
{
"name": "dimention name two",
"avgValue": 9.5
}
]
}
]
}
Can I implement this using Mongo aggregation or should I do it at service level?
What I have tried doing
db.getCollection('collResp').aggregate([
{
$match:
{
company: ObjectId("5a7848e8ca70273218e9d743"),
department: "1234567890"
}
},
{
$group: {
_id: {
topic: "$response.topic",
category: "$response.topicCategory"
},
responses: {
$push: "$response"
}
}
},
{
$lookup: {
from: 'topics',
localField: '_id.topic',
foreignField: '_id',
as: "topic"
}
},
{
$unwind: {
path: "$topic"
}
},
{
$lookup: {
from: 'topicCategories',
localField: '_id.category',
foreignField: '_id',
as: "category"
}
},
{
$unwind: {
path: "$category"
}
}
])
What I got
/* 1 */
{
"_id" : {
"topic" : ObjectId("5a78512cca70273218e9d8bd"),
"category" : ObjectId("5a784a10ca70273218e9d76f")
},
"responses" : [
{
"question" : ObjectId("5a7851a6ca70273218e9d8d9"),
"topic" : ObjectId("5a78512cca70273218e9d8bd"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"dimention" : ObjectId("5a784fdcca70273218e9d869"),
"rating" : 8,
"color" : "red"
},
{
"question" : ObjectId("5a78580eca70273218e9da10"),
"topic" : ObjectId("5a78512cca70273218e9d8bd"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"dimention" : ObjectId("5a7851d5ca70273218e9d8ee"),
"rating" : 7,
"color" : "red"
}
],
"topic" : {
"_id" : ObjectId("5a78512cca70273218e9d8bd"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "TopicTwo",
"order" : 2,
"label" : "twoTopic",
"color" : "green"
},
"category" : {
"_id" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "topicCtegoryOne",
"order" : 1
}
}
/* 2 */
{
"_id" : {
"topic" : ObjectId("5a784cc3ca70273218e9d7d3"),
"category" : ObjectId("5a784a10ca70273218e9d76f")
},
"responses" : [
{
"question" : ObjectId("5a78519aca70273218e9d8d7"),
"topic" : ObjectId("5a784cc3ca70273218e9d7d3"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"dimention" : ObjectId("5a784fdcca70273218e9d869"),
"rating" : 5,
"color" : "red"
},
{
"question" : ObjectId("5a785807ca70273218e9da0e"),
"topic" : ObjectId("5a784cc3ca70273218e9d7d3"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"dimention" : ObjectId("5a7851d5ca70273218e9d8ee"),
"rating" : 3,
"color" : "red"
}
],
"topic" : {
"_id" : ObjectId("5a784cc3ca70273218e9d7d3"),
"topicCategory" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "TopicOne",
"order" : 1,
"label" : "oneTopic",
"color" : "red"
},
"category" : {
"_id" : ObjectId("5a784a10ca70273218e9d76f"),
"name" : "topicCtegoryOne",
"order" : 1
}
}
What more is required
Each response had a question. And each question has a dimention
I need to get average rating based on dimention.
Thank you.

Mongodb : get whether a document is the latest with a field value and filter on the result

I am trying to port an existing SQL schema into Mongo.
We have document tables, with sometimes several times the same document, with a different revision but the same reference. I want to get only the latest revisions of the documents.
A sample input data:
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC305",
"code" : "305-D",
"title" : "Document 305",
"creationdate" : ISODate("2011-11-24T15:13:28.887Z"),
"creator" : "X"
},
{
"Uid" : "xxx",
"status" : "COMMENTED",
"reference" : "DOC306",
"code" : "306-A",
"title" : "Document 306",
"creationdate" : ISODate("2011-11-28T07:23:18.807Z"),
"creator" : "X"
},
{
"Uid" : "xxx",
"status" : "COMMENTED",
"reference" : "DOC306",
"code" : "306-B",
"title" : "Document 306",
"creationdate" : ISODate("2011-11-28T07:26:49.447Z"),
"creator" : "X"
},
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC501",
"code" : "501-A",
"title" : "Document 501",
"creationdate" : ISODate("2011-11-19T06:30:35.757Z"),
"creator" : "X"
},
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC501",
"code" : "501-B",
"title" : "Document 501",
"creationdate" : ISODate("2011-11-19T06:40:32.957Z"),
"creator" : "X"
}
Given this data, I want this result set (sometimes I want only the last revision, sometimes I want all revisions with an attribute telling me whether it's the latest):
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC305",
"code" : "305-D",
"title" : "Document 305",
"creationdate" : ISODate("2011-11-24T15:13:28.887Z"),
"creator" : "X",
"lastrev" : true
},
{
"Uid" : "xxx",
"status" : "COMMENTED",
"reference" : "DOC306",
"code" : "306-B",
"title" : "Document 306",
"creationdate" : ISODate("2011-11-28T07:26:49.447Z"),
"creator" : "X",
"lastrev" : true
},
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC501",
"code" : "501-B",
"title" : "Document 501",
"creationdate" : ISODate("2011-11-19T06:40:32.957Z"),
"creator" : "X",
"lastrev" : true
}
I already have a bunch of filters, sorting, and skip/limit (for pagination of data), so the final result set should be mindful of these constraints.
The current "find" query (built with the .Net driver), which filters fine but gives me all revisions of each document:
coll.find(
{ "$and" : [
{ "$or" : [
{ "deletedid" : { "$exists" : false } },
{ "deletedid" : null }
] },
{ "$or" : [
{ "taskid" : { "$exists" : false } },
{ "taskid" : null }
] },
{ "objecttypeuid" : { "$in" : ["xxxxx"] } }
] },
{ "_id" : 0, "Uid" : 1, "lastrev" : 1, "title" : 1, "code" : 1, "creator" : 1, "owner" : 1, "modificator" : 1, "status" : 1, "reference": 1, "creationdate": 1 }
).sort({ "creationdate" : 1 }).skip(0).limit(10);
Using another question, I have been able to build this aggregation, which gives me the latest revision of each document, but with not enough attributes in the result:
coll.aggregate([
{ $sort: { "creationdate": 1 } },
{
$group: {
"_id": "$reference",
result: { $last: "$creationdate" },
creationdate: { $last: "$creationdate" }
}
}
]);
I would like to integrating the aggregate with the find query.
I have found the way to mix aggregation and filtering:
coll.aggregate(
[
{ $match: {
"$and" : [
{ "$or" : [
{ "deletedid" : { "$exists" : false } },
{ "deletedid" : null }
] },
{ "$or" : [
{ "taskid" : { "$exists" : false } },
{ "taskid" : null }
] },
{ "objecttypeuid" : { "$in" : ["xxx"] } }
]
}
},
{ $sort: { "creationdate": 1 } },
{ $group: {
"_id": "$reference",
"doc": { "$last": "$$ROOT" }
}
},
{ $sort: { "doc.creationdate": 1 } },
{ $skip: skip },
{ $limit: limit }
],
{ allowDiskUse: true }
);
For each result node, this gives me a "doc" node with the document data. It has too much data still (it's missing projections), but it's a start.
Translated in .Net:
FilterDefinitionBuilder<BsonDocument> filterBuilder = Builders<BsonDocument>.Filter;
FilterDefinition<BsonDocument> filters = filterBuilder.Empty;
filters = filters & (filterBuilder.Not(filterBuilder.Exists("deletedid")) | filterBuilder.Eq("deletedid", BsonNull.Value));
filters = filters & (filterBuilder.Not(filterBuilder.Exists("taskid")) | filterBuilder.Eq("taskid", BsonNull.Value));
foreach (var f in fieldFilters) {
filters = filters & filterBuilder.In(f.Key, f.Value);
}
var sort = Builders<BsonDocument>.Sort.Ascending(orderby);
var group = new BsonDocument {
{ "_id", "$reference" },
{ "doc", new BsonDocument("$last", "$$ROOT") }
};
var aggregate = coll.Aggregate(new AggregateOptions { AllowDiskUse = true })
.Match(filters)
.Sort(sort)
.Group(group)
.Sort(sort)
.Skip(skip)
.Limit(rows);
return aggregate.ToList();
I'm pretty sure there are better ways to do this, though.
You answer is pretty close. Instead of $last, $max is better.
About $last operator:
Returns the value that results from applying an expression to the last document in a group of documents that share the same group by a field. Only meaningful when documents are in a defined order.
Get the last revision in each group, see code below in mongo shell:
db.collection.aggregate([
{
$group: {
_id: '$reference',
doc: {
$max: {
"creationdate" : "$creationdate",
"code" : "$code",
"Uid" : "$Uid",
"status" : "$status",
"title" : "$title",
"creator" : "$creator"
}
}
}
},
{
$project: {
_id: 0,
Uid: "$doc.Uid",
status: "$doc.status",
reference: "$_id",
code: "$doc.code",
title: "$doc.title",
creationdate: "$doc.creationdate",
creator: "$doc.creator"
}
}
]).pretty()
The output as your expect:
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC501",
"code" : "501-B",
"title" : "Document 501",
"creationdate" : ISODate("2011-11-19T06:40:32.957Z"),
"creator" : "X"
}
{
"Uid" : "xxx",
"status" : "COMMENTED",
"reference" : "DOC306",
"code" : "306-B",
"title" : "Document 306",
"creationdate" : ISODate("2011-11-28T07:26:49.447Z"),
"creator" : "X"
}
{
"Uid" : "xxx",
"status" : "ACCEPTED",
"reference" : "DOC305",
"code" : "305-D",
"title" : "Document 305",
"creationdate" : ISODate("2011-11-24T15:13:28.887Z"),
"creator" : "X"
}

Group multi-dimensional array after unwinding elements

Again with mongoDB. I really like aggregation, but still can't "get it".
So here is my array:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"id_basket" : 1,
"card" : [
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
},
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"id_basket" : 1,
"card" : [
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
},
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
}
],
"full_amount" : "40",
},
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
"id_user" : 97,
}
I want to output something like this:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
}
I've tried many combinations with unwinding, projecting and grouping and failed to get what I want. Can someone help me with this?
You probably shouldn't be using the aggregation framework for tasks like this that do not actually "aggregate" anything between documents. This really is a "projection" task since all you are asking is to "alter" the structure of a document, and that is a task probably better suited to coding in the client after the document is retrieved.
A very good reason for this is that operations like $unwind are very costly in terms of performance. What $unwind does is produce a "copy" of the document content for each array member present, which results in a lot more documents to process.
Think of that like a "SQL Join" with a "one to many" relationship, the only difference being the data is self contained in one document. Processing $unwind simulates the "join" results in that the "master" (one) document contents are reproduced for every "child" (many) document.
In order to counter such operations being done by people, MongoDB 2.6 introduced the $map operator, which processes array elements within the document itself.
So instead of doing multiple ( or any ) $unwind actions, you can instead just process the arrays within the document itself using $map in a $project stage:
db.collection.aggregate([
{ "$project": {
"orders": { "$map": {
"input": "$orders",
"as": "o",
"in": {
"id": "$$o.id",
"name": "$$o.name",
"card": { "$map": {
"input": "$$o.card",
"as": "c",
"in": {
"id": "$$c.id",
"serial": "$$c.serial",
"name": "$$c.name",
"ticket": { "$map": {
"input": "$$c.ticket",
"as": "t",
"in": {
"id": "$$t.id",
"name": "$$t.name",
"price": "$$t.price.price_disp"
}
}}
}
}},
"full_amount": "$$o.full_amount"
}
}},
"rate": 1,
"date": 1
}}
])
The operations are fairly simple there as each "array" is assigned it's own variable name, and for a simple projection operation such as this all that is really left is selecting which fields you want.
In earlier versions, processing using $unwind is much more difficult:
db.collection.aggregate([
{ "$unwind": "$orders" },
{ "$unwind": "$orders.card" },
{ "$unwind": "$orders.card.ticket" },
{ "$group": {
"_id": {
"_id": "$_id",
"orders": {
"id": "$orders.id",
"name": "$orders.name",
"card": {
"id": "$orders.card.id",
"serial": "$orders.card.serial",
"name": "$orders.card.name"
},
"full_amount": "$orders.full_amount"
},
"rate": "$rate",
"date": "$date"
},
"ticket": {
"$push": {
"id": "$orders.card.ticket.id",
"name": "$orders.card.ticket.name",
"price": "$orders.card.ticket.price.price_disp"
}
}
}},
{ "$group": {
"_id": {
"_id": "$_id._id",
"orders": {
"id": "$_id.orders.id",
"name": "$_id.orders.name",
"full_amount": "$_id.orders.full_amount"
},
"rate": "$_id.rate",
"date": "$_id.date"
},
"card": {
"$push": {
"id": "$_id.orders.card.id",
"serial": "$_id.orders.card.serial",
"name": "$_id.orders.card.name",
"ticket": "$ticket"
}
}
}},
{ "$group": {
"_id": "$_id._id",
"orders": {
"$push": {
"id": "$_id.orders.id",
"name": "$_id.orders.name",
"card": "$card",
"full_amount": "$_id.orders.full_amount"
}
},
"rate": { "$first": "$_id.rate" },
"date": { "$first": "$_id.date" }
}}
])
So following through that carefully, you should see that since you $unwind three times it is necessary to $group "three times" as well, while carefully grouping all the distinct values at each "level" and re-constructing the arrays via $push.
This really is not advised at all as was mentioned earlier:
You "are not grouping/aggregating anything" and each sub-document "must" contain a "unique" itentifier because of the "grouping" operations required to re-construct arrays. ( See: NOTE )
The $unwind operation here is very costly. All of the document information is re-produced by a factor of "n" array X "n" array elements and so on. So there is much more data in the aggregation pipeline than your collection or query selection actually contains in itself.
Therefore in conclusion, for the general processing of "reformatting your data" you should instead be processing each document in your code rather than be "throwing it" at the aggregation pipeline to do.
If your document data requires "sufficient" manipulation that makes a "substantial difference" to the returned result size that you deem to be more efficient than pulling the whole document and manipulating in the client, then and "only" then should you be using the $project form as shown with the $map operations.
Sidebar
Your original "tag" here mentions "PHP".
All MongoDB queries including the aggregation have nothing language specific about them and are just "data structures" and are represented as such mostly in the "native form" for those languages (PHP,JavaScript,python,etc), and with "builder methods" for those languages without "native" expressive formats for free structures ( C,C#,Java ).
In all cases, there are simple parsers available for JSON, which is a common "linqua franca" here as the MongoB Shell itself is JavaScript based and understands JSON structre ( as actual JavaScript Objects ) natively.
So when working with such examples use tools like:
json_decode: to get more of an insight into how your native data structure is constructed.
json_encode: in order to check your native data structure against any JSON represented sample.
All content here is just simple "key/value" array() notation, though nested. But it is probably good practice to be aware of the tools and use them regularly.
NOTE:
The data sample you give looks very much like you have "cut and paste" data in order to create multiple items, as various "sub-items" all share the same "id" values.
Your "real" data should not do this! So I hope it does not, but if so then fix it.
In order to make the second example workable ( first is perfectly fine as is ) the data needs to be altered to included "unique" "id" values for each sub-element.
As I used here:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002a",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000031",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000032",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "251",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000033",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000034",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002b",
"name" : "XYZ",
"card" : [
{
"id" : "252",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000035",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000036",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "253",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000037",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000038",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
}
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
}