$and operation syntax - mongodb

I should take a data with condition:
(Founded in 2004 OR Founded in October) AND (social OR web)
I used to try like this:
db.companies.find({
$or: [
{"founded_year": 2004},
{"founded_month": 10}
],
$or: [
{"category_code": "web"},
{"category_code": "social"}
]
}).count()
but this goes wrong. The corrent answer is:
db.companies.find({ "$and": [
{ "$or": [ { "founded_year": 2004 },
{ "founded_month": 10 } ] },
{ "$or": [ { "category_code": "web" },
{ "category_code": "social" }]}]}).count()
Why should i use $and operator in the inner {}? I thought i didn't have to write this operator and i can just list the conditions in {} with comma, because in MongoDB university course there where this expression:

In the query
{
"$or": [
{"founded_year": 2004},
{"founded_month": 10}
],
"$or": [
{"category_code": "web"},
{"category_code": "social"}
]
}
you have two keys named $or. Or a mongo query follows the JSON notation. And in JSON format, only the last key with the duplicate name is going to be used.
Using the $and will fix this issue. Because $and is a list, you will not have anymore an object containing the two same keys

Related

Query to find the a collection with a number in it

So I have a query in which we are supposed to find all the numbers which contain "9".
One of my entries is as follows:
`
{
"_id": {
"$oid": "63945fb591a4f6443e8edbeb"
},
"customer_id": {
"$oid": "63945f6191a4f6443e8edbea"
},
"customer_name": [
"Spike",
"Takahashi"
],
"customer_age": {
"$numberLong": "23"
},
"mobile_no.": [
"7898654324",
"9232111456"
]
}
`
Now I know $regex is used to do the same for strings but I can't seem to get the output which i want. Probably coz i am using array and numbers as strings. Can anyone help me figure this out?
Now I know $regex is used to do the same for strings but I can't seem to get the output which i want. Probably coz i am using array and numbers as strings.
Actually I don't think the problem is the array here. MongoDB has pretty intuitive syntax for specifying that you want to match any item in the array. In general, something like this would work:
> db.foo.find()
[
{ _id: 1, x: [ '123', '456' ] },
{ _id: 2, x: [ '789', '000' ] }
]
> db.foo.find({x:/9/})
[
{ _id: 2, x: [ '789', '000' ] }
]
Why do I say "in general" and why didn't I use your exact sample document? It's because the field name of interest currently ends in a "." character. Continuing on the theme of MongoDB syntax, the "." character is what the database typically uses to denote nested fields:
> db.foo.find({'x.y':'abc'})
[
{ _id: 3, x: { y: 'abc', z: 0 } }
]
You can read more about querying nested fields using dot notation here.
The trailing dot in the field name (mobile_no.) is sort of confusing the database. We can see in this playground example that a regex similar to the one above (just using the slightly more verbose syntax due to a limitation of the playground) fails to return any results:
db.collection.find({
"mobile_no.": {
$regex: "9"
}
})
no document found
But if we remove the "." character from the document and the query, the document is retrieved as expected:
db.collection.find({
"mobile_no": {
$regex: "9"
}
})
[
{
"_id": ObjectId("63945fb591a4f6443e8edbeb"),
"customer_age": NumberLong(23),
"customer_id": ObjectId("63945f6191a4f6443e8edbea"),
"customer_name": [
"Spike",
"Takahashi"
],
"mobile_no": [
"7898654324",
"9232111456"
]
}
]
Playground example here.
MongoDB improved the way that we can interact with field names containing periods and dollar signs last year as outlined on this page. While you technically could query this document using those new techniques, it is quite convoluted and will not perform efficiently. The query would look something like this:
db.collection.find({
$expr: {
$reduce: {
input: {
$getField: "mobile_no."
},
initialValue: false,
in: {
$or: [
"$$value",
{
$regexMatch: {
input: "$$this",
regex: "9"
}
}
]
}
}
}
})
Playground example here
Personally I think it is much more straightforward to remove the trailing "." from the field name as it does not provide any particular value being stored in the backend database anyway.

Mongoose custom $match function

I have objects that look like this
{
name: 'Object 1',
fruitList: ['apple','pear','orange','grape']
},
{
name: 'Object 2',
fruitList: ['melon','pear','apple','kiwi']
}
I need to retrieve all the objects that have apple before pear in their fruitList, in this example it would mean Object 1 only. Can I do a custom match function that iterates over that list and checks it it matches my criteria ?
You need a mechanism to compare the indexes of the fruits in question and use the comparison as a match condition with the $expr operator. Leverage the aggregation pipeline operators:
$indexOfArray - Searches an array for an occurrence of a specified value and returns the array index (zero-based) of the first occurrence.
$subtract - return the difference between the two indexes. If the value is negative then apple appears before pear in the list.
$lt - the comparison operator to use in $expr query that compares two values and returns true when the first value is less than the second value.
To get a rough idea of these operators at play in an aggregation pipeline, check out the following Mongo Playground.
The actual query you need is as follows:
db.collection.find({
$expr: {
lt: [
{
$subtract: [
{ $indexOfArray: [ '$fruitList', 'apple' ] },
{ $indexOfArray: [ '$fruitList', 'pear' ] }
]
},
0
]
}
})
Mongo Playground
For a generic regex based solution where the fruitList array may contain a basket of assorted fruits (in different cases) for instance:
"fruitList" : [
"mango",
"Apples",
"Banana",
"strawberry",
"peach",
"Pears"
]
The following query can address this challenge:
const getMapExpression = (fruit) => {
return {
$map: {
input: '$fruitList',
as: 'fruit',
in: {
$cond: [
{ $regexMatch: { input: '$$fruit', regex: fruit, options: 'i' } },
{ $literal: fruit },
'$$fruit'
]
}
}
}
}
db.collection.find({
$expr: {
$lt: [
{
$subtract: [
{ $indexOfArray: [ getMapExpression('apple'), 'apple' ] },
{ $indexOfArray: [ getMapExpression('pear'), 'pear' ] }
]
},
0
]
}
})

Perform $group and count in mongoDB aggregation

Given that I have a complex grouping requirement, I was wondering what would be the best approach to achieving my desired result.
My data (result of $project stage) would look something like this:
{
_id:$id
status:"available"
inspectionStatus:"done"
state:"completed"
category:"One"
},
{
_id:$id
status:"booked"
inspectionStatus:"none"
state:"active"
category:"Two"
},
.
.
.
I have tried using $facet to create multiple buckets since the grouping I am trying to create are aggregations of $status + $state + $inspection, but the execution time is way unacceptable, taking something around 1639763842 milliseconds.
I can't use use $accumulator because of mongoDB version (although we can always upgrade to 4.4.x) but I am not sure whether using $accumulator would produce a better response time.
The $facet stage is included:
{
"available": [
{"$match":
{$and: [
{"status": "available"},
{"inspectionStatus": "done"}
]}
}
],
"matched": [
{"$match":
{$and: [
{"status": "booked"},
{"state": "booked"}
]
}
}
],
"inIntake": [
{"$match":
{$and: [
{"status": "available"},
{"inspectionStatus": {$ne: "done"}}
]
}
}
],
"active": [
{"$match":
{$and: [
{"status": "booked"},
{"state": "active"}
]
}
}
],
"unreturned":[
{"$match":
{"status": "forceCompleted"}
}
]
}
If you really want to push the logic to the DB, here's a solution -- but you still have to examine the XX field doc by doc:
db.foo.aggregate([
{$addFields: {XX: {$switch: {
branches: [
{ case: {
$and: [{$eq:["$status","available"]},{$eq:["$inspectionStatus","done"]}]
}, then:'AVAILABLE' },
{ case: {
$and: [{$eq:["$status","booked"]},{$eq:["$state","booked"]}]
}, then:'MATCHED' },
{ case: {
$and: [{$eq:["$status","available"]},{$ne:["$inspectionStatus","done"]}]
}, then:'IN_INTAKE' },
{ case: {
$and: [{$eq:["$status","booked"]},{$eq:["$state","active"]}]
}, then:'ACTIVE' },
{ case: {
$eq:["$status","forceCompleted"]
}, then:'UNRETURNED' },
],
default: null
}}
}}
,{$match: {XX: {$ne: null}}}
]);
The end-to-end timing on this is actually a bunch of millis better than simple find() because less material is transferred but of course the DB engine is working a little harder processing the pipeline.

Conditional filtering in mongoDB

Can I use one of the $and filters conditionally, ie. when some condition is met I would like to use the filter otherwise do not use filter:
Collection.find({
$and: [
{ date },
{ room: [isConditionMet] ? room : [do not use room filter at all] },
]
}
Is it possible on mongo query level?
Isn't it the same as using $or?
Collection.find({
$and: [
{ date: ... },
{ $or: [
{isConditionNOTmet},
{room: ... }
]}
]
})

How to $concat two fields inside a subdocument in mongodb

WHAT I WANT TO ACHIEVE
Let us say I have this object:
{
"_id" : ObjectId("5aec063380a7490014e88792"),
"personal_info" : {
"dialing_code": "+44",
"phone_number": "67467885664"
}
}
I need to concat the two values personal_info.dialing_code (+44) and phone_number (67467885664) into one. +4467467885664 and compare it to a value. I need to retrieve a specific record from the database that will match the said value.
PROBLEM
I am having trouble concatinating two fields inside a subdocument and I am receiving this error:
{
"name": "MongoError",
"message": "$concat only supports strings, not object",
"ok": 0,
"errmsg": "$concat only supports strings, not object",
"code": 16702,
"codeName": "Location16702"
}
ATTEMPT #1
I have tried this:
UserModel.aggregate([
{ $unwind: '$personal_info' },
{$project: {
concat_p: {$concat: [
'$personal_info.dialing_code',
'$personal_info.phone_number'
]}
}}
])
It is giving me an error as mentioned above and in result I cannot do a $match right after.
ATTEMPT #2
I also tried this:
UserModel.aggregate([
{ $unwind: '$personal_info' },
{$project: {
p_dialing_code: '$personal_info.dialing_code',
p_phone_number: '$personal_info.phone_number',
concat_p: {$concat: [
'$p_dialing_code',
'$p_phone_number'
]}
}}
])
I have successfully took out the subdocument values one level however when I tried concatinating, it is producing me null values. This is the result I am getting:
{
"_id": "5af0998036daa90014129d6e",
"p_dialing_code": "+44",
"p_phone_number": "13231213213244",
"concat_p": null
}
I know how to do it on the $match pipeline but I have no luck concatinating the values inside the subdocument. Clearly, I need to do this first before I can compare. Thanks
It seems like you have different types under personal_info.dialing_code and personal_info.phone_number fields. In your example $concat is applied to every document in your collection and that's why you're getting an exception since $concat strictly expects its parameters to be strings.
So it will be working fine for document posted in your question but will throw an exception for something like this:
{
"_id" : ObjectId("5aec063380a7490014e88792"),
"personal_info" : {
"dialing_code": {},
"phone_number": "67467885664"
}
}
One way to fix this is to add $match condition before $project and use $type operator to get only documents having strings on those fields you want to concatenate.
db.UserModel.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$personal_info.dialing_code" }, "string" ] },
{ $eq: [ { $type: "$personal_info.phone_number" }, "string" ] }
]
}
}
},
{$project: {
concat_p: {$concat: [
"$personal_info.dialing_code",
"$personal_info.phone_number"
]}
}}
])