mongoDb aggregation values as key-value - mongodb

my data looks like this:
this is a doc.
[{
"name": "moses",
"display" : [
{"SQL commands that triggered the attack:" : ""},
{"Timetsamp" : "Query:"},
[[{ "ISODate" : "2021-03-14T17:14:58.064Z"}, "EXECUTE IMMEDIATE"],
[{"ISODate" : "2021-03-14T17:14:58.064Z"}, "EXECUTE IMMEDIATE"],
[{ "ISODate" : "2021-03-14T17:14:58.064Z"}, "EXECUTE IMMEDIATE"]]]
}]
how can i make it look like this:
{
"name": "moses"
"display" : [
{ "SQL commands" : ""},
{"Timetsamp" : "Query:"},
{"2021-03-14T17:14:58.064Z" : "EXECUTE IMMEDIATE"},
{"2021-03-14T17:14:58.064Z" : "EXECUTE IMMEDIATE"},
{"2021-03-14T17:14:58.064Z" : "EXECUTE IMMEDIATE"}]
}
is that even possible???

You can do by using $arrayToObject, like so:
db.collection.aggregate([
{
$replaceRoot: {
newRoot: {
"$arrayToObject": [
[
{
k: "$a.ISODate",
v: "$b"
}
]
]
}
}
}
])
Mongo Playground
--- EDIT ---
The concept of your new request is still the same, it ain't pretty due to the nested nature of the structure.
db.collection.aggregate([
{
"$addFields": {
display: {
$reduce: {
input: {
$map: {
input: "$display",
as: "dis",
in: {
$cond: [
{
"$isArray": "$$dis"
},
{
$map: {
input: "$$dis",
as: "deepdis",
in: {
"$arrayToObject": [
[
{
k: {
"$arrayElemAt": [
{
$map: {
input: {
"$objectToArray": {
"$arrayElemAt": [
"$$deepdis",
0
]
}
},
as: "dd",
in: "$$dd.v"
}
},
0
]
},
v: {
"$arrayElemAt": [
"$$deepdis",
1
]
}
}
]
]
}
}
},
"$$dis"
]
}
}
},
initialValue: [],
in: {
"$concatArrays": [
"$$value",
{
$cond: [
{
"$isArray": "$$this"
},
"$$this",
[
"$$this"
]
]
}
]
}
}
}
}
}
])
Mongo Playground

Related

Can't access sub array fields in $map

I have an object like so
{
"_id": "62334a3c86f03ce0985f11a1",
"stops": [
{
"location": {
"baseLocation": "622e29bd0a56c69f81e0e6e9",
"extraLocation": "622e29bd0a56c69f81e0e6eb"
},
"timesData": [
{
"wType": "date"
}
],
},
{
"location": {
"baseLocation": "622e70a59975be022276178c",
"extraLocation": "622e70a59975be022276178e"
},
"timesData": [
{
"wType": "date"
}
],
}
},
],
}
I need to find the index of objects that have a baseLocation equal to a value and which have a wType of date and I do so using a $map like the one below
{
$map: {
input: "$stops",
in: {
$and: [
{
$eq: ["$$this.location.baseLocation", mongoose.Types.ObjectId(from.id)]
},
{
$eq: ["$$this.timesData.0.wType", "date"]
}
]
}
}
},
I get no matches, but if I do it like this
{
$map: {
input: "$stops",
in: {
$eq: ["$$this.location.baseLocation", mongoose.Types.ObjectId(from.id)]
}
}
},
It works, so I guess the problem is that I can't access "$$this.timesData.0.wType"
How about using $arrayElemAt:
$map: {
input: "$stops",
in: {
$and: [
{
$eq: [
"$$this.location.baseLocation",
mongoose.Types.ObjectId(from.id)]
]
},
{
$eq: [
{
"$arrayElemAt": [
"$$this.timesData",
0
]
},
{
"wType": "date"
}
]
}
]
}
}
}
}
You can check it on the playground: https://mongoplayground.net/p/p5eRv0pXYXM

MongoDb Create Aggregate Create query

I have 3 table users,shifts,temporaryShifts,
shifts:[{_id:ObjectId(2222),name:"Morning"},{_id:ObjectId(454),name:"Night"}]
users:[{_id:ObjectId(123),name:"Albert",shift_id:ObjectId(2222)}]
temporaryShifts:[
{_id:2,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:987,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:945,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-08"},
{_id:23,userId:ObjectId(123),shiftId:ObjectId(454),date:"2020-02-09"}]
i want to make a mongoose aggregate query then give me result :
get result between two dates for example :2020-02-01 2020-02-05,
resullts is :
[
{_id:ObjectId(123),name:"Albert",shift:[
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-02"},
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-04"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-05"},
]}
]
in result type temporary mean selected date in table temporaryShift document available else type permanent
MongoPlayGround You Can edit
You can first project a date range array using $range, in your example it will be like [2020-02-01, 2020-02-02, 2020-02-03, 2020-02-04, 2020-02-05], then you can use the array to perform $lookup
db.users.aggregate([
{
$limit: 1
},
{
"$addFields": {
"startDate": ISODate("2020-02-01"),
"endDate": ISODate("2020-02-05")
}
},
{
"$addFields": {
"dateRange": {
"$range": [
0,
{
$add: [
{
$divide: [
{
$subtract: [
"$endDate",
"$startDate"
]
},
86400000
]
},
1
]
}
]
}
}
},
{
"$addFields": {
"dateRange": {
$map: {
input: "$dateRange",
as: "increment",
in: {
"$add": [
"$startDate",
{
"$multiply": [
"$$increment",
86400000
]
}
]
}
}
}
}
},
{
"$unwind": "$dateRange"
},
{
"$project": {
"name": 1,
"shiftId": 1,
"dateCursor": "$dateRange"
}
},
{
"$lookup": {
"from": "temporaryShifts",
"let": {
dateCursor: "$dateCursor",
shiftId: "$shiftId"
},
"pipeline": [
{
"$addFields": {
"parsedDate": {
"$dateFromString": {
"dateString": "$date",
"format": "%Y-%m-%d"
}
}
}
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$$dateCursor",
"$parsedDate"
]
}
]
}
}
}
],
"as": "temporaryShiftsLookup"
}
},
{
"$unwind": {
path: "$temporaryShiftsLookup",
preserveNullAndEmptyArrays: true
}
},
{
$project: {
shiftId: 1,
type: {
"$ifNull": [
"$temporaryShiftsLookup.type",
"permanent"
]
},
date: "$dateCursor"
}
}
])
Here is the Mongo Playground for your reference.

Zip two array and create new array of object

hello all i'm working with a MongoDB database where each data row is like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
"userhasRate" : [
"51",
"52",
],
"ratings" : [
4,
3
],
}
and i need to change it to be like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
rate : [
{userhasrate: "51", value: 4},
{userhasrate: "52", value: 3},
]
}
I already try using this method,
db.getCollection('contens').aggregate([
{ '$group':{
'rates': {$push:{ value: '$ratings', user: '$userhasRate'}}
}
}
]);
and my result become like this
{
"rates" : [
{
"value" : [
5,
5,
5
],
"user" : [
"51",
"52",
"53"
]
}
]
}
Can someone help me to solve my problem,
Thank you
You can use $arrayToObject and $objectToArray inside $map to achieve the required output.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
}
}
},
"as": "el",
"in": {
"userhasRate": "$$el.k",
"value": "$$el.v"
}
}
}
}
}
])
Alternative Method
If userhasRate contains repeated values then the first solution will not work. You can use arrayElemAt and $map along with $zip if it contains repeated values.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
},
"as": "el",
"in": {
"userhasRate": {
"$arrayElemAt": [
"$$el",
0
]
},
"value": {
"$arrayElemAt": [
"$$el",
1
]
}
}
}
}
}
}
])
Try below aggregate, first of all you used group without _id that grouped all the JSONs in the collection instead set it to "$_id" also you need to create 2 arrays using old data then in next project pipeline concat the arrays to get desired output:
db.getCollection('contens').aggregate([
{
$group: {
_id: "$_id",
rate1: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
0
]
},
value: {
$arrayElemAt: [
"$ratings",
0
]
}
}
},
rate2: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
1
]
},
value: {
$arrayElemAt: [
"$ratings",
1
]
}
}
}
}
},
{
$project: {
_id: 1,
rate: {
$concatArrays: [
"$rate1",
"$rate2"
]
}
}
}
])

How can I do a lookup based on conditional selection?

I have 2 collection based on collection1 I need to fetch from collection2
collection1
[
{
"_id": ObjectId("5ce7454f77af2d1143f84c38"),
"menu_name": "mainmenu1",
"sub_menus": [
{
"name": "submenu1",
"project": [
"All"
]
},
{
"name": "submenu2",
"project": [
"p2"
]
}
]
}
]
based on project field I need to fetch the record. If the project field is "All", I need to fetch all the projects under that submenu. if it is specific project only those project I need to fetch.
Here is my collection2
collection2
"project": [
{
"project_name": "p1",
"sub_menus": "submenu1",
},
{
"project_name": "p2",
"sub_menus": "submenu2",
}
{
"project_name": "p2",
"sub_menus": "submenu1",
},
{
"project_name": "p3",
"sub_menus": "submenu2",
}
{
"project_name": "p3",
"sub_menus": "submenu1",
},
{
"project_name": "p4",
"sub_menus": "submenu2",
}
]
https://mongoplayground.net/p/qH9fuJorq6z.
Can I do a conditional lookup?
Expected Result is
[
{
"_id": ObjectId("5ce7454f77af2d1143f84c38"),
"menu_name": "mainmenu1",
"sub_menus": [
{
"projectData": [
{
"project_name": "p1"
},
{
"project_name": "p2"
},
{
"project_name": "p3"
}
],
"sub_menu_name": "submenu1"
},
{
"projectData": [
{
"project_name": "p2"
}
],
"sub_menu_name": "submenu2"
}
]
}
]
Yes, you can define your own matching condition for $lookup pipeline but since your structure is deeply nested you need to flatten your sub_menus using $reduce before you run your $lookup. Once you bring all projects that match to any submenu you can use $map with $filter to put them into releval sub_menu:
db.collection1.aggregate([
{
$addFields: {
sub_menus_flat: {
$reduce: {
input: "$sub_menus",
initialValue: [],
in: {
$concatArrays: [
"$$value",
{ $map: { input: "$$this.project", as: "p", in: { name: "$$this.name", project: "$$p" } } }
]
}
}
}
}
},
{
$lookup: {
from: "collection2",
let: { sub_menus_flat: "$sub_menus_flat" },
pipeline: [
{
$match: {
$expr: {
$anyElementTrue: {
$map: {
input: "$$sub_menus_flat",
in: {
$and: [
{ $eq: [ "$$this.name", "$sub_menus" ] },
{ $in: [ "$$this.project", [ "All", "$project_name" ] ] }
]
}
}
}
}
}
}
],
as: "projects"
}
},
{
$project: {
_id: 1,
menu_name: 1,
sub_menus: {
$map: {
input: "$sub_menus",
in: {
sub_menu_name: "$$this.name",
projectData: {
$filter: {
input: "$projects",
as: "p",
cond: {
$and: [
{ $eq: [ "$$p.sub_menus", "$$this.name" ] }
]
}
}
}
}
}
}
}
},
{
$project: {
"sub_menus.projectData._id": 0,
"sub_menus.projectData.sub_menus": 0
}
}
])
MongoDB Playground

How can I compare two string format times?

I have a collection called "project" which is having a field expected time and actual time both are in string format
{
"_id" : ObjectId("5ce7455d77af2d1143f84d49"),
"project_name" : "p1",
"expected" : "0:11:30",
"actual" : "7:30:00",
}
How can I compare two string format times using mongodb?
I want to find if actual time is more than expected
You can use $split with $toInt (MongoDB 4.0 or newer) to convert your string values to a number of seconds and then use $expr to compare both fields:
db.col.aggregate([
{
$addFields: {
expected: {
$let: {
vars: {
parts: {
$split: [ "$expected", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
},
actual: {
$let: {
vars: {
parts: {
$split: [ "$actual", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
}
}
},
{
$match: {
$expr: { $gt: [ "$expected", "$actual" ] }
}
}
])
You can convert time to any date you want using $dateFromString operator and then can easily use $lte $gte to perform simple match operations.
db.collection.find({
"$expr": {
"$gt": [
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$actual"]
}
}},
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$expected"]
}
}}
]
}
})