Unable to use $elemMatch in aggregation - arrays

As the title suggests I am unable to use $elemMatch inside aggregate. I have attached my code below, and I would like to know if there is an alternative to this code. I'm trying to match and get all the elements of an array.
db.collection.aggregate([
{
$match: {
s: "A",
$or: [
{
id: "123"
},
{
name: "Raj"
}
],
condition: {
$elemMatch: {
$and: [
{
"depend.id": "123",
"depend.ques": "test ques"
},
{
depend: {
$size: 1
}
}
]
}
}
}
},
{
$lookup: {
from: "categories",
localField: "category",
foreignField: "_id",
as: "category"
}
},
{
$unwind: "$category"
}
])
Sample document:
{
"_id" : ObjectId("5d723e34ef5e6630fde5b71d"),
"id" : 1,
"name" : "Raj",
"category" : 123,
"condition" : [
{
"depend" : [
{
"id" : 1,
"ques" : "test ques"
}
]
},
{
"depend" : [
{
"id" : 2,
"ques" : "test 2nd ques"
}
]
}
]
}
The sample document should be matched with the above query.

Issues:
There is no s field in the sample document and you are applying a filter on in i.e. s: "A"
Both id and depend.id are numeric but are matched with
string literals
There is no document in condition array with depend.id as 123
Please refer the following query:
db.collection.aggregate([
{
$match:{
$or:[
{
"id":123
},
{
"name":"Raj"
}
],
"condition":{
$elemMatch:{
$and:[
{
"depend.id":1,
"depend.ques": "test ques"
},
{
"depend":{
$size:1
}
}
]
}
}
}
}
]).pretty()

This Query is perfectly working on your data.
db.getCollection('stackans').aggregate([
{
$match: {
$or: [
{
id: 1
},
{
name: "Raj"
}
],
condition: {
$elemMatch: {
$and: [
{
"depend.id": 1,
"depend.ques": "test ques"
},
{
depend: {
$size: 1
}
}
]
}
}
}
} ,{
$lookup: {
from: "categories",
localField: "category",
foreignField: "_id",
as: "category"
}
}
])

Related

Mongodb $ (dollar sign) project query for arrays

I have the following data:
{
_id: "1"
transitions: [
{
"_id" : "11"
"name" : "Tr1"
"checkLists" : [
{ _id: "111", name: "N1"},
{ _id: "112", name: "N2"}
]
}
]
}
I used the following code to get the name N2 by query of _id:112
db.collection.findOne({ 'transitions.checkLists._id: new ObjectId("112") } }}, { 'transitions.checkLists.$': 1 })
but the result returns back both of them:
{ _id: ObjectId("1"),
transitions:
[ { checkLists:
[ { name: 'N1', _id: ObjectId("111") },
{ name: 'N2', _id: ObjectId("112") } ] } ] }
I would like to find and get only the name N2 by query of _id:112
Expected Result:
{ _id: ObjectId("1"),
transitions:
[ { checkLists:
[ { name: 'N2', _id: ObjectId("112") } ] } ] }
You can do it via the aggregation framework as follow:
db.collection.aggregate([
{
$match: {
"transitions.checkLists._id": "111"
}
},
{
"$addFields": {
"transitions": {
"$map": {
"input": "$transitions",
"as": "t",
"in": {
"$mergeObjects": [
"$$t",
{
"checkLists": {
"$filter": {
"input": "$$t.checkLists",
"as": "c",
"cond": {
$eq: [
"$$c._id",
"111"
]
}
}
}
}
]
}
}
}
}
},
{
"$addFields": {
transitions: {
$filter: {
input: "$transitions",
as: "elem",
cond: {
"$ne": [
"$$elem.checkLists",
[]
]
}
}
}
}
}
])
Explained:
Match the transitions.checkLists._id element in first stage.
Map/mergeobjects with filtered checklists to filter only the needed object from the transitions.checkLists array.
Remove the transitions elements where no checkList exists.
( this need to be done to remove elements for same document where there is no matching _id's )
Playground

How can I find subdocument using Mongoose?

I have defined a model like this.
const ShotcountSchema = new Schema({
shotCountId : {
type : ObjectId,
required : true,
ref : 'member-info'
},
userId : {
type : String,
required : true,
unique : true
},
shot : [{
shotId : {
type : String,
required : true,
unique : true
},
clubType : {
type : String,
required : true
}
createdAt : {
type : Date,
default : Date.now
}
}]
});
If you perform a query to find subdocuments based on clubType as follows, only the results of the entire document are output.
For example, if I write the following code and check the result, I get the full result.
const shotCount = await ShotcountSchema.aggregate([
{
$match : { shotCountId : user[0]._id }
},
{
$match : { 'shot.clubType' : 'driver' }
}
]);
console.log(shotCount[0]); // Full result output
I would like to filter the subdocuments via clubType or createdAt to explore. So, I want these results to be printed.
{
_id: new ObjectId("61d67f0a74ec8620f34c57ed"),
shot: [
{
shotId: 'undefinedMKSf*Tf#!qHxWpz1hPzUBTz%',
clubType: 'driver',
shotCount: 20,
_id: new ObjectId("61d67f0a74ec8620f34c57ef"),
createdAt: 2022-01-06T05:32:58.391Z
}
]
}
How should I write the code?
db.collection.aggregate([
{
"$match": {
_id: ObjectId("61d67f0a74ec8620f34c57ed"),
"shot.clubType": "driver",
shot: {
$elemMatch: {
$and: [
{
"createdAt": {
$gte: ISODate("2022-01-07T05:32:58.391Z")
}
},
{
"createdAt": {
$lte: ISODate("2022-01-09T05:32:58.391Z")
}
}
]
}
}
}
},
{
"$set": {
shot: {
"$filter": {
"input": "$shot",
"as": "s",
"cond": {
$and: [
{
"$eq": [
"$$s.clubType",
"driver"
]
},
{
"$gte": [
"$$s.createdAt",
ISODate("2022-01-07T05:32:58.391Z")
]
},
{
"$lte": [
"$$s.createdAt",
ISODate("2022-01-09T05:32:58.391Z")
]
}
]
}
}
}
}
}
])
mongoplayground

How to query two collections with related data?

I have 2 collections, collection A has some documents like {'id':1,'field':'name'},{'id':1,'field':'age'},and collection B has some documents like
{'_id':1,'name':'alice','age':18,'phone':123},{'_id':2,'name':'bob','age':30,'phone':321}
and I want to find all the document whose '_id' is in collectionA, and just project the corresponding field.
for example:
collection A
{'id':1,'field':'name'},
{'id':1,'field':'age'}
collection B
{'_id':1,'name':'alice','age':18,'phone':123},
{'_id':2,'name':'bob','age':30,'phone':321}
the result is:
{'name':'alice','age':18},
I don't know if there is an easy way to do that?
You can use $lookup to join two collection
db.col1.aggregate([
{
$match: {
id: 1
}
},
{
"$lookup": {
"from": "col2",
"localField": "id",
"foreignField": "_id",
"as": "listNames"
}
},
{
$project: {
listNames: {
$first: "$listNames"
}
}
},
{
$project: {
_id: 0,
name: "$listNames.name",
age: "$listNames.age"
}
}
])
Mongo Playground: https://mongoplayground.net/p/E-0WvK_SUS_
So the idea is:
Convert the documents in to key, value pair for both the collections using $objectToArray.
Then perform a join operation based on key k and (id <-> _id) using $lookup.
Replace the result as root element using $replaceRoot.
Convert array to object using $arrayToObject and again $replaceRoot.
Query:
db.colB.aggregate([
{
$project: {
temp: { $objectToArray: "$$ROOT" }
}
},
{
$lookup: {
from: "colA",
let: { temp: "$temp", colB_id: "$_id" },
pipeline: [
{
$addFields: {
temp: { k: "$field", v: "$id" }
}
},
{
$match: {
$expr: {
$and: [
{ $in: ["$temp.k", "$$temp.k"] },
{ $eq: ["$temp.v", "$$colB_id"] }
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$$temp",
as: "item",
cond: { $eq: ["$field", "$$item.k"] }
}
}
}
}
}
],
as: "array"
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Output:
{
"name" : "alice",
"age" : 18
}

How to query on embedded documents

{
"_id" : ObjectId("5fa919a49bbe481d117506c9"),
"isDeleted" : 0,
"productId" : 31,
"references" : [
{
"_id" : ObjectId("5fa919a49bbe481d117506ca"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5fa919a49bbe481d117506ba")
},
{
"_id" : ObjectId("5fa91cc7d7d52f1e389dee1f"),
"languageCode" : "ar",
"languageId" : 2,
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
}
],
"createdAt" : ISODate("2020-11-09T10:27:48.859Z"),
"updatedAt" : ISODate("2020-11-09T10:27:48.859Z"),
"__v" : 0
},
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3a"),
"isDeleted" : 0,
"productId" : 21,
"references" : [
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3b"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
}
],
"createdAt" : ISODate("2020-10-29T11:44:29.852Z"),
"updatedAt" : ISODate("2020-10-29T11:44:29.852Z"),
"__v" : 0
}
This is my mongoDB collection in which i store the multilingual references to product collection. In productId are the references to product Collection. Now If we have ar in our request, then we will only have the productId of ar languageCode. If that languageCode does not exist then we will have en langCode productId.
For Example if the user pass ar then the query should return
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
I have tried using $or with $elemMatch but I am not able to get the desired result. Also i am thinking of using $cond. can anyone help me construct the query.
We can acheive
$facet helps to categorized the incoming documents
In the arArray, we get all documents which has"references.languageCode": "ar" (This document may or may not have en), then de-structure the references array, then selecting the "references.languageCode": "ar" only using $match. $group helps to get all productIds which belong to "references.languageCode": "ar"
In the enArray, we only get documents which have only "references.languageCode": "en". Others are same like arArray.
$concatArrays helps to concept both arArray,enArray arrays
$unwind helps to de-structure the array.
$replaceRoot helps to make the Object goes to root
Here is the mongo script.
db.collection.aggregate([
{
$facet: {
arAarray: [
{
$match: {
"references.languageCode": "ar"
}
},
{
$unwind: "$references"
},
{
$match: {
"references.languageCode": "ar"
}
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
],
enArray: [
{
$match: {
$and: [
{
"references.languageCode": "en"
},
{
"references.languageCode": {
$ne: "ar"
}
}
]
}
},
{
$unwind: "$references"
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
]
}
},
{
$project: {
combined: {
"$concatArrays": [
"$arAarray",
"$enArray"
]
}
}
},
{
$unwind: "$combined"
},
{
"$replaceRoot": {
"newRoot": "$combined"
}
}
])
Working Mongo playground
You can test this solution to see if it is useful for you question:
db.collection.aggregate([
{
$addFields: {
foundResults:
{
$cond: {
if: { $in: ["ar", "$references.languageCode"] }, then:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'ar'] },
]
}
}
}
, else:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'en'] },
]
}
}
}
}
}
}
},
{ $unwind: "$foundResults" },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$foundResults"] } } },
{ $project: { _id: 0, "productId": 1 } }
])

Update value in nested array of mongo 3.5

I want to make an upsert call to update as well as insert my data in nested array of mongo db.
This is my mongo document.
{
"_id" : "575",
"_class" : "com.spyne.sharing.SpyneShareUserProject",
"spyneSharePhotoList" : [
{
"_id" : "fxLO68XyMR",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : false
},
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
]
},
{
"_id" : "nVpD0KoQAI",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : true
}
]
},
{
"_id" : "Pm0B3Q9Igv",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : true
}
]
}
]
}
Here my requirement is,
lets say i have an ID i.e. 575 (_id)
Then i will have the nested array ID i.e. fxLO68XyMR (spyneSharePhotoList._id)
Then i will have nested email id as ID i.e. chittaranjan#eventila.com (spyneSharePhotoList.spyneShareUsers._id) and selectedByClient (boolean)
Now i want is to check if this ID (spyneSharePhotoList.spyneShareUsers._id) is already present in the desired location i want to update the boolean value i.e. selectedByClient (true / false) according to that email id.
If the id is not present in the array, the it will make a new entry. as
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
in spyneShareUsers list.
Please help me to do this task. Thank you
Most likely this is not the smartest solution, but it should work:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR",
ShareUserId: "chittaranjan_new#eventila.com"
}
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$facet: {
root: [{ $match: {} }],
update: [
{ $unwind: "$spyneSharePhotoList" },
{ $match: { "spyneSharePhotoList._id": shareUserProject.PhotoListId } },
{
$set: {
"spyneSharePhotoList.spyneShareUsers": {
$concatArrays: [
{
$filter: {
input: "$spyneSharePhotoList.spyneShareUsers",
cond: { $ne: ["$$this._id", shareUserProject.ShareUserId] }
}
},
[{
_id: shareUserProject.ShareUserId,
selectedByClient: { $in: [shareUserProject.ShareUserId, "$spyneSharePhotoList.spyneShareUsers._id"] }
}]
]
}
}
}
]
}
},
{ $unwind: "$root" },
{ $unwind: "$update" },
{
$set: {
"root.spyneSharePhotoList": {
$concatArrays: [
["$update.spyneSharePhotoList"],
{
$filter: {
input: "$root.spyneSharePhotoList",
cond: { $ne: ["$$this._id", shareUserProject.PhotoListId] }
}
}
]
}
}
},
{ $replaceRoot: { newRoot: "$root" } }
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
I did not check whether all operators are available in MongoDB 3.5
My goal was to process everything in aggregation pipeline and run just a single replaceOne() at the end.
Here another solution based on $map operator:
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$set: {
spyneSharePhotoList: {
$map: {
input: "$spyneSharePhotoList",
as: "photoList",
in: {
$cond: {
if: { $eq: [ "$$photoList._id", shareUserProject.PhotoListId ] },
then: {
"_id": "$$photoList._id",
spyneShareUsers: {
$cond: {
if: { $in: [ shareUserProject.ShareUserId, "$$photoList.spyneShareUsers._id" ] },
then: {
$map: {
input: "$$photoList.spyneShareUsers",
as: "shareUsers",
in: {
$cond: {
if: { $eq: [ "$$shareUsers._id", shareUserProject.ShareUserId ] },
then: { _id: shareUserProject.ShareUserId, selectedByClient: true },
else: "$$shareUsers"
}
}
}
},
else: {
$concatArrays: [
"$$photoList.spyneShareUsers",
[ { _id: shareUserProject.ShareUserId, selectedByClient: false } ]
]
}
}
}
},
else: "$$photoList"
}
}
}
}
}
}
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
You can achieve the same result also with two updates:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR_x",
ShareUserId: "chittaranjan_new#gmail.com"
}
ret = db.collection.updateOne(
{ _id: shareUserProject.id },
{ $pull: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)
db.collection.updateOne(
{ _id: shareUserProject.id },
{ $push: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId, selectedByClient: ret.modifiedCount == 1 } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)

Resources