MongoDB use $and with $or operator in the same query - mongodb

Hi I want to set filter to collection on mongodb.
I want to do set filter (Code field startswith "0" or "2") and (FirmId eq to "5186224fdff7421bd4552f7f")
But query result is null. What is wrong?
My mongo query is;
db.Customers.find({ "$and" : [{ "FirmId" : ObjectId("5186224fdff7421bd4552f7f") }, { "$or" : [{ "Code" : /^0/ }, { "Code" : /^2/ }] }] })
_
{
$and: [
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f")
},
{
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}
]
}
or use
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f"),
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}

You should not be using either $and nor $or here.
{ FirmId: ObjectId("xxx"), Code:/^[02]/ }
If you don't get any results then possibly no records match this criteria.

In this case you don't need to specify global $and, the outermost object will act as if there was one.
The following query should work:
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f"),
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}

Related

Querying a multi-nested array in MongoDb 3.4.2

MongoDB Version - 3.4.2
I'm trying to query within the Sitecore Analytics database, trying to retrieve all users that are associated with a given List Id.
The example dataset I have follows the default Sitecore Analytics setup:
"Tags" : {
"Entries" : {
"ContactLists" : {
"Values" : {
"0" : {
"Value" : "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}",
"DateTime" : ISODate("2020-10-23T17:38:13.891Z")
},
"1" : {
"Value" : "{28BECCD3-476B-4B1D-9A75-02E59EF21286}",
"DateTime" : ISODate("2018-04-18T14:22:41.763Z")
},
"2" : {
"Value" : "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}",
"DateTime" : ISODate("2018-05-10T14:26:08.494Z")
},
"3" : {
"Value" : "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}",
"DateTime" : ISODate("2018-10-27T02:41:28.776Z")
},
}
}
}
},
I want to iterate through all the entries within Values without having to specify 0/1/2/3, avoiding the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values.1.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"})
I've tried the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values": {$elemMatch : {"Value":"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"}}})
db.getCollection('Contacts').find({'Tags' : {$elemMatch : {$all : ['{28BECCD3-476B-4B1D-9A75-02E59EF21286}']}}})
db.getCollection('Contacts').forEach(function (doc) {
for(var i in doc.Tags.Entries.ContactLists.Values)
{
doc.Tags.Entries.ContactLists.Values[i].Value = "{28BECCD3-476B-4B1D-9A75-02E59EF21286}";
}
})
And a few other variations which I cannot recall now. And none work.
Any ideas if this is possible or on how to do this?
I want the outcome to just show filter out the results showing only the entries containing the matching GUID
Many thanks!
Demo - https://mongoplayground.net/p/upgYxgzPwJQ
It can be done using aggregation pipeline
Use $objectToArray to convert array
Use $filter to filter the array
db.collection.aggregate([
{
$addFields: {
filteredValue: {
$filter: {
input: {
$objectToArray: "$Tags.Entries.ContactLists.Values"
},
as: "val",
cond: {
$eq: [ // filter condition
"$$val.v.Value",
"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
]
}
}
}
}
}
])
Output -
[
{
"Tags": {
"Entries": {
"ContactLists": {
"Values": {
"0": {
"DateTime": ISODate("2020-10-23T17:38:13.891Z"),
"Value": "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}"
},
"1": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
},
"2": {
"DateTime": ISODate("2018-05-10T14:26:08.494Z"),
"Value": "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}"
},
"3": {
"DateTime": ISODate("2018-10-27T02:41:28.776Z"),
"Value": "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}"
}
}
}
}
},
"_id": ObjectId("5a934e000102030405000000"),
"filteredValue": [
{
"k": "1",
"v": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
}
]
}
]
You can not use $elemMatch because Values is not array, but object. You can solve the problem with Aggregation Pipeline:
$addFields to add new field Values_Array that will be array representation of Values object.
$objectToArray to transform Values object to array
$match to find all documents that has requested value in new Values_Array field
$project to specify which properties to return from the result
db.getCollection('Contacts').aggregate([
{
"$addFields": {
"Values_Array": {
"$objectToArray": "$Tags.Entries.ContactLists.Values"
}
}
},
{
"$match": {
"Values_Array.v.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
},
{
"$project": {
"Tags": 1
}
}
])
Here is the working example: https://mongoplayground.net/p/2gY-vu3Qrvz

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

Query MongoDB with $and and Multiple $or

As stated in the documentation, this is not possible.
AND Queries With Multiple Expressions Specifying the Same Operator
Consider the following example:
db.inventory.find( {
$and : [
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
]
} )
This query will return all select all documents where:
the price field value equals 0.99 or 1.99, and
the sale field value is equal to true or the qty field value is less than 20.
This query cannot be constructed using an implicit AND operation, because it uses the $or operator more than once.
What is a workaround to query something like this? This query returns no results on MongoDB 3.2. I have tested the $or blocks separately and they are working fine, but not when they are wrapped in $and block. I assumed I didn't read the documentation incorrectly that this is not supposed to work. The only alternative I have is to push the data to ElasticSearch and query it there instead, but that's also just a workaround.
{
"$and": [
{
"$or": [
{
"title": {
"$regex": "^.*html .*$",
"$options": "i"
}
},
{
"keywords": {
"$regex": "^.*html .*$",
"$options": "i"
}
}
]
},
{
"$or": [
{
"public": true
},
{
"domain": "cozybid"
}
]
}
]
}
the documentation doesn't say that this is impossible. It only says
This query cannot be constructed using an implicit AND operation,
because it uses the $or operator more than once.
this means that this will work :
db.inventory.find( {
$and : [
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
]
} )
but this won't, because it's an implicit $and with two $or
db.inventory.find({
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
})
try it online: mongoplayground.net/p/gL_0gKzGA-u
Here is a working case with an implicit $and:
db.inventory.find({ price: { $ne: 1.99, $exists: true } })
I guess the problem you're facing is that there is no document matching your request in your collection

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

MongoDb aggregate and group by two fields depending on values

I want to aggregate over a collection where a type is given. If the type is foo I want to group by the field author, if the type is bar I want to group by user.
All this should happen in one query.
Example Data:
{
"_id": 1,
"author": {
"someField": "abc",
},
"type": "foo"
}
{
"_id": 2,
"author": {
"someField": "abc",
},
"type": "foo"
}
{
"_id": 3,
"user": {
"someField": "abc",
},
"type": "bar"
}
This user field is only existing if the type is bar.
So basically something like that... tried to express it with an $or.
function () {
var results = db.vote.aggregate( [
{ $or: [ {
{ $match : { type : "foo" } },
{ $group : { _id : "$author", sumAuthor : {$sum : 1} } } },
{ { $match : { type : "bar" } },
{ $group : { _id : "$user", sumUser : {$sum : 1} } }
} ] }
] );
return results;
}
Does someone have a good solution for this?
I think it can be done by
db.c.aggregate([{
$group : {
_id : {
$cond : [{
$eq : [ "$type", "foo"]
}, "author", "user"]
},
sum : {
$sum : 1
}
}
}]);
The solution below can be cleaned up a bit...
For "bar" (note: for "foo", you have to change a bit)
db.vote.aggregate(
{
$project:{
user:{ $ifNull: ["$user", "notbar"]},
type:1
}
},
{
$group:{
_id:{_id:"$user.someField"},
sumUser:{$sum:1}
}
}
)
Also note: In you final answer, anything that is not of type "bar" will have an _id=null
What you want here is the $cond operator, which is a ternary operator returning a specific value where the condition is true or false.
db.vote.aggregate([
{ "$group": {
"_id": null,
"sumUser": {
"$sum": {
"$cond": [ { "$eq": [ "$type", "user" ] }, 1, 0 ]
}
},
"sumAuhtor": {
"$sum": {
"$cond": [ { "$eq": [ "$type", "auhtor" ] }, 1, 0 ]
}
}
}}
])
This basically tests the "type" of the current document and decides whether to pass either 1 or 0 to the $sum operation.
This also avoids errant grouping should the "user" and "author" fields contain the same values as they do in your example. The end result is a single document with the count of both types.