is it possible to write regular expression in $cond in MongoDB - regex

I need to use $cond to combine differenet column, and one $cond I need to write is as following:
create_widget: {
$sum:{
$cond:[{$and: [ {$eq: ['$Method', 'POST']},
{Url:{$regex: /.*\/widgets$/}} ]}, 1, 0]
}
}
and this code is not right, it seems, regular expression can not be put here.Is there any other way to do this? I want to match Url and regular expression and put the code under $cond.
A sample data looks as
{"BrandId":"a","SessionId":"a1","Method":"POST","Url":"/sample/widgets"}
{"BrandId":"a","SessionId":"a2","Method":"POST","Url":"/sample/blog"}
{"BrandId":"b","SessionId":"b1","Method":"PUT","Url":"/sample/widgets"}
The whole code I wrote is as following:
db.tmpAll.aggregate([
{$group: {
_id: {BrandId:'$BrandId'},
SessionId: {$addToSet: '$SessionId'},
create_widget: {
$sum:{
$cond:[{$and: [ {$eq: ['$Method', 'POST']},
{} ]}, 1, 0]
}
}
}},
{$group: {
_id: '$_id.BrandId',
distinct_session: {$sum: {$size: '$SessionId'}},
create_widget: {$sum: '$create_widget'}
}}
]);
The expected result of sample code is
{ "_id" : "a", "distinct_session" : 2, "create_widget" : 1 }
{ "_id" : "b", "distinct_session" : 1, "create_widget" : 0 }

For MongoDB 4.2 and newer production releases, and in the 4.1.11 and newer development versions, use $regexMatch which is a syntactic sugar on top of $regexFind which can be used for regex matching and capturing.
db.tmpAll.aggregate([
{ "$group": {
"_id": {
"BrandId": "$BrandId",
"SessionId": "$SessionId"
},
"widget_count": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": ["$Method", "POST"] },
{ "$regexMatch": {
"input": "$Url",
"regex": /widget/
} }
]
}, 1, 0
]
}
},
"session_count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.BrandId",
"create_widget": { "$sum": "$widget_count" },
"distinct_session": { "$sum": "$session_count" }
} }
]);
There is an open JIRA issue for this SERVER-8892 - Use $regex as the expression in a $cond. However, as a workaround, For older MongoDB versions which do not have the above features, use the following workaround in your aggregation pipeline.
It uses the $substr operator in the $project operator stage to extract the part of the URL and acts as a workaround for the regex. :
db.tmpAll.aggregate([
{ "$group": {
"_id": {
"BrandId": "$BrandId",
"SessionId": "$SessionId"
},
"widget_count": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": ["$Method", "POST"] },
{ "$eq": [ { "$substr": [ "$Url", 8, -1 ] }, "widget"] }
]
}, 1, 0
]
}
},
"session_count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.BrandId",
"create_widget": { "$sum": "$widget_count" },
"distinct_session": { "$sum": "$session_count" }
} }
]);
Output
/* 1 */
{
"result" : [
{
"_id" : "a",
"create_widget" : 1,
"distinct_session" : 2
},
{
"_id" : "b",
"create_widget" : 0,
"distinct_session" : 1
}
],
"ok" : 1
}

Related

How to write this query in elasticsearch

I have SQL query:
WHERE A = 1 AND B = 2 AND C REGEXP (eee|fff|ggg)
How can I write this query in elasticsearch ?
Not tested but something like this QUERY-DSL you need for your case with filter and regexp,
GET /_search
{
"size": 10,
"query": {
"filter" : {
"bool" : {
"must" : [
{ "term" : {"A" : 1}},
{ "term" : {"B" : 2}}
]
}
},
"regexp": {
"C": {
"value": "eee|fff|ggg"
}
}
}
}
OR
GET /_search
{
"size": 10,
"query": {
"filter": {
"bool": {
"must": [
{
"term": {
"A": 1
}
},
{
"term": {
"B": 2
}
},
{
"regexp": {
"C": {
"value": "eee|fff|ggg"
}
}
}
]
}
}
}
}

Use $regex inside $expr in mongodb aggregation

My doc looks as follows
doc = {
name: 'abc',
age:20
}
and my query looks like
{ $expr: {$and:[{ $gt:[ "$age", 10 ] },
{ $regex:["$name",'ab']}
]
}
} }
But it's not working and I get an error
Unrecognized expression '$regex'
How can I make it work?
My original query looks like this
db.orders.aggregate([{
$match: {}},
{$lookup: {
from: "orders",
let: {
"customer_details": "$customerDetails"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $or: [
{
$eq: ["$customerDetails.parentMobile","$$customer_details.parentMobile"]
},
{$eq: ["$customerDetails.studentMobile","$$customer_details.parentMobile"]
},
{$eq: ["$customerDetails.studentMobile","$$customer_details.parentMobile"]
},
{$eq: ["$customerDetails.studentMobile","$$customer_details.studentMobile"]
}
]
},
{$eq: ["$customerDetails.zipCode","$$customer_details.zipCode"]},
{$eq: ["$customerDetails.address","$$customer_details.address"]}
]
}
}
}],
as: "oldOrder"
}
}])
I want to use regex for matching address.
Any help will be greatly appreciated. Thanks in advance.
If your mongoDB version is 4.2, then you can use $regexMatch
try this
db.collection.find({
$expr: {
$and: [
{
$gt: [
"$age",
10
]
},
{
$regexMatch: {
input: "$name",
regex: "ab"
}
}
]
}
})
check this Mongo Playground
$regex is a query operator you cannot use inside $expr because it only supports aggregation pipeline operators.
{
"$expr": { "$gt": ["$age", 10] } ,
"name": { "$regex": "ab" }
}
If you have mongodb 4.2, you can use $regexMatch
{ "$expr": {
"$and": [
{ "$gt": ["$age", 10] },
{
"$regexMatch": {
"input": "$name",
"regex": "ab", //Your text search here
"options": "i",
}
}
]
}}

MongoDB: Create an Exact Match field

I'm trying order some data, and want to have the exact match as first results and then the rest.
I tried with this $eq in $project, but it seems that something won't work.
Is it possible to do something similar?
db.getCollection('words').aggregate([
{ $match: { "kanji.text": /^彼/ } },
{ $project: {
"kanji": 1,
"kana": 1,
"sense":1,
"exact": {
$eq : [ "$kanji.0.text", "彼" ]
}
}
},
{ $sort: {"exact": 1} }
])
Document Structure:
EDIT: I have found a bad workaround
db.getCollection('words').aggregate([
{ $match: { "kanji.text": /^彼/ } },
{ $limit : 10 },
{ $addFields: {
exact: {
$filter: {
input: '$kanji.text',
cond: {
$eq: ['$$this', "彼"]
}
}
}
}},
{ $project: {
"kanji": 1,
"kana": 1,
"sense":1,
"exact": 1
}
},
{ $sort: {"exact.0": -1} }
])

MongoDB Aggregate Regex Match or Full Text Search returns whole Document

Ex. Record
[
{
"_id": "5528cfd2e71144e020cb6494",
"__v": 11,
"Product": [
{
"_id": "5528cfd2e71144e020cb6495",
"isFav": true,
"quantity": 27,
"price": 148,
"description": "100g",
"brand": "JaldiLa",
"name": "Grapes",
"sku": "GRP"
},
{
"_id": "552963ed63d867b81e18d357",
"isFav": false,
"quantity": 13,
"price": 290,
"description": "100g",
"brand": "JaldiLa",
"name": "Apple",
"sku": "APL"
}
],
"brands": [
"Whole Foods",
"Costco",
"Bee's",
"Masons"
],
"sku": "FRT",
"name": "Fruits"
}
]
My Mongoose function to return query from AngularJS(http://localhost:8080/api/search?s=)
router.route('/search')
.get(function(req, res) {
Dept.aggregate(
{ $match: { $text: { $search: req.query.s } } },
{ $project : { name : 1, _id : 1, 'Product.name' : 1, 'Product._id' : 1} },
{ $unwind : "$Product" },
{ $group : {
_id : "$_id",
Category : { $addToSet : "$name"},
Product : { $push : "$Product"}
}}
)
});
RESULT: e.g. http://localhost:8080/api/search?s=Apple / Grape / Carrot, result is same for all.
[
{
"_id": "5528cfd2e71144e020cb6494",
"Category": ["Fruits"],
"Product": [
{
"_id": "5528cfd2e71144e020cb6495",
"name": "Grapes"
},
{
"_id": "552963ed63d867b81e18d357",
"name": "Apple"
},
{
"_id": "552e61920c530fb848c61510",
"name": "Carrots"
}
]
}
]
PROBLEM: On a query of "apple", it returns all objects within Product instead of just "grapes", i think maybe putting match after unwind would do the trick or $regex case
WHAT I WANT: e.g. for a searchString of "grape"
Also I want it to start sending results as soon as I send in the first two letters of my query.
[{
"_id": ["5528cfd2e71144e020cb6494"], //I want this in array as it messes my loop up
"Category": "Fruits", //Yes I do not want this in array like I'm getting in my resutls
"Product": [{
"_id": "5528cfd2e71144e020cb6495",
"name": "Grapes"
}]
}]
Thanks for being patient.
Use the following aggregation pipeline:
var search = "apple",
pipeline = [
{
"$match": {
"Product.name": { "$regex": search, "$options": "i" }
}
},
{
"$unwind": "$Product"
},
{
"$match": {
"Product.name": { "$regex": search, "$options": "i" }
}
},
{
"$project": {
"Category": "$name",
"Product._id": 1,
"Product.name": 1
}
}
];
db.collection.aggregate(pipeline);
With the above sample document and a regex (case-insensitive) search for "apple" on the name field of the Product array, the above aggregation pipeline produces the result:
Output:
/* 1 */
{
"result" : [
{
"_id" : "5528cfd2e71144e020cb6494",
"Product" : {
"_id" : "552963ed63d867b81e18d357",
"name" : "Apple"
},
"Category" : "Fruits"
}
],
"ok" : 1
}

using $regex in mongodb aggregation framework in $group

Consider the following example:
db.article.aggregate(
{ $group : {
_id : "$author",
docsPerAuthor : { $sum : 1 },
viewsPerAuthor : { $sum : "$pageViews" }
}}
);
This groups by the author field and computes two fields.
I have values for $author = FirstName_LastName.
Now instead of grouping by $author, I want to group by all authors who share the same LastName.
I tried $regex to group by all matching strings after the '_'
$author.match(/_[a-zA-Z0-9]+$/)
db.article.aggregate(
{ $group : {
_id : "$author".match(/_[a-zA-Z0-9]+$/),
docsPerAuthor : { $sum : 1 },
viewsPerAuthor : { $sum : "$pageViews" }
}}
);
also tried the following:
db.article.aggregate(
{ $group : {
_id : {$author: {$regex: /_[a-zA-Z0-9]+$/}},
docsPerAuthor : { $sum : 1 },
viewsPerAuthor : { $sum : "$pageViews" }
}}
);
Actually there is no such method which provides this kind of functionality or i could not find the appropriate version which contains it. That will not work with $regexp i think : http://docs.mongodb.org/manual/reference/operator/regex/ it is just for pattern matching.
There is an improvement request in the jira : https://jira.mongodb.org/browse/SERVER-6773
It is in open unresolved state.
BUT
in github i found this disscussion: https://github.com/mongodb/mongo/pull/336
And if you check this commit: https://github.com/nleite/mongo/commit/2dd175a5acda86aaad61f5eb9dab83ee19915709
it contains more or less exactly the method you likely to have. I do not really get the point of the state of this improvement: in 2.2.3 it is not working .
Use mapReduce: it is the general form of aggregation. This is how to proceed in mongo shell:
Define the map function
var mapFunction = function() {
var key = this.author.match(/_[a-zA-Z0-9]+$/)[0];
var nb_match_bar2 = 0;
if( this.bar.match(/bar2/g) ){
nb_match_bar2 = 1;
}
var value = {
docsPerAuthor: 1,
viewsPerAuthor: Array.sum(this.pageViews)
};
emit( key, value );
};
and the reduce function
var reduceFunction = function(key, values) {
var reducedObject = {
_id: key,
docsPerAuthor: 0,
viewsPerAuthor: 0
};
values.forEach( function(value) {
reducedObject.docsPerAuthor += value.docsPerAuthor;
reducedObject.viewsPerAuthor += value.viewsPerAuthor;
}
);
return reducedObject;
};
run mapReduce and save the result in map_reduce_result
>db.st.mapReduce(mapFunction, reduceFunction, {out:'map_reduce_result'})
query map_reduce_result to have the result
>db.map_reduce_result.find()
A possible workaround with the aggregation framework consists in using $project to compute the author name. However, it is dirty as you need to manually loop through the different first name sizes:
Here, we compute the field name as the substring after the '_' character, trying each of its possible position (this is why there is a chain of $cond), and fallbacking in returning the whole $author if the first name is too long:
http://mongotry.herokuapp.com/#?bookmarkId=52fb5f24a0378802003b4c68
[
{
"$project": {
"author": 1,
"pageViews": 1,
"name": {
"$cond": [
{
"$eq": [
{
"$substr": [
"$author",
0,
1
]
},
"_"
]
},
{
"$substr": [
"$author",
1,
999
]
},
{
"$cond": [
{
"$eq": [
{
"$substr": [
"$author",
1,
1
]
},
"_"
]
},
{
"$substr": [
"$author",
2,
999
]
},
{
"$cond": [
{
"$eq": [
{
"$substr": [
"$author",
2,
1
]
},
"_"
]
},
{
"$substr": [
"$author",
3,
999
]
},
{
"$cond": [
{
"$eq": [
{
"$substr": [
"$author",
3,
1
]
},
"_"
]
},
{
"$substr": [
"$author",
4,
999
]
},
{
"$cond": [
{
"$eq": [
{
"$substr": [
"$author",
4,
1
]
},
"_"
]
},
{
"$substr": [
"$author",
5,
999
]
},
"$author"
]
}
]
}
]
}
]
}
]
}
}
},
{
"$group": {
"_id": "$name",
"viewsPerAuthor": {
"$sum": "$pageViews"
}
}
}
]
$group combining $addFields and $arrayElemAt works for me (version ≥ 3.4).
Say we have following data in collection faculty, database school:
{ "_id" : ObjectId("5ed5a59b1febc4c796a88e80"), "name" : "Harry_Potter" }
{ "_id" : ObjectId("5ed5a60e1febc4c796a88e81"), "name" : "Edison_Potter" }
{ "_id" : ObjectId("5ed5a6231febc4c796a88e82"), "name" : "Jack_Potter" }
{ "_id" : ObjectId("5ed5a62f1febc4c796a88e83"), "name" : "Alice_Walker" }
{ "_id" : ObjectId("5ed5a65f1febc4c796a88e84"), "name" : "Bob_Walker" }
{ "_id" : ObjectId("5ed5a6731febc4c796a88e85"), "name" : "Will_Smith" }
Following can group each document by the last name:
db.faculty.aggregate([
{
$addFields: {
lastName: {
$arrayElemAt: [ { $split: ["$name", "_"] }, 1 ]
}
}
},
{
$group: {
_id: "$lastName",
count: {$sum: 1}
}
}
])
Running result is:
{ "_id" : "Potter", "count" : 3 }
{ "_id" : "Walker", "count" : 2 }
{ "_id" : "Smith", "count" : 1 }
The trick I used is to add a field named lastName. Based on what you have for the name field, it can be split into an array by _. Last name is at index 1 and first name at index 0.
Reference
$addFields (aggregation)
$arrayElemAt (aggregation)