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
Related
As specified below, I have section and class schema in my MongoDB.
How can I query all the students of a given list of Class IDs?
const sectionsSchema = new Schema({
section: {
type: String,
required: false
},
students:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
const classSchema = new Schema({
name:{
type: String,
required: true
},
institution: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Institution'
},
divisions :[{
type: sectionsSchema,
required: true
}]
},{timestamps: true})
Below is the Example Data
Class: [{
_id: 'Class1',
institution: 'inst1',
divisions:[{
section: 'A',
students: ['Stud1','Stud2']
},
{
section: 'B',
students: ['Stud3','Stud4']
}]
},
{
_id: 'Class2',
institution: 'inst1',
divisions:[{
section: 'A',
students: ['Stud5','Stud6']
}]
}]
input: ['Class1','Class2'];
Expected Output: ['Stud1','Stud2','Stud3','Stud4','Stud5','Stud6'];
My suggestion is to use an aggregate query as following:
.aggregate([
{
$match: {
_id: {
$in: [
"Class1",
"Class2"
]
}
}
},
{
$unwind: "$divisions"
},
{
$unwind: "$divisions.students"
},
{
"$replaceRoot": {
"newRoot": "$divisions"
}
},
])
Example output:
[
{
"section": "A",
"students": "Stud1"
},
{
"section": "A",
"students": "Stud2"
},
{
"section": "B",
"students": "Stud3"
},
{
"section": "B",
"students": "Stud4"
},
{
"section": "A",
"students": "Stud5"
},
{
"section": "A",
"students": "Stud6"
}
]
You afterwards have to map the documents returned to simple strings, since mongoose is not able to return plain strings.
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.
I am new to MongoDB and mongoose.
I am trying to retrieve the entire array of object hierarchy from the database as a JSON.
By searching, I learned how to group nested lookup with 3 child levels without array, but the problem is I cannot find a way to deal with the nested arrays.
Here is what I am struggling with.
user:
[
{
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
{
_id: ObjectId("a2"),
username: "user2",
name: "name2"
},
{
_id: ObjectId("a3"),
username: "user2",
name: "name2"
},
...
]
reply:
[
{
_id: ObjectId("a"),
author: {ObjectId("a3")},
title: {"Reply1"};
body: {"Hello World!"}
},
{
_id: ObjectId("b"),
author: {ObjectId("a1")},
title: {"Reply2"};
body: {"Hello World!"}
},
{
_id: ObjectId("c"),
author: {ObjectId("a2")},
title: {"Reply3"};
body: {"Hello World!"}
},
{
_id: ObjectId("d"),
author: {ObjectId("a2")},
title: {"Reply4"};
body: {"Hello World!"}
}
...
]
post:
[
//First post
{
_id: ObjectId("0"),
title: 'post title',
author: {ObjectId("a1")},
reply: [{ObjectId("a")}, {ObjectId("b")}, ...],
},
//Second Post
{
_id: ObjectId("1"),
title: 'post title2',
author: {ObjectId("a2")},
reply: [{ObjectId("c")}, {ObjectId("d")}, ...],
},
...
]
Expected example:
[
//First post
{
_id: ObjectId("0"),
title: 'post title',
author: {
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
reply: [{
_id: ObjectId("a"),
author: {
_id: ObjectId("a3"),
username: "user2",
name: "name2"
},
title: {"Reply1"},
body: {"Hello World!"}
},
{
_id: ObjectId("b"),
author: {
_id: ObjectId("a1"),
username: "user1",
name: "name1"
},
title: {"Reply2"};
body: {"Hello World!"}
},
...
],
},
{
//Second post
},
...
]
Here is the code that I used.
posts = await Post.aggregate([{
$match: searchQuery
},
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'author'
}
},
{
$lookup: {
from: 'replies',
localField: 'reply',
foreignField: '_id',
as: 'reply'
}
},
{
$unwind: '$author',
},
{
$lookup: {
from: 'users',
localField: 'reply.author',
foreignField: '_id',
as: 'reply.author'
}
},
{
$project: {
title: 1,
author: 1,
reply: 1
}
},
]).exec();
How should I change the code if I want to get the expected example?
Here's one way to do it. Lots of "$lookup" for author of post, reply to the post, author ids of reply, and then author document matching _id.
db.post.aggregate([
{
"$lookup": {
"from": "user",
"localField": "author",
"foreignField": "_id",
"as": "author"
}
},
{
"$set": {
"author": {
"$first": "$author"
}
}
},
{
"$lookup": {
"from": "reply",
"localField": "reply",
"foreignField": "_id",
"as": "reply"
}
},
{
"$lookup": {
"from": "user",
"localField": "reply.author",
"foreignField": "_id",
"as": "replyAuthors"
}
},
{
"$project": {
"title": 1,
"author": 1,
"reply": {
"$map": {
"input": "$reply",
"as": "repObj",
"in": {
"$mergeObjects": [
"$$repObj",
{
"author": {
"$first": {
"$filter": {
"input": "$replyAuthors",
"as": "repA",
"cond": {
"$eq": [
"$$repA._id",
"$$repObj.author"
]
}
}
}
}
}
]
}
}
}
}
}
])
Try it on mongoplayground.net.
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 below schema
{
id: 123,
values:[
{valueId: "12444", name: "asd"},
{valueId: "555", name: "www"},
]
}
i want to convert it into (combine name into single string)
{
id: 123,
values: "asdwww"
}
i have tried below aggregate which puts all name value in an array
$project: {
attributes: {
"$map": {
"input": "$attributes",
"as": "attr",
"in": {
"id": "$$attr.id",
"values": "$$attr.values.name"
}
}
}
},
which makes it into
{
id: 123,
values:[
"asd",
"www"
]
}
i want to have values as single string value as "asd,www" or "asdwww"
You need $reduce instead of $map:
db.collection.aggregate([
{
$project: {
_id: 1,
values: {
$reduce: {
input: "$values",
initialValue: "",
in: { $concat: [ "$$value", "$$this.name" ] }
}
}
}
}
])
Mongo Playground
Here's an example which shows how to handle delimiters