how to convert query to use regex instead of equal - mongodb

I'm using MongoDB and i have the following records:
{
name: "a",
status: [
{ age: 15, closed: true},
{ age: 38, closed: true},
{ age: 42, closed: false},
]
},
{
name: "b",
status: [
{ age: 29, closed: true},
{ age: 5, closed: false},
]
}
I want to check if the before last object in status has for example age = 29.
so for that i have this working query:
db.col.find({
$expr: {
$eq: [
29,
{
$let: {
vars: { beforeLast: { $arrayElemAt: [ "$status", -2 ] } },
in: "$$beforeLast.age"
}
}
]
}
})
but i want now to check for example if age contain a value like "2". i need to use regex expression. is there anyway to convert that query to use regex inside/instead $eq operator ?
PS: i don't want to use aggregation because i'm working with an old version.

You can use $indexOfCP to find sub string inside an string character
db.collection.find({
"$expr": {
"$ne": [
{
"$indexOfCP": [
{ "$toLower": {
"$let": {
"vars": {
"beforeLast": {
"$arrayElemAt": [
"$status",
-2
]
}
},
"in": "$$beforeLast.age"
}
}},
"2"
]
},
-1
]
}
})

Try with aggregation
db.collection.aggregate([
{
$project:
{
last: { $arrayElemAt: [ "$status", -2 ] },
}
},
{$addFields:{
ageInString: { $substr: [ "$last.age", 0, 3 ] }
}},
{$match:{ageInString:{ $regex: '^2' }}}
])

Related

Unable to match objects in array

I have several documents like the following and I'm trying to retrieve the documents where the first element of the scores array was created within the past 24hrs:
[
{
"id": 1,
"scores": [
{
"score": 1,
created_at: ISODate("2022-11-19T00:05:00.000+00:00")
},
{
"score": 2,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
{
"id": 2,
"scores": [
{
"score": 3,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
},
{
"score": 5,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
]
This is the query:
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
"$scores.0.created_at",
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
https://mongoplayground.net/p/L1jI10efWGL
However, nothing is returned. Does anyone know what might be wrong?
Instead of using $scores.0.created_at, use $getField to get the value of created_at from the first element of the scores array.
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
{
$getField: {
field: "created_at",
input: {
$first: "$scores"
}
}
},
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
Demo # Mongo Playground

Mongodb $exists inside $expr in mongodb

I want to add multiple conditions on join. Join those docs (of the same collection) who met the following conditions:
Have opposite gender
Have age (IF EXISTS) between the primary doc age preference and primary doc have age (IF EXISTS) between the foreign doc preference (i.e two-way check)
My attempt is the following but has two issues:
$exists can't be used inside $expr idk why
Age query is one way right now
$lookup: {
"from": "appusers",
"let": { 'gen': "$gender",'pref': "$preference" },
"pipeline": [{
$match: {$expr: {
$and: [
{ $ne: ["$gender", "$$gen"]},
{ $or: [
{$exists: {"$age": false}},
{$and: [
{ $gte: ["$age", '$$pref.age_from' ] },
{ $lte: [ "$age", '$$pref.age_to' ] }
]}
]}
]
}}}],
"as": "matches"
}
Example:
Input Docs:
{
name: "person1",
age: 36,
gender: "Male",
preference: {
age_from: 25,
age_to: 35
}
}
{
name: "person2",
age: 18,
gender: "Female",
preference: {
age_from: 25,
age_to: 40
}
}
{
name: "person3",
age: 26,
gender: "Female",
preference: {
age_from: 30,
age_to: 35
}
}
{
name: "person4",
age: 26,
gender: "Female",
preference: {
age_from: 30,
age_to: 40
}
}
Output:
For person 1 the matches array will show only person 4 (and similarly person 4 match will show person 1) i.e.:
{
name: person1,
age: 36,
gender: "Male",
preference: {
age_from: 28,
age_to: 35
},
matches: [
{
name: person4,
...
}
]
}
I have viewed this and this but didn't help
$exists can't be used inside $expr idk why
$expr Allows the use of aggregation expressions within the query language, and $exists is not an aggregation operator,
You just need to correct the 2 things:
put $expr condition inside first $and condition
put $expr in last $and condition
db.appusers.aggregate([
{
$lookup: {
from: "appusers",
let: { gen: "$gender", pref: "$preference" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $ne: ["$gender", "$$gen"] } },
{
$or: [
{ age: { $exists: false } },
{
$expr: {
$and: [
{ $gte: ["$age", "$$pref.age_from"] },
{ $lte: ["$age", "$$pref.age_to"] }
]
}
}
]
}
]
}
}
],
as: "matches"
}
}
])
Playground
For the $exists problem, you can wrap age with $ifNull and use $eq to check for the existence.
For the 2-way age matching, I think you just need to repeat your age matching criteria from person1 to person4 for person4 to person1. Although in your current given test case, no match will be found as person4's age is out of person1's preference.
db.appusers.aggregate([
{
"$match": {
name: "person1"
}
},
{
$lookup: {
"from": "appusers",
"let": {
"a": "$age",
"gen": "$gender",
"pref": "$preference"
},
"pipeline": [
{
$match: {
$expr: {
$and: [
{
$ne: [
"$$gen",
"$gender"
]
},
{
$and: [
{
$or: [
{
$eq: [
{
"$ifNull": [
"$age",
"age-not-exists"
]
},
"age-not-exists"
]
},
{
$and: [
{
$gte: [
"$age",
"$$pref.age_from"
]
},
{
$lte: [
"$age",
"$$pref.age_to"
]
}
]
}
]
},
{
$or: [
{
$eq: [
{
"$ifNull": [
"$$a",
"age-not-exists"
]
},
"age-not-exists"
]
},
{
$and: [
{
$gte: [
"$$a",
"$preference.age_from"
]
},
{
$lte: [
"$$a",
"$preference.age_to"
]
}
]
}
]
}
]
}
]
}
}
}
],
"as": "matches"
}
}
])
Here is the Mongo playground for your reference.
You can use $eq undefined for the field age instead of the $exists
{
"from": "appusers",
"let": { 'gen': "$gender",'pref': "$preference" },
"pipeline": [{
$match: {$expr: {
$and: [
{ $ne: ["$gender", "$$gen"]},
{ $or: [
{$eq: ["$age" , undefined]},
{$and: [
{ $gte: ["$age", '$$pref.age_from' ] },
{ $lte: [ "$age", '$$pref.age_to' ] }
]}
]}
]
}}}],
"as": "matches"
}

mongodb can $unionWith use in $push

I want to get related data based on current item processing.
Sample:
[
{ field1: 1, field2: 2, value: 12 },
{ field1: 1, field2: 2, value: 21 },
{ field1: 1, value: 1 },
{ field2: 2, value: 2 },
{ field1: 2, field2: 3, value: 23 }
];
and result:
[
{
_id: { field1: 1, field2: 2 },
value: [12, 12],
relatedValue: [1, 2], // of item 1 and 2 because field 1 = 1 or field 2 = 2
},
];
Sample query:
db.collectionA.aggregate([
{
$match: { field1: 1 }
},
{
"$group":{
"_id":{
"field1":"$field1",
"field2":"$field2"
},
"alerts":{
"$push":{
"_id":"$_id",
"value":"$value",
"relatedData": {
"$unionWith": {
"coll": "collectionA",
"pipeline": [{
"$match": {
"$or": [
{ "field1": "$field1" },
{ "field2": "$field2" }
]
}
}]
}
}
}
}
}
}
])
I tried run this query but error, Please help me fix or give a solution
// Edited: value should be array because I want to group data by field1, field2 and push all value of group to an array
You're trying to use $unionWith within $group but it is a "pipeline stage" meaning it can't be used like that, the same way you can't use $group within a $group.
Additionally this stage is used to "union" two collections and not to populate data based on value matches ( which it seems you're trying to do here ), for this case you want to use $lookup, like so:
db.collection.aggregate([
{
$lookup: {
from: "collection",
let: {
field1: "$field1",
field2: "$field2",
docId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$or: [
{
$eq: [
"$$field1",
"$field1"
]
},
{
$eq: [
"$$field2",
"$field2"
]
}
]
},
{
$ne: [
"$$docId",
"$_id"
]
}
]
}
}
},
{
$project: {
value: 1
}
}
],
as: "relatedData"
}
},
{
$group: {
_id: {
field1: "$field1",
field2: "$field2"
},
values: {
$push: "$value"
},
relatedValue: {
$push: {
$map: {
input: "$relatedData",
in: "$$this.value"
}
}
}
}
},
{
$project: {
field1: "$_id.field1",
field2: "$_id.field2",
values: 1,
relatedValues: {
"$setDifference": [
{
"$reduce": {
input: "$relatedValue",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
"$values"
]
}
}
}
])
Mongo Playground

query the before last object in array

I'm using MongoDB and i have the following records:
{
name: "a",
status: [
{ age: 15, closed: true},
{ age: 38, closed: true},
{ age: 42, closed: false},
]
},
{
name: "b",
status: [
{ age: 29, closed: true},
{ age: 5, closed: false},
]
}
I want to check if the before last object in status has for example age = 29.
is there anyway to do that without using aggregation ?
You can use $arrayElemAt and pass -2 as an argument to get before last element (-1 represents last one),
If negative, $arrayElemAt returns the element at the idx position, counting from the end of the array.
Try:
db.col.aggregate([
{
$addFields: {
beforeLast: { $arrayElemAt: [ "$status", -2 ] }
}
},
{
$match: {
"beforeLast.age": 29
}
},
{
$project: {
beforeLast: 0
}
}
])
EDIT: alternatively you can use $expr with $let keyword to define a temporary variable:
db.col.find({
$expr: {
$eq: [
29,
{
$let: {
vars: { beforeLast: { $arrayElemAt: [ "$status", -2 ] } },
in: "$$beforeLast.age"
}
}
]
}
})

MongoDb $addFields inside arrays that $multiply two values within the array

MongoDb version 3.4.4
How to aggregate a new key 'total', whose value is the product of 'course' and 'quantity' for each object inside array snapshot.
Sample document:
{
cur: "EUR",
snapshot: [
{
id: "24352345",
course: 58.12,
quantity: 13
},
{
id: "34552345",
course: 18.12,
quantity: 63
}
]
}
Desired result:
{
cur: "EUR",
snapshot: [
{
id: "24352345",
course: 58.12,
quantity: 13,
total: 755.56
},
{
id: "34552345",
course: 18.12,
quantity: 63,
total: 1141.56
}
]
}
first attempt:
db.mycoll.aggregate([{
$addFields: {
"snapshot.total": {
$multiply:["$snapshot.quantity", "$snapshot.course"]
}
}
}])
"errmsg" : "$multiply only supports numeric types, not array"
Second attempt:
db.mycoll.aggregate( [
{ "$addFields": {
"snapshot.total": {
"$map": {
"input": "$snapshot",
"as": "row",
"in": { "$multiply": [
{ "$ifNull": [ "$$row.quantity", 0 ] },
{ "$ifNull": [ "$$row.course", 0 ] }
]}
}
}
}}
])
The undesired value of 'total' is an array with the totals of all the objects:
{
cur: "EUR",
snapshot: [
{
id: "24352345",
course: 58.12,
quantity: 13,
total: [
755.56,
1141.56
]
},
{
id: "34552345",
course: 18.12,
quantity: 63,
total: [
755.56,
1141.56
]
}
]
}
Modify your second attempt using the $map operator to map the whole snapshot embedded document with its fields as
db.mycoll.aggregate([
{
"$addFields": {
"snapshot": {
"$map": {
"input": "$snapshot",
"as": "row",
"in": {
"id": "$$row.id",
"course": "$$row.course",
"quantity": "$$row.quantity",
"total": { "$multiply": [
{ "$ifNull": [ "$$row.quantity", 0 ] },
{ "$ifNull": [ "$$row.course", 0 ] }
]}
}
}
}
}
}
])