MongoDB returns only the specified query - mongodb

db.customerOrder.insert({
firstName: "Andrew",
lastName: "Lee",
DOB: ISODate("1974-10-28T00:00:00Z"),
phone: "+1 (959) 567-3312",
email: "mark#gmail.com",
address: {
street: "Cornish Street, Victoria",
houseNumber: "68",
postalCode: "3024",
country: "Australia",
},
language: ["English", "Mandarin"],
balance: 0,
orders: [
{
orderNumber: "ord003",
orderDate: ISODate("2020-01-10T00:00:00Z"),
staffNumber: "stf789"
}
]
});
Given the document above, and other documents which contain other orders and order number, how do i specify an aggregation so that it will only list all orderNumbers that's handled by a staffNumber x?
Example, orderNumber ord004 and ord005 is handled by staffNumber stf890
I tried doing
db.customerOrder.aggregate([ {"$match":{"orders.staffNumber":"stf890"}}, {"$project":{"orders.orderNumber":1, "_id":0}} ])
but the result was
{
"orders" : [
{
"orderNumber" : "ord003"
},
{
"orderNumber" : "ord003"
},
{
"orderNumber" : "ord005"
}
]
}
{
"orders" : [
{
"orderNumber" : "ord001"
},
{
"orderNumber" : "ord005"
}
]
}
{
"orders" : [
{
"orderNumber" : "ord003"
},
{
"orderNumber" : "ord004"
}
]
}
I expect the result to output only ord004 and ord005
How do i achieve this?
Thank you for your help

Try this! your query is almost correct but you're missing the case of matching orderNumber.
db.customerOrder.aggregate([
{
"$match":{
"orders.staffNumber":"stf890"
}
},
{
$unwind:{
"path":"$orders"
}
},
{
"$match":{
"orders.orderNumber":{$in:["ord004","ord005"]}
}
},
{
"$project":{
"orders.orderNumber":1,
"_id":0
}
}
])

If you don't care about the structure you can just $unwind and then match. otherwise you need to use something like $filter
Option 1:
db.customerOrder.aggregate([
{
"$match": {
"orders.staffNumber": "stf890"
}
},
{
"$unwind": "$orders"
},
{
"$match": {
"orders.staffNumber": "stf890"
}
},
{
"$project": {"orders.orderNumber": 1, "_id": 0}
}])
Option 2:
db.customerOrder.aggregate([
{
"$match": {
"orders.staffNumber": "stf890"
}
},
{
$project: {
orders: {
$filter: {
input: "$orders",
as: "order",
cond: {
$eq: ["$$order.staffNumber", "stf890"]
}
}
}
}
},
{
"$project": {
"orders.orderNumber": 1,
"_id": 0
}
}
])

Related

Need help to MongoDB aggregate $group state

I have a collection of 1000 documents like this:
{
"_id" : ObjectId("628b63d66a5951db6bb79905"),
"index" : 0,
"name" : "Aurelia Gonzales",
"isActive" : false,
"registered" : ISODate("2015-02-11T04:22:39.000+0000"),
"age" : 41,
"gender" : "female",
"eyeColor" : "green",
"favoriteFruit" : "banana",
"company" : {
"title" : "YURTURE",
"email" : "aureliagonzales#yurture.com",
"phone" : "+1 (940) 501-3963",
"location" : {
"country" : "USA",
"address" : "694 Hewes Street"
}
},
"tags" : [
"enim",
"id",
"velit",
"ad",
"consequat"
]
}
I want to group those by year and gender. Like In 2014 male registration 105 and female registration 131. And finally return documents like this:
{
_id:2014,
male:105,
female:131,
total:236
},
{
_id:2015,
male:136,
female:128,
total:264
}
I have tried till group by registered and gender like this:
db.persons.aggregate([
{ $group: { _id: { year: { $year: "$registered" }, gender: "$gender" }, total: { $sum: NumberInt(1) } } },
{ $sort: { "_id.year": 1,"_id.gender":1 } }
])
which is return document like this:
{
"_id" : {
"year" : 2014,
"gender" : "female"
},
"total" : 131
}
{
"_id" : {
"year" : 2014,
"gender" : "male"
},
"total" : 105
}
Please guide to figure out from this whole.
db.collection.aggregate([
{
"$group": { //Group things
"_id": "$_id.year",
"gender": {
"$addToSet": {
k: "$_id.gender",
v: "$total"
}
},
sum: { //Sum it
$sum: "$total"
}
}
},
{
"$project": {//Reshape it
g: {
"$arrayToObject": "$gender"
},
_id: 1,
sum: 1
}
},
{
"$project": { //Reshape it
_id: 1,
"g.female": 1,
"g.male": 1,
sum: 1
}
}
])
Play
Just add one more group stage to your aggregation pipeline, like this:
db.persons.aggregate([
{ $group: { _id: { year: { $year: "$registered" }, gender: "$gender" }, total: { $sum: NumberInt(1) } } },
{ $sort: { "_id.year": 1,"_id.gender":1 } },
{
$group: {
_id: "$_id.year",
male: {
$sum: {
$cond: {
if: {
$eq: [
"$_id.gender",
"male"
]
},
then: "$total",
else: 0
}
}
},
female: {
$sum: {
$cond: {
if: {
$eq: [
"$_id.gender",
"female"
]
},
then: "$total",
else: 0
}
}
},
total: {
$sum: "$total"
}
},
}
]);
Here's the working link. We are grouping by year in this last step, and calculating the counts for gender conditionally and the total is just the total of the counts irrespective of the gender.
Besides #Gibbs mentioned in the comment which proposes the solution with 2 $group stages,
You can achieve the result as below:
$group - Group by year of registered. Add gender value into genders array.
$sort - Order by _id.
$project - Decorate output documents.
3.1. male - Get the size of array from $filter the value of "male" in "genders" array.
3.2. female - Get the size of array from $filter the value of "female" in "genders" array.
3.3. total - Get the size of "genders" array.
Propose this method if you are expected to count and return the "male" and "female" gender fields.
db.collection.aggregate([
{
$group: {
_id: {
$year: "$registered"
},
genders: {
$push: "$gender"
}
}
},
{
$sort: {
"_id": 1
}
},
{
$project: {
_id: 1,
male: {
$size: {
$filter: {
input: "$genders",
cond: {
$eq: [
"$$this",
"male"
]
}
}
}
},
female: {
$size: {
$filter: {
input: "$genders",
cond: {
$eq: [
"$$this",
"female"
]
}
}
}
},
total: {
$size: "$genders"
}
}
}
])
Sample Mongo Playground

MongoDB Query based on value in array and return a single field

I have a collection called countries:
{
"_id" : "123456",
"enabled" : true,
"entity" : {
"name" : [
{
"locale" : "en",
"value" : "Lithuania"
},
{
"locale" : "de",
"value" : "Litauen"
}
]
}
}
I like to return only the ObjectId and the value when the locale is "en".
{"_id":"123456", "value":"Lithuania"}
Ideally renaming value to country for:
{"_id":"123456", "country":"Lithuania"}
Using a projection like:
db.countries.aggregate([
{$project:
{country: {$arrayElemAt:["$entity.name",0]}}
}
])
returns almost the desired results:
{"_id" : "1234565", "country" : { "locale" : "en", "value" : "Lithuania" } }
This this one:
db.collection.aggregate([
{
$set: {
country: {
$filter: {
input: "$entity.name",
cond: { $eq: [ "$this.locale", "en" ] }
}
}
}
},
{ $project: { country: { $first: "$country.value" } } },
])
See Mongo playground
You can try,
$reduce to iterate loop of entity.name array, $cond check locale is "en" then return value
db.collection.aggregate([
{
$project: {
country: {
$reduce: {
input: "$entity.name",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this.locale", "en"] },
"$$this.value",
"$$value"
]
}
}
}
}
}
])
Playground
I should have specified the MongoDB Version. Server is running 3.6.20. For that the following solution works:
db.countries.aggregate([
{
$addFields: {
country: {
$filter: {
input: "$entity.name",
cond: { $eq: [ "$$this.locale", "en" ] }
}
}
}
},
{ $project: { country: { $arrayElemAt: [["$country.value"],0] } } },
])

Mongodb count sum of fields in a collection with conditions

I have collections in mongoDb like this:
{
_id: ObjectId(""),
fieldA: "123",
fieldB: {
subFieldB:[
{
fieldC: ""
},
{
fieldC: ""
},
{
fieldC: ""
}
]
}
},
{
_id: ObjectId(""),
fieldA: "123",
fieldB: {
subFieldB:[
{
fieldC: ""
},
{
fieldC: ""
},
{
fieldC: ""
}
]
}
},
{
_id: ObjectId(""),
fieldA: "456",
fieldB: {
subFieldB:[
{
fieldC: ""
},
{
fieldC: ""
},
{
fieldC: ""
}
]
}
}
I want to get sum of number of subFieldB where fieldA is "123"
So the goal is getting something like {sumOfSubFieldB: 6}
I tried aggregate like:
db.collection.aggregate([
{
$match : { "fieldA" : "123" }
},
{
$project: {
numberOfFieldB: { $cond: { if: { $isArray: "$fieldB.subFieldB" }, then: { $size: "$fieldB.subFieldB" }, else: "NA"} },
}
}
] )
So far I get sum of fieldB per doc like:
{
"_id" : ObjectId(""),
"numberOfFieldB" : 3
}
{
"_id" : ObjectId(""),
"numberOfFieldB" : 3
}
How should I do the query to get total number of fieldB?
Thank you
Try this one, or something similar with $group:
db.collection.aggregate(
[{
$match: {
"fieldA": "123"
}
}, {
$group: {
_id: "$fieldA",
count: {
$sum: { $size: "$fieldB.subFieldB" } }
}
}]
);

Query datevalue of a inner Array element

Need help with some MongoDB query:
The document I have is below and I am trying to search based on 2 conditions
The meta.tags.code = "ABC"
Its LastSyncDateTime should
meta.extension.value == "" (OR)
the meta.extension.value is less than meta.lastUpdated
Data :
{
"meta" : {
"extension" : [
{
"url" : "LastSyncDateTime",
"value" : "20190206-00:49:25.694"
},
{
"url" : "RetryCount",
"value" : "0"
}
],
"lastUpdate" : "20190207-01:21:41.095",
"tags" : [
{
"code" : "ABC",
"system" : "type"
},
{
"code" : "XYZ",
"system" : "SourceSystem"
}
]
}
}
Query:
db.proc_patients_service.find({
"meta.tags.code": "ABC",
$or: [{
"meta.extension.value": ""
}, {
$expr: { "$lt": [{ "mgfunc": "ISODate", "params": [{ "$arrayElemAt": ["$meta.extension.value", 0] }] }, { "mgfunc": "ISODate", "params": ["$meta.lastUpdate"] }] }
}]
})
But it is only fetching ABC Patients whose LastSyncDateTime is empty and ignores the other condition.
Using MongoDB Aggregation, I have converted your string to date with operator $dateFromString and then compare the value as per your criteria.
db.proc_patients_service.aggregate([
{ $match: { "meta.tags.code": "ABC", } },
{ $unwind: "$meta.extension" },
{
$project: {
'meta.tags': '$meta.tags',
'meta.lastUpdate': { '$dateFromString': { 'dateString': '$meta.lastUpdate', format: "%Y%m%d-%H:%M:%S.%L" } },
'meta.extension.url': '$meta.extension.url',
'meta.extension.value': {
$cond: {
if: { $ne: ["$meta.extension.value", "0"] }, then: { '$dateFromString': { 'dateString': '$meta.extension.value', format: "%Y%m%d-%H:%M:%S.%L" } }, else: 0
}
}
}
},
{
$match: {
$or: [
{ "meta.extension.value": 0 },
{ $expr: { $lt: ["$meta.extension.value", "$meta.lastUpdate"] } }
]
}
},
{
$group: { _id: '_id', 'extension': { $push: '$meta.extension' }, "lastUpdate": { $first: '$meta.lastUpdate' }, 'tags': { $first: '$meta.tags' } }
},
{
$project: { meta: { 'extension': '$extension', lastUpdate: '$lastUpdate', 'tags': '$tags' } }
}
])

Mongo DB - Second Level Search - elemMatch

I am trying to fetch all records (and count of all records) for a structure like the following,
{
id: 1,
level1: {
level2:
[
{
field1:value1;
},
{
field1:value1;
},
]
}
},
{
id: 2,
level1: {
level2:
[
{
field1:null;
},
{
field1:value1;
},
]
}
}
My requirement is to fetch the number of records that have field1 populated (atleast one in level2). I need to say fetch all the ids or the number of such ids.
The query I am using is,
db.table.find({},
{
_id = id,
value: {
$elemMatch: {'level1.level2.field1':{$exists: true}}
}
}
})
Please suggest.
EDIT1:
This is the question I was trying to ask in the comment. I was unable to elucidate in the comment properly. Hence, editing the question.
{
id: 1,
level1: {
level2:
[
{
field1:value1;
},
{
field1:value1;
},
]
}
},
{
id: 2,
level1: {
level2:
[
{
field1:value2;
},
{
field1:value2;
},
{
field1:value2;
}
]
}
}
{
id: 3,
level1: {
level2:
[
{
field1:value1;
},
{
field1:value1;
},
]
}
}
The query we used results in
value1: 4
value2: 3
I want something like
value1: 2 // Once each for documents 1 & 3
value2: 1 // Once for document 2
You can do that with the following find query:
db.table.find({ "level1.level2" : { $elemMatch: { field1 : {$exists: true} } } }, {})
This will return all documents that have a field1 in the "level1.level2" structure.
For your question in the comment, you can use the following aggregation to "I had to return a grouping (and the corresponding count) for the values in field1":
db.table.aggregate(
[
{
$unwind: "$level1.level2"
},
{
$match: { "level1.level2.field1" : { $exists: true } }
},
{
$group: {
_id : "$level1.level2.field1",
count : {$sum : 1}
}
}
]
UPDATE: For your question "'value1 - 2` At level2, for a document, assume all values will be the same for field1.".
I hope i understand your question correctly, instead of grouping only on the value of field1, i added the document _id as an xtra grouping:
db.table.aggregate(
[
{
$unwind: "$level1.level2"
},
{
$match: {
"level1.level2.field1" : { $exists: true }
}
},
{
$group: {
_id : { id : "$_id", field1: "$level1.level2.field1" },
count : {$sum : 1}
}
}
]
);
UPDATE2:
I altered the aggregation and added a extra grouping, the aggregation below gives you the results you want.
db.table.aggregate(
[
{
$unwind: "$level1.level2"
},
{
$match: {
"level1.level2.field1" : { $exists: true }
}
},
{
$group: {
_id : { id : "$_id", field1: "$level1.level2.field1" }
}
},
{
$group: {
_id : { id : "$_id.field1"},
count : { $sum : 1}
}
}
]
);