equivalent of capture group for matched subdocument - javascript

I have a Chats collection with a participants subdocument like so..
{
_id: '1',
participants: [ { _id: 'A', seen: false }, { _id: 'B', seen: false } ],
messages: []
}
There are only 2 participants and one of them is the currentUser. I don't know which. Also, there is only ever one chat for any pair of users.
In order to find a chat I use both user ids. So a find query looks like this:
Chats.find(
{ $and: [
{ participants: {$elemMatch: {_id: otherUserId}}},
{ participants: {$elemMatch: {_id: currentUserId}}}
]}
)
I'd like to allow the currentUser to update his own seen field in one update operation.
Currently I have to find the chat first, determine which index of the two users is the currentUser, then build a document to update that user and update it in a separate operation.
Is there something similar to a regex capture group so I can know the id of the element that matched currentUserId? Possibly like this.....
Chats.update( { $and: [
{ participants: {$elemMatch: {_id: otherUserId}}},
{ participants: capture({$elemMatch: {_id: currentUserId}})}
]},
{
$set: {"participants.(capture[0]).seen": true}
})
Or is there a better way?

Well this may not be a solution you're looking for but I thought I should suggest in case it helps.
Chats.update( { $and: [
{ participants: {$elemMatch: {_id: otherUserId}}},
{ participants: {$elemMatch: {_id: currentUserId}}}
]},
{
$set: {"participants.$.seen": true}
})
This will work for you because the $elemMatch stores the index of the matched array. As your anding the $eleMatch, it will store the index matched by the last $eleMatch which will be current user in your case. So, when you use the positional opertor, it will update the seen field for current user.

A simple way to easily update the 'seen' status would be to structure the data like this:
{
_id: '1',
participants: [ 'A', 'B'],
seen: [],
messages: []
}
Then you can update the 'A' has seen the message like this:
Chats.update(
{ $and: [ {participants: 'A'}, {participants: 'B'} ] },
{ $addToSet: { seen: 'A' } }
)
$elemMatch is not required if you have only a single query
condition inside it
$addToSet will only add 'A' if it does not
already exist.

Related

Finding a nested object in an array of object using mongoose

{
_id: new ObjectId("61da0ab855483312e8f4483b"),
products: [
{
createdAt: 2022-01-08T22:05:44.635Z,
_id: new ObjectId("61da0ab855483312e8f4483c"),
productCode: 'otf',
productName: 'facebookmeta',
claims: [Array],
permissions: []
},
{
createdAt: 2022-01-08T22:05:44.635Z,
_id: new ObjectId("61da0ab855483312e8f4483f"),
productCode: '4pf',
productName: 'twitteroauth',
claims: [Array],
permissions: [Array]
}
],
__v: 0
}
Now i’ve been trying to get just one object from this array with the find() and findOne method without any luck. if i pass in a certain conditions, it still ends up giving me back an array with both objects. i just want to be able to dynamically pass conditions that belongs to a single object in the array and retrieve that object
MongoDB is applying query conditions on collection and returns the result with the matching documents. As both products twitteroauth & facebookmeta are part of the same document so the whole matching document will be returned.
If you want only a single matching entry in the document then you can use the MongoDB aggregation pipeline (with $unwind) where you can modify the result set.
For example:
db.collection_name.aggregate([
{
"$match": {
// pass matching criteria/conditions here
}
},
{
"$unwind": "$products" //to deconstruct products field in the result
},
{
"$match": {
"products.productName": "twitteroauth"
}
}
])
Note that the second match condition is used to add additional matching criteria/conditions on the deconstructed result set so that products can be filtered.
This will get you the result something like this:-
{
_id: new ObjectId("61da0ab855483312e8f4483b"),
products: {
createdAt: 2022-01-08T22:05:44.635Z,
_id: new ObjectId("61da0ab855483312e8f4483f"),
productCode: '4pf',
productName: 'twitteroauth',
claims: [Array],
permissions: [Array]
},
__v: 0
}
Reference: https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

How do I pick then sort MongoDB documents and then find next document to mine?

I have a collection of documents, which I need to first narrow down by set criteria, then sort alphabetically by string value inside those documents — let's say that's a "search result". I then need to find document that matches a given _id and then pick a document next to it (before or after) from the above "search result".
Background:
I use mongoose to query my database via Node.js.
I have a set of "special sections" in my blog that are comprised of all the articles that must have three particular conditions associated within the keys in the document. I can get the list of articles belonging to said section like so:
const specialSectionListQuery = Article.find({
tag: { $ne: "excluded" },
[`collections.cameras`]: { $exists: true },
status: "published",
})
To finish creating the "special section," I must sort the documents alphabetically via their title attribute:
.sort({ [`collections.cameras.as.title`]: "asc" })
Now I want to add a link to "next article within the same special section" at the bottom of such articles. I know _id and any other value needed from the current article. The above query gives me an ordered list of documents within the section so I can easily find it within that list specialSectionListQuery.findOne({ _id: "xxx" }).exec().
However, I need to find the next article within the above list. How do I do that?
My attempts thus far:
I tried to create article list via aggregation, which led me nowhere (I simply made my app do exactly the same thing — make a list for a "special sectin"):
Article.aggregate([
{
$match: {
tag: { $ne: "excluded" },
[`collections.cameras`]: { $exists: true },
status: "published",
},
},
{
$sort: {
[`collections.cameras.as.title`]: 1,
},
}
]).exec()
But I can't for the life of me figure out how to iterate to the next document in the list properly.
I have thought of saving the list in Node.js memory (variable) and then finding what I need via JavaScript but that can't be scalable.
I have considered creating a new collection and saving the above list there but that would require me to either 1) do it every time a document is altered/added/deleted via Node.js — which is a lot of code and it may break if I interact with database another way 2) rebuild the colleciton every time I run the query, but that feels like it'll lack in performance.
Please help and thank you!
P.S.:
Example collection which should cover most of the cases I'm looking to solve for:
[
{
_id: 1,
name: "Canon",
collections: { cameras: { as: { title: "Half-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 2,
name: "Pentax",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 3,
name: "Kodak",
collections: { film: { as: { title: "35mm Film" } } },
tag: "included",
status: "published"
},
{
_id: 4,
name: "Ricoh",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "published"
},
{
_id: 5,
name: "Minolta",
collections: { cameras: { as: { title: "Half-Frame Review" } } },
tag: "excluded",
status: "published"
},
{
_id: 4,
name: "FED",
collections: { cameras: { as: { title: "Full-Frame" } } },
tag: "included",
status: "draft"
}
]
One thing you can try is to extend your $sort by adding _id so that it always returns documents in deterministic order:
{
$sort: {
"collections.cameras.as.title": 1,
_id: 1
}
},
{
$limit: 1
}
Once your first query returns the document with _id: 2 and collections.cameras.as.title: Full-Frame, you can use below query to get subsequent document:
{
$match: {
$and: [
{
tag: { $ne: "excluded" },
"collections.cameras": { $exists: true },
status: "published",
},
{
$or: [
{
$and: [
{ "collections.cameras.as.title": { $eq: "Full-Frame" } },
{ "_id": { $gt: 2 } }
]
},
{ "collections.cameras.as.title": { $gt: "Full-Frame" } }
]
}
]
}
},
{
$sort: {
"collections.cameras.as.title": 1,
_id: 1
}
},
{
$limit: 1
}
In this case due to deterministic $sort you can exclude previously found document by adding additional filtering criteria and the order should be preserved.
Mongo Playground

Add nested collections with aggregation in mongoose?

I'm new to mongoose, I'm confuse while create the query. Can you help me?
I have video collection like this:
{
_id: 603dea86cef0aed372cd9ce6,
category: [
"603dea86cef0aed372cd9cd8", // array of category objectId
"603dea86cef0aed372cd9cd9"
],
hashtag: [
"603dea86cef0aed372cd9cee" // array of hashtag objectId
],
video_id: '6925666264463576320',
share_id: 'greate_video',
author: 603dea86cef0aed372cd9cd8, // ref to Author objectId
cover: 'https://path.to/img.jpg',
post_by: 60083f24edae03650406ad48, // ref to User objectId
status: 1, // enum status [0, 1, 2]
date: 2021-03-02T07:34:30.635Z
}
I want to query to get data with structure like below. I mean, I will find by _id and get related data form other collections, more than that, I want the video list show with status 1, 2 (not 0) and sort by video _id: -1.
{
_id: 603dea86cef0aed372cd9ce6,
category: [
{_id: "603dea86cef0aed372cd9cd8", category_name: Tech},
{_id: "603dea86cef0aed372cd9cd9", category_name: Mobile},
],
hashtag: [
{_id: "603dea86cef0aed372cd9cee", hashtag_name: tech},
],
video_id: '6925666264463576320',
share_id: 'greate_video',
author: {_id: "603dea86cef0aed372cd9cd8", author_name: Nani, avatar: 'https://path.to/avatar.jpg'},
cover: 'https://path.to/img.jpg',
post_by: {_id: "603dea86cef0aed372cd9cd8", user_name: Username, avatar: 'https://path.to/avatar.jpg'},
status: 1,
date: 2021-03-02T07:34:30.635Z
}
How do I write the aggregation query? I tried with query like this but doesn't work, it show empty [] result.
const videoList = await Video.aggregate([
{
$lookup:
{
from: 'Author',
localField: "author",
foreignField: "_id",
as: "author_info"
}
}
])
Thank you
name of collection usually written in plural lowercase letters so I think you should change Author to authors

Reduce mongodb aggregation with condition

I have an array of objects call "extra" with different properties: some objects have "plus" and some haven't.
I want to create inside this "extra" array, 2 different arrays one called "cheap" with all the object that don't have the "plus" property and one called "exp" with only the objects with the "plus" property.
I think I can use the $reduce method in mongodb aggregate with $concatArrays and check with $cond if the property plus exists or not.
Something like that:
Data example:
{
extra: [
{
description: "laces",
type: "exterior",
plus: '200'
},
{
description: "sole",
type: "interior"
},
{
description: "logo",
type: "exterior"
},
{
description: "stud",
type: "exterior",
plus: '450'
}
],
}
{
$project: {
extra: {
$reduce: {
input: ['$extra'],
initialValue: {cheap: [], exp: []},
$cond: {
if: {$eq: ['$$this.plus', null]},
then: {
in: {
cheap: {
$concatArrays: ['$$value.cheap', '$$this'],
},
},
},
else: {
in: {
exp: {
$concatArrays: ['$$value.exp', '$$this'],
},
},
},
},
},
},
},
}
It doesn't work...I tried many ways or writing the $cond part without luck.
I can't figure it out.
Thank you all.
K.
Apart from some minor syntax issues you've had another problem is your understand of the $ne operator.
In this case you expect a missing value to be equal to null, this is not how Mongo works. so for a document:
{ name: "my name" }
The aggregation query:
{ $cond: { $eq: ["$missingField", null] } }
Will not give true as you expect as missing is not equal to null. I took the liberty to fix the syntax issues you've had, this working pipeline is the way to go:
db.collection.aggregate([
{
$project: {
extra: {
$reduce: {
input: "$extra",
initialValue: {
cheap: [],
exp: []
},
in: {
cheap: {
"$concatArrays": [
"$$value.cheap",
{
$cond: [
"$$this.plus",
[],
[
"$$this"
],
]
}
]
},
exp: {
"$concatArrays": [
"$$value.exp",
{
$cond: [
"$$this.plus",
[
"$$this"
],
[]
]
}
]
}
}
},
},
},
}
])
Mongo Playground
One thing to note is that $cond evaluates the plus field, meaning if the field does exist with a null value or a 0 value then it will consider this document matched for the cheap array. This is something to consider and change in case these are possible.

Multiple search and filter Meteor & Mongodb

I have simple collection :
nom: {
type: String,
},
prenom:{
type: String,
},
categorie:{
type: String,
},
age:{
type:String,
} ,
ville : {
type : String
}
I want to find easy way to multiple search and filter elements into a table . for exemple filter by name and age or filter by age and city . or filter by all of them
Here is my table :
my table
In my case what's the best way to make this multiple filter on mongodb and meteor without searchengine package
This is how I tend to do my searches, so that it also finds similar results as well. So if there is a letter "A" in my result, if I search on "A" it will bring up everything with the the letter "A" until I get more specific.
Note you might want to make the search value and search keys to lowercase Because "A" != "a" and will not return anything.
I also put an int value you just incase that question came up.
let collection = yourCollection.find({
"nom": {
$regex: ".*CONDITION_VALUE.*"
},
"prenom": {
$regex: ".*CONDITION_VALUE.*"
},
"categorie": {
$regex: ".*CONDITION_VALUE.*"
},
"age": {
$gt: minAge,
$lt: maxAge
},
"ville": {
$regex: ".*CONDITION_VALUE.*"
}
});
When using direct queries on MongoDB you can use the following examples:
The simplest way to check existence of a field is by using the $exist:
db.collectionName.find({fieldName: {$exists: true}})
If you are looking for specific field value:
db.collectionName.find({'fieldName': 'value'})
Combination of $or and exist can look like that:
db.collectionName.find({ $or: [ {name: {$exists: true}}, {age: {$exists: true}} ] })
For multiple $or you can use the following query:
db.collectionName.find({ $or: [ {name: {$exists: true}}, {age: {$exists: true}} ] },
{ $or: [ {age: {$exists: true}}, {city: {$exists: true}} ] })

Categories