Join array elements using MongoDB aggregation framework - javascript

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"
]
}
}
}
}
}
])

Related

How to return nested document with Mongoose?

If I have this collection
[
{
"_id": "637cbf94b4741277c3b53c6c",
"text": "outter",
"username": "test1",
"address": [
{
"text": "inner",
"username": "test2",
"_id": "637cbf94b4741277c3b53c6e"
}
],
"__v": 0
}
]
and would like to search for the nested document by _id and return all of the nested document. If I do
db.collection.find({
_id: "637cbf94b4741277c3b53c6c"
},
{
address: {
$eq: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
I get
query failed: (Location16020) Expression $eq takes exactly 2 arguments. 1 were passed in.
Playground link
Question
Can anyone see what I am doing wrong?
use $elemMatch and also you have extra unneeded brackets. try
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
Edit: if you only want to return the address add projection like this
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
},
{
_id: 0,
address: 1
})
One option is to use find:
db.collection.find({},
{
_id: 0,
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
See how it works on the playground example
The other option is to use aggregation pipeline:
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"637cbf94b4741277c3b53c62",
"$address._id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$address",
cond: {
$eq: [
"$$this._id",
"637cbf94b4741277c3b53c6e"
]
}
}
}
}
}
}
])
See how it works on the playground example

Mongoose Populate Null Reference

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.

How to convert array to string in mongodb

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

Query an object array to check if it contains an array of strings in mongoose

say i have the following inside my db:
knights
{
name: 'Knightley',
skills: [
{ name: 'sword', level: 2 },
{ name: 'shield', level: 1 }
]
},
{
name: 'Cowardly',
skills: [
{ name: 'sword', level: 1 },
{ name: 'shield', level: 5 }
]
}
and i want to return all knights with skills of sword and shield.
something like this (pseudo):
Knight.find({ skills.name: which contains ['sword', 'shield'] })
how do i do this kind of query?
thanks!
You need to use $elemMatch to find inside the array with $in operator
db.collection.find({
skills: {
$elemMatch: {
name: { $in: ["sword", "shield"] }
}
}
})
Output
[
{
"name": "Knightley",
"skills": [
{
"level": 2,
"name": "sword"
},
{
"level": 1,
"name": "shield"
}
]
},
{
"name": "Cowardly",
"skills": [
{
"level": 1,
"name": "sword"
},
{
"level": 5,
"name": "shield"
}
]
}
]
Check it here

Mongoose find with multiple matches

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.

Categories