How to write a complex query using `expr` in mongodb? - mongodb

I'm trying to write a bit complicated query to the MongoDB and I honestly don't know how to write it correctly. In SQL pseudocode it's something like:
SELECT *
FROM Something
WHERE
(IF Item.NestedItem THEN NULL ELSE Item.NestedItem.Id) == 'f4421f9e-5962-4c68-8049-a45600f36f4b'
OR (IF Item.NestedItem THEN NULL ELSE Item.NestedItem.OtherId) == 'd6b799dc-f464-4919-8435-a7b600cc408a'
I succeeded in writing IF as
{ "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.Id"] }
{ "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.OtherId"] }
But now I'm not sure how to compare them to constants and then join. I'm trying to write a translator from SQL-like language to MongoDb and this is example of query I cannot translate. I want something like
{ "$and" :
{ "$eq" : { "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.Id"] }, "f4421f9e-5962-4c68-8049-a45600f36f4b" },
{ "$eq" : { "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.OtherId"] }, "d6b799dc-f464-4919-8435-a7b600cc408a" } }
But it's an invalid query as I get it

The $cond is a aggregation expression, you need to use $expr before $and operation, in your SQL query you have user OR so just updating in query $and to $or
corrected $eq syntax after $or
db.collection.aggregate([
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$cond: [
{ $eq: ["$Item.NestedItem", null] },
null,
"$Item.NestedItem.Id"
]
},
"f4421f9e-5962-4c68-8049-a45600f36f4b"
]
},
{
$eq: [
{
$cond: [
{ $eq: ["$Item.NestedItem", null] },
null,
"$Item.NestedItem.OtherId"
]
},
"d6b799dc-f464-4919-8435-a7b600cc408a"
]
}
]
}
}
}
])
Playground

Related

Get rows by comparing field dates in mongo db

I am trying to compare two fields of collection mongo itself to get rows, but not able to get it.
[{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-29T05:01:15.370+0000"),
},
{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-19T05:01:15.370+0000"),
},
{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
}]
Query
db.getCollection("collection_name").aggregate([
{
$match: {
status: '1',
$or: [
{
date_modified: { $gt: ISODate('$date_created') }
},
{
date_modified: {
"$exists": false,
},
},
],
},
},
]);
Expected result:
[{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-29T05:01:15.370+0000"),
},{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
}]
Current result: date_created is not defined
You need to use $expr for comparison between fields. Use $ifNull to cater your requirement for checking the existence for the date_created field.
db.collection.find({
$expr: {
$or: [
{
$eq: [
{
$ifNull: [
"$date_modified",
null
]
},
null
]
},
{
$gt: [
"$date_modified",
"$date_created"
]
}
]
}
})
Here is the Mongo Playground for your reference.

Aggregate Lookup with pipeline and match not working mongodb

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"]}])

On MongoDb Aggregation lookup, does the let need special formating?

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

MongoDB $cond with embedded document array

I am trying to generate a new collection with a field 'desc' having into account a condition in field in a documment array. To do so, I am using $cond statement
The origin collection example is the next one:
{
"_id" : ObjectId("5e8ef9a23e4f255bb41b9b40"),
"Brand" : {
"models" : [
{
"name" : "AA"
},
{
"name" : "BB"
}
]
}
}
{
"_id" : ObjectId("5e8ef9a83e4f255bb41b9b41"),
"Brand" : {
"models" : [
{
"name" : "AG"
},
{
"name" : "AA"
}
]
}
}
The query is the next:
db.runCommand({
aggregate: 'cars',
'pipeline': [
{
'$project': {
'desc': {
'$cond': {
if: {
$in: ['$Brand.models.name',['BB','TC','TS']]
},
then: 'Good',
else: 'Bad'
}
}
}
},
{
'$project': {
'desc': 1
}
},
{
$out: 'cars_stg'
}
],
'allowDiskUse': true,
})
The problem is that the $cond statement is always returning the "else" value. I also have tried $or statement with $eq or the $and with $ne, but is always returning "else".
What am I doing wrong, or how should I fix this?
Thanks
Since $Brand.models.name returns an array, we cannot use $in operator.
Instead, we can use $setIntersection which returns an array that contains the elements that appear in every input array
db.cars.aggregate([
{
"$project": {
"desc": {
"$cond": [
{
$gt: [
{
$size: {
$setIntersection: [
"$Brand.models.name",
[
"BB",
"TC",
"TS"
]
]
}
},
0
]
},
"Good",
"Bad"
]
}
}
},
{
"$project": {
"desc": 1
}
},
{
$out: 'cars_stg'
}
])
MongoPlayground | Alternative $reduce

Mongodb find() query criteria implicit AND?

Given the query
var p_other = 111;
var p_timestamp = ISODate("2015-05-08T07:00:00.000Z");
db.test.find({
other: p_other,
$or: [
{ "startTime": null },
{ "startTime": { $lte: p_timestamp }}
],
$or: [
{ "endTime": null },
{ "endTime": { $gte: p_timestamp }}
]
})
on the following data:
{
"_id" : "1",
"other" : 111,
"startTime" : ISODate("2015-05-08T07:01:30.868Z")
}
{
"_id" : "2",
"other" : 111,
"startTime" : ISODate("2015-05-08T06:04:30.040Z"),
"endTime" : ISODate("2015-05-08T07:01:30.868Z")
}
Both docs are returned, where I would expect only the second one.
Running an explain() I get the following parsed query:
"parsedQuery" : {
"$and" : [
{
"$or" : [
{
"endTime" : {
"$eq" : null
}
},
{
"endTime" : {
"$gte" : ISODate("2015-05-08T07:00:00.000Z")
}
}
]
},
{
"other" : {
"$eq" : 111
}
}
]
}
What's the reason the first $or is ignored?
From what I know, there should be an implicit AND between the criteria expressions of a find(), although this is not mentioned in the doc (at least not here).
The only mention regarding the usage of multiple $or is in the $and doc:
This query cannot be constructed using an implicit AND operation,
because it uses the $or operator more than once.
But either I don't understand it or this is not really an explanation :-)
Please read document of $and carefully. You will find following-
The query cannot be constructed using an implicit AND operation, when it uses the $or operator multiple times.
Following query will give you desired result :
var p_other = 111;
var p_timestamp = ISODate("2015-05-08T07:00:00.000Z");
db.collection.find({
other: p_other,
$and: [{
$or: [{
"startTime": null
}, {
"startTime": {
$lte: p_timestamp
}
}]
}, {
$or: [{
"endTime": null
}, {
"endTime": {
$gte: p_timestamp
}
}]
}]
})