Passing Variables to a MongoDB View - mongodb

i'm trying to make views in MongoDB to avoid unnecessary returns. In the documentation says that the aggregation functions can take variables with a double dollar sign, taking this in mind i have created a view that in this example should take one variable to filter customerIds and group the results to sum the payments of different documents.
Example:
db.createView(
"viewName",
"myCollection",
[{
$match: { "customerId": "$$customerId", }
},{
$group: {
_id: null,
total: "$amount",
}
}]
)
The view is created OK and if i put some valid customerId in the aggregation function that works ok, but i don't have the slightest idea how to execute the view and pass the customerID that i need.
Any ideas? The mongodb documentation does not help me in this situation and i really need to create this as a view, since there are many applications that will connect to this view(s).
I have tried:
db.viewName.find({customerId: "some valid id"});

You can access it just like a collection, for example I am creating a view via:
db.runCommand({
create: 'AuthorsView',
viewOn: 'authors',
pipeline: [{
"$group": {
"_id": "$email",
"count": {
"$sum": 1
}
}
}]
})
Since this is now an existing view I can simply do:
db.getCollection('AuthorsView').find({})
To see all the documents or to add more parameters to the find
Not sure what you mean by passing variables since views are just like collections ... you run queries against them via find & aggregate.

First, you can't pass variables to $match without $expr. There is no error because "$$..." is interpreted as a string.
Second, If we fix things like this:
db.createView(
"viewName",
"myCollection",
[{
$match: {$expr:{$eq:["$customerId","$$customerId"]}}, }
},{
$group: {
_id: null,
total: "$amount",
}
}]
)
But $$ is not a system variable, so this won't work. You could pass a system variable like $$ROOT or a field path $field.path; the user-defined variables are made up of system variables or collection data.

Related

Push an object to a nested array within a document but use a value from a field in the root document

I've been scratching my head with this problem. I've attempted to search for a solution but I didn't find anything relating to my specific use case.
Would anyone be able to help me out?
Say I have a collection of "discount" documents, and importantly they have an "amount" field to say how much the discount is worth. Whenever a discount is redeemed I currently want to track what the worth was at the time of the redemption.
To do this I've been attempting to use the following code:
await datastore.collection('discounts').updateOne(
{
$expr: { $gt: [ '$maxUses', '$uses' ] },
...criteria
},
{
$set: {
uses: 1
},
$push: {
redemptions: {
name: concatNames(user),
email: user.email,
amount: '$amount', // <-- use amount from root document
when: new Date()
}
}
}
)
Unfortunately $amount does not pull the value from the root document, instead it just becomes "$amount" as a string. I've also attempted to convert this update to use a pipeline but $push is not a valid pipeline stage.
Here's a quick Mongo playground link.
Thanks in advance.
In order to refer to another fields value, you'll need to use the aggregation pipeline form of update. However, '$push' is an update operator, not an aggregation operator.
$concatArrays gets most of the way there like
{$set: {redepmtions: {$concatArrays: [ "$redemptions", [{amount: "$amount"}]}}
That will throw an error if $redemptions doesn't already exist, so use $cond to subsitute an empty array in that case:
.updateOne(
{ ...criteria },
[{$set: {
redemptions: {$concatArrays: [
{$cond: [{$eq: ["array", {$type: "$redemptions"}]}, "$redemptions", []]},
[{amount: "$amount"}]
]}
}}]
)
Playground

MongoDB query that looks for documents with lowercase values

Is it possible to make a MongoDB query that searches a field for completely lowercase string values?
Something like this pseudo query perhaps?
{ address: { $eq: { $toLower: "$address" } } }
...that would return docs with data like: { "address": "123 main st" }, but won't return docs like { "address": "123 Main St" }, or is such a query not possible with MongoDB?
Based on the clarification, yes what you want is possible and you were pretty close with the original syntax. Try something like the following:
db.collection.find({
$expr: {
$eq: [
{
$toLower: "$address"
},
"$address"
]
}
})
Playground link is here.
There may be some extra considerations depending on language, collation, etc. But this should serve as a good starting point.
Yes, you can use aggregation pipeline that makes specific fields lowercase and than does matching against them, for examples look at
https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/#example
and https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#examples
On large datasets this way of querying would not be efficient, but for one time queries may be useful.

Merge Names From Data For Message Application

Hello guys I'm writing a Message Application with Node.js and Mongoose. I keep datas in mongodb like that:
I want to list users who messaged before so I need to filter my 'Messages' collection but I can't do what exactly I want. If he sent a message to a person I need to take persons name but, if he take a message from a person I need to take persons name however in first situation person name in reciever, in second situation person name in sender. I made a table for explain more easily. I have left table and I need 3 name like second table.(Need to eliminate one John's name)
Sorry, if this problem asked before but I don't know how can I search this problem.
I tried this but it take user name who logged in and duplicate some names.
Message.find({$or: [{sender: req.user.username}, {reciever: req.user.username}]})
One option is to use an aggregation pipeline to create two sets and simply union them:
db.collection.aggregate([
{$match: {$or: [{sender: req.user.username}, {reciever: req.user.username}]}},
{$group: {
_id: 0,
recievers: {$addToSet: "$reciever"},
senders: {$addToSet: "$sender"}
}},
{$project: {
_id: req.user.username,
previousChats: {"$setDifference":
[
{$setUnion: ["$recievers", "$senders"]},
[req.user.username]
]
}
}}
])
See how it works on the playground example
This is a tricky one, but can be solved with a fairly simple aggregation pipeline.
Explanation
On our first stage of the pipeline, we will want to get all the messages sent or received by the user (in our case David), for that we will use a $match stage:
{
$match: {
$or: [
{sender: 'David'},
{receiver: 'David'}
]
}
}
After we found all the messages from or to David, we can start collecting the people he talks to, for that we will use a $group stage and use 2 operations that will help us to achieve this:
$addToSet - This will add all the names to a set. Sets only contain one instance of the same value and ignore any other instance trying to be added to the set of the same value.
$cond - This will be used to add either the receiver or the sender, depending which one of them is David.
The stage will look like this:
{
$group: {
_id: null,
chats: {$addToSet: {$cond: {
if: {$eq: ['$sender', 'David']},
then: '$receiver',
else: '$sender'
}}}
}
}
Combining these 2 stages together will give us the expected result, one document looking like this:
{
"_id": null, // We don't care about this
"chats": [
"John",
"James",
"Daniel"
]
}
Final Solution
Message.aggregate([{
$match: {
$or: [
{
sender: req.user.username
},
{
receiver: req.user.username
}
]
}
}, {
$group: {
_id: null,
chats: {
$addToSet: {
$cond: {
'if': {
$eq: [
'$sender',
req.user.username
]
},
then: '$receiver',
'else': '$sender'
}
}
}
}
}])
Sources
Aggregation
$match aggregation stage
$group aggregation stage
$addToSet operation
$cond operation

Remove duplicates by field based on secondary field

I have a use case where I am working with objects that appear as such:
{
"data": {
"uuid": 0001-1234-5678-9101
},
"organizationId": 10192432,
"lastCheckin": 2022-03-19T08:23:02.435+00:00
}
Due to some old bugs in our application, we've accumulated many duplicates for these items in the database. The origin of the duplicates has been resolved in an upcoming release, but I need to ensure that prior to the release there are no such duplicates because the release includes a unique constraint on the "data.uuid" property.
I am trying to delete records based on the following criteria:
Any duplicate record based on "data.uuid" WHERE lastCheckin is NOT the most recent OR organizationId is missing.
Unfortunately, I am rather new to using MongoDB and do not know how to express this in a query. I have tried aggregated to obtain the duplicate records and, while I've been able to do so, I have so far been unable to exclude the records in each duplicate group containing the most recent "lastCheckin" value or even include "organizationId" as a part of the aggregation. Here's what I came up with:
db.collection.aggregate([
{ $match: {
"_id": { "$ne": null },
"count": { "$gt": 1 }
}},
{ $group: {
_id: "$data.uuid",
"count": {
"$sum": 1
}
}},
{ $project: {
"uuid": "$_id",
"_id": 0
}}
])
The above was mangled together based on various other stackoverflow posts describing the aggregation of duplicates. I am not sure whether this is the right way to approach this problem. One immediate problem that I can identify is that simply getting the "data.uuid" property without any additional criteria allowing me to identify the invalid duplicates makes it hard to envision a single query that can delete the invalid records without taking the valid records.
Thanks for any help.
I am not sure if this is possible via a single query, but this is how I would approach it, first sort the documents by lastCheckIn and then group the documents by data.uuid, like this:
db.collection.aggregate([
{
$sort: {
lastCheckIn: -1
}
},
{
$group: {
_id: "$data.uuid",
"docs": {
"$push": "$$ROOT"
}
}
},
]);
Playground link.
Once you have these results, you can filter out the documents, according to your criteria, which you want to delete and collect their _id. The documents per group will be sorted by lastCheckIn in descending order, so filtering should be easy.
Finally, delete the documents, using this query:
db.collection.remove({_id: { $in: [\\ array of _ids collected above] }});

MongoDB querying aggregation in one single document

I have a short but important question. I am new to MongoDB and querying.
My database looks like the following: I only have one document stored in my database (sorry for blurring).
The document consists of different fields:
two are blurred and not important
datum -> date
instance -> Array with an Embedded Document Object; Our instance has an id, two not important fields and a code.
Now I want to query how many times an object in my instance array has the group "a" and a text "sample"?
Is this even possible?
I only found methods to count how many documents have something...
I am using Mongo Compass, but i can also use Pymongo, Mongoengine or every other different tool for querying the mongodb.
Thank you in advance and if you have more questions please leave a comment!
You can try this
db.collection.aggregate([
{
$unwind: "$instance"
},
{
$unwind: "$instance.label"
},
{
$match: {
"instance.label.group": "a",
"instance.label.text": "sample",
}
},
{
$group: {
_id: {
group: "$instance.label.group",
text: "$instance.label.text"
},
count: {
$sum: 1
}
}
}
])