Given:
A single document in collection. The Document contains an array. (i.e. Only one document entry exist in Collection)
Expectation :
perform group by on city and print counts
Sample Data:
{
name: "ABC Bank",
hospitalList: [
{city: "Delhi", hspName: "EEE hospital"},
{city: "Delhi", hspName: "FFF hospital"},
{city: "Surat", hspName: "MMM hospital"},
{city: "Noida", hspName: "GGG hospital"},
{city: "Surat", hspName: "HHH hospital"},
{city: "Surat", hspName: "NNN hospital"},
{city: "Surat", hspName: "PPP hospital"},
]
}
Expected Output:
Delhi - 2
Noida - 1
Surat - 3
Tried (but did NOT work)
$group: {
_id: "hospitalList.city",
count: { $sum: 1 }
}
$match - to match the document bank name
$unwind - split the array of
object in own document
$group - to group it by city then calculate
count
db.collection.aggregate([
{
$match: {
name: "ABC Bank"
}
},
{
"$unwind": "$hospitalList"
},
{
$group: {
_id: "$hospitalList.city",
count: {
$sum: 1
}
}
}
])
Mongo playground: https://mongoplayground.net/p/Ac7RkJd7vU9
as per expected output:
Mongo playground: https://mongoplayground.net/p/zeqB7P-BYmI
Related
I have model User:
const UserSchema = new Schema({
profile: {
type: Schema.Types.ObjectId,
ref: "profiles",
},
country: {
type: String,
required: true,
},
});
module.exports = User = mongoose.model("users", UserSchema);
I have model Profile:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users",
},
institution: {
type: Schema.Types.ObjectId,
ref: "institutions",
},
videoURL: {
type: String,
},
});
As you may have noticed the two models are associated with each other by referencing each other in profile and user field respectively.
I am trying to write a function that would get me the list of institutions of users of a certain country who uploaded a video and then filter that list with a institution_search_pattern:
const getListOfInstitutionsOfUsersWhoUploadedVideosByCountry = function getListOfInstitutionsOfUsersOfWhoUploadedVideosByCountry(
country_name,
institution_search_pattern
)
However, this seems like an expensive operation I will have to:
Search users of country_name by using mongoose find with a field filter and populate the profile.institution and profile.videoURL
fields
Filter with normal javascript functions on the returned array of users by user.profile.videoURL where videoURL is not undefined
or null
Create an array of the list of institutions of those users by using user.profile.institution
Filter again in the created array with the institution_search_pattern provided by the user by using a fuzzy search module
Send back in response the filtered institutions list
Is there a way to perform all of this with mongoose queries without resorting to filtering with javascript functions or modules?
For example, can I use a filter on the User model based on a Profile Model field?
In other words, in one single query:
Filter User.country with a country_name
Filter by Profile.videoURL where videoURL is not undefined or null
Filter by Profile.institution with a pattern (1)
(1): Filtering institutions by a pattern means:
The user sends a string search_pattern
I use that string to perform a fuzzy search on the institution field.
.
UPDATE based on comments: "Removed all institutions related code."
const country_name = "India";
const users = await UserSchema.aggregate([
{
$match: { country: country_name }
},
{
$lookup: {
from: "profiles",
let: { profiles_id: "$profile" },
pipeline: [
{
$match: {
videoURL: { $nin: [undefined, null] },
$expr: { $eq: ["$_id", "$$profiles_id"] }
}
}
],
as: "profiles"
}
},
{ $unwind: "$profiles" }
]);
Why are you maintaining users reference in profiles? Its redundant. Instead have a reference to profiles only in users collection. All the tasks that you mentioned can be performed in single query. Check this query (change it precisely to your requirement):
const country_name = "India";
const institution_pattern = /^Insti/;
const users = await UserSchema.aggregate([
{
$match: { country: country_name }
},
{
$lookup: {
from: "profiles",
let: { profiles_id: "$profile" },
pipeline: [
{
$match: {
videoURL: { $nin: [undefined, null] },
$expr: { $eq: ["$_id", "$$profiles_id"] }
}
},
{
$lookup: {
from: "institutions",
localField: "institution",
foreignField: "_id",
as: "institution"
}
},
{ $unwind: "$institution" }
],
as: "profiles"
}
},
{ $unwind: "$profiles" },
{
$match: {
"profiles.institution.name": {
$regex: institution_pattern,
$options: "i"
}
}
}
]);
Output
{
"_id" : ObjectId("604cb4c36b2dcb17e8b152b8"),
"profile" : ObjectId("604cb4b16b2dcb17e8b152b5"),
"country" : "India",
"profiles" : {
"_id" : ObjectId("604cb4b16b2dcb17e8b152b5"),
"institution" : {
"_id" : ObjectId("604cb49a6b2dcb17e8b152b2"),
"name" : "Institute 1"
},
"videoURL" : "http://abc1.xyz"
}
}
Test data:
usres collection:
/* 1 createdAt:3/13/2021, 6:19:07 PM*/
{
"_id" : ObjectId("604cb4c36b2dcb17e8b152b8"),
"profile" : ObjectId("604cb4b16b2dcb17e8b152b5"),
"country" : "India"
},
/* 2 createdAt:3/13/2021, 6:19:07 PM*/
{
"_id" : ObjectId("604cb4c36b2dcb17e8b152b9"),
"profile" : ObjectId("604cb4b16b2dcb17e8b152b6"),
"country" : "India"
},
/* 3 createdAt:3/13/2021, 6:19:07 PM*/
{
"_id" : ObjectId("604cb4c36b2dcb17e8b152ba"),
"profile" : ObjectId("604cb4b16b2dcb17e8b152b7"),
"country" : "U.S"
}
profiles collection
/* 1 createdAt:3/13/2021, 6:18:49 PM*/
{
"_id" : ObjectId("604cb4b16b2dcb17e8b152b5"),
"institution" : ObjectId("604cb49a6b2dcb17e8b152b2"),
"videoURL" : "http://abc1.xyz"
},
/* 2 createdAt:3/13/2021, 6:18:49 PM*/
{
"_id" : ObjectId("604cb4b16b2dcb17e8b152b6"),
"institution" : ObjectId("604cb49a6b2dcb17e8b152b3")
},
/* 3 createdAt:3/13/2021, 6:18:49 PM*/
{
"_id" : ObjectId("604cb4b16b2dcb17e8b152b7"),
"institution" : ObjectId("604cb49a6b2dcb17e8b152b4"),
"videoURL" : "http://abc3.xyz"
}
institutions collection:
/* 1 createdAt:3/13/2021, 6:18:26 PM*/
{
"_id" : ObjectId("604cb49a6b2dcb17e8b152b2"),
"name" : "Institute 1"
},
/* 2 createdAt:3/13/2021, 6:18:26 PM*/
{
"_id" : ObjectId("604cb49a6b2dcb17e8b152b3"),
"name" : "Institute 2"
},
/* 3 createdAt:3/13/2021, 6:18:26 PM*/
{
"_id" : ObjectId("604cb49a6b2dcb17e8b152b4"),
"name" : "Institute 3"
}
Help to "flatten" (to pull nested fields at same level as document's fields) a mongodb document in a query
//this is "anagrafiche" collection
[{
"name": "tizio"
,"surname": "semproni"
,"birthday": "01/02/1923"
,"home": {
"road": "via"
,"roadname": "bianca"
,"roadN": 12
,"city": "rome"
,"country": "italy"
}
},
{
"name": "caio"
,"surname": "giulio"
,"birthday": "02/03/1932"
,"home": {
"road": "via"
,"roadname": "rossa"
,"roadN": 21
,"city": "milan"
,"country": "italy"
}
},
{
"name": "mario"
,"surname": "rossi"
// birthday is not present for this document
,"home": {
"road": "via"
,"roadname": "della pace"
,"roadN": 120
,"city": "rome"
,"country": "italy"
}
}
]
my query:
db.anagrafiche.aggregate([ {$match {"home.city": "rome"}}
{$project:{"name": 1, "surname":1, <an expression to flatten the address>, "birthday": 1, "_id":0}}
]
);
expected result:
{
,"name": "tizio"
,"surname": "semproni"
,"address": "via bianca 12 rome"
,"birthday": 01/02/1923
},{
,"name": "mario"
,"surname": "rossi"
,"address": "via della pace 120 rome"
,"birthday": NULL
}
You can use $objectToArray to get nested document keys and values and then use $reduce along with $concat to concatenate values dynamically:
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
surname: 1,
birthday: 1,
address: {
$reduce: {
input: { $objectToArray: "$home" },
initialValue: "",
in: {
$concat: [
"$$value",
{ $cond: [ { $eq: [ "$$value", "" ] }, "", " " ] },
{ $toString: "$$this.v" }
]
}
}
}
}
}
])
Mongo Playground
There's a 'Created' field in my collection, the date format is ISO date. How can I convert it to 'yyyy-mm-dd hh:mm:ss'?
Document sample:
{
"_id" : ObjectId("432babb4d3281999g902a378"),
"ID" : "290283667",
"Data" : {
"Product-2713890" : {
"Created" : ISODate("2016-08-23T20:55:39.437Z"),
"Product" : "Product-2713890"
}
}
}
Expected Result:
{
"_id" : ObjectId("432babb4d3281999g902a378"),
"ID" : "290283667",
"Product" : "Product-2713890",
"Created" : "2016-08-23 20:55:39"
}
Here is my code:
db.getCollection('BasicInfo').aggregate([
{$match:{Type:'subscriptions'}},
{$project: {hashmaps: { $objectToArray: '$$ROOT.Data'},ID:'$$ROOT'}},
{$project: {ID:'$ID.ID',
Product: '$hashmaps.v.Product',
Created: '$hashmaps.v.Created'} },
{$unwind:'$Product'},
{$unwind:'$Created'}
])
I tried to use $dateToString like below, but it gave the error message : "can't convert from BSON type array to Date"
db.getCollection('BasicInfo').aggregate([
{$match:{Type:'subscriptions'}},
{$project: {hashmaps: { $objectToArray: '$$ROOT.Data'},ID:'$$ROOT'}},
{$project: {ID:'$ID.ID',
Product: '$hashmaps.v.Product',
Created: {$dateToString:{format:'%Y-%m-%d',date:'$hashmaps.v.Created'}}} },
{$unwind:'$Product'},
{$unwind:'$Created'}
])
Following aggregation query should get the expected output.
With the help of $addFields and $arrayElemAt why? because Data value in sample document converted with $objectToArray will be at 0th index. Playground Link
Note: considering that Data field can contain more than one product information in which case before we pick the first element the hashmaps array should be filtered $filter (one way) based on product key to work this out correctly.
db.collection.aggregate([
{$match:{Type:'subscriptions'}},
{
$addFields: {
hashmaps: {
$objectToArray: "$$ROOT.Data"
}
}
},
{
$project: {
"ID": 1,
"hashmaps": {
$arrayElemAt: [
"$hashmaps",
0
]
}
}
},
{
$project: {
"ID": 1,
"Product": "$hashmaps.k",
"Created": {
$dateToString: {
format: "%Y-%m-%d %H:%M:%S",
date: "$hashmaps.v.Created"
}
}
}
}
])
++ Updated Query based on comments: Case where the data would indeed have multiple products. Using $map
db.collection.aggregate([
{
$addFields: {
productsMap: {
$objectToArray: "$$ROOT.Data"
}
}
},
{
$project: {
"Products": {
$map: {
input: "$productsMap",
as: "p",
in: {
Product: "$$p.k",
Created: {
$dateToString: {
format: "%Y-%m-%d %H:%M:%S",
date: "$$p.v.Created"
}
},
ID: "$ID",
_id: "$_id"
}
}
},
_id: 0
}
}
])
Sample output: playground with new query
[
{
"Products": [
{
"Created": "2016-08-23 20:55:39",
"ID": "290283667",
"Product": "Product-2713890",
"_id": ObjectId("5a934e000102030405000000")
},
{
"Created": "2017-07-23 20:55:39",
"ID": "290283667",
"Product": "Product-6943532",
"_id": ObjectId("5a934e000102030405000000")
}
]
}]
In MongoDB, I am trying to write a query where I have two input array Bills, Names where the first one contains billids and the second one contains names of the person. Also in reality Bills at index i and Names at index i is the actual document which we want to search in MongoDB.
I want to write a query such that Bills[i] = db_billid && Names[i] = db_name which means I want to return the result where at a particular index both billid and name matches.
I thought of using $in but the thing is I can apply $in in Bills but I don't know at which index that billid is found.
{ $and: [{ billid: { $in: Bills } }, {name: Names[**index at which this bill is found]}] }
Can anyone please help me how can I solve this ??
MongoDB Schema
var transactionsschema = new Schema({
transactionid: {type: String},
billid: {type: String},
name: {type: String}
});
Sample documents in MongoDB
{ _id: XXXXXXXXXXX, transactionid: 1, billid : bnb1234, name: "sudhanshu"}, { _id: XXXXXXXXXXX, transactionid: 2, billid : bnb1235, name: "michael"}, { _id: XXXXXXXXXXX, transactionid: 3, billid : bnb1236, name: "Morgot"}
Sample arrays
Bills = ["bill1", "bill2", "bill3"], Names = ["name1", "name2", "name"]
Edit - If $in can work in array of objects then I can have array of object with keys as billid and name
var arr = [{ billid: "bill1", "name": "name1"}, {billid: "bill2", "name": "name2"}, {billid: "bill3", "name": "name3"}]
But the thing is then how can put below query
{ $and: [{ billid: { $in: arr.Bills } }, {name: arr.Names}] }
Knowing that bills are unique but names may have duplicates you can use $indexOfArray to get index of matching bill and then use that index to compare names array at evaluated index (using $arrayElemAt to retrieve value). Also you have to check if value returned by $indexOfArray is not equal to -1 (no match)
var bills = ["bnb1234", "bnb1235"];
var names = ["sudhanshu", "michael"];
db.col.aggregate([
{
$match: {
$expr: {
$and: [
{ $ne: [{ $indexOfArray: [ bills, "$billid" ] }, -1] },
{ $eq: ["$name", { $arrayElemAt: [ names, { $indexOfArray: [ bills, "$billid" ] } ] }] },
]
}
}
}
])
alternatively $let can be used to avoid duplication:
db.col.aggregate([
{
$match: {
$expr: {
$let: {
vars: { index: { $indexOfArray: [ bills, "$billid" ] } },
in: { $and: [ { $ne: [ "$$index", -1 ] }, { $eq: [ "$name", { $arrayElemAt: [ names, "$$index" ] }] }]}
}
}
}
}
])
Here is an example of a nested document that I have in my collection:
{
"title" : "front-end developer",
"age" : 25,
"name" : "John",
"city" : "London",
"skills" : [
{
"name" : "js",
"project" : "1",
"scores" : [
{
max: 76,
date: date
},
{
max: 56,
date: date
}
]
},
{
"name" : "CSS",
"project" : "5",
"scores" : [
{
max: 86,
date: date
},
{
max: 36,
date: date
},
{
max: 56,
date: date
},
]
}
]
}
Is there a simple way of determining whether other documents have an identical/duplicate structure to the skills array only? e.g. has the same keys, value and array indexes? Any help would be greatly appreciated. Thanks!
Here's how you get that:
collection.aggregate({
"$group": {
"_id": "$skills",
"docs": {
"$push": "$$ROOT"
},
"count": {
$sum: 1
}
}
}, {
$match: {
"count": {
$gt: 1
}
}
})
If you are looking for developers with the same skillset, you can use the $all operator:
var john = db.developers.findOne(...);
var devs = db.developers.find({ 'skills.name': { $all: john.skills.map(x => x.name) } });