Doctrine Mongodb ODM Add Dynamic Dates in Aggregation - mongodb

I'm trying to know if specific motorcycle in date range has contract or not.
My schema looks like:
{
"_id" : ObjectId("575b7c0b0419c906e262d54b"),
"customer" : {
"id" : ObjectId("575b7c0b0419c906e262d54b")
},
"name" : "Harley Store",
"description" : "Harley Store",
"contracts" : [
{
"_id" : ObjectId("575b7c0b0419c906e262d54b"),
"bike" : {
"id" : ObjectId("575b7c0b0419c906e262d54b")
},
"from" : ISODate("2050-01-01T00:00:00.000Z"),
"till" : ISODate("2050-01-05T00:00:00.000Z"),
"cost" : 10000,
"lapse" : [
ISODate("2050-01-01T00:00:00.000Z"),
ISODate("2050-01-02T00:00:00.000Z"),
ISODate("2050-01-03T00:00:00.000Z"),
ISODate("2050-01-04T00:00:00.000Z"),
ISODate("2050-01-05T00:00:00.000Z")
]
},
{
"_id" : ObjectId("575b7c0b0419c906e262d54c"),
"bike" : {
"id" : ObjectId("575b7c0b0419c906e262d54c")
},
"from" : ISODate("2050-01-01T00:00:00.000Z"),
"till" : ISODate("2050-01-05T00:00:00.000Z"),
"cost" : 10000,
"lapse" : [
ISODate("2050-01-06T00:00:00.000Z"),
ISODate("2050-01-07T00:00:00.000Z"),
ISODate("2050-01-08T00:00:00.000Z"),
ISODate("2050-01-09T00:00:00.000Z")
]
}
]
}
I have the following query in the mongo shell:
db.getCollection('BikeStore').aggregate([
{
$unwind:'$contracts'
},
{
$project:{
contract:'$contracts',
_id: 0
}
},
{
$match:{
'contract.bike.id': ObjectId("575b7c0b0419c906e262d54b")
}
},
{
$match:{
$or: [
{'contract.lapse': {$eq: ISODate("2049-01-31T00:00:00.000Z")}},
{'contract.lapse': {$eq: ISODate("2050-02-01T00:00:00.000Z")}},
{'contract.lapse': {$eq: ISODate("2050-02-02T00:00:00.000Z")}}
]
}
}
])
The query in mongo shell works fine, but the dates are generated dynamically from-till and I can not find the way to get this done using query builder.
My query builder:
public function hasContracts(string $bikeId, \DateTime $from, \DateTime $till): bool
{
$filterDate = \DateTimeImmutable::createFromMutable($from);
$days = $from->diff($till)->days;
$qb = $this->createAggregationBuilder();
$qb->unwind('$contracts');
$qb->project()
->field('contract')
->expression('$contracts')
->field('_id')
->expression(0);
$qb->match()->field('contract.bike.id')->equals(new ObjectId($bikeId));
for($i;$days){ //$i menor or equal $days
$qb->match()->addOr(
$qb->matchExpr()->field('contract.lapse')->equals(
new UTCDateTime(
$filterDate->add(
\DateInterval::createFromDateString(sprintf('%d day', $i)))
->setTime(0, 0)->getTimestamp() * 1000
)
)
);
}
return 0 !== $qb->execute()->count();
}
The query that generates the odm is the following:
{
"aggregate": true,
"pipeline": [
{
"$unwind": "$contracts"
},
{
"$project": {
"contract": "$contracts",
"_id": 0
}
},
{
"$match": {
"contract.bike.id": {
"$oid": "575b7c0b0419c906e262d54b"
}
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2524780800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2524867200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2524953600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525040000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525126400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525212800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525299200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525385600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525472000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525558400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525644800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525731200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525817600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525904000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2525990400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526076800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526163200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526249600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526336000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526422400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526508800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526595200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526681600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526768000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526854400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2526940800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527027200000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527113600000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527200000000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527286400000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527372800000"
}
}
}
]
}
},
{
"$match": {
"$or": [
{
"contract.lapse": {
"$date": {
"$numberLong": "2527459200000"
}
}
}
]
}
}
],
"options": {
"cursor": true
},
"db": "store",
"collection": "BikeStore"
}
How do I add the dates dynamically into the match and not duplicate the match ?
Thx for you help!!!

Each time you call $qb->match() you're creating a new $match stage. This should do:
$qb->match();
for($i;$days){ //$i menor or equal $days
$qb->addOr(/* ... */);
}

Related

How can I compare and filter two fields in the same document in MongoDB?

My data is like this (I have taken out unnecessary fields and only left the dates in):
[
{
"date": "2022-09-25T12:35:51.833Z",
"scans": [
{
"date": "2022-09-01T05:00:00.000Z",
},
{
"date": "2022-08-04T05:00:00.000Z",
},
{
"date": "2022-09-01T05:00:00.000Z",
},
{
"date": "2022-09-06T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T12:55:12.018Z",
"scans": [
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1926-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
},
{
"date": "1919-11-30T07:00:00.000Z",
}
],
},
{
"date": "2022-09-25T13:49:20.639Z",
"scans": [
{
"date": "2022-09-15T05:00:00.000Z",
},
{
"date": "2022-09-12T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T13:58:02.755Z",
"scans": [
{
"date": "2022-09-13T05:00:00.000Z",
},
{
"date": "2022-08-20T05:00:00.000Z",
},
{}
],
},
{
"date": "2022-09-25T14:17:04.947Z",
"scans": [
{
"date": "2022-09-12T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T14:17:49.489Z",
"scans": [
{
"date": "2022-09-13T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T14:19:26.068Z",
"scans": [{}],
},
{
"date": "2022-09-25T14:20:07.569Z",
"scans": [
{
"date": "2022-09-12T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T14:33:17.783Z",
"scans": [
{
"date": "2022-08-15T07:00:00.000Z",
}
],
},
{
"date": "2022-09-25T14:33:41.050Z",
"scans": [
{
"date": "2022-08-19T07:00:00.000Z",
}
],
},
{
"date": "2022-09-25T14:34:03.172Z",
"scans": [
{
"date": "2022-09-07T07:00:00.000Z",
}
],
},
{
"date": "2022-09-25T15:28:23.723Z",
"scans": [
{
"date": "2022-08-19T05:00:00.000Z",
}
],
},
{
"date": "2022-09-25T15:28:49.211Z",
"scans": [
{
"date": "2022-09-09T05:00:00.000Z",
}
],
}
]
I would like to get back the same list of documents but filtered according to these:
Include documents that have at least one scan where there is no date field.
Include documents that have scans where the date is before 90 days of the date field of the document.
Include documents that have scans where the date is after the date field of the document.
Bottom line is that I am trying to find documents with "incorrect" scans. How can this be achieved?
You can try the following aggregation query:
db.collection.aggregate([
{
"$addFields": {
"diffData": {
"$map": {
"input": "$scans",
"as": "item",
"in": {
"$dateDiff": {
"startDate": {
"$toDate": "$$item.date"
},
"unit": "day",
"endDate": {
"$toDate": "$date"
}
}
}
}
}
}
},
{
"$match": {
"$or": [
{
diffData: {
$elemMatch: {
"$eq": null
}
}
},
{
diffData: {
$elemMatch: {
"$gt": 90
}
}
},
{
diffData: {
$elemMatch: {
"$lt": 0
}
}
}
]
}
},
{
"$unset": "diffData"
}
])
Playground link.
In this query, we create a new field called diffData, where we store the difference between the scan date and the root date in days. Finally, we filter the elements, based on the criteria specified.

Create a Set only if value exists aggregate mongodb

I have a dataset:
[
{
"_id": 1,
"Data": {
"a": {
"levela": {
"fname": "fname",
"lname": "lname"
},
"levelfacility": []
}
}
},
{
"_id": 2,
"Data": {
"a": {
"levela": {},
"levelfacility": [
{
"facility": "facility"
}
]
}
}
},
{
"_id": 3,
"Data": {
"a": {
"levela": {},
"levelfacility": []
}
}
}
]
I want to apply an upper case to the values only if exists but when I apply a $set, it just is an empty string if it doesn't exist.
[
{
"_id": 1,
"Data": {
"a": {
"levela": {
"fname": "fname",
"fnameNORM": "FNAME",
"lname": "lname"
},
"levelfacility": []
}
}
},
{
"_id": 2,
"Data": {
"a": {
"levela": {},
"levelfacility": [
{
"facility": "facility",
"facilityNORM": "FACILITY"
}
]
}
}
},
{
"_id": 2,
"Data": {
"a": {
"levela": {},
"levelfacility": []
}
}
}
]
I want to apply something like a $conditional so I am not left with new fields with the original field doesnt exist
I have tried this for a string
{
$set: {
"Data.a.levela.fnameNORM": {
$cond: {
if: {
"$Data.a.levela.fname": {
$exist: true
}
},
then: {
$toUpper: "$Data.a.levela.fname"
},
else: "$$REMOVE"
}
}
}
}
I have tried this for an array
{
$set: {
"Data.a.levelfacility": {
$map: {
input: "$Data.a.levelfacility",
in: {
$mergeObjects: [
"$$this",
{
"facilityNORM": {
$cond: {
if: {
"$$this.FacilityName": {
$exist: true
}
},
then: {
$toUpper: "$$this.FacilityName"
},
else: "$$REMOVE"
}
}
}
]
}
}
}
}
}
You are actually on the right track. Just use $map to handle levelfacility array.
db.collection.aggregate([
{
"$addFields": {
"Data.a.levela": {
"$cond": {
"if": {
$ne: [
{
"$ifNull": [
"$Data.a.levela.fname",
null
]
},
null
]
},
"then": {
"fname": "$Data.a.levela.fname",
"fnameNORM": {
"$toUpper": "$Data.a.levela.fname"
}
},
"else": "$Data.a.levela"
}
}
}
},
{
"$addFields": {
"Data.a.levelfacility": {
"$map": {
"input": "$Data.a.levelfacility",
"as": "lf",
"in": {
"$cond": {
"if": {
$ne: [
{
"$ifNull": [
"$$lf.facility",
null
]
},
null
]
},
"then": {
"facility": "$$lf.facility",
"facilityNORM": {
"$toUpper": "$$lf.facility"
}
},
"else": "$$lf"
}
}
}
}
}
}
])
Here is the Mongo playground for your reference.

need to phonenumber from table abc in mongodb

{
"id": "1234",
"applicant": [
{
"phone": [
{
"prirotynumber": "1",
"areacode": "407",
"linenumber": "1234",
"exchangenumber": "7899"
},
{
"prirotynumber": "27",
"areacode": "407",
"linenumber": "1234",
"exchangenumber": "79999"
}
]
}
]
}
for this id=1234 i need to fetch homephonenuber as applicant.phone.areacode+applicant.phone+linenumber+ applicant.phone+exchangenumber if prirotynumber=1
and
cellphone as applicant.phone.areacode+applicant.phone+linenumber+ applicant.phone+exchangenumber if prirotynumber=27
Expected result here:
{
"key":"value"
}
If this isn't what you need, make your expected result more clarify with right sample data.
db.collection.aggregate([
{
"$match": {
"id": "1234",
"applicant.phone.prirotynumber": "1"
}
},
{
"$unwind": "$applicant"
},
{
"$unwind": "$applicant.phone"
},
{
"$match": {
"applicant.phone.prirotynumber": "1"
}
},
{
"$set": {
"homePhoneNumber ": {
$concat: [
"$applicant.phone.areacode",
"-",
"$applicant.phone.linenumber",
"-",
"$applicant.phone.exchangenumber"
]
}
}
}
])
mongoplayground

NODE JS + Mongodb aggregation pipelines from MongoCharts

Currently can't figure out why one pipeline works and the other doesn't. I got both pipelines from MongoDB charts and they both returned something and displaying charts on MongoDBCharts. However, when I use them in my code, only the first pipeline returns something. I used the same data for all cases. Any suggestions would be greatly appreciated!
The first one doesn't filter the last 30 days (hard coded by Mongo), both pipelines are copied from Mongodb charts and are not altered.
[
{
"$addFields": {
"trigger_time": {
"$convert": {
"input": "$trigger_time",
"to": "date",
"onError": null
}
}
}
},
{
"$match": {
"event_type": {
"$nin": [
null,
"",
"AC Lost",
"Device Lost",
"logged into Database",
"logged into Nexus Database",
"logged out of Nexus Database",
"Low Battery"
]
}
}
},
{
"$addFields": {
"trigger_time": {
"$cond": {
"if": {
"$eq": [
{
"$type": "$trigger_time"
},
"date"
]
},
"then": "$trigger_time",
"else": null
}
}
}
},
{
"$addFields": {
"__alias_0": {
"hours": {
"$hour": "$trigger_time"
}
}
}
},
{
"$group": {
"_id": {
"__alias_0": "$__alias_0"
},
"__alias_1": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0,
"__alias_0": "$_id.__alias_0",
"__alias_1": 1
}
},
{
"$project": {
"y": "$__alias_1",
"x": "$__alias_0",
"_id": 0
}
},
{
"$sort": {
"x.hours": 1
}
},
{
"$limit": 5000
}
]
The second one
[
{
"$addFields": {
"trigger_time": {
"$convert": {
"input": "$trigger_time",
"to": "date",
"onError": null
}
}
}
},
{
"$match": {
"event_type": {
"$nin": [
null,
"",
"AC Lost",
"Device Lost",
"logged into Database",
"logged into Nexus Database",
"logged out of Nexus Database",
"Low Battery"
]
},
"trigger_time": {
"$gte": {
"$date": "2021-03-29T08:35:47.804Z"
}
}
}
},
{
"$addFields": {
"trigger_time": {
"$cond": {
"if": {
"$eq": [
{
"$type": "$trigger_time"
},
"date"
]
},
"then": "$trigger_time",
"else": null
}
}
}
},
{
"$addFields": {
"__alias_0": {
"hours": {
"$hour": "$trigger_time"
}
}
}
},
{
"$group": {
"_id": {
"__alias_0": "$__alias_0"
},
"__alias_1": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0,
"__alias_0": "$_id.__alias_0",
"__alias_1": 1
}
},
{
"$project": {
"y": "$__alias_1",
"x": "$__alias_0",
"_id": 0
}
},
{
"$sort": {
"x.hours": 1
}
},
{
"$limit": 5000
}
]
I end up solving my own problem. After a bit of digging and asking.
Node.js does some funny things with Mongodb when it comes to using '$date', that's why the pipeline didn't work.
The resolve was to remove '$date' and pass in a date object. For my case,
"trigger_time": {
"$gte": new Date("2021-03-29T08:35:47.804Z")
}

REST API query string

I want to filter out the Sum_PKTS which value is lower than 10.
How could I merge the two query string?
Is it possible?
BTW, the "Sum_PKTS" field is sum by "field" : "Packet.
the goal is to filter the local IP and aggregate "packet" field, and finally filter the Sum_PKTS which value is lower than 10.
{
"range":{
"Sum_PKTS":{
"gte": 10
}
}
}
--
GET /_search
{
"size" : 0,
"query": {
"bool": {
"should": [
{
"match":{"IPV4_DST_ADDR":"192.168.0.0/16"}
},
{
"match":{"IPV4_SRC_ADDR":"192.168.0.0/16"}
}
],
"minimum_should_match": 1,
"must":[
{
"range":{
"#timestamp":{
"gte":"now-5m"
}
}
}
]
}
},
"aggs": {
"DST_Local_IP": {
"filter": {
"bool": {
"filter": {
"match":{"IPV4_DST_ADDR":"192.168.0.0/16"}
}
}
},
"aggs": {
"genres":{
"terms" : {
"field" : "IPV4_DST_ADDR" ,
"order" : { "Sum_PKTS" : "desc" }
},
"aggs":{
"Sum_PKTS": {
"sum" : { "field" : "Packet" }
}
}
}
}
},
"SRC_Local_IP": {
"filter": {
"bool": {
"filter": {
"match":{"IPV4_SRC_ADDR":"192.168.0.0/16"}
}
}
},
"aggs": {
"genres":{
"terms" : {
"field" : "IPV4_SRC_ADDR" ,
"order" : { "Sum_PKTS" : "desc" }
},
"aggs":{
"Sum_PKTS": {
"sum" : { "field" : "Packet" }
}
}
}
}
}
}
}
thank you in advance!
You can achieve what you want using a bucket selector pipeline aggregation (see the two Sum_PKTS_gte_10 aggregations below):
{
"size": 0,
"query": {
"bool": {
"should": [
{
"match": {
"IPV4_DST_ADDR": "192.168.0.0/16"
}
},
{
"match": {
"IPV4_SRC_ADDR": "192.168.0.0/16"
}
}
],
"minimum_should_match": 1,
"must": [
{
"range": {
"#timestamp": {
"gte": "now-5m"
}
}
}
]
}
},
"aggs": {
"DST_Local_IP": {
"filter": {
"bool": {
"filter": {
"match": {
"IPV4_DST_ADDR": "192.168.0.0/16"
}
}
}
},
"aggs": {
"genres": {
"terms": {
"field": "IPV4_DST_ADDR",
"order": {
"Sum_PKTS": "desc"
}
},
"aggs": {
"Sum_PKTS": {
"sum": {
"field": "Packet"
}
},
"Sum_PKTS_gte_10": {
"bucket_selector": {
"buckets_path": {
"sum_packets": "Sum_PKTS"
},
"script": "params.sum_packets >= 10"
}
}
}
}
}
},
"SRC_Local_IP": {
"filter": {
"bool": {
"filter": {
"match": {
"IPV4_SRC_ADDR": "192.168.0.0/16"
}
}
}
},
"aggs": {
"genres": {
"terms": {
"field": "IPV4_SRC_ADDR",
"order": {
"Sum_PKTS": "desc"
}
},
"aggs": {
"Sum_PKTS": {
"sum": {
"field": "Packet"
}
},
"Sum_PKTS_gte_10": {
"bucket_selector": {
"buckets_path": {
"sum_packets": "Sum_PKTS"
},
"script": "params.sum_packets >= 10"
}
}
}
}
}
}
}
}