How to compare fields from different collections in mongodb - mongodb

Here, I have multiple fields from multiple tables those values needs to compared and need to display desired result.
SQL QUERY:
select pd.service_id,ps.service_id from player pd, service ps where pd.subject_id=ps.subject_id and pd.service_id = ps.service_id
Mongo query:
db.player.aggregate([
{
"$lookup":{
"from":"service",
"localField":"player.subject_id",
"foreignField":"subject_id",
"as":"ps"
}
},
{
"$unwind":"$ps"
},
{
"$match":{
"service_id":{
"$eq": "ps.service_id"
}
}
}
];
sample input records:
player:
[{subject_id:23,service_id:1},{subject_id:76,service_id:9}]
service:
[{subject_id:76,service_id:9},{subject_id:99,service_id:10}]
The match is not working. I have to match service_id's of both collections. Need to get matched records. But not able to see any result. Can anyone please help me to find out the mistake...

In your query, if you want to compare 2 values from the document itself, you need to use $expr operator
{
"$match":{
"$expr":{
"$eq": ["$service_id", "$ps.service_id"]
}
}
}
MongoPlayground
Alternative solution: You need to use Uncorrelated sub-query to "* join" with 2 o more conditions
db.player.aggregate([
{
"$lookup": {
"from": "service",
"let": {
subject_id: "$subject_id",
service_id: "$service_id"
},
"pipeline": [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$$subject_id",
"$subject_id"
]
},
{
$eq: [
"$$service_id",
"$service_id"
]
}
]
}
}
}
],
"as": "ps"
}
},
// Remove non matched results
{
$match: {
"ps.0": {
$exists: true
}
}
},
// Remove temporal "ps" field
{
$addFields: {
"ps": "$$REMOVE"
}
}
])
MongoPlayground

Related

get document with same 3 fields in a collection

i have a collection with more then 1000 documents and there are some documents with same value in some fields, i need to get those
the collection is:
[{_id,fields1,fields2,fields3,etc...}]
what query can i use to get all the elements that have the same 3 fields for example:
[
{_id:1,fields1:'a',fields2:1,fields3:'z'},
{_id:2,fields1:'a',fields2:1,fields3:'z'},
{_id:3,fields1:'f',fields2:2,fields3:'g'},
{_id:4,fields1:'f',fields2:2,fields3:'g'},
{_id:5,fields1:'j',fields2:3,fields3:'g'},
]
i need to get
[
{_id:2,fields1:'a',fields2:1,fields3:'z'},
{_id:4,fields1:'f',fields2:2,fields3:'g'},
]
in this way i can easly get a list of "duplicate" that i can delete if needed, it's not really important get id 2 and 4 or 1 and 3
but 5 would never be included as it's not 'duplicated'
EDIT:
sorry but i forgot to mention that there are some document with null value i need to exclude those
This is the perfect use case of window field. You can use $setWindowFields to compute $rank in the grouping/partition you want. Then, get those rank not equal to 1 to get the duplicates.
db.collection.aggregate([
{
$match: {
fields1: {
$ne: null
},
fields2: {
$ne: null
},
fields3: {
$ne: null
}
}
},
{
"$setWindowFields": {
"partitionBy": {
fields1: "$fields1",
fields2: "$fields2",
fields3: "$fields3"
},
"sortBy": {
"_id": 1
},
"output": {
"duplicateRank": {
"$rank": {}
}
}
}
},
{
$match: {
duplicateRank: {
$ne: 1
}
}
},
{
$unset: "duplicateRank"
}
])
Mongo Playground
I think you can try this aggregation query:
First group by the feilds you want to know if there are multiple values.
It creates an array with the _ids that are repeated.
Then get only where there is more than one ($match).
And last project to get the desired output. I've used the first _id found.
db.collection.aggregate([
{
"$group": {
"_id": {
"fields1": "$fields1",
"fields2": "$fields2",
"fields3": "$fields3"
},
"duplicatesIds": {
"$push": "$_id"
}
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$duplicatesIds"
},
1
]
}
}
},
{
"$project": {
"_id": {
"$arrayElemAt": [
"$duplicatesIds",
0
]
},
"fields1": "$_id.fields1",
"fields2": "$_id.fields3",
"fields3": "$_id.fields2"
}
}
])
Example here

MongoDB conditionial update

I am running a bit in circles here and would appreciate some help. What I am looking to do is either update or create a nested object contained in an array depending on whether this object exists.
I have a users collection and a user document has the following structure:
{
schema_version: 1,
display_name: 'xxxxxx',
email: 'xxxxxx',
email_verified:'xxxxxx',
...
custom_data: {
stripe_id: 'xxx',
subscriptions: [{
subscription_id: xxxx,
....
}],
...
},
}
In webhook calls from Stripe I am getting a subscription object with a subscription_id and a stripe_id.
What I want to do is check if subscription_id exists, if so, update the document, if not then create the document in the subscriptions array for the user document where stripe_id matches.
If I do something along the lines of:
db.collection.update(
{subscription_id: subscription.id},
{ $set: { 'custom_data.subscriptions': subscriptionData } },
{ upsert: true }
)
The problem is that I am creating subscription objects not bound to my user document where stripeID matches.
On the other hand, if I do something like this:
db.collection.update(
{'custom_data.stripe_id': stripe_id},
{ $set: { 'custom_data.subscriptions': subscriptionData } },
{ upsert: true }
)
I will potentially end up creating dupes in the subscriptions array when, in fact I would want to update the existing object where subscription_id matches.
Is there any way to do that in one query with Mongo, or will I have to resort to using 2 queries in an if statement?
Thanks in advance for any clarification on this.
You can do the followings with an aggregation pipeline:
$match with $or condition to search for custom_data.subscriptions.subscription_id or custom_data.stripe_id
$addFields with $map to conditional update your subscription object when matched
$addFields with $setUnion to insert an entry of incoming subscription object for the insert case
$merge to update the back into the original collection
db.collection.aggregate([
{
$match: {
$expr: {
$or: [
{
$eq: [
"$custom_data.subscriptions.subscription_id",
"xxxx"
]
},
{
$eq: [
"$custom_data.stripe_id",
"xxx"
]
}
]
}
}
},
{
"$addFields": {
"custom_data": {
subscriptions: {
"$map": {
"input": "$custom_data.subscriptions",
"as": "s",
"in": {
"$cond": {
// if subscription_id matched, replace with your incoming object
"if": {
$eq: [
"$$s.subscription_id",
"xxxx"
]
},
"then": {
subscription_id: "incoming_sub_id"
},
// if not matched, keep the original object
"else": "$$s"
}
}
}
}
}
}
},
{
"$addFields": {
"custom_data": {
subscriptions: {
// insert case; if the subscription array is empty, then union with your incoming object
$setUnion: [
"$custom_data.subscriptions",
[
{
subscription_id: "incoming_sub_id"
}
]
]
}
}
}
},
{
"$merge": {
"into": "collection",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

Mongodb lookup like search: local field as array of objects

I have two collections userProfile and skills,
Eg:userProfile
{
"_id": "5f72c6d4e23732390c96b031",
"name":"name"
"other_skills": [
"1","2"
],
"primary_skills": [
{
"_id": "607ffd1549e13876fef7f2c5",
"years": 4.5,
"skill_id": "1"
},
{
"_id": "607ffd1549e13876fef7f2c6",
"years": 2,
"skill_id": "2"
},
{
"_id": "607ffd1549e13876fef7f2c7",
"years": 1,
"skill_id": "3"
}
]
}
Eg:Skills
{
"_id":1,
"name": "Ruby on Rails",
}
{
"_id":2,
"name": "PHP",
}
{
"_id":3,
"name": "php",
}
I want to retrieve the userprofile based on the skills
eg: input of skill php i want to retrieve the userprofiles that matches either in primary_skills or other_skills
But I got confused about the implementation, I think it can do with pipeline in lookup and the elemMatch. This is the query I tried so far
const skills = ['php','PHP']
userProfile.aggrigate([{
$lookup:{
from:'skills',
let:{'primary_skills':'$primary_skills'},
pipeline:[
{
$match:{
primary_skills:{
$elemMatch:{
name:'' //not sure how to write match
}
}
}
}
]
}
}])
Can somebody help me with this, Thanks in advance
I'll first show you how to correct your pipeline to work, however this approach is very inefficient as you will have to $lookup on every single user in your db which is obviously a lot of overhead.
Here is how to properly match your condition:
const skills = ['php','PHP']
db.userProfile.aggregate([
{
$lookup: {
from: "skills",
let: {
"primary_skills": {
$map: {
input: "$primary_skills",
as: "skill",
in: "$$skill.skill_id"
}
},
"other_skills": "$other_skills"
},
pipeline: [
{
$match: {
$expr: {
"$in": [
"$_id",
{
"$concatArrays": [
"$$other_skills",
"$$primary_skills"
]
}
]
}
}
}
],
as: "skills"
}
},
{
$match: {
'skills.name': {$in: skills}
}
}
])
Mongo Playground
As I've said I recommend you do not do this. what I suggest you do is split it into 2 calls, first fetch the relevant skill ids. and then query on users.
By doing this you can also utilize indexes for much faster queries, like so:
const skills = ['php', 'PHP'];
const matchedSkillIds = await skills.distinct('_id', {name: {$in: skills}});
const users = await userProfile.find({
$or: [
{
'primary_skills.skill_id': {$in: matchedSkillIds}
},
{
'other_skills': {$in: matchedSkillIds}
}
]
})
Finally if you do insist on doing it in one query at the very least start the pipeline from the skill collection.

MongoDB, How Do I combine a find and sort with the $cond in aggregation?

I have written a find query, which works, the find query returns records where name and level exist
db.docs.find( { $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1})
and now want to combine it with something like the code below which also works, but needs to be merged with the above to pull the correct data
db.docs.aggregate(
[
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
]
)
The find query returns records where name and level exist, but I need to enhance the result with new column called Honours, showing True of False depending on whether the level is gte (greater than or equal to 8)
So I am basically trying to combine the above find filter with the $cond function (which I found and modified example here : $cond)
I tried the below and a few other permutations to try and make find and sort with the $project and$cond aggregate, but it returned errors. I am just very new to how to construct mongodb syntax to make it all fit together. Can anyone please help?
db.docs.aggregate(
[{{ $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1}
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
}
]
)
Try below aggregation pipeline :
db.docs.aggregate([
/** $match is used to filter docs kind of .find(), lessen the dataset size for further stages */
{
$match: {
$and: [{ name: { $exists: true } }, { level: { $exists: true } }]
}
},
/** $project works as projection - w.r.t. this projection it will lessen the each document size for further stages */
{
$project: {
_id: 0,
name: 1,
Honours: {
$cond: { if: { $gte: ["$level", 8] }, then: "True", else: "False" }
}
}
},
/** $sort should work as .sort() */
{ $sort: { name: 1 } }
]);

how to use $elemMatch on array specifying an upper field as part of the query

I'd like to retrieve for a specific user, his chats with unread messages.
Lets say I have a simplified chat model like that :
{
lastMessageAt: Date,
participants: [
{
user: String(id),
lastReadAt: Date
}
]
}
How can I achieve my query ?
I have tried several thing like with $elemMatch, but lastMessageAt is unknown at this level...
ChatDB.find({
'participants': {
$elemMatch: { user: '12345', lastReadAt: { $lt: '$lastMessageAt' } }
}
}
Thanks in advance for your help ! :)
$elemMatch operator will find those documents in ChatDB collection that have at least 1 element in participants that matches your criteria. Also my research ended with the conslusion that it is not yet possible to access other document field in $elemMatch operator. Anyway, if this is your goal, then you can use this query:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
}
])
Mongo playground
If you also want to filter participants that really matched the criteria, then you need to add a projection stage:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
},
{
$project: {
participants: {
$filter: {
input: "$participants",
as: "participant",
cond: {
$and: [
{
$eq: [
"$$participant.user",
"12345"
]
},
{
$lt: [
"$$participant.lastReadAt",
"$lastMessageAt"
]
}
]
}
}
}
}
}
])
Mongo playground
I have found the solution witch is to use the aggregator with the $unwind operator.
await ChatDB.aggregate([
{
$unwind: '$participants'
},
{
$match: {
'participants.user': '12345',
$expr: {
$lt: [
'$participants.lastReadAt',
'$lastMessageAt'
]
}
}
}]);
Hope this will be usefull