MongoDB query with aggregation - mongodb

I'm using MongoDB and I have this data model for my collection 'book':
{
"_id" : ObjectId("5b32566e2796789e15f18cbe"),
"service" : ObjectId("5b32550fe66a2c2d781a021e"),
"state" : "paid",
"observations" : "",
"datetime_paid" : "",
"price" : 50,
"responsible" : {
"name" : "Mark",
"surname" : "Beckam",
"birthdate" : "",
"email" : "mark#example.com",
"phone" : ""
},
"updates" : [],
"tickets" : [
{
"passenger" : {
"name" : "Mark",
"surname" : "Beckam",
"birthdate" : "",
"email" : "mark#beckam.com",
"phone" : "666 666 666"
},
"qr_id" : "pwenci3y32m",
"roundtrip" : "ida_vuelta",
"price" : 50,
}
]
}
Now I'm trying to query all the tickets and the responsible from every book that matches with a service id. My approach is use aggregate this:
db.getCollection('books').aggregate([
{ $match: { service: ObjectId("5b32550fe66a2c2d781a021e") } },
{
$group: {
_id: 0,
tickets: { $push: "$tickets" }
}
},
{
$project: {
tickets: {
$reduce: {
input: "$tickets",
initialValue: [],
in: { $setUnion: ["$$value", "$$this"] }
}
}
}
}
]);
With that query I'm returning the full list of the tickets throught differents books, but I also need the responsible data (that is in every book) in each ticket:
{
"_id" : 0.0,
"tickets" : [
{
"passenger" : {
"name" : "Mark",
"surname" : "Beckam",
"birthdate" : "",
"email" : "mark#beckam.com",
"phone" : "666 666 666"
},
"qr_id" : "",
"roundtrip" : "ida_vuelta",
"price" : 50,
// I need here the responsible field associated to the book that
contains that ticket
}
]
}
Thank you in advance

How about this:
db.getCollection('books').aggregate([
{ $match: { service: ObjectId("5b32550fe66a2c2d781a021e") } },
{ $addFields: { "tickets.responsible": "$responsible" } }, // push the responsible information into the "tickets" array
{ $unwind: "$tickets" }, // flatten out all "tickets" to individual documents
{
$group: {
_id: 0,
tickets: { $addToSet: "$tickets" } // addToSet will eliminate duplicate entries
}
}
]);

Related

Facing a problem with the lookup in the second (student) table that matches all incoming output records mongodb aggregation

I'm facing a problem with the lookup in the second (student) table that matches all incoming output records of the first(test) table. I have two collections "tests" and "students". "Test" collection contains all school tests and the "student" table contains student's attended tests. Student table contains "pastTest"(test attended in past with status "pass" or "fail")array. I want to retrieve student who passed all incoming tests (we retrieve from the tests table)
test table: _id (primary ket)
student.pastTests.testId (need to match with test._id)
Test Document:
{
"_id" : ObjectId("5c9b5c1005729b2bf23f3290"),
"testDate" : {
"term" : 1,
"week" : 7
},
"retestDate" : {
"term" : 1,
"week" : 10
},
"testOrder" : "1.1",
"testDateScheduled" : true,
"retestDateScheduled" : true
}
Student Document:
{
"_id" : ObjectId("5c92dd994e8e6b2c1647d0d0"),
"completedYears" : [],
"firstName" : "Andrew",
"lastName" : "Jonhson",
"teacherId" : ObjectId("5bf36b1076696374e65feb4f"),
"yearGroup" : "0",
"schoolId" : 40001,
"currentTest" : ObjectId("5c9b5c1005729b2bf23f3290"),
"pastTests" : [
{
"_id" : ObjectId("5d3570645045863d373f6db1"),
"testId" : ObjectId("5c9b5c1005729b2bf23f3290"),
"status" : "pass"
},
{
"_id" : ObjectId("5d425af07708f5636c3bec1c"),
"testId" : ObjectId("5c9b5fc460e39c2c58e44109"),
"status" : "pass"
},
{
"_id" : ObjectId("5d5e54a875fab079f4d03570"),
"testId" : ObjectId("5c9b6492bb581c2ceb553fef"),
"status" : "fail"
},
],
"createdAt" : ISODate("2019-03-21T00:40:57.401Z"),
"updatedAt" : ISODate("2020-09-24T19:55:38.291Z"),
"__v" : 0,
"holdTests" : [],
"completedTests" : [],
"className" : "dd",
}
Query:
db.getCollection('tests').aggregate([
{
$match: {
yearGroup: '-1',
$or : [
{
$and: [
{'retestDateScheduled': true},
{ 'retestDate.term': { $lt: 4 } },
]
},
{
$and: [
{'testDateScheduled': true},
{ 'testDate.term': { $lt: 4 } },
]
}
]
}
},
{
$lookup: {
from: 'students',
let: {testId: '$_id', schoolId: 49014, yearGroup: '-1'},
pipeline: [
]
}
}
])
Note: Initial match query returns all tests of the term-1, now I have to retrieve students who passed in all tests of the term-1.
Lookup stage is pending - facing problem with lookup in second (student) table who match all incoming output records of first(test) collection
Thanks in advance !!
Try this:
db.tests.aggregate([
{
$match: {
// Your match condition
}
},
{
$group: {
_id: null,
term_1_testIds: { $push: "$_id" },
test_count: { $sum: 1 }
}
},
{
$lookup: {
from: "students",
let: { term_1_testIds: '$term_1_testIds', schoolId: 40001, totalTestCount: "$test_count" },
pipeline: [
{
$match: {
$expr: { $eq: ["$schoolId", "$$schoolId"] }
}
},
{ $unwind: "$pastTests" },
{
$match: {
"pastTests.status": "pass",
$expr: { $in: ["$pastTests.testId", "$$term_1_testIds"] }
}
},
{
$group: {
_id: "$_id",
firstName: { $first: "$firstName" },
yearGroup: { $first: "$yearGroup" },
schoolId: { $first: "$schoolId" },
currentTest: { $first: "$currentTest" },
passedTestCount: { $sum: 1 },
pastTests: { $push: "$pastTests" }
}
},
{
$match: {
$expr: { $eq: ["$passedTestCount", "$$totalTestCount"] }
}
}
],
as: "students"
}
}
]);
Output:
{
"_id" : null,
"term_1_testIds" : [
ObjectId("5c9b5c1005729b2bf23f3290"),
ObjectId("5c9b5fc460e39c2c58e44109"),
ObjectId("5c9b6492bb581c2ceb553fef")
],
"test_count" : 3,
"students" : [
{
"_id" : ObjectId("5c92dd994e8e6b2c1647d0d1"),
"firstName" : "Dheemanth",
"yearGroup" : "0",
"schoolId" : 40001,
"currentTest" : ObjectId("5c9b5c1005729b2bf23f3290"),
"passedTestCount" : 3,
"pastTests" : [
{
"_id" : ObjectId("5d3570645045863d373f6db1"),
"testId" : ObjectId("5c9b5c1005729b2bf23f3290"),
"status" : "pass"
},
{
"_id" : ObjectId("5d425af07708f5636c3bec1c"),
"testId" : ObjectId("5c9b5fc460e39c2c58e44109"),
"status" : "pass"
},
{
"_id" : ObjectId("5d5e54a875fab079f4d03570"),
"testId" : ObjectId("5c9b6492bb581c2ceb553fef"),
"status" : "pass"
}
]
}
]
}
This how my tests collection looks like
/* 1 createdAt:3/27/2019, 5:24:58 PM*/
{
"_id" : ObjectId("5c9b6492bb581c2ceb553fef"),
"name" : "Test 3"
},
/* 2 createdAt:3/27/2019, 5:04:28 PM*/
{
"_id" : ObjectId("5c9b5fc460e39c2c58e44109"),
"name" : "Test 2"
},
/* 3 createdAt:3/27/2019, 4:48:40 PM*/
{
"_id" : ObjectId("5c9b5c1005729b2bf23f3290"),
"name" : "Test 1"
}
This is how my students collection looks like:
/* 1 createdAt:3/21/2019, 6:10:57 AM*/
{
"_id" : ObjectId("5c92dd994e8e6b2c1647d0d1"),
"firstName" : "Dheemanth",
"yearGroup" : "0",
"schoolId" : 40001,
"currentTest" : ObjectId("5c9b5c1005729b2bf23f3290"),
"pastTests" : [
{
"_id" : ObjectId("5d3570645045863d373f6db1"),
"testId" : ObjectId("5c9b5c1005729b2bf23f3290"),
"status" : "pass"
},
{
"_id" : ObjectId("5d425af07708f5636c3bec1c"),
"testId" : ObjectId("5c9b5fc460e39c2c58e44109"),
"status" : "pass"
},
{
"_id" : ObjectId("5d5e54a875fab079f4d03570"),
"testId" : ObjectId("5c9b6492bb581c2ceb553fef"),
"status" : "pass"
}
]
},
/* 2 createdAt:3/21/2019, 6:10:57 AM*/
{
"_id" : ObjectId("5c92dd994e8e6b2c1647d0d0"),
"firstName" : "Andrew",
"yearGroup" : "0",
"schoolId" : 40001,
"currentTest" : ObjectId("5c9b5c1005729b2bf23f3290"),
"pastTests" : [
{
"_id" : ObjectId("5d3570645045863d373f6db1"),
"testId" : ObjectId("5c9b5c1005729b2bf23f3290"),
"status" : "pass"
},
{
"_id" : ObjectId("5d425af07708f5636c3bec1c"),
"testId" : ObjectId("5c9b5fc460e39c2c58e44109"),
"status" : "pass"
},
{
"_id" : ObjectId("5d5e54a875fab079f4d03570"),
"testId" : ObjectId("5c9b6492bb581c2ceb553fef"),
"status" : "fail"
}
]
}
Also:
In your first $match stage, $and operator is redundant inside $or array it should be like this:
{
$match: {
yearGroup: '-1',
$or: [
{
'retestDateScheduled': true,
'retestDate.term': { $lt: 4 }
},
{
'testDateScheduled': true,
'testDate.term': { $lt: 4 }
}
]
}
}

How to change the type of field in an array of sub-documents

I have a collection of documents, each of which possesses an array of subdocuments (ranging from 1-10,000 objects). In a small portion of these documents, a field in the arrayed sub-documents has been set to a string instead of an integer and I need to convert these values to an Integer
Here is a structural sample. Note that the DB Admin built the database and collection names with a '.' notation which has complicated some of my work thus far:
Collection Name: "employee.roster"
{
"_id" : ObjectId("5f11d4c28663f32e940696e0"),
"PdfId" : NumberInt(100),
"Staff" : [
{
"StaffId" : NumberInt(1),
"StaffName" : "John Doe"
},
{
"StaffId" : NumberInt(2),
"StaffName" : "John Smith"
},
{
"StaffId" : "3",
"StaffName" : "John Jones"
}
]
}
{
"_id" : ObjectId("5f11d4c28663f32e940696e1"),
"PdfId" : NumberInt(110),
"Staff" : [
{
"StaffId" : "4",
"StaffName" : "Bob Loblaw"
},
{
"StaffId" : NumberInt(5),
"StaffName" : "Edward Nigma"
},
{
"StaffId" : "6",
"StaffName" : "Hugh Mongus"
}
]
}
I have tried a variety of methods without success. Based on other posts, I thought something like this should work but I've generated nothing but errors:
db.getCollection("staff.roster").update(
{},
[{ $set: { "Staff.$[elem].StaffId": { $toInt: "$Staff.$[elem].StaffId" } } }],
{ "arrayFilters": [{ "elem.StaffId": { $type: 2 } } ], "multi": true }
)
ERROR MESSAGE:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 9,
"errmsg" : "arrayFilters may not be specified for pipeline-syle updates"
}
})
I've also tried this but I believe my notation is wrong because of the sub-documents:
db.getCollection("staff.roster").find( { "Staff.StaffId" : { $type : 2 } } ).forEach( function (x) {
x."Staff.StaffId" = new NumberInt(x."Staff.StaffId");
db.getCollection("staff.roster").save(x);
});
My output should look like this:
{
"_id" : ObjectId("5f11d4c28663f32e940696e0"),
"PdfId" : NumberInt(100),
"Staff" : [
{
"StaffId" : NumberInt(1),
"StaffName" : "John Doe"
},
{
"StaffId" : NumberInt(2),
"StaffName" : "John Smith"
},
{
"StaffId" : NumberInt(3),
"StaffName" : "John Jones"
}
]
}
{
"_id" : ObjectId("5f11d4c28663f32e940696e1"),
"PdfId" : NumberInt(110),
"Staff" : [
{
"StaffId" : NumberInt(4),
"StaffName" : "Bob Loblaw"
},
{
"StaffId" : NumberInt(5),
"StaffName" : "Edward Nigma"
},
{
"StaffId" : NumberInt(6),
"StaffName" : "Hugh Mongus"
}
]
}
You can use update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of Staff array, change the type of StaffId and merge objects with other fields using $mergeObjects
db.getCollection("staff.roster").update({},
[{
$set: {
Staff: {
$map: {
input: "$Staff",
in: {
$mergeObjects: [
"$$this",
{ StaffId: { $toInt: "$$this.StaffId" } }
]
}
}
}
}
}]
)
Playground

Group by array element in Mongodb

We have nested document and trying to group by array element. Our document structure looks like
/* 1 */
{
"_id" : ObjectId("5a690a4287e0e50010af1432"),
"slug" : [
"true-crime-the-10-most-infamous-american-murder-mysteries",
"10-most-infamous-american-murder-mysteries"
],
"tags" : [
{
"id" : "59244aa6b1be5055278e9b5b",
"name" : "true crime",
"_id" : "59244aa6b1be5055278e9b5b"
},
{
"id" : "5924524db1be5055278ebd6e",
"name" : "Occult Museum",
"_id" : "5924524db1be5055278ebd6e"
},
{
"id" : "5a690f0fc1a72100110c2656",
"_id" : "5a690f0fc1a72100110c2656",
"name" : "murder mysteries"
},
{
"id" : "59244d71b1be5055278ea654",
"name" : "unsolved murders",
"_id" : "59244d71b1be5055278ea654"
}
]
}
We want to find list of all slugs group by tag name. I am trying with following and it gets result but it isn't accurate. We have hundreds of records with each tag but i only get few with my query. I am not sure what i am doing wrong here.
Thanks in advance.
// Requires official MongoShell 3.6+
db.getCollection("test").aggregate(
[
{
"$match" : {
"item_type" : "Post",
"site_id" : NumberLong(2),
"status" : NumberLong(1)
}
},
{$unwind: "$tags" },
{
"$group" : {
"_id" : {
"tags᎐name" : "$tags.name",
"slug" : "$slug"
}
}
},
{
"$project" : {
"tags.name" : "$_id.tags᎐name",
"slug" : "$_id.slug",
"_id" : NumberInt(0)
}
}
],
{
"allowDiskUse" : true
}
);
Expected output is
TagName Slug
----------
true crime "true-crime-the-10-most-infamous-american-murder-mysteries",
"10-most-infamous-american-murder-mysteries"
"All records where tags true crime"
Instead of using slug as a part of _id you should use $push or $addToSet to accumulate them, try:
db.test.aggregate([
{
$unwind: "$tags"
},
{
$unwind: "$slug"
},
{
$group: {
_id: "$tags.name",
slugs: { $addToSet: "$slug" }
}
},
{
$project: {
_id: 1,
slugs: {
$reduce: {
input: "$slugs",
initialValue: "",
in: {
$concat: [ "$$value", ",", "$$this" ]
}
}
}
}
}
])
EDIT: to get comma separated string for slugs you can use $reduce with $concat
Output:
{ "_id" : "murder mysteries", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "Occult Museum", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "unsolved murders", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "true crime", "slugs" : ",10-most-infamous-american-murder- mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }

mongodb find the document by id and then group the result based on name field

I have a collection with multiple documents like
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"status" : "",
"Number" : 53,
"values" : [
{
"date" : "2015-05-18",
"value" : 12.41
},
{
"date" : "2015-05-19",
"value" : 12.45
},
],
"Name" : "ABC Banking",
"scheme":"ABC1",
"createdDate" : "21-01-2018"
}
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"status" : "",
"Number" : 53,
"values" : [
{
"date" : "2015-05-18",
"value" : 13.41
},
{
"date" : "2015-05-19",
"value" : 13.45
},
],
"Name" : "ABC Banking",
"scheme":"ABC2",
"createdDate" : "21-01-2018"
}
I am Querying collection based on Number field like
db.getCollection('mfhistories').find({'Number':53})
to get all the documents with this Number.
Now I want to group all the collection with Name 'ABC Banking' into an array. so that I will get result based on Name.
so the result should be like
{
"Name":"ABC Banking",
[
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"status" : "",
"Number" : 53,
"values" : [
{
"date" : "2015-05-18",
"value" : 13.41
},
{
"date" : "2015-05-19",
"value" : 13.45
},
],
"scheme":"ABC1",
"createdDate" : "21-01-2018"
},
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"status" : "",
"Number" : 53,
"values" : [
{
"date" : "2015-05-18",
"value" : 13.41
},
{
"date" : "2015-05-19",
"value" : 13.45
}
],
"scheme":"ABC2",
"createdDate" : "21-01-2018"
}
]
}
Please help..
Thanks,
J
You can use Aggregation Framework for that:
db.col.aggregate([
{
$match: { Number: 53, Name: "ABC Banking" }
},
{
$group: {
_id: "$Name",
docs: { $push: "$$ROOT" }
}
},
{
$project: {
Name: "$_id",
_id: 0,
docs: 1
}
}
])
$$ROOT is a special variable which captures entire document. More here.
db.mfhistories.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
Number: 53
}
},
// Stage 2
{
$group: {
_id: {
Name: '$Name'
},
docObj: {
$addToSet: '$$CURRENT'
}
}
},
// Stage 3
{
$project: {
Name: '$_id.Name',
docObj: 1,
_id: 0
}
}
]
);

adding data in fields of a single document in mongodb

I have this data:
_id : 1
status:1,
name:"name",
details:
{
crm:115,
webs:
{ tag:"blog" , url:"http://..."},
contacts:
{
_id:1,
name:"me",
phones:
{ tag:"home", number:"123..." },
{tag:"mobile", number:"123456789"}
}
}
I want one more entry in "phones" with {tag:office", number:"9823..."}
What would be the command/query for that?
You can easily push this into the array with the following query (I had to modify the JSON you pasted, as it was not valid a little):
db.collection.drop();
db.collection.insert( {
_id : 1,
status: 1,
name: "name",
details: {
crm:115,
webs: {
tag:"blog",
url:"http://..."
},
contacts: {
_id: 1,
name: "me",
phones: [
{ tag: "home", number: "123..." },
{ tag:"mobile", number:"123456789" }
]
}
}
} );
db.collection.update(
{ _id: 1 },
{ $push : { 'details.contacts.phones' : { tag:"office", rname:"9823" } } }
);
db.collection.find().pretty();
{
"_id" : 1,
"details" : {
"contacts" : {
"_id" : 1,
"name" : "me",
"phones" : [
{
"tag" : "home",
"number" : "123..."
},
{
"tag" : "mobile",
"number" : "123456789"
},
{
"tag" : "office",
"rname" : "9823"
}
]
},
"crm" : 115,
"webs" : {
"tag" : "blog",
"url" : "http://..."
}
},
"name" : "name",
"status" : 1
}
The value of the $push operator must refer to the array to be updated. So when the array field is embedded in other documents you need to use dot notation like this:
db.abcd.update({_id: 1},
{$push: {"details.contacts.phones": {tag:"office", rname:"9823"}}});