Transform mongodb array document to a single field - node.js

I have such document structure:
{
favourite_shops: [
{
shop_id: "5961352278cba91cc6a6e8cc",
time: "2017-07-11T14:43:35.465Z"
},
{
shop_id: "5964e446c15c760f9b646d99",
time: "2017-07-11T14:44:40.429Z"
},
{
shop_id: "5964e446c15c760f9b646d98",
time: "2017-07-11T14:44:50.988Z"
}
]
}
How can I transform this to something like this:
{
favourite_shops: [
"5961352278cba91cc6a6e8cc",
"5964e446c15c760f9b646d99",
"5964e446c15c760f9b646d98"
]
}

You can $map on the array itself and get only the fields you need. (This is done within the query itself not after you receive the documents)
example.
given documents;
> db.Shopping.find().pretty()
{
"_id" : ObjectId("59651ce38828356e1a39fde9"),
"favourite_shops" : [
{
"shop_id" : "5961352278cba91cc6a6e8cc",
"time" : "2017-07-11T14:43:35.465Z"
},
{
"shop_id" : "5964e446c15c760f9b646d99",
"time" : "2017-07-11T14:44:40.429Z"
},
{
"shop_id" : "5964e446c15c760f9b646d98",
"time" : "2017-07-11T14:44:50.988Z"
}
]
}
{
"_id" : ObjectId("59665cf3d8145b41e5d2f5da"),
"favourite_shops" : [
{
"shop_id" : "2222",
"time" : "2017-07-11T14:43:35.465Z"
},
{
"shop_id" : "4444",
"time" : "2017-07-11T14:44:40.429Z"
},
{
"shop_id" : "6666",
"time" : "2017-07-11T14:44:50.988Z"
}
]
}
$map on favourite_shops array ({$match} block is optional, you can remove if you want shopping id for all documents)
> db.Shopping.aggregate([[
{
"$match": {
"_id": ObjectId("59651ce38828356e1a39fde9")
}
},
{
"$project": {
"my_favourite_shops": {
"$map": {
"input": "$favourite_shops",
"as": "each_shop",
"in": "$$each_shop.shop_id"
}
}
}
}
]).pretty()
{
"_id" : ObjectId("59651ce38828356e1a39fde9"),
"my_favourite_shops" : [
"5961352278cba91cc6a6e8cc",
"5964e446c15c760f9b646d99",
"5964e446c15c760f9b646d98"
]
}
And, with mongodb 3.4.4, I simply could $project on nested field,
db.Shopping.aggregate([{"$project": {"my_favourite_shops": "$favourite_shops.shop_id"}}]).pretty()
{
"_id" : ObjectId("59651ce38828356e1a39fde9"),
"my_favourite_shops" : [
"5961352278cba91cc6a6e8cc",
"5964e446c15c760f9b646d99",
"5964e446c15c760f9b646d98"
]
}
{
"_id" : ObjectId("59665cf3d8145b41e5d2f5da"),
"my_favourite_shops" : [
"2222",
"4444",
"6666"
]
}

Assuming we have such data structure:
const data = {
favourite_shops: [
{
shop_id: "5961352278cba91cc6a6e8cc",
time: "2017-07-11T14:43:35.465Z"
},
{
shop_id: "5964e446c15c760f9b646d99",
time: "2017-07-11T14:44:40.429Z"
},
{
shop_id: "5964e446c15c760f9b646d98",
time: "2017-07-11T14:44:50.988Z"
}
]
};
Just a bit of ES6 magic:
const result = {
favourite_shops: []
};
data.favourite_shops.forEach(el => result.favourite_shops.push(el.shop_id));
Second elegant approach:
const result = {
favourite_shops: data.favourite_shops.map(el => el.shop_id)
};

Related

Mongo: strings converted to date but not 'querable'

I have a collection with date stored as strings YYYY-mm-DD_HH:MM:SS.UUUZ like 2020-10-20_12:15:22.123+0100
My goal is to query on strings treating those as dates.
What am I doing:
I'm unwinding some header data on multiple documents:
{
"$unwind": {
"path": "$events",
"preserveNullAndEmptyArrays": true
}
}
and also
{
"$unwind": {
"path": "$events.hi2",
"preserveNullAndEmptyArrays": true
}
}
I'm adding a new field made with the string parsed as Date
{
"$addFields": {
"events.hi2.ConnectTimets": {
"$dateFromString": {
"dateString": "$events.hi2.ConnectTime",
"format": "%Y-%m-%d_%H:%M:%S.%L%Z"
}
}
}}
then on a $match stage I try to filter all records with date newer than 1 June 2020:
{
"$match":{
"events.hi2.ConnectTimets": {
"$gt": {"$dateFromString": {
"dateString": "2020-06-01",
"format": "%Y-%m-%d"
}
}
}
}
}
my result is Fetched 0 record(s) in 0ms
even though exists (at least a single document) in the database with a date matching the filter:
{
"_id" : ObjectId("5f438dfbf1feb13c4352e9f4"),
"timestamp" : NumberLong(1598262779045),
"attribute1" : [
"common"
],
"events" : [
{
"eventType" : "ty1",
"timestamp" : NumberLong(1598262779018),
"docId" : NumberLong(282578800148736),
"hi2" : {
"Priority" : 3,
"ClientId" : "client1",
"ConnectTime" : "2020-08-24_09:52:58.993+0000",
"Direction" : 1
}
},
{
"eventType" : "ty2",
"timestamp" : NumberLong(1598262781071),
"docId" : NumberLong(282578800148736),
"hi2" : {
"ref" : "bbbb"
}
}
]
}
When I espected something like
{
"_id" : ObjectId("5f438dfbf1feb13c4352e9f4"),
"timestamp" : NumberLong(1598262779045),
"attribute1" : [
"common"
],
"events" : [
{
"eventType" : "ty1",
"timestamp" : NumberLong(1598262779018),
"docId" : NumberLong(282578800148736),
"hi2" : {
"Priority" : 3,
"ClientId" : "client1",
"ConnectTime" : "2020-08-24_09:52:58.993+0000",
"Direction" : 1
}
}
}
Note_: the add field is ok because if i fire it without the match stage outputs a field with the string parsed as date
You should never store date/time values as string, use always proper Date objects.
Then the query is much simpler:
db.logging.aggregate([
{
$addFields: {
"events.hi2.ConnectTimets": {
$dateFromString: {
dateString: "$events.hi2.ConnectTime",
format: "%Y-%m-%d_%H:%M:%S.%L%Z"
}
}
}
},
{ $match: { "events.hi2.ConnectTimets": { $gte: ISODate("2020-06-01") } } }
])
When you have to work with date/time values then I recommend moment.js. Then you query could look like these:
{ $match: { "events.hi2.ConnectTimets": { $gte: moment("2020-06-01").toDate() } } }
{ $match: { "events.hi2.ConnectTimets": { $gte: moment("2020-06-01").tz('Europe/Zurich').toDate() } } }
{ $match: { "events.hi2.ConnectTimets": { $lte: moment.tz('Europe/Zurich').endOf('day').toDate() } } }

wrong counting with $filter

My db cofiguration looks like:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"boats" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"model" : "Dufour",
"year" : 2019,
"about" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 14,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 200
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 120
},
]
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 8,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "benzin",
"fuelCap" : 180
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 100
},
]
},
{...},
{...},
]
}
Now i am trying to count the number of boats which have "fuel" : "petrol", so i use the code bellow:
router.get('/boat', async(req, res)=>{
try{
const fuelData = await Boat.aggregate([
{
$project: {
fuelData: {
$filter: {
input: "$boats",
as: "boats",
cond: {
$filter:{
input:"$$boats.about",
as:"about",
cond:{
$filter:{
input:"$$about.characteristics",
as:"characteristics",
cond:{
$eq:["$$activity1.activity.type", "STILL"]
}
}
}
}
}
}
}
}
},
{
$project: {
boatsCount: {$size : "$fuelData" }
}
}
])
res.status(201).send(fuelData)
}catch(e){
res.send(e)
}
})
The problem is that return wrong number of boatCount. And it seems like it returns the number of the boats which are inside the db. Any help how to count correctly the boats which have "fuel" : "petrol"?
Is there anything wrong in my code?
https://mongoplayground.net/p/D0FEhTMJEJ1
Hope this is what you need.
The sample data you have provided is missing a ] & }.
So I added 1 additional boat with 2 about.
db.collection.aggregate([
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$unwind: "$boats"
},
{
$unwind: "$boats.about"
},
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
])

how to get data in mongoose where last element in array?

how to get data in mongoose where last element in array?
I have data looks like this:
[
{
"_id" : ObjectId("5b56eb3deb869312d85a8e69"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
}
]
},
{
"_id" : ObjectId("5b56eb3deb869312d8589765"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:03:30.347Z")
},
{
"status" : "done",
"createdAt" : ISODate("2018-07-24T09:04:22.347Z")
}
]
}
]
And, I want to get data above where last object transactionStatus.status = process, so the result should be:
{
"_id" : ObjectId("5b56eb3deb869312d85a8e69"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
}
]
}
how to do that with mongoose?
You can use $expr (MongoDB 3.6+) inside of match. Using $let and $arrayElemAt passing -1 as second argument you can get the last element as a temporary variable and then you can compare the values:
db.col.aggregate([
{
$match: {
$expr: {
$let: {
vars: { last: { $arrayElemAt: [ "$transactionStatus", -1 ] } },
in: { $eq: [ "$$last.status", "process" ] }
}
}
}
}
])
The same result can be achieved for lower versions of MongoDB using $addFields and $match. You can add $project then to remove that temporary field:
db.col.aggregate([
{
$addFields: {
last: { $arrayElemAt: [ "$transactionStatus", -1 ] }
}
},
{
$match: { "last.status": "process" }
},
{
$project: { last: 0 }
}
])
//Always update new status at Position 0 using $position operator
db.update({
"_id": ObjectId("5b56eb3deb869312d85a8e69")
},
{
"$push": {
"transactionStatus": {
"$each": [
{
"status": "process",
"createdAt": ISODate("2018-07-24T09:02:53.347Z")
}
],
"$position": 0
}
}
}
)
//Your Query for checking first element status is process
db.find(
{
"transactionStatus.0.status": "process"
}
)
refer $position, $each

How to use aggregation correctly in MongoDB with embbeded documents?

I have a collection with folowing data:
{
"_id" : ObjectId("5b5066b716d3112cfc2a5deb"),
"username" : "admin",
"password" : "123456",
"token" : "0123",
"bots" : [
{
"name" : "mybot",
"installations" : [
{
"date" : ISODate("2018-07-19T10:23:51.774Z")
},
{
"date" : ISODate("2018-07-19T10:23:51.774Z")
}
],
"commands" : [
{
"name" : "read",
"date" : ISODate("2018-07-19T10:23:51.774Z")
},
{
"name" : "answer",
"date" : ISODate("2018-07-19T10:23:51.774Z")
},
{
"name" : "get",
"date" : ISODate("2018-07-19T11:55:28.858Z")
},
{
"name" : "get",
"date" : ISODate("2018-07-19T11:56:47.419Z")
},
{
"name" : "get",
"date" : ISODate("2018-07-19T11:56:48.499Z")
},
{
"name" : "get",
"date" : ISODate("2018-07-19T11:56:49.089Z")
}
]
}
]
},
{
"_id" : ObjectId("5b50bbfe3ed35b6f2bde6923"),
"username" : "user",
"password" : "123456",
"token" : "44444",
"bots" : [
{
"name" : "anotherBotName",
"installations" : [
{
"date" : ISODate("2018-07-19T16:27:42.012Z")
},
{
"date" : ISODate("2018-07-19T16:27:42.012Z")
}
],
"commands" : [
{
"name" : "update",
"date" : ISODate("2018-07-19T16:27:42.012Z")
},
{
"name" : "update",
"date" : ISODate("2018-07-19T16:27:42.012Z")
}
]
}
]
}
I want to execute SQL-equivalent query
SELECT commands.name, COUNT(commands.name), GROUP BY commands.name
and get a result like:
[
{update: 2},
{get: 4},
{read: 1},
{answer: 1}
]
but when I execute this query in mongo:
.collection(collectionName).aggregate({{'$group': {_id: "$bots.commands.name",count:{$sum:1}}}
}).toArray(callback)
I get such a result:
[
{
_id: [
[ 'test', 'test1' ]
],
count: 1
},
{
_id: [
[ 'read', 'answer', 'get', 'get', 'get', 'get' ]
],
count: 1
}
]
I googled and read about agregation in MongoDB and still don't get much. It's hard to move from SQL to NoN-SQL database
My questions are:
Why my query shows not the result I want to see?
How to fix it?
Thanks in advance!
Since you have two nested arrays in your schema you should use $unwind operator twice before you apply your $group. After $unwind you'll get separate document for each name. Try:
db.col.aggregate([
{
$unwind: "$bots"
},
{
$unwind: "$bots.commands"
},
{
$group: {
_id: "$bots.commands.name",
count: { $sum: 1 }
}
},
{
$replaceRoot: {
newRoot: {
$let: {
vars: { obj: [ { k: "$_id", v: "$count" } ] },
in: { $arrayToObject: "$$obj" }
}
}
}
}
])
In the last stage you can use $replaceRoot with $arrayToObject to set _id as keys in your final objects.
Outputs:
{ "update" : 2 }
{ "get" : 4 }
{ "answer" : 1 }
{ "read" : 1 }

MongoDB querying issue

How to query :
{
"_id" : Object Id("58787242f7d06edbbb88f46e"),
"name" : "aah",
"values" : [
"1484196300685",
"10"
],
"attributes" : {
}
}
i need time where value=10 result 1484196300685
db.collection.find(
{ values: { $elemMatch: { $eq: "10" } } }
)

Resources