Mongodb find() query criteria implicit AND? - mongodb

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

Related

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

How to write a complex query using `expr` in 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

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

Return null default value if no result found

I have a collection that looks like this:
{
"value" : "20",
"type" : "square",
"name" : "form1"
}
{
"value" : "24",
"type" : "circle",
"name" : "form2"
}
{
"value" : "12",
"type" : "square",
"name" : "form3"
}
I want to extract a document with name = form2 so I type:
db.myCollec.find({"name":"form2"} , {"name":1, "type":1, "_id":0})
The result is:
{ "name" : "form2", "type" : "circle" }
Now if I want to find a document with name = form4 I type:
db.myCollec.find({"name":"form4"} , {"name":1, "type":1, "_id":0})
But this returns nothing because there is no document with this name.
However I want the return value to look like this:
{ "name" : "form4", "type" : null }
How would I do this?
You can use below aggregation
Mongodb doesn't produce the result if there is not $matched data found with the query.
But you can use $facet aggregation which processes multiple aggregation pipeline within single stage.
So first use $facet to get the $matched documents and use $projection if no ($ifNull) data found.
let searchTerm = "form2"
db.myCollec.aggregate([
{ "$facet": {
"data": [
{ "$match": { "name": searchTerm }},
{ "$project": { "name": 1, "type": 1, "_id": 0 }}
]
}},
{ "$project": {
"name": {
"$ifNull": [{ "$arrayElemAt": ["$data.name", 0] }, searchTerm ]
},
"type": {
"$ifNull": [{ "$arrayElemAt": ["$data.type", 0] }, null]
}
}}
])
Why you dont check in callback if result==null and create your own empty object?
let name = "form4";
db.myCollec.find({"name":name} , {"name":1, "type":1, "_id":0}, function(err, result){
if(err) {
// Error handling
return;
}
if (result==null){
result = {"name":name, "type":null};
}
});

MongoDB aggregation on another aggreatation suggestions

I have a Json file imported into MongoDB. Every line on it is a user, and I have a field product, with the name of it. I know the value of every product, they are just few.
But this information is not stored on the Json.
I was able to do aggregation to retrieve the number of time that a user bought a product, but I would like to do a query to get directly the amount of money that each user spent.
This is my query:
db.source.aggregate([
{"$match": {
"$and":[
{"productName":{
"$in":[
"product2","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": {
"id_user": "$id_user",
"productName": "$productName"
},
"count": { "$sum": 1}
}},
{ "$sort" : { "count": -1 } }
])
so the output is like that:
{ "_id" : { "id_user" : "user1", "productID" : "product2" }, "count" : 433 }
{ "_id" : { "id_user" : "user2", "productID" : "product1" }, "count" : 370 }
{ "_id" : { "id_user" : "user1", "productID" : "product3" }, "count" : 300 }
{ "_id" : { "id_user" : "user3", "productID" : "product6" }, "count" : 250 }
{ "_id" : { "id_user" : "user2", "productID" : "product5" }, "count" : 140 }
{ "_id" : { "id_user" : "user3", "productID" : "product4" }, "count" : 90 }
I know that product 1 costs 20$, product 2 costs 40$, product 3 costs 55$, product 4 costs -90$, product 5 costs 110$, product 6 costs 200$.
I would like to have an output like that:
{ "_id" : { "id_user" : "user1"}, "money_spent" : 600$ }
{ "_id" : { "id_user" : "user2"}, "money_spent" : 400$ }
etc
Can you help to get that result, I am new with MongoDB.
Thanks in advance.
If you cannot go to the original source data an are only working with an import then do this:
db.source.aggregate([
{"$match": {
"$and":[
{ "productName": {
"$in":[
"product1","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": "$id_user",
"cost": {
"$sum": {
"$cond": [
{ "$eq": ["$_id.productId", "product1"] },
20,
{ "$cond": [
{ "$eq": ["$productName", "product2"] },
40,
{ "$cond": [
{ "$eq": [ "$productName", "product3"] },
55,
{ "$cond": [
{ "$eq": [ "$productName", "product4" ] },
-90,
{ "$cond": [
{ "$eq": [ "$productName", "product5" ] },
110,
200
]}
]}
]}
]}
}
}
}
}}
])
The $cond operator evaluates whether your field value matches the condition and places the appropriate value simply just $sum to get your result.
$cond provides a "ternary" operator or "if .. then .. else" that is used to evaluate the condition you provide in the first argument. You construct this to "cascade" where the condition evaluates to false in order to move on to the next condition to evaluate, otherwise return the value that matches your condition.
In this way your "known" values are applied as you aggregate for your expected total.