Querying mongo array of embedded documents in aggregation pipeline $eq diff value - javascript

I have an array of mogoDB embedded documents
const ArticleSchema = new mongoose.Schema({
title: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
group: { type: mongoose.Schema.Types.ObjectId, ref: "ArticleGroup" },
});
// ArticleGroup
const ArticleGroupSchema = new mongoose.Schema({
name: { type: String, required: true },
desc: { type: String, default: "" },
state: { type: String, required: true, default: "public" },
members: [
{
desc: String,
role: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
},
],
});
// User
const userSchema = new mongoose.Schema(
{
email: { type: String, unique: true },
password: String,
},
);
test data :
{
_id: "6131aa5b367318e2df14b988",
title: "功能更新清单",
author: ObjectId("607edeb4b1e1bea9acb5af38"),
group: ObjectId("612d00a43c52975d4ade10d4")
}
I want to add a new field 'can_edit' to the document by comparing 'author._id == group.members. User' in 'aggregate' pipeline.
Article.aggregate([
{
$lookup: {
from: "users",
localField: "author",
foreignField: "_id",
as: "author",
},
},
{
$lookup: {
from: "articlegroups",
localField: "group",
foreignField: "_id",
as: "group"
}
},
{
$addFields: {
can_edit: {
$eq: ["$group.members.user", "$author._id"],
},
}
}
])
However, the 'can_edit' I got was always' false ', and I'm sure that the 'author._id' value in my test data is the same as' group.members.
I want to get the data:
{
_id: "6131aa5b367318e2df14b988",
can_edit: true,
title: "功能更新清单",
author: {
_id: "607edeb4b1e1bea9acb5af38",
email: "frmachao#126.com",
},
group: {
_id: "612d00a43c52975d4ade10d4",
desc: "开发",
state: "public",
name: "开发小组",
members: [
{
_id: "612d00a43c52975d4ade10d5",
role: "admin",
user: "607edeb4b1e1bea9acb5af38",
}
]
}
}
Is there any way I can meet my needs? Does anyone know how to do that? I would grateful to you 🙏.
Just now, I try :
Article.aggregate([
{
$lookup: {
from: "users",
localField: "author",
foreignField: "_id",
as: "author",
},
},
{
$lookup: {
from: "articlegroups",
localField: "group",
foreignField: "_id",
as: "group"
}
},
{
$addFields: {
can_edit: {
$in: [
"$author._id",
"$group.members.user"
],
},
},
}
])
However, the 'can_edit' I got was always' false '

With $in (aggregation),
Returns a boolean indicating whether a specified value is in an array.
$in: [
"$author._id",
"$group.members.user"
]
Updated:
Since Post Owner added the issue:
$in requires an array as a second argument
which means $group.members potentially is null and not an array.
Add condition checking for checking $group.members must be an array.
{
case: {
$ne: [
{
"$type": "$group.members"
},
"array"
]
},
then: false
},
$ne - Not equal check
$type - Return the BSON type of field
Complete query:
db.collection.aggregate([
{
$addFields: {
can_edit: {
$switch: {
branches: [
{
case: {
$ne: [
{
"$type": "$group.members"
},
"array"
]
},
then: false
},
{
case: {
$ne: [
{
"$type": "$group.members"
},
"array"
]
},
then: false
},
{
case: {
$in: [
"$author._id",
"$group.members.user"
]
},
then: true
}
],
default: false
}
}
}
}
])
Sample 1 in Mongo playground
Alternatively, the query can be shortened with $switch not needed as $in returns the same boolean result.
$and - Logical AND of multiple expressions
Complete query:
db.collection.aggregate([
{
$addFields: {
can_edit: {
$and: [
{
$eq: [
{
"$type": "$group.members"
},
"array"
]
},
{
$in: [
"$author._id",
"$group.members.user"
]
}
]
}
}
}
])
Sample 2 in Mongo Playground

Related

Mongo Aggregation pipeline update or push

I have a MongoDB Model which consist of array of members as obejcts.
const guestSchema = new mongoose.Schema({
salutation: {
type: String,
},
members: [membersSchema],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
},
});
Members Schema:
const membersSchema = new mongoose.Schema({
name: String,
status: {
type: String,
enum: ['regular', 'helper'],
default: 'regular',
},
});
I want to achieve of doing an update in case documet with given ID exist or push to an array in case ID with document in array does not exist. I use aggregation pipeline, however I am not able to achieve pushing new document to array. Why can't I use push after else statement like this.
const subDocumentToUpsert = { 'name': mem.name, 'status': mem.status, '_id': ObjectId(mem.id)}
const subDocumentNoID = { 'name': mem.name, 'status': mem.status}
await Guest.findOneAndUpdate(
{ "_id": req.params.id },
[
{
$set: {
members: {
$cond: {
if: { $in: [subDocumentToUpsert._id, '$members._id'] },
then: {
$map: {
input: '$members',
as: 'sub_document',
in: {
$cond: {
if: { $eq: ['$$sub_document._id', subDocumentToUpsert._id] },
then: subDocumentToUpsert,
else: '$$sub_document',
},
},
},
},
else: {
$push: {
subDocumentNoID
},
},
},
},
},
},
},
]);
What is the best way of doing so? Thank you
You can do as follow:
db.collection.update({
_id: {
$in: [
1,
2
]
}
},
[
{
$set: {
members: {
$cond: {
if: {
$in: [
5,
"$members._id"
]
},
then: {
$map: {
input: "$members",
as: "sub",
in: {
$cond: {
if: {
$eq: [
"$$sub._id",
5
]
},
then: {
_id: 5,
status: "regular_updated",
name: "Negan_updated"
},
else: "$$sub"
},
},
},
},
else: {
$concatArrays: [
"$members",
[
{
_id: 5,
status: "regular_upserted",
name: "Negan_upserted"
}
]
]
}
}
}
}
}
}
],
{
multi: true
})
Explained:
Check if _id:5 exist in the subobject and update via $map/$cond only the object that has the _id:5.
In case there is no _id:5 add the new object to the array with $concatArrays.
Playground

MongoDB - Aggregate lookup array field and match

With MongoDB aggregate, how do you lookup child documents from an array on the parent document and match a field on the child to a value?
I want to lookup the ShopItems for a Shop. And I only want the ShopItems that are not kidFriendly.
Details:
Let's say I have Shops collection
Shop Schema
{
shopItems: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'ShopItem',
},
],
}
And a shop can have multiple Shop Items:
ShopItem Schema
[
itemName: { type: String },
kidFriendly: { type: Boolean, default: false },
]
I want to lookup the ShopItems for a Shop. And I only want the ShopItems that are not kidFriendly.
I tried
const result3 = await Shop.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId('623ae52ba5b1af0004e1c4ec'),
},
},
{
$lookup: {
from: ShopItem.collection.collectionName,
pipeline: [
{
$match: {
kidFriendly: { $ne: true },
_id: { $in: '$shopItems' },
},
},
],
as: 'adultOnlyItems',
},
},
]);
but I get an error:
$in needs an array.
Example data
// shops
[
{
_id: ObjectId('623ae52ba5b1af0004e1c4ec'),
shopItems: [ObjectId('631e6b133b688a0004a17265'), ObjectId('62f4cc974a255f00044c01b5'), ObjectId('625ffc48eec7b20004c9294c')]
},
{
_id: ObjectId('623ae52ba5b1af0004e1c4eb'),
shopItems: [ObjectId('631e6b133b688a0004a17263')]
}
]
//shop items
[
{
_id: ObjectId('631e6b133b688a0004a17265'),
itemName: "Barbie",
kidFriendly: true
},
{
_id: ObjectId('62f4cc974a255f00044c01b5'),
itemName: "Alcohol",
kidFriendly: false
},
{
_id: ObjectId('625ffc48eec7b20004c9294c'),
itemName: "Glass Vase",
kidFriendly: false
},
{
_id: ObjectId('631e6b133b688a0004a17263'),
itemName: "Beach Ball",
kidFriendly: true
}
]
When you use $lookup with pipeline, you need to have the let to store the field value from shops into variable. And use the $expr operator as well.
db.shops.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId('623ae52ba5b1af0004e1c4ec')
}
},
{
$lookup: {
from: ShopItem.collection.collectionName,
let: {
shopItems: "$shopItems"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$ne: [
"$kidFriendly",
true
]
},
{
$in: [
"$_id",
"$$shopItems"
]
}
]
}
}
}
],
as: "adultOnlyItems"
}
}
])
Demo # Mongo Playground
Reference
Join Conditions and Subqueries on a Joined Collection

How to do a full match search with two parameters?

I have database model
Schema({
members: [
{
type: String,
required: true,
ref: "User"
}
],
createdAt: {
type: Date,
default: Date.now(),
required: true
},
lastMessage: {
message: {
type: String,
required: true
},
from: {
type: String,
required: true
},
createdAt: {
type: Date,
required: true
}
},
messages: [
{
createdAt: {
type: Date,
required: true
},
message: {
type: String,
required: true
},
from: {
type: String,
ref: "User",
required: true
}
}
]
});
That code I use to find some documents
Chats.countDocuments(
{
members: {
$in: ["userIdOne", "userIdTwo"]
}
},
cb
)
I use $in to find data what I need, but $in find all documents which contains one of this userId.... Me need to find only one document which contains that user's ids.
How can i do this?
Use $all instead
Chats.countDocuments(
{
members: {
$all: ["userIdOne", "userIdTwo"]
}
},
cb
)
If you need exact match, you can use $setEquals aggregation operator.
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$setEquals: [
"$members",
[
"userIdOne",
"userIdTwo"
]
]
},
true
]
}
}
},
{
$count: "count"
}
])
Input:
[
{
"members": [
"userIdOne"
]
},
{
"members": [
"userIdTwo"
]
},
{
"members": [
"userIdOne",
"userIdTwo"
]
},
{
"members": [
"userIdOne",
"userIdTwo",
"userIdThere"
]
}
]
Output:
[
{
"count": 1
}
]
Playground
You can integrate this to mongoose like this:
const result = await Chats.aggregate([
{
$match: {
$expr: {
$eq: [
{
$setEquals: ["$members", ["userIdOne", "userIdTwo"]]
},
true
]
}
}
},
{
$count: "count"
}
]);
const count = result[0].count;

MongoDB aggregate function not working as expected

I am trying to display a list of the last messages between User A and any other User. Here is what I have so far, but let me explain what wrong with the below query:
If User A messages 'Test1' to User B, and User B replies with 'Test2', and then User A replies with 'Test3', then only the Test2 message will show up.
The 'Test1' message will show up first, as it's the first message in the conversation, but from there on, it will only show User B's messages.
The behavior I want is for the query to always return 'Test3', in this case. Here is what I have:
My Model:
const MostRecentMessageSchema = new Schema({
to: {
type: mongoose.Schema.Types.ObjectId,
ref: "user"
},
from: {
type: mongoose.Schema.Types.ObjectId,
ref: "user"
},
conversation: {
type: mongoose.Schema.Types.ObjectId,
ref: "conversation"
},
deletedBy: {
type: [String]
},
date: {
type: Date,
default: Date.now
}
});
await MostRecentMessages.aggregate(
[
{
$match: {
$or: [
{ from: mongoose.Types.ObjectId(userId) },
{ to: mongoose.Types.ObjectId(userId) }
],
deletedBy: { $ne: mongoose.Types.ObjectId(userId) },
_id: { $ne: filteredIds }
}
},
{ $project: { _id: 1, from: 1, to: 1, conversation: 1, date: 1 } },
{ $sort: { date: -1 } },
{
$group: {
_id: {
userConcerned: {
$cond: {
if: {
$eq: ["$to", mongoose.Types.ObjectId(userId)]
},
then: "$to",
else: "$from"
}
},
interlocutor: {
$cond: {
if: {
$eq: ["$to", mongoose.Types.ObjectId(userId)]
},
then: "$from",
else: "$to"
}
}
},
from: { $first: "$from" },
to: { $first: "$to" },
date: { $first: "$date" },
conversation: { $first: "$conversation" }
}
},
{
$lookup: {
from: "conversations",
localField: "conversation",
foreignField: "_id",
as: "conversation"
}
},
{ $unwind: { path: "$conversation" } },
{
$lookup: {
from: "users",
localField: "to",
foreignField: "_id",
as: "to"
}
},
{ $unwind: { path: "$to" } },
{
$lookup: {
from: "users",
localField: "from",
foreignField: "_id",
as: "from"
}
},
{ $unwind: { path: "$from" } }
],
function(err, mostRecentMessages) {
if (err) {
console.log("Error fetching most recent messages", err);
} else {
if (connectedUsersAllMessages[userId]) {
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", {
mostRecentMessages
});
}
}
}
);
}, 5000);
}
What am I doing wrong here? Here is a POC. It should return
{
from: "A",
to: "C",
number: 3,
message: "A-C Test 4"
},
but it returns,
{
from: "C",
to: "A",
number: 3,
message: "A-C Test 3"
},

mongodb unknown argument to $lookup: foreignKey

I'm trying to "join" two models (schedule and user) in Schedule aggregate using $lookup, but my response is "unknown argument to $lookup: foreignKey". I'm using Node v8.11.3 and MongoDB 4.0 I'm using I've been searched for days and don't know how to solved this.
routes/report.js
Schedule.aggregate([{
$match: {
'store': req.body.store,
'scheduleStart': {
$lte: start,
$gte: req.body.period
},
'status': {
$lte: 3,
$gte: 1
}
}
},
{
$group: {
"_id": {
"name": "$customer.name",
"cpf": "$customer.cpf",
"id": "$customer.id",
"phone": "$customer.phone"
},
"totalValue": {
$sum: "$value"
},
"totalServices": {
$sum: 1
},
}
},
{
$lookup: {
from: 'user',
localField: 'customer.id',
foreignKey: '_id',
as: 'user_detail'
}
}
])
models/schedule.js
const ScheduleSchema = new Schema({
store: {
type: String,
required: true
},
customer: {
id: {
type: String,
required: true
},
name: {
type: String,
required: true
},
avatar: String,
phone: {
type: String,
required: true
},
cpf: {
type: String,
required: true
},
}, {
timestamps: {
createdAt: 'created',
updatedAt: 'updated'
}
});
models/user.js
const UserSchema = new Schema({
name: {
type: String,
required: true
},
storeKey: {
type: String,
required: true
},
avatar: String,
birthday: String,
phone: {
type: String,
required: true
},
cpf: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
passwordHash: String,
salt: String
},
}, {
timestamps: true
});
The foreignKey field in the $lookup aggregation stage should be foreignField per https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#equality-match.

Categories

Resources