$pull operator in MongoDB - mongodb

{
"_id" : ObjectId("5badfada90fd543fd8aa7f96"),
"__v" : 0,
"deleted" : false,
"groups" : [
{
"group" : "grp",
"_id" : ObjectId("5bae09a601123357e58b66a2"),
"activities" : [
ObjectId("5bae09a601123357e58b66a3"),
ObjectId("5bae10de01123357e58b66a6")
]
},
{
"group" : "123",
"_id" : ObjectId("5bae0f1001123357e58b66a4"),
"activities" : [
ObjectId("5bae0f1001123357e58b66a5")
]
}
],
"nextActivityId" : 22,
"name" : "test",
"year" : "1",
"status" : "2",
"vision" : ObjectId("5bab2f4872acf42a81c124d0")
}
The Above Schema is a "Plan" Schema
I have to write a query for removing an Activity inside "activities" array. What will be the optimum solution for this? And how will I use $pull to achieve this
This was my solution, but it will delete the complete groups array
Plan.update({ _id: PLAN ID }, { $pull: { groups: { activities: ACTIVITY ID } } })
PLAN ID BEING: "_id" : ObjectId("5badfada90fd543fd8aa7f96"),
ACTIVITY ID FOR EXAMPLE BEING: ObjectId("5bae09a601123357e58b66a3")
Thank you!

You need to use the positional $ update operator.
db.Plan.update(
{ "_id" : PLAN ID },
{ "$pull": { "groups.$.activities": ACTIVITY ID } }
)

You can try this,
db.Plan.update({ _id: ObjectId("5badfada90fd543fd8aa7f96"),"groups.activities":{$in:[ ObjectId("5bae09a601123357e58b66a3") ]}},
{ $pull: { "groups.$.activities": ObjectId("5bae09a601123357e58b66a3") } });
The positional operator did not find the match needed from the query warning when use $ only update object.

Related

Mongodb Query to get the nth document

I need to create a query in mongodb that needs to return the SECOND TO THE LAST document. I am planning to use $group for this query but i dont know what aggregation function to use. I only know $first and $last.
I have an example collection below and also include the expected output. Thank you!
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84be"),
"userid" : ObjectId("5ffe702d59a9205db81fcb69"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),
"userid" : ObjectId("5ffe644f9493e05db9245192"),
"action" : "EDITPROFILE"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bc"),
"userid" : ObjectId("5ffe64949493e05db9245197"),
"action" : "DELETETRANSACTION"
} ]
"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bb"),
"userid" : ObjectId("5ffe64b69493e05db924519b"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84ba"),
"userid" : ObjectId("5ffe65419493e05db92451d4"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b9"),
"userid" : ObjectId("5ffe65689493e05db92451d9"),
"action" : "CHANGEACCESS"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),
"userid" : ObjectId("5ffe65819493e05db92451dd"),
"action" : "DELETETRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b7"),
"userid" : ObjectId("5ffe65df9493e05db92451f3"),
"action" : "EDITPROFILE",
]
OUTPUT:
{"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),"userid" : ObjectId("5ffe644f9493e05db9245192"),"action" : "EDITPROFILE"},
{"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),"userid" : ObjectId("5ffe65819493e05db92451dd"),"action" : "DELETETRANSACTION"}
You can't have two _id keys in one single object.
I've made the parent object's id to _parentId you can give it's a name anything you want except _id
Aggregation:
db.collection.aggregate([
{
$unwind: "$auditlogs"
},
{
"$project": {
"_parentId": "$_id",
"_id": "$auditlogs._id",
"action": "$auditlogs.action",
"userid": "$auditlogs.userid",
}
}
])
Playground
You can slice the array by -2 to get the last two item, then by 1 to get first one. Therefore, the array will be left the second to the last. Finally, unwind auditlogs so it can be changed from array to object which is structure that you want.
db.collection.aggregate([
{
$project: { auditlogs : { $slice: [ "$auditlogs", -2 ] } }
},
{
$project: { auditlogs : { $slice: [ "$auditlogs", 1 ] } }
},
{
$unwind: "$auditlogs"
}
])

Mongodb $nin not working with nested array

I can't make $nin work with nested arrays, can you guys spot any issue with this query?
I'm basically trying to update the status of every document under items to "closed" in case their hash field is not in a list of hashes provided.
db.getCollection('projects').update({
name: 'test',
'issues.hash': { $nin: [
'8ff28fcc9cbf10c9b690bb331e5609efbd3c526be4f536ebca02cc51bd63eac7',
'd5368ad5658ec11103796255d127d26da7f3324cdedbd124bdd5db50812d588e',
'37298229097785ebc9d419cc1a3f13e0d090a15ceb9a8e6bea3505366902556d',
'fad290f2ddd0e097e4098c3b2c3d65611406cf208a3f86924d45c7736393b44b'
]}
},
{
$set: { "issues.$.status": "closed" }
}
)
This is the data:
{
"_id" : ObjectId("611bb4d2ee06769a5f6d906d"),
"name" : "test",
"issues" : [
{
"_id" : ObjectId("611bb4d20b2fb200167aa588"),
"status" : "open",
"hash" : "8ff28fcc9cbf10c9b690bb331e5609efbd3c526be4f536ebca02cc51bd63eac7"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa589"),
"status" : "open",
"hash" : "3b83e469049e46b16d3471a188d3f5e3ddbf6b296995a71765bbf17b7289e6ea"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58a"),
"status" : "open",
"hash" : "bef5f50628b669b9930b89cdc040361b9c8cc2b4aab3c2059c171786d38d507e"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58b"),
"status" : "open",
"hash" : "1b4a91eb5de97d6ad7493b6e1ffa48a2a648084b4af7b37916c723533a07c37c"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58c"),
"status" : "open",
"hash" : "bb64ba7b2612856dcd95c3ac2fad3f7368e5d463168545b12f4c869af56b55b7"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58d"),
"status" : "open",
"hash" : "1d5fc04739b10414dea8d327998df4f200f47ce57da243bd578d4ae102f2d670"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58e"),
"status" : "open",
"hash" : "d5368ad5658ec11103796255d127d26da7f3324cdedbd124bdd5db50812d588e"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa58f"),
"status" : "open",
"hash" : "37298229097785ebc9d419cc1a3f13e0d090a15ceb9a8e6bea3505366902556d"
},
{
"_id" : ObjectId("611bb4d20b2fb200167aa590"),
"status" : "open",
"hash" : "fad290f2ddd0e097e4098c3b2c3d65611406cf208a3f86924d45c7736393b44b"
}
]
}
And the is my result:
Updated 0 record(s) in 12ms
Thank you!
You have to use arrayFilters in this way:
db.collection.update({
"name": "test"
},
{
"$set": {
"issues.$[element].status": "closed"
}
},
{
"arrayFilters": [
{
"element.hash": {
"$nin": [
"8ff28fcc9cbf10c9b690bb331e5609efbd3c526be4f536ebca02cc51bd63eac7",
"d5368ad5658ec11103796255d127d26da7f3324cdedbd124bdd5db50812d588e",
"37298229097785ebc9d419cc1a3f13e0d090a15ceb9a8e6bea3505366902556d",
"fad290f2ddd0e097e4098c3b2c3d65611406cf208a3f86924d45c7736393b44b"
]
}
}
]
})
Example here.
Note that update query has the format: update(query, update, options) (Check the docs).
So with your find query mongo doesn't find anything. Check this example.
This is why you are telling mongo: "Give me a DOCUMENT where name is test and issues array NOT contains a field called hash with these values".
So, as mongo search by the whole document, there is no any document where hash value is not on the $nin array.
As another example to exaplain better: Check this example where hash value is 1. In this case, find query works because it matches two conditions:
There is a field name with value test
There is not any field hash into issues with values into $nin array.
You can use arrayFilters, like this:
db.collection.update({
"name": "test"
},
{
"$set": {
"issues.$[elem].status": "closed"
}
},
{
"multi": true,
"arrayFilters": [
{
"elem.hash": {
"$nin": [
"8ff28fcc9cbf10c9b690bb331e5609efbd3c526be4f536ebca02cc51bd63eac7",
"d5368ad5658ec11103796255d127d26da7f3324cdedbd124bdd5db50812d588e",
"37298229097785ebc9d419cc1a3f13e0d090a15ceb9a8e6bea3505366902556d",
"fad290f2ddd0e097e4098c3b2c3d65611406cf208a3f86924d45c7736393b44b"
]
}
}
]
})
Here is the working example: https://mongoplayground.net/p/8wZkmlBgKiq

Project only some fields of array items in sub document

How can I project only particular fields of items in array in sub document?
Consider the following (simplified) example:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"name" : "Altima",
"body" : {
"type" : 2,
"maxprice" : 31800.00,
"minprice" : 21500.00
}
},
{
"name" : "Maxima",
"body" : {
"type" : 2,
"maxprice" : 39200.00,
"minprice" : 28800.00
}
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"name" : "Accord",
"body" : {
"type" : 2,
"maxprice" : 34100.00,
"minprice" : 20400.00
}
},
{
"name" : "Civic",
"body" : {
"type" : 3,
"maxprice" : 27900.00,
"minprice" : 19800.00
}
}
]
}
After aggregation, I'd like to get the following output:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"type" : 2,
"minprice" : 21500.00
},
{
"type" : 2,
"minprice" : 28800.00
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"type" : 2,
"minprice" : 20400.00
},
{
"type" : 3,
"minprice" : 19800.00
}
]
}
So it basically gets all documents, all fields of documents, all items in models array, BUT only some fields of the array items in models. Please help.
You need to $project the "models" field using the $map operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"models": {
"$map": {
"input": "$models",
"as": "m",
"in": {
"type": "$$m.body.type",
"minprice": "$$m.body.minprice"
}
}
}
}}
])
$unwind is your friend
First you can basically filter the (non nested) fields you want.
var projection = {$project:{name:'$name', models:'$models'}};
db.dum.aggregate(projection)
Foreach of your models, you issue a document
var unwindModels = {$unwind:{'$models'}}
db.dum.aggregate(projection, unwindModels)
The idea is that every document issued from your models field will be regrouped later on via the _id field.
Foreach document, you only keep the (sub)fields you want
var keepSubFields = {$project:{name:'$name', type:'$models.body.type', minprice:'$models.body.minprice'}}
db.dum.aggregate(projection, unwindModels, keepSubFields)
Then you reaggregate your models as an array (thanks to the _id of each record which tracks the original record)
var aggregateModels = {$group:{_id:'$_id', name:{$last:'$name'}, models:{$push:{type:'$type', minprice:'$minprice'}}}}
db.dum.aggregate(projection, unwindModels, keepSubFields, aggregateModels)
note1: Here we can use $last because our primary key is not _id but <_id, name>. ($first would be good too)
note2: we refer type by $type, because when you iterate the collection on the aggregateModels stage, your record is of the form
<_id, name, type, minprice>

MongoDB filtering out subdocuments with lookup aggregation

Our project database has a capped collection called values which gets updated every few minutes with new data from sensors. These sensors all belong to a single sensor node, and I would like to query the last data from these nodes in a single aggregation. The problem I am having is filtering out just the last of ALL the types of sensors while still having only one (efficient) query. I looked around and found the $group argument, but I can't seem to figure out how to use it correctly in this case.
The database is structured as follows:
nodes:
{
"_id": 681
"sensors": [
{
"type": "foo"
},
{
"type": "bar"
}
]
}
values:
{
"_id" : ObjectId("570cc8b6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 10
}
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 681,
"value" : 20
}
{
"_id" : ObjectId("167bc997bb66750d5740665e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 200,
"value" : 20
}
{
"_id" : ObjectId("110cc9c6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-09T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 12
}
so let's imagine I want the data from node 681, I would want a structure like this:
nodes:
{
"_id": 681
"sensors": [
{
"_id" : ObjectId("570cc8b6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 10
},
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 681,
"value" : 20
}
]
}
Notice how one value of foo is not queried, because I want to only get the latest value possible if there are more than one value (which is always going to be the case). The ordering of the collection is already according to the timestamp because the collection is capped.
I have this query, but it just gets all the values from the database (which is waaay too much to do in a lifetime, let alone one request of the web app), so I was wondering how I would filter it before it gets aggregated.
query:
db.nodes.aggregate(
[
{
$unwind: "$sensors"
},
{
$match:{
nodeid: 681
}
},
{
$lookup:{
from: "values", localField: "sensors.type", foreignField: "type", as: "sensors"
}
}
}
]
)
Try this
// Pipeline
[
// Stage 1 - sort the data collection if not already done (optional)
{
$sort: {
"timestamp":1
}
},
// Stage 2 - group by type & nodeid then get first item found in each group
{
$group: {
"_id":{type:"$type",nodeid:"$nodeid"},
"sensors": {"$first":"$$CURRENT"} //consider using $last if your collection is on reverse
}
},
// Stage 3 - project the fields in desired
{
$project: {
"_id":"$sensors._id",
"timestamp":"$sensors.timestamp",
"type":"$sensors.type",
"nodeid":"$sensors.nodeid",
"value":"$sensors.value"
}
},
// Stage 4 - group and push it to array sensors
{
$group: {
"_id":{nodeid:"$nodeid"},
"sensors": {"$addToSet":"$$CURRENT"}
}
}
]
as far as I got document structure, there is no need to use $lookup as all data is in readings(values) collection.
Please see proposed solution:
db.readings.aggregate([{
$match : {
nodeid : 681
}
},
{
$group : {
_id : {
type : "$type",
nodeid : "$nodeid"
},
readings : {
$push : {
timestamp : "$timestamp",
value : "$value",
id : "$_id"
}
}
}
}, {
$project : {
_id : "$_id",
readings : {
$slice : ["$readings", -1]
}
}
}, {
$unwind : "$readings"
}, {
$project : {
_id : "$readings.id",
type : "$_id.type",
nodeid : "$_id.nodeid",
timestamp : "$readings.timestamp",
value : "$readings.value",
}
}, {
$group : {
_id : "$nodeid",
sensors : {
$push : {
_id : "$_id",
timestamp : "$timestamp",
value : "$value",
type:"$type"
}
}
}
}
])
and output:
{
"_id" : 681,
"sensors" : [
{
"_id" : ObjectId("110cc9c6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-09T12:06:46.344Z"),
"value" : 12,
"type" : "foo"
},
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"value" : 20,
"type" : "bar"
}
]
}
Any comments welcome!

How to do this query in mongo: get newest messages for a list of users

I have a collection of messages with fields user_id, created_time, and content. Given a list of user_id, I would like to get back a list of messages, where for each user_id it contains a message newest with respect to that user. I thought about using a distinct command together with sort in mongo but that doesn't seem to be supported. Is there a way to do this in mongo using a single query?
MongoDB has the Aggregation framework which you can use for tasks that require some manipulation of your data in your collection
Consider the following dataset
> db.messages.find().pretty()
{
"_id" : ObjectId("52ecb77486d35a12f3552aa1"),
"user_id" : "fred",
"create_date" : ISODate("1392-09-21T00:00:00Z")
}
{
"_id" : ObjectId("52ecb79286d35a12f3552aa2"),
"user_id" : "fred",
"create_date" : ISODate("1392-06-01T00:00:00Z")
}
{
"_id" : ObjectId("52ecb7a386d35a12f3552aa3"),
"user_id" : "marty",
"create_date" : ISODate("1393-04-06T00:00:00Z")
}
{
"_id" : ObjectId("52ecb7af86d35a12f3552aa4"),
"user_id" : "marty",
"create_date" : ISODate("1386-02-12T00:00:00Z")
}
So in passing this to aggregate we want to group on user_id and get the most recent or maximum create_date
> db.messages.aggregate([
{ $group: { _id: { user_id: "$user_id" }, create_date: { $max: "$create_date" }} }
])
{
"result" : [
{
"_id" : {
"user_id" : "marty"
},
"create_date" : ISODate("1393-04-06T00:00:00Z")
},
{
"_id" : {
"user_id" : "fred"
},
"create_date" : ISODate("1392-09-21T00:00:00Z")
}
],
"ok" : 1
}
That's not bad but you can clean it up with $project
> db.messages.aggregate([
{ $group: { _id: { user_id: "$user_id" }, create_date: { $max: "$create_date" }} },
{ $project: { _id: 0, user_id: "$_id.user_id", create_date: 1} }
])
{
"result" : [
{
"create_date" : ISODate("1393-04-06T00:00:00Z"),
"user_id" : "marty"
},
{
"create_date" : ISODate("1392-09-21T00:00:00Z"),
"user_id" : "fred"
}
],
"ok" : 1
}
So that actually looks like a clean record to use. In latest drivers the returned value from aggregate should be a cursor you can iterate over. So the results are just the same to work with as using find.
Additional documentation on operators to use can be found here.