How do I exclude a sepcific object from an array in mongodb? - mongodb

I have a collection with the following structure:
{
"_id" : ObjectId("59ef54445134d7d70e1cf531"),
"CustomerId" : "Gym_2",
"History" : [
{
"Created_At" : ISODate("2017-10-24T14:54:59Z"),
"Unit" : 600,
"ReferenceCode" : "1cd15b4d-bc42-4a51-a8b3-307db6dc3dee",
},
{
"Created_At" : ISODate("2017-10-28T00:22:19Z"),
"Sent" : true
},
{
"Created_At" : ISODate("2017-10-29T10:22:23Z"),
"Unit" : 600,
"ReferenceCode" : "998e7fce-8a1c-4f7c-b48c-c02cb5c5ad5c",
}
]
}
{
"_id" : ObjectId("59ef54465134d7d70e1cf534"),
"CustomerId" : "Gym_1",
"History" : [
{
"Created_At" : ISODate("2017-10-24T14:55:02Z"),
"Unit" : 600,
"ReferenceCode" : "d19ebeec-bd81-4a0a-aed5-006f746b50ff",
},
{
"Unit" : 600,
"ReferenceCode" : "a991504f-be1f-4e77-b59f-fba73c59e6f1",
"Created_At" : ISODate("2017-10-26T13:51:14Z")
}
]
}
I'm trying to build a query that returns only CustomerId along with history objects that do not have the "Sent" field set.
The result should look like this:
{
"_id" : ObjectId("59ef54445134d7d70e1cf531"),
"CustomerId" : "Gym_2",
"History" : [
{
"Created_At" : ISODate("2017-10-24T14:54:59Z"),
"Unit" : 600,
"ReferenceCode" : "1cd15b4d-bc42-4a51-a8b3-307db6dc3dee",
},
{
"Created_At" : ISODate("2017-10-29T10:22:23Z"),
"Unit" : 600,
"ReferenceCode" : "998e7fce-8a1c-4f7c-b48c-c02cb5c5ad5c",
}
]
}
{
"_id" : ObjectId("59ef54465134d7d70e1cf534"),
"CustomerId" : "Gym_1",
"History" : [
{
"Created_At" : ISODate("2017-10-24T14:55:02Z"),
"Unit" : 600,
"ReferenceCode" : "d19ebeec-bd81-4a0a-aed5-006f746b50ff",
},
{
"Unit" : 600,
"ReferenceCode" : "a991504f-be1f-4e77-b59f-fba73c59e6f1",
"Created_At" : ISODate("2017-10-26T13:51:14Z")
}
]
}
The closest I could reach is the following query:
db.Customers.aggregate([
{$project:{"Sent":{$exists:false},count:{$size:"$History" }}}
]);
But I get "errmsg" : "Unrecognized expression '$exists'" .
How can I achieve this result?

There is a solution for your problem, and aggregation framework is definitely the right way to achieve what you want. To modify nested collection you have to:
Unwind this collection ($unwind)
Filter out documents where Sent exists
Group by common properties
Project your data to receive it's original form
db.customers.aggregate([
{$unwind: "$History"},
{$match: {"History.Sent": {$exists: false}}},
{$group: {"_id": { "_id": "$_id", "CustomerId": "$CustomerId" }, History: { $push: "$History"} }},
{$project: { "_id": "$_id._id", "CustomerId": "$_id.CustomerId", History: 1}}
]);
As you can see this query is rather complicated and for larger collections you might encounter problems with performance because we're doing a lot more than simple collection filtering. So although it works I'd suggest you should consider changing your data model, for instance having each history item as a separate document like this:
{
_id: "some_id"
"Created_At" : ISODate("2017-10-24T14:54:59Z"),
"CustomerId" : "Gym_2",
"Unit" : 600,
"Sent" : true //can be set or not
"ReferenceCode" : "1cd15b4d-bc42-4a51-a8b3-307db6dc3dee"
}
Then your query will be just simple find with $exists.

Related

How to model this data on MongoDB

I have this data:
{
"_id" : ObjectId("5a75baada0f20bd4e612d480"),
"Number" : 400,
"Page" : 24,
"DC" : "NE",
}
{
"_id" : ObjectId("5a75baada0f20bd4e612d489"),
"Number" : 300,
"Page" : 14,
"DC" : "100",
}
And i want to model this data so it stays like this:
{Crs:{[
{Cr: {
"_id" : ObjectId("5a75baada0f20bd4e612d480"),
"Number" : 400,
"Page" : 24,
"DC" : "NE",
}},
{Cr: {
"_id" : ObjectId("5a75baada0f20bd4e612d489"),
"Number" : 300,
"Page" : 14,
"DC" : "100",
}},
]}}
I read something about Model One-to-Many Relationships with Embedded Documents but i really dont know how it works.
https://docs.mongodb.com/manual/core/data-model-design/
use aggregation
> db.crs.aggregate(
[
{$group : {_id : null, crs : {$push : {cr : "$$ROOT"}}}},
{$project : {_id : 0}}
]
).pretty()
you can also write the result data to another collection using $out
add below as last stage in aggregate pipeline
{$out : "crs"} // create collection crs
output
> db.crs.aggregate([{$group : {_id : null, crs : {$push : {cr : "$$ROOT"}}}}, {$project : {_id : 0}}]).pretty()
{
"crs" : [
{
"cr" : {
"_id" : ObjectId("5a75baada0f20bd4e612d480"),
"Number" : 400,
"Page" : 24,
"DC" : "NE"
}
},
{
"cr" : {
"_id" : ObjectId("5a75baada0f20bd4e612d489"),
"Number" : 300,
"Page" : 14,
"DC" : "100"
}
}
]
}
>
I think the documentation here should be of some help: https://docs.mongodb.com/manual/reference/operator/update/positional/#update-documents-in-an-array
And in your case it would be something similar to this:
db.nameOfCollection.updateOne(
{ _id: ObjectId('idhere'), "Crs.Cr.Number": 400 },
{ $set: { "Crs.$.Cr.DC" : "SomethingNew" } }
)
One thing here, I'm not sure you're data structure seems correct as you have an array inside an object...why not just an array, i.e.
Crs: [{...}, {...}]

MongoDB, Grouping by Multiple Fields

pretty new to Mongo and am finding some simple things that i would do in SQL frustratingly difficult in Mongo.
I have an object similar to this below
[{
"_id" : ObjectId("5870fb29a1fe030e1a2909db"),
"updatedAt" : ISODate("2017-01-07T14:28:57.224Z"),
"createdAt" : ISODate("2017-01-07T14:28:57.224Z"),
"state" : "Available",
},
{
"_id" : ObjectId("5870fb29a1fe030e1a2909dc"),
"updatedAt" : ISODate("2017-01-07T14:28:57.224Z"),
"createdAt" : ISODate("2017-01-07T14:28:57.224Z"),
"state" : "notReady",
},
{
"_id" : ObjectId("5870fb29a1fe030e1a2909d9"),
"updatedAt" : ISODate("2017-01-07T14:28:57.224Z"),
"createdAt" : ISODate("2017-01-07T14:28:57.224Z"),
"state" : "Disconnected",
}]
What i'm looking to do it group the data by the Maximum date and the state.
Ideally the result i would be looking for would be something like the following.
{
latestDate: "2017-01-07T14:28:57",
states : {
available : 10,
disconnected : 5,
notReady : 2
}}
Basically i'm looking for the SQL equivalent of this:
SELECT createdAt, state, COUNT(rowid)
FROM db
WHERE date = (SELECT MAX(createdAt) FROM db)
GROUP BY 1,2
I've searched around here and have found some good info but am probably missing something straight forward. Ive only managed to get here so far
db.collection.aggregate([
{$project: {"_id" : 0,"state": 1, "date" : "$createdAt"}},
{$group : {"_id" : {"date":"$date", "state": "actual"}, "count":{"$sum":1}}}
])
Any help would be appreciated :)
db.collection.aggregate([
{
$group : {
_id : {
date : "$createdAt",
state : "$state"
},
count : {$sum : 1}
}
},
{
$group : {
_id : "$_id.date",
states : {
$addToSet : {
state : "$_id.state",
count : "$count"
}
}
}
},
{
$sort : {_id : -1}
},
{
$limit : 1
},
{
$project : {
_id : 0,
latestDate : "$_id",
states : "$states"
}
}
])
output :
{
"latestDate" : ISODate("2017-01-07T14:28:57.224Z"),
"states" : [
{
"state" : "Available",
"count" : 1
},
{
"state" : "notReady",
"count" : 1
},
{
"state" : "Disconnected",
"count" : 1
}
]
}

Get matched embedded document(s) from array

I've got a lot of documents using the following structure in MongoDB:
{
"_id" : ObjectId("..."),
"plant" : "XY_4711",
"hour" : 1473321600,
"units" : [
{
"_id" : ObjectId("..."),
"unit_id" : 10951,
"values" : [
{
"quarter" : 1473321600,
"value" : 395,
},
{
"quarter" : 1473322500,
"value" : 402,
},
{
"quarter" : 1473323400,
"value" : 406,
},
{
"quarter" : 1473324300,
"value" : 410,
}
]
}
]
}
Now I need to find all embedded document values where the quarter is between some given timestamps (eg: { $gte: 1473324300, $lte: 1473328800 }).
I've only got the unit_id and the quarter timestamp from/to for filtering the documents. And I only need the quarter and value grouped and ordered by unit.
I'm new in MongoDB and read something about find() and aggregate(). But I don't know how to do it. MongoDB 3.0 is installed on the server.
Finally I've got it:
I simply have to take apart each array, filtering out the things I don't need and put it back together:
db.collection.aggregate([
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$unwind: "$units"},
{$unwind: "$units.values"},
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$project: {"units": {values: {quarter: 1, "value": 1}, unit_id: 1}}},
{$group: {"_id": "$units.unit_id", "quarter_values": {$push: "$units.values"}}} ,
{$sort: {"_id": 1}}
])
Will give:
{
"_id" : 10951,
"quarter_values" : [
{
"quarter" : 1473324300,
"value" : 410
},
{
"quarter" : 1473325200,
"value" : 412
},
{
"quarter" : 1473326100,
"value" : 412
},
{
"quarter" : 1473327000,
"value" : 411
},
{
"quarter" : 1473327900,
"value" : 408
},
{
"quarter" : 1473328800,
"value" : 403
}
]
}
See: Return only matched sub-document elements within a nested array for a detailed description!
I think I have to switch to $map or $filter in the future. Thanks to notionquest for supporting my questions :)
Please see the sample query below. I didn't exactly get your grouping requirement. However, with this sample query you should be able to change and get your desired output.
db.collection.aggregate([
{$unwind : {path : "$units"}},
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$project : {"units" : {values : {quarter : 1, "value" : 1}, unit_id : 1}}},
{$group : { _id : "$units.unit_id", quarter_values : { $push :{ quarter : "$units.values.quarter", value : "$units.values.value"}}}},
{$sort : {_id : 1 }}
]);
Sample output:-
{
"_id" : 10951,
"quarter_values" : [
{
"quarter" : [
1473321600,
1473322500,
1473323400,
1473324300
],
"value" : [
395,
402,
406,
410
]
}
]
}

Mongodb aggregate match array item with child array item

I would like to find documents that contains specific values in a child array.
This is an example document:
{
"_id" : ObjectId("52e9658e2a13df5be22cf7dc"),
"desc" : "Something somethingson",
"imageurl" : "http://",
"tags" : [
{
"y" : 29.3,
"brand" : "52d2cecd0bd1bd844d000018",
"brandname" : "Zara",
"type" : "Bow Tie",
"x" : 20,
"color" : "52d50c19f8f8ca8448000001",
"number" : 0,
"season" : 0,
"cloth" : "52d50d57f8f8ca8448000006"
},
{
"y" : 29.3,
"brand" : "52d2cecd0bd1bd844d000018",
"brandname" : "Zara",
"type" : "Bow Tie",
"x" : 20,
"color" : "52d50c19f8f8ca8448000001",
"number" : 0,
"season" : 0,
"cloth" : "52d50d57f8f8ca8448000006"
}
],
"user_id" : "52e953942a13df5be22cf7af",
"username" : "Thompson",
"created" : 1386710259971,
"occasion" : "ID",
"sex" : 0
}
The query I would like to do should look something like this:
db.posts.aggregate([
{$match: {tags.color:"52d50c19f8f8ca8448000001", tags.brand:"52d2cecd0bd1bd844d000018", occasion: "ID"}},
{$sort:{"created":-1}},
{$skip:0},
{$limit:10}
])
my problem is that I dont know how to match anything inside an array in the document like "tags". How can I do this?
You could try to do it without aggregation framework:
db.posts.find(
{
occasion: "ID",
tags: { $elemMatch: { color:"52d50c19f8f8ca8448000001", brand:"52d2cecd0bd1bd844d000018" } }
}
).sort({created: -1}).limit(10)
And if you want to use aggregation:
db.posts.aggregate([
{$match:
{
tags: { $elemMatch: { color:"52d50c19f8f8ca8448000001", brand: "52d2cecd0bd1bd844d000018" } },
occasion: "ID"
}
},
{$sort:{"created":-1}},
{$limit:10}
])

How to ensure grouping via two separate criteria

Expanded from How to average the summed up values in mongodb?
Using MongoDB 2.4.8,
I have the following records
{
"category" : "TOYS",
"price" : 12,
"status" : "online",
"_id" : "35043"
}
{
"category" : "TOYS",
"price" : 13,
"status" : "offline",
"_id" : "35044"
}
{
"category" : "TOYS",
"price" : 22,
"status" : "online",
"_id" : "35045"
}
{
"category" : "BOOKS",
"price" : 13,
"status" : "offline",
"_id" : "35046"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "online",
"_id" : "35047"
}
{
"category" : "TOYS",
"price" : 19,
"status" : "unavailable",
"_id" : "35048"
}
{
"category" : "BOOKS",
"price" : 10,
"status" : "unavailable",
"_id" : "35049"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "unavailable",
"_id" : "35050"
}
I want to find the average price of all categories whose status is online OR offline and total price within a category is more than 50.
Toys offline and Toys online are considered two separate categories.
I adapted the answer given.
db.items.aggregate([
{$match:
{
$or: [
{status:"online"},
{status:"offline"}
]
}
},
{$group :
{
_id: "$category",
total_price: {$sum:"$price"},
}
},
{$match:
{
total_price:{$gt:50}
}
},
{$group :
{
_id: "1",
avg_price: {$avg:"$total_price"},
}
},
]);
But I believe this query I adapted grouped categories of the same name together which is not what I am looking for.
If online and offline are the only values for status, you can remove the initial $match step. If it is needed, it would be more appropriate to use the $in operator as these values could be found in the same index (if one existed).
I think the only step you are missing is that you can $group by multiple fields (i.e. category and status):
db.items.aggregate(
// If 'online' and 'offline' are the only possible status values, this may be unnecessary
{ $match: {
'status' : { $in: [ 'online', 'offline' ] }
}},
// Group by category & status
{ $group: {
_id: { category: "$category", status: "$status" },
total_price: { $sum: "$price" },
}},
// Only find groups where total_price is > 50
{ $match: {
total_price: { $gt:50 }
}},
// Find the average price for the group
{ $group : {
_id: null,
avg_price: {$avg:"$total_price"},
}}
)