I have two simple models defined in Mongoose, composed of two schema Client and City, I have the property city defined in Client as a ObjectId, ref: 'City', so far so good.
If I query for a client and want also to filter by the 'province' property of City, I do this:
const client = await Client
.find({ name: "Gérard" })
.populate([{
path: 'city',
model: City,
match: { province: 'BA' }
}]);
And the output is just fine:
{
"id": "627264e3ec261a883d42ead9",
"name": "Gérard",
"email": "gerard#depardieu.fr",
"date": "1948-12-27",
"active": true,
"city": {
"id": "627264e3ec261a883d42ead1",
"name": "Buenos Aires",
"province": "BA"
}
}
Howerver, if I input a province code of a nonexistent city:
const client = await Client
.find({ name: "Gérard" })
.populate([{
path: 'city',
model: City,
match: { province: 'CA' }
}]);
It would return me that:
{
"id": "627264e3ec261a883d42ead9",
"name": "Gérard",
"email": "gerard#depardieu.fr",
"date": "1948-12-27",
"active": true,
"city": null
}
I don't want in this particular scenario, any instance of Client to be returned, and I don't know how to avoid this behavior with Mongoose, a behavior I never had to worry about with Spring Data for instance.
Any tips for me?
Thanks in advance.
I solved it myself, I had to go a little lower level with Mongoose and use aggregates and lookups.
const client = await Client.aggregate([
{
$match: { name: "Gérard" }
},
{
$lookup: {
from: City.collection.name,
pipeline: [
{
$match: {
province: 'BA'
}
}
], as: "city"
}
},
{
$unwind: "$city"
},
{
$match: {
city: { $ne: [] }
}
}
]);
Expected result:
{
"id": "627264e3ec261a883d42ead9",
"name": "Gérard",
"email": "gerard#depardieu.fr",
"date": "1948-12-27",
"active": true,
"city": {
"id": "627264e3ec261a883d42ead1",
"name": "Buenos Aires",
"province": "BA"
}
}
Witch is ok, client name "Gérard" lives in "Buenos Aires", situated in province "BA".
On the other hand:
const client = await Client.aggregate([
{
$match: { name: "Gérard" }
},
{
$lookup: {
from: City.collection.name,
pipeline: [
{
$match: {
province: 'CA'
}
}
], as: "city"
}
},
{
$unwind: "$city"
},
{
$match: {
city: { $ne: [] }
}
}
]);
Returns nothing, once the city of "Buenos Aires" is not located in province "CA".
Notice here the last parameter (as an object) the array passed to Client.aggregate() receives:
{
$match: {
city: { $ne: [] }
}
}
This tells MongoDB that in order to return data city must be not equal to an empty array.
And with that the issue is solved.
Related
I have the next schemas:
Product Schema:
_id: String
name: String
description: String
company: Company
Company Schema:
_id: String
name: string
I'm trying to find all the products that match my query string with the name or company name.
I already tried:
const query: any[] = [
{
"company.name": {
$regex: name,
$options: "i",
},
},
{
"name": {
$regex: name,
$options: "i",
},
}
];
return Product.find()
.or(query)
.populate("company")
Data - Products:
{ _id: "6067b7d7b5913759d9bb8b39", "name": "Test 1", "description": "Desc", "company": "6067b809c78a4a39ebeae4d4" }
Data - Companies:
{ _id: "6067b809c78a4a39ebeae4d4", "name": "Facebook" },
{ _id: "59862209c78a4a39ebeae4d4", "name": "Apple" },
This query will filter only products, will not work in your case as you want to filter by company.name also
Product.find()
.or(query)
.populate("company")
Demo - https://mongoplayground.net/p/VqREj1vvkss
Use aggregation query to achieve this
db.companies.aggregate([
{ $lookup: { "from": "products", "localField": "_id", "foreignField": "company", "as": "products"} },
{ $unwind: "$products" },
{ $match: {
$or: [
{ "name": { $regex: "Test 1",$options: "i", }, },
{ "products.name": { $regex: "Test 1",$options: "i" } }
]}
}
])
https://mongoosejs.com/docs/api/aggregate.html#aggregate_Aggregate
I'm using nodejs with mongoose (mongodb) and I want to filter inside a subdocument array the language selected.
User schema:
var localUserSchema = new mongoose.Schema({
firstName: {
type: String
},
moreInformation: {
experience: {
type: Number
},
specializations: [{
...
sports:[{
type: Schema.Types.ObjectId,
ref: 'sport'
}]
}]
});
User data:
[{
"_id": {
"$oid": "5fc6a379b1d5ff2c42a9a536"
},
"moreInformation": {
"experience" : 2,
"specializations": [{
"sports": [{
"$oid": "5fc6aa91b1db6cd15702241c"
}, {
"$oid": "5fcb741e786f0703646befe2"
}]
}]
}
}]
Sport schema:
var sportSchema = new Schema({
name: {
en: {
type: String
},
it: {
type: String
}
},
icon: {
type: Schema.Types.ObjectId, ref: 'file'
}
});
Sport data:
[{
"_id": {
"$oid": "5fc6aa91b1db6cd15702241c"
},
"name": {
"en": "Football",
"it": "Calcio"
},
"icon": {
"$oid": "5fc9598a0955177dee8a3bc4"
}
},{
"_id": {
"$oid": "5fcb741e786f0703646befe2"
},
"name": {
"en": "Swimming",
"it": "Nuoto"
},
"icon": {
"$oid": "5fc9598a0955177dee8a3bc5"
}
}
I want all users joined with sports by sport id, but filtered by language key like below and replace the sports name languages with the name selected without the language key.
So, if I wanted to select and filter the english language 'en', i would like to get this result:
[
{
"_id": "5fc6a379b1d5ff2c42a9a536",
"moreInformation": {
"specializations": [{
...
"sports": [
{
"_id": "5fc6aa91b1db6cd15702241c",
"name": "Football",
"icon": "5fc9598a0955177dee8a3bc4"
},
{
"_id": "5fcb741e786f0703646befe2",
"name": "Swimming",
"icon": "5fc9598a0955177dee8a3bc5"
}]
}]
}
}
}
How I can do it?
You will need to use aggregation methods like $unwind, $lookup, $group last but not least $project.
They define a sequence of steps to help you retrieve your data as you expect
The get the response that you want take a look into the following code and the running example in here
Ps: I suggest you to look into the example in the mongo playground ( link above ) and separate the aggregation pipelines to understand better the proccess
db.users.aggregate([
{
// move specializations array to separated objects
"$unwind": "$moreInformation.specializations"
},
{
// move specialization.sports to separated objects
"$unwind": "$moreInformation.specializations.sports"
},
{
// search into sports collection based on the temporary "sports" attribute
$lookup: {
from: "sports",
localField: "moreInformation.specializations.sports",
foreignField: "_id",
as: "fullSport"
}
},
{
// as the lookup resolves an array of a single result we move it to be an object
"$unwind": "$fullSport"
},
{
// here we select only the attributes that we need and the selected language
"$project": {
"moreInformation.experience": 1,
"moreInformation.specializations.sports._id": "$fullSport._id",
"moreInformation.specializations.sports.icon": "$fullSport.icon",
"moreInformation.specializations.sports.name": "$fullSport.name.en"
}
},
{
// then we group it to make the separated objects an array again
"$group": {
"_id": "$_id",
// we group by the $_id and move it to temporary "sports" attribute
"sports": {
$push: "$moreInformation.specializations.sports"
},
"moreInformation": {
$first: "$moreInformation"
}
}
},
{
$project: {
"moreInformation.experience": 1,
// move back the temporary "sports" attribute to its previous path
"moreInformation.specializations.sports": "$sports"
}
}
])
I have a user entity with the following relationship:
User.associate = (models) => {
User.belongsToMany(User, {
as: "friends",
through: "user_friend",
onDelete: "CASCADE",
})
User.hasMany(models.Message, { as: "sentMessages", foreignKey: "senderId" })
User.hasMany(models.Message, {
as: "receivedMessages",
foreignKey: "recipientId",
})
}
and a messages entity with relationship:
Message.associate = (models) => {
Message.belongsTo(models.User, { foreignKey: "senderId" })
Message.belongsTo(models.User, { foreignKey: "recipientId" })
}
and I'm attempting to do something like: get me a user, include their friends, include their conversation history with each friend using the following:
let user = await User.findOne({
where: {
auth_key: auth_key,
},
include: {
association: "friends",
attributes: ["first_name", "last_name", "chat_name", "profile_picture"],
through: {
attributes: [],
},
include: [
{
required: false,
association: "sentMessages",
where: {
recipientId: 1,
},
},
{
required: false,
association: "receivedMessages",
where: {
senderId: 1,
},
},
],
},
})
which does kind of work but it seems really messy and I don't like having the messages split into sent and received, I would just like them grouped together. Currently the response looks like this:
{
"id": 1,
"username": "ajsmith",
"first_name": "Anthony",
"last_name": "Smith",
"chat_name": "Anthony.Smith",
"email": "a#gmail.com",
"createdAt": "2020-06-15T19:59:58.000Z",
"updatedAt": "2020-06-15T19:59:58.000Z",
"friends": [
{
"first_name": "Sarah",
"last_name": "Smith",
"chat_name": "Sarah.Smith",
"sentMessages": [
{
"id": 2,
"senderId": 2,
"recipientId": 1,
"type": "text",
"content": "Hi",
"createdAt": "2020-06-15T19:59:58.000Z",
"updatedAt": "2020-06-15T19:59:58.000Z"
}
],
"receivedMessages": [
{
"id": 1,
"senderId": 1,
"recipientId": 2,
"type": "text",
"content": "Hello",
"createdAt": "2020-06-15T19:59:58.000Z",
"updatedAt": "2020-06-15T19:59:58.000Z"
}
]
}
]
}
I'm not sure if my relationships are wrong, my querying is wrong or it's just not possible to do but over the last week I've searched the sequelize docs, stackoverflow and everything in between and haven't found a solution. It seems like if I could include an OR statement on the LEFT OUTER JOIN which is occurring, then I could have my messages include both sent and received where the senderId or recipientId is my user.
I have an array of data returned by MongoDB. I would like to know how to join first name and last name from the below array using mongodb aggregation framework.
i have seen couple of posts similer to my query,however i did not understand the answer and hence i am posting a new question.
I have written a sample code.any helping corrcting my code would be really appreciated
var playersData = [
{
firstName: 'David',
LastName: 'John',
country: 'India'
},
{
firstName: 'Chris',
LastName: 'Jericho',
country: 'USA'
},
{
firstName: 'John',
LastName: 'Cena',
country: 'USA'
}
];
code
playerModel.aggregate([
{
"$match": {
[{ "country": 'USA' }]
}
},
{
"$project": {
"_id": 0, "playersData.firstName": 1, "playersData.lastName": 1,
fullName: {
$reduce: {
input: '$playersData',
initialValue: '',
in: {
$concat: ["$$value", "$$this"]
}
}
}
}
}
], function (err, result) {
})
You can try below aggregation.
$match to keep player array where at least one of the country is USA followed by $filter to filter elements with country as USA and $map with $concat to join the first name and last name to produce the full name array.
playerModel.aggregate([
{
"$match": {
"playersData.country": "USA"
}
},
{
"$project": {
"_id": 0,
"playersData.firstName": 1,
"playersData.lastName": 1,
"fullname": {
"$map": {
"input": {
"$filter": {
"input": "$playersData",
"as": "playerf",
"cond": {
"$eq": [
"$$playerf.country", "USA"
]
}
}
},
"as": "playerm",
"in": {
"$concat": [
"$$playerm.firstName",
",",
"$$playerm.lastName"
]
}
}
}
}
}
])
I'm new to this technology and working with Node and Express server that uses Mongoose. I have following schema for a document collection.
var empSchema = new mongoose.Schema({
_id: String,
orgName: {type: String, required: true},
locName: {type: String, required: true},
empName: {type: String, required: true}
});
Here I get a list of location names like "NewYork", "London", "Paris" etc... in a request and needs to return the documents in the response as following....
{
result:[{locName:"NewYork",
empList:[
{orgName:"abc", empName:"emp1"},
{orgName:"xyz", empName:"emp2"}]
},
{locName:"London",
empList:[
{orgName:"pkq", empName:"emp13"},
{orgName:"mns", empName:"emp23"}]
}]
}
What would be the best way to use mongoose from Node. I think making multiple queries (each one with a location) to mongodb is a bad idea.
Is there a way to get the expected json response with single call to mongoose? Thanks.
Yes, use the aggregation framework to get the desired output. The aggregation pipeline will consist of a $group operator pipeline stage which groups the documents by the locName field and the $addToSet accumulator operator to add the orgName and empName fields to an array empList. The last pipeline stage $project operator then replaces the _id field from the previous aggregation stream with a new field locName.
To demonstrate this concept, suppose you have a sample collection which you insert with mongo shell:
db.employees.insert([
{
_id: "1",
orgName: "abc",
locName: "New York",
empName: "emp1"
},
{
_id: "2",
orgName: "xyz",
locName: "New York",
empName: "emp2"
},
{
_id: "3",
orgName: "pkq",
locName: "London",
empName: "emp13"
},
{
_id: "4",
orgName: "mns",
locName: "London",
empName: "emp23"
}
])
The following aggregation produces the desired result:
db.employees.aggregate([
{
"$group": {
"_id": "$locName",
"empList": {
"$addToSet": {
"orgName": "$orgName",
"empName": "$empName"
}
}
}
},
{
"$project": {
"_id": 0,
"locName": "$_id",
"empList": 1
}
}
])
Output:
/* 0 */
{
"result" : [
{
"empList" : [
{
"orgName" : "mns",
"empName" : "emp23"
},
{
"orgName" : "pkq",
"empName" : "emp13"
}
],
"locName" : "London"
},
{
"empList" : [
{
"orgName" : "xyz",
"empName" : "emp2"
},
{
"orgName" : "abc",
"empName" : "emp1"
}
],
"locName" : "New York"
}
],
"ok" : 1
}
In Mongoose, you can use the aggregation pipeline builder like this:
Employee.aggregate()
.group({
"_id": "$locName",
"empList": {
"$addToSet": {
"orgName": "$orgName",
"empName": "$empName"
}
}
})
.project({
"_id": 0,
"locName": "$_id",
"empList": 1
})
.exec(function (err, res) {
if (err) return handleError(err);
console.log(res);
});
// Or the simple aggregate method
var pipeline = [
{
"$group": {
"_id": "$locName",
"empList": {
"$addToSet": {
"orgName": "$orgName",
"empName": "$empName"
}
}
}
},
{
"$project": {
"_id": 0,
"locName": "$_id",
"empList": 1
}
}
]
Employee.aggregate(pipeline, function (err, res) {
if (err) return handleError(err);
console.log(res);
});
All queries, when you need to group by sum values called aggregate. You can read about it in the mongo docs and same methods have model in Mongoose. To produce your query, you can use code like this:
Employee
.aggregate()
.group({ _id: '$locName', empList: { $push: "$$ROOT" }})
.exec(function (err, res) {
});
If you need not to query all table, there is also have a match method.