Mongodb use findAndModify on specific document among multiple match - node.js

I have documents that look like below
let object = [
{
id: 4,
parents:
[
1, 2,
],
children: 2
},
{
id: 5,
parents:
[
1, 2,
],
children: 1
},
{
id: 8,
parents:
[
1, 2, 4
],
children: 0
},
{
id: 9,
parents:
[
1, 2, 4
],
children: 0
},
{
id: 10,
parents:
[
1,2,5
],
children:0
}
]
I would like to use findAndModify in order to update the children value. It has to be findAndModify because I will be working in an multithreaded environment so selection and update must happen in single transaction.
The conditions that I am looking for is when '2' is included in the parents array and children value is less than 2 where the children value is highest and parents count is lowest among the sufficing documents. Then I would like to increment the value of children of the first matching document.
The query that I have come up with right now is below
let query =
{
parents:
{
$elemMatch:2
},
children:
{
$lt: 2
}
};
which suffices first few conditionals but unfortunately I don't know how I can select out
{
id: 5,
parents:
[
1, 2,
],
children: 1
},
out of
{
id: 8,
parents:
[
1, 2, 4
],
children: 0
},
{
id: 9,
parents:
[
1, 2, 4
],
children: 0
},
{
id: 10,
parents:
[
1,2,5
],
children:0
}
which is what is currently selected with my query. Again, it has to be a single transaction so writing out another set of query is not possible.
As to why it has to be single transaction, refer to this post How to limit maximum reference of the parental node in mongodb

Try this
query.findAndModify({
query: { $and: [ { children :{$lt : 2 }}, { parents: { $size: 2 } } ] },
update: { according to you }
});

Related

Find rank of a player based on 2 fields in a collection in mongoose

I have more than 100,000 documents in a collection and I want to get rank of player based on his totalCoins field first and in case 2 players have same coins, then based on their level.
Below is Player collection example:
const players = [
{
_id: 1,
name: "abc",
totalCoins: 100,
level: 1
},
{
_id: 2,
name: "bcd",
totalCoins: 200,
level: 2
},
{
_id: 3,
name: "cde",
totalCoins: 300,
level: 3
},
{
_id: 4,
name: "def",
totalCoins: 100,
level: 4
},
{
_id: 5,
name: "efg",
totalCoins: 100,
level: 4
}
]
Let's say I am finding rank of player with _id = 4.
How can i achieve this in mongoose.
Thanks in advance.
If you are using MongoDB 5, then $rank method should work (not 100% sure because I am using version 4.4 ).
If you are not using 5.x then this should work
Step 1 - sorted the data based on level first and then totalCoins
Step 2 - pushed the sorted data in an array using group and push
Step 3 - unwinded the data and added the index of document based on sorted array with the help of includeArrayIndex and named it globalRank (name it whatever you want :) )
Step 4 - finally projected the data again and added 1 in globalRank so that the rank starts at 1 instead of 0 (this return the rank like 1.0 or 2.0... if you don't want this then you can avoid $add and handle this in frontend)
Step 5 - $match to find a specific index
db.collection.aggregate([
{
$project: {
_id: 1,
name: "$name",
totalCoins: "$totalCoins",
level: "$level",
},
},
{
$sort: {
level: -1,
totalCoins: -1,
},
},
{
$group: {
_id: null,
arr: {
$push: {
_id: "$_id",
name: "$name",
totalCoins: "$totalCoins",
level: "$level",
},
},
},
},
{
$unwind: {
path: "$arr",
includeArrayIndex: "globalRank",
},
},
{
$project: {
_id: "$arr._id",
name: "$arr.name",
level: "$arr.level",
totalCoins: "$arr.totalCoins",
globalRank: { $add: ["$globalRank", 1] },
},
},
{ $match: { _id: 4 } },
]);
collection.count({
"$expr": {
"$gt": [
{ "$add": [ "$totalCoins", {"$divide":["$level",10000] ] },
100.0004
]
}
})
The idea is to count the number of records with higher coins/level than the user. The idea I put here is to divide the level by maximum level + 1 (here the level is max 9999) so you will always end up with lower than 1 decimal. When you add the coins to that you will get 100.0004, coins.level . Then it is just to compare two numbers accordingly.

Filter on tag-field on children

First of all, I'm not sure I've set this up as it should be, like by the book. I'm from the SQL world and jumped into the NOSQL land.
Ok, so. I have this collection with Projects, and inside the projects I have files as a child-ref. I can populate and all that stuff, works really well. But I want to filter with tags. I have a tags field inside the File collection, an array with strings, pretty straight forward.
What I would like to do is; send a projectId and a string with a spec filter and get the files, belonging to the project and also containing the tag. Oh, and also, populated.
Is this even the right approach with NOSQL/MONGO? I know how I would do it in SQL, with parent_id's and with some joins etc. I've looked into some aggregate but I'm too novice to work it out it seems.
edit, just to show how my collections are built:
Project Collection
[{
id: 1,
name: 'Project01',
files: [
id: 1,
id: 2,
id: 3,
id: 4,
id: 5,
...
]
},
...
]
Files Collection
[{
id: 1,
name: 'filename'
tags: ['a','b']
},{
id: 2,
name: 'filename2'
tags: ['b', 'c']
},{
id: 3,
name: 'filename3'
tags: ['a', 'd', 'e', 'f']
},
...]
The result I'm going for (get all files in project 1 where tags includes 'b'.
{
id: 1,
name: 'Project01',
files: [
{
id: 1,
name: 'filename'
tags: ['a','b']
},{
id: 2,
name: 'filename2'
tags: ['b', 'c']
}
]
}
try this $unwind operation in mongodb
Collections as per your requirement
[
{
_id: 1,
name: "Project01",
files: [
{
id: 1,
name: "filename11",
tags: [
"a",
"b"
]
},
{
id: 2,
name: "filename12",
tags: [
"b",
"c"
]
},
{
id: 3,
name: "filename13",
tags: [
"a",
"c"
]
}
]
},
{
_id: 2,
name: "Project02",
files: [
{
id: 1,
name: "filename21",
tags: [
"a",
"b"
]
},
{
id: 2,
name: "filename22",
tags: [
"a",
"c"
]
},
{
id: 3,
name: "filename23",
tags: [
"b",
"c"
]
}
]
}
]
Method 1: for your project collection
db.collection.aggregate([
{
$match: {
_id: 1
}
},
{
$unwind: "$files"
},
{
$match: {
_id: 1,
"files.tags": {
$in: [
"b"
]
}
}
}
])
Method 2 for files collection
db.collection.aggregate([
{
$unwind: "$tags"
},
{
$match: {
tags: "xyz"
}
}
])
Try it here Mongoplayground

mongoose match multiple ids and count if duplicate found

I have a list of ids and it may contain duplicates, so ignore duplicates and count the occurrence of total duplicates. I will explain it in details.
IDS
[
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
]
I have used the below code to get the result,
var user_id = [
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
];
User.aggregate([
{
$match: {
_id: {
$in: user_id
}
},
},
{
$group: {
_id: "$_id",
count: {
$sum: 1
}
}
},
],
(err, resp) => {
console.log(resp)
})
The output for above code is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 1 }
]
The required output is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 2 }
]
I have tried many code but no success, is there anyway to achieve required output.
you trying to get the count from _id. _id is unique for all the documents so you need to change the _id to some other fields which is not unique.
example,
table
[
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 3,
values: [
1,
2,
3
]
}
]
Query
db.collection.aggregate([
{
$match: {
id: {
$in: [
1,
3
]
}
},
},
{
$group: {
_id: "$id",
count: {
$sum: 1
}
}
},
])
output
[
{
"_id": 1,
"count": 3
},
{
"_id": 3,
"count": 1
}
]

Aggregate recursive tree structure (parent reference)

We are trying to build a simple CMS that can manage pages and its subPages. And every subPage can also have subPages etc.
The Problem we are currently facing is that we receive a flat tree after the second level and we would have to reorganise the tree with JS.
Is it possible to receive a real tree structure (see "desired output") without the use of JS.
The following tree structure is currently used:
[{
_id: 1,
name: "top-level",
parent: null
},{
_id: 2,
name: "second-level-a",
parent: 1 // top-level
},{
_id: 3,
name: "second-level-b",
parent: 1 // top-level
},{
_id: 4,
name: "third-level",
parent: 2 // second-level-a
}]
We are currently trying to receive the complete tree via this query:
Page.aggregate([
{
"$graphLookup": {
"from": "Page",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "parent",
"as": "subPages",
"maxDepth": 1,
"depthField": "depth"
}
}, {
"$match": {
"options.parent": null
}
}
]).exec((err, pages) => {
if (err) {
res.send(err);
return;
}
res.json(pages);
});
What we are receiving is something like this:
[
{
_id: 1,
name: "first-level",
parent: null,
subPages: [
{
_id: 2,
name: "second-level-a",
depth: 0,
[...]
},{
_id: 3,
name: "second-level-b",
depth: 0,
[...]
},{
_id: 4,
name: "third-level",
depth: 1,
[...]
}
]
}
]
With this output it's not directly visible where third-level is placed inside the tree. The desired output should look like this:
[
{
_id: 1,
name: "first-level",
parent: null,
subPages: [
{
_id: 2,
name: "second-level-a",
depth: 0,
[...]
subPages: [
{
_id: 4,
name: "third-level",
depth: 1,
[...]
}
]
},{
_id: 3,
name: "second-level-b",
depth: 0,
[...]
}
]
}
]
The question is, is this even possible to achieve using MongoDB in combination with mongoose or do we need to sort the result with JS.

Retrieving a list of nodes along with a list of IDs of nodes that are directly related to them in Neo4j

In my database I have :User nodes, and they are related by :Friendship relationships. I want to get a structure like this:
[
{
id: 1,
username: "Whatever",
email: "whatever#test.com"
...
},
[ 6, 7, 8, ... ]
],
[
{
id: 2,
username: "Another user",
email: "anotheruser#test.com"
...
},
[ 15, 16, 17, 18, ... ]
],
...
...where the numbers are the IDs of the nodes that the node is directly related to with a :Friendship relationship.
This answer has some queries that almost do the work:
Can I find all the relations between two nodes in neo4j?
But the closest one I came up with was:
match p=(a:User)-[:Friendship]->(d:User)
return d, reduce(nodes = [],n in nodes(p) | nodes + [id(n)]) as node_id_col
...which returns this structure:
[
{
id: 1,
username: "Whatever",
email: "whatever#test.com"
...
},
[ 1, 6 ]
],
[
{
id: 1,
username: "Whatever",
email: "whatever#test.com"
...
},
[ 1, 7 ]
],
[
{
id: 1,
username: "Whatever",
email: "whatever#test.com"
...
},
[ 1, 8 ]
],
[
{
id: 2,
username: "Another user",
email: "anotheruser#test.com"
...
},
[ 2, 15 ]
],
[
{
id: 2,
username: "Another user",
email: "anotheruser#test.com"
...
},
[ 2, 16 ]
],
...
That is not good, because it is returning a lot of redundant data.
So what would be the proper Cypher query for this?
Thanks!
I think you may be over complicating things OR I am not properly understanding the problem. Does something like this work for you?
match (a:User)-[:Friendship]->(d:User)
return a, collect(id(d))

Resources