arguments to $lookup must be strings - mongodb

Trying to use this $lookup query in mongo DB.
db.request_user.aggregate( [
{
$lookup:
{
from: 'request',
let: {req_id: "$requestId",curr_user:"$user"},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{$eq: [ "$requestId","$$req_id"]}
{$eq: [ "$currentUser","$$curr_user"]}
]
}
}
},
],
as: "result"
}
}
] )
I am getting this error:
{
"message" : "arguments to $lookup must be strings, let: { req_id: '$requestId' } is type object",
"ok" : 0,
"code" : 4570,
"codeName" : "Location4570"
}
Found some sources saying let is not compatible with mongoDB 3 ~ versions. I am using version 3.4. If it's true.. can some please suggest an alternative.

As I mentioned in the comments this $lookup syntax is only available starting version 3.6. The alternative would be to use the "old" $lookup syntax which allows a join only on a single field.
Then to add additional filtering based on the other condition, like so:
db.request_user.aggregate( [
{
$lookup:
{
from: 'request',
localField: "user",
foreignField: "currentUser",
as: "result"
}
},
{
$addFields: {
result: {
$filter: {
input: "$result",
cond: {$eq: ["$$this.requestId", "$requestId"]}
}
}
}
}
] )
It would be better if you use the field that will match less documents in the original $lookup and the other field in the $filter expression, only you can know the distribution based on your data.

Related

MongoDB join 2 tables and get ids on condition

We are really new to MongoDB query writing. We have 2 MongoDB tables Supplier1 & Supplier 2. Both have the same _id. But the version number of these objects can be different sometimes.
We need to find out _id when the version of 2 collections are different (i.e. Suplier1.version != Supplier2.version)
Supplier1
{
"_id" : ObjectId("60cd86b914dfed073d77300f"),
"companyName" : "Main Supplier",
"version" : NumberLong(246),
}
Supplier2
{
"_id" : ObjectId("60cd86b914dfed073d77300f"),
"companyName" : "Main Supplier",
"version" : NumberLong(247),
}
What we have written up to now and no idea to move forward with this. Any help is highly appreciated.
db.getCollection("Supplier1").aggregate([
{
$lookup: {
from: "Supplier2",
localField: "_id",
foreignField: "_id",
as: "selected-supplier"
}
},
You can simply use a sub-pipeline in $lookup. Simply $unwind the result array to filter out unwanted result.
db.Supplier1.aggregate([
{
"$lookup": {
"from": "Supplier2",
"let": {
id1: "$_id",
version1: "$version"
},
"pipeline": [
{
"$match": {
$expr: {
$and: [
{
$eq: [
"$$id1",
"$_id"
]
},
{
$ne: [
"$$version1",
"$version"
]
}
]
}
}
}
],
"as": "selected-supplier"
}
},
{
"$unwind": "$selected-supplier"
}
])
Here is the Mongo playground for your reference.

Lookup where array of second collection has words from array of first collection

I am trying to do an aggregate where I'd like to relate items from one array to another.
The idea is, get sentences related with terms where sentence contains all words from term items. The output will be a project with all texts fields and one custom field with all attributes from terms.
Here is my first collection:
db.terms.insertMany([
{ "_id" : 1, "items" : ["sun", "day"] },
{ "_id" : 2, "items" : ["moon", "night"] },
])
And the second one:
db.texts.insertMany([
{ "_id" : 1, "sentence" : ["a beautiful sun makes a bright day", "not usefull here"] },
])
The intent aggregate:
db.texts.aggregate([
{$lookup: {
from: "terms",
let: { term_items: "$items" },
pipeline: [
{ $match: { $expr: { "$sentence": { $all: "$$term_items" } } } }
],
as: "term_obj"
}},
]);
When I execute this aggregate I am receiving this error:
org.graalvm.polyglot.PolyglotException: Command failed with error 168
(InvalidPipelineOperator): 'Unrecognized expression '$$term_items'' on
server localhost:27019. The full response is {"ok": 0.0, "errmsg":
"Unrecognized expression '$$term_items'", "code": 168, "codeName":
"InvalidPipelineOperator"}
Another intent:
db.texts.aggregate([
{$lookup: {
from: "terms",
let: { term_items: "$items" },
pipeline: [
{ $match: { $expr: { $in: ["$$term_items", "$sentence"] } } }
],
as: "term_obj"
}},
]);
The error:
org.graalvm.polyglot.PolyglotException: Command failed with error
40081 (Location40081): '$in requires an array as a second argument,
found: missing' on server localhost:27019. The full response is {"ok":
0.0, "errmsg": "$in requires an array as a second argument, found: missing", "code": 40081, "codeName": "Location40081"}
What I am missing here?
In the existing lookup, you are using $items before it has a value. $let is where you should be assigning $sentence from the outer document to a variable.
One possible solution to accomplish this lookup:
$map over the sentence array
for each sentence, $reduce over the items array and test with $in
$reduce over the resulting array of booleans to see if there was a sentence that matched all of the items
test the result using $match and $expr
db.texts.aggregate([
{$lookup: {
from: "terms",
let: {sentences: "$sentence"},
pipeline: [
{$match: {
$expr: {
$reduce: {
initialValue: false,
input: {
$map: {
input: "$$sentences",
as: "sentence",
in: {$reduce: {
input: "$items",
initialValue: "true",
in: {$and: [
"$$value",
{$regexMatch: {
regex: "$$this",
input: "$$sentence"
}}
]}
}}
}
},
in: {$or: ["$$this", "$$value"]}
}
}
}}
],
as: "term_obj"
}}
])
Playground

How filter using part of a field in a lookup collection

I've doing a "join" between two mongodb collections and want to filter a lookup collection (before join) using part of a field as a criteria. My first option would be using regex, but I didn't find how to do it in mongodb doc. I found 3 different ways to use regex $regex, $regexMatch e $regexFind.
No one worked or I dont know how to manage with it.
Any idea ?
I tried to use some of these 3 regex in this part of example, without success
$and: [{ $eq: ['$id', '$$key'] }, { $eq: ['$x', 0], [here regex maybe or something] }]
I want something like in SQL "WHERE substr(field,3,1) = 'A'" for example
db.collection('collectionA').aggregate([
{
$lookup: {
from: 'collectionB',
let: { key: '$key },
pipeline: [
{
$match: {
$expr: {
$and: [{ $eq: ['$id', '$$key'] }, { $eq: ['$x', 0] }]
}
}
}
],
as: 'i'
}
}
])
Why not using the substring stage for this?
db.collection('collectionA').aggregate([
{
$lookup: {
from: 'collectionB',
let: { key: '$key },
pipeline: [
{
$match: {
$expr: {
$and: [{ $eq: ['$id', '$$key'] }, {$eq: { $substr: {'$x', 3, 1}, 'A'} }]
}
}
}
],
as: 'i'
}
}
])
This should be the equivalent for the SQL statement you mentioned above.

MongoDB - How to compare fields of different collections in $match of aggregate?

Let's suppose I have the collection A and the collection B. My query is like following:
db.A.aggregate([
{
$lookup: {
from: "B",
localField: "_id",
foreignField: "custom_id",
as: "B"
}
},
{
$match: {
"B.anotherId": "A.anotherId" // not working, is it possible?
}
])
I'm curious to know if it's possible to do what I tried to do in $match. The goal is to get only the documents that have the same "anotherId" value in A and B documents. Is it supported? And if yes, how do to it?
You can use $lookup with aggregation pipeline,
let to define your both fields, and check expression condition in $match and $and
db.A.aggregate([
{
$lookup: {
from: "B",
let: {
custom_id: "$_id",
anotherId: "$anotherId
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$$custom_id", "$custom_id"] },
{ $eq: ["$$anotherId", "$anotherId"] }
]
}
}
}
],
as: "B"
}
}
])
Not sure what you are trying to achieve here. $lookup provides an array of values. Are you trying to filter the array? Which would mean you have to use $filter.
However, based on your question of how to compare two fields, you have to use $expr.
{
$match: {
$expr: {
$eq: ["$firstField", "$secondField"]
}
}
}
If however you are trying to filter the collection B based on a value in A, you will have to use $filter
{
$set: {
B: {
$filter: {
input: "$B",
as: "b",
cond: {
$eq: ["$A.anotherId", "$$b.anotherId"]
}
}
}
}
}

MongoDB: Using match with input document variables

Why do I have to use this code: { $match: { $expr: { <aggregation expression> } } } to match a document using a document input variable as opposed to doing: { $match: { <query> } } ?
For example:
$lookup: {
from: "comments",
let: { myvar: '$myInputDocVariable'},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$varFromCommentDocument", "$$myvar" ] },
]
}
}
},
],
as: "returnedValue"
}
The query above works fine but the query below does not work as expected. Why is this? Does this mean that if you are using input variables in a $lookup pipeline you have to use $expr? why is that?
$lookup: {
from: "comments",
let: { myvar: '$myInputDocVariable'},
pipeline: [
{ $match: { "$varFromCommentDocument", "$$myvar" } }
],
as: "returnedValue"
}
When you perform uncorrelated sub-queries for $lookup operator:
If you need to compare parent collection's field within pipeline, MongoDB cannot apply the standard query syntax (field:value) for variable / Aggregation expressions. In this case, you need to use $expr operator.
Example:
{ $match:
{ $expr:
{ $and:[
{ $eq: [ "$varFromCommentDocument", "$$myvar" ] },
]}
}
}
if it matches against "hard-coded" values, you don't need to use $expr operator.
Example:
$lookup: {
from: "comments",
pipeline: [
{ $match:{
"key": "value",
"key2": "value2"
}}
],
as: "returnedValue"
}
Does this mean that if you are using input variables in a $lookup
pipeline you have to use $expr
Yes correct, by default in filters i.e; in filter part of .find() or in $match aggregation stage you can't use an existing field in the document.
If at all if you need to use existing field's value in your query filter then you need to use aggregation pipeline, So in order to use aggregation pipeline in .find() or in $match you need to wrap your filter query with $expr. Same way to access local variables got created using let of $lookup filter in $match needs to be wrapped by $expr.
Let's consider below example :
Sample Docs :
[
{
"key": 1,
"value": 2
},
{
"key": 2,
"value": 4
},
{
"key": 5,
"value": 5
}
]
Query :
db.collection.find({ key: { $gt: 1 }, value: { $gt: 4 } })
Or
db.collection.aggregate([ { $match: { key: { $gt: 1 }, value: { $gt: 4 } } } ])
Test : mongoplayground
If you see the above query both input 1 & 4 are passed into query but it you check below query where you try to match key field == value field - it doesn't work :
db.collection.aggregate([ { $match: { key: { $eq: "$value" } } } ])
Test : mongoplayground
Above as you're comparing two existing fields then you can't do that as it mean you're checking for docs with key field value as string "$value". So to say it's not a string it's actually a reference to value field you need to use $eq aggregation operator rather than $eq query operator like below :
db.collection.aggregate([ { $match: { $expr: { $eq: [ "$key", "$value" ] } } } ])
Test : mongoplayground