I am trying to use the MongoDB $lookup with the Uncorrelated Subqueries.
Using MongoDB 3.6.12 (support began on 3.6)
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
The following pipeline step is working, however if I swap out the first "userB" with the second, no results are returned.
{
from: 'friendships',
let: { requestuser: ObjectId("5c0a9c37b2365a002367df79"), postuser: ObjectId("5c0820ea17a69b00231627be") },
pipeline: [
{
$match : {
$or : [
{
"userA" : ObjectId("5c0820ea17a69b00231627be"),
"userB" : ObjectId("5c0a9c37b2365a002367df79")
// "userB" : '$$requestuser'
},
{
"userA" : ObjectId("5c0a9c37b2365a002367df79"),
"userB" : ObjectId("5c0820ea17a69b00231627be")
}
],
"accepted" : true
}
},
{
$project: {v: 'true'}
}
],
as: "match"}
Results with hard coded ObjectId:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : "true"
}
]
Results using variable:
"match" : [
]
I feel like ObjectIds need special treatment. All the examples I could find are using simple variables like strings.
To verify the '$$requestUser' contained a value, I tested it on the projection:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : ObjectId("5c0a9c37b2365a002367df79")
}
]
When you use un co-related sub queires, you need to use $expr to pass a variable.
You can try something like following.
{
$match: {
$expr: {
$or: [
{
$and:[
{
$eq: [ "userA", ObjectId("5c0820ea17a69b00231627be") ]
},
{
$eq: [ "userB", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", "$$requestuser" ]
}
]
},
{
$and:[
{
$eq: [ "userA", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", ObjectId("5c0820ea17a69b00231627be") ]
}
]
}
]
},
"accepted": true,
}
}
I have created a sample demo to show how $expr works inside the lookup : Sample demo for Uncorrelated Subquery
Related
I have these 2 simple collections:
items:
{
"id" : "111",
"name" : "apple",
"status" : "active"
}
{
"id" : "222",
"name" : "banana",
"status" : "active"
}
inventory:
{
"item_id" : "111",
"qty" : 3,
"branch" : "main"
}
{
"item_id" : "222",
"qty" : 3
}
Now I want to to only return the items with "status" == "active" and with "branch" that exist and is equal to "main" in the inventory collection. I have this code below but it returns all documents, with the second document having an empty "info" array.
db.getCollection('items')
.aggregate([
{$match:{$and:[
{"status":'active'},
{"name":{$exists:true}}
]
}},
{$lookup:{
as:"info",
from:"inventory",
let:{fruitId:"$id"},
pipeline:[
{$match:{
$and:[
{$expr:{$eq:["$item_id","$$fruitId"]}},
{"branch":{$eq:"main"}},
{"branch":{$exists:true}}
]
}
}
]
}}
])
Can anyone give me an idea on how to fix this?
Your code is doing well. I think you only need a $match stage in the last of your pipeline.
db.items.aggregate([
{
$match: {
$and: [
{ "status": "active" },
{ "name": { $exists: true } }
]
}
},
{
$lookup: {
as: "info",
from: "inventory",
let: { fruitId: "$id" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: [ "$item_id", "$$fruitId" ] } },
{ "branch": { $eq: "main" } },
{ "branch": { $exists: true } }
]
}
}
]
}
},
{
"$match": {
"info": { "$ne": [] }
}
}
])
mongoplayground
Query
match
lookup on id/item_id, and match branch with "main" (if it doesn't exists it will be false anyways)
keep only the not empty
*query is almost the same as #YuTing one,but i had written it anyways, so i send it, for the small difference of alternative lookup syntax
Test code here
items.aggregate(
[{"$match":
{"$expr":
{"$and":
[{"$eq":["$status", "active"]},
{"$ne":[{"$type":"$name"}, "missing"]}]}}},
{"$lookup":
{"from":"inventory",
"localField":"id",
"foreignField":"item_id",
"pipeline":[{"$match":{"$expr":{"$eq":["$branch", "main"]}}}],
"as":"inventory"}},
{"$match":{"$expr":{"$ne":["$inventory", []]}}},
{"$unset":["inventory"]}])
I have two collections
AdPlans
Tasks
Each task is related to AdPlan, In ad Plan I have number of columns, In the query I am using below AdPlan columns
drip_feed : Boolean
drip_feed_views : Number
drip_feed_interval : Number (Represent how many hpurs)
drip_feed can be either true or false. Here is my ageegate query:
var adPlanWhere = [
{
$lookup :{
from: 'tasks',
let: { "adPlanId": "$_id", "drip_feed" : "$drip_feed", "drip_feed_views" : "$drip_feed_views", "drip_feed_interval" : "$drip_feed_interval"},
pipeline: [
{
$match: {
$expr: {
$and: [
{$eq: ["$adPlan", "$$adPlanId"]},
{$eq: ["$status", "Completed"]},
{
$or : [
{
$and : [
{ $eq: ["$$drip_feed", true] },
{ $gte: ["$updatedAt", moment(Date.now()).subtract(parseInt("$$drip_feed_interval"),'hours').toDate()] }, //this one is not working
{ $lte: ["$updatedAt", moment(Date.now()).toDate()] },
]
},
{
$and : [
{ $eq: ["$$drip_feed", false] }
]
}
]
}
]
}
}
}
],
as: 'adPlanTasks'
}
},
]
If drip_feed is true then I want to get only those tasks that are in drip_feed_interval
Having the following collections and data on them
db.a.insert([
{ "_id" : ObjectId("5b56989172ebcb00105e8f41"), "items" : [{id:ObjectId("5b56989172ebcb00105e8f41"), "instock" : 120}]},
{ "_id" : ObjectId("5b56989172ebcb00105e8f42"), "items" : [{id:ObjectId("5b56989172ebcb00105e8f42"), "instock" : 120}] },
{ "_id" : ObjectId("5b56989172ebcb00105e8f43"), "items" : [{ObjectId("5b56989172ebcb00105e8f43"), "instock" : 80}] }
])
db.b.insert([
{ "_id" : ObjectId("5b56989172ebcb00105e8f41")},
{ "_id" : ObjectId("5b56989172ebcb00105e8f42")},
{ "_id" : ObjectId("5b56989172ebcb00105e8f43")},
{ "_id" : ObjectId("5b56989172ebcb00105e8f44")},
{ "_id" : ObjectId("5b56989172ebcb00105e8f45")}
])
executing an lookup aggregation like
db.b.aggregate([
{
$lookup:
{
from: "b",
let: { bId: "$_id", qty: 100 },
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$items.id", "$$bId" ] },
{ $gte: [ "$instock", "$$qty" ] }
]
}
}
}
],
as: "a"
}
}
])
does not bring any results in the expected lookup operation. Is there any restriction to use ObjectId as a comparison? In the official documentations does not say any about it and it works like a charm with any other kind of data type, like strings
I am not sure if this is a bug in mongodb or not but the query only works after adding an $unwind stage first.
db.b.aggregate([
{
$lookup:
{
from: "a",
let: { bId: "$_id", qty: 100 },
pipeline: [
{
$unwind: {
path: "$items"
}
},
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$items.id", "$$bId" ] },
{ $gte: [ "$items.instock", "$$qty" ] },
]
}
}
}
],
as: "a"
}
}
]);
Note: Join Conditions and Uncorrelated Sub-queries were added in mongo 3.6
I have a collection in which I store data in below format I want to apply sort by in below collection.
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e")
]
},
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e")
]
},
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e")
]
}
i want data to be like as below
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e")
]
},
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e"),
ObjectId("58eb607531f78831a8894a3e")
]
},
{
"job_count" : [
ObjectId("58eb607531f78831a8894a3e")
]
}
my complete query is
Jobs.aggregate(
{"$match" : $condArray},
{"$unwind" : { path: "$mytradesmen.hired", preserveNullAndEmptyArrays: true}},
{"$lookup" : {
"from":"users",
"localField":"mytradesmen.hired",
"foreignField":"_id",
"as": "user_details"
}
},
{"$unwind": { path: "$user_details", preserveNullAndEmptyArrays: true}},
{ "$sort" : {"job_count":-1}})
Can anyone help me figure out the query i should modify to get the expected result,
please let me know in case of any further detail required i will edit my question accordingly
Use the $size operator to create an extra field that holds the count of the elements in the array and then $sort on that field:
Jobs.aggregate([
{
"$project": {
"count": { "$size": "$job_count" },
"job_count": 1
}
},
{ "$sort" : { "count": -1 } }
])
I have a collection in Mongo where the structure is known.
It consists of
{
"_id" : "123456__data",
"fields" : {
"field1" : {
"type" : "boolean",
"writeAccess" : "someWriteAccess",
},
"field2" : {
"type" : "integer",
"writeAccess" : "secondWriteAccess",
},
"field3" : {
"someConcretePermissionOperation" : "set",
"writeAccess" : "thirdWriteAccess",
}
}
}
Now I got to find all documents, and preferably, all concrete values of "someConcretePermissionOperation", while the "field1", 2, 3 can be up to the user. That is, in different documents they could have different names. The only thing I know is the constant depth - if someConcretePermissionOperation will appear it will be under fields.XXX.someConcretePermissionOperation, where XXX can be anything.
Anybody got any ideas?
Just found something close to what I am looking for:
var operationOptions = [ "push", "set", "pushUnique" ];
db.mytable.aggregate(
[
{ $redact:
{
$cond:
{
if: { $gt: [ { $size: { $setIntersection: [ "$someConcretePermissionOperation", operationOptions ] } }, 0 ] },
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
]
)
But receiving
uncaught exception: aggregate failed: {
"errmsg" : "exception: The argument to $size must be an Array, but was of type: NULL",
Don't exactly know yet how to write "if exists, otherwise disregard" in this aggregation query.
Use the $ifNull operator to check if the field is either not an array or not present:
var operationOptions = [ "push", "set", "pushUnique" ];
db.mytable.aggregate(
[
{ $redact:
{
$cond:
{
if: {
$gt: [
{
$size: {
"$ifNull": [
{ $setIntersection: ["$someConcretePermissionOperation", operationOptions] },
[]
]
}
},
0
]
},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
]
)