Suppose I have the following schema:
"_id" : 1,
"n" : [{
"a" : ObjectId("4ef0ca414653b7c866040000"),
"d" : new Date("Thu, 22 Dec 2011 04:53:56 GMT +04:00")
}, {
"a" : ObjectId("4ef0ca414653b9c866040000"),
"d" : new Date("Thu, 22 Dec 2011 04:54:11 GMT +04:00")
}, {
"a" : ObjectId("4ef0ca424653b9c866040000"),
"d" : new Date("Thu, 22 Dec 2011 04:54:30 GMT +04:00"),
}]
and I need to remove all n, where d is less than specific date.
So I thought I will be able to do this in the following way:
db.coll.update({
'_id': 1
},{
$pullAll : {
n.d : {
$lte : new Date(2000, 10, 11)
}
}
})
but the problem is, that it is not working this way.
Any suggestions?
That is not how $pullAll works. You cannot specify a matching condition, you can only specify an array of objects to be deleted (that need to match exactly).
Fortunately, you can use $pull instead (which does accept a matching condition):
db.coll.update({
'_id': 1
},{
$pull : {
n.d : {
$lte : new Date(2000, 10, 11)
}
}
})
Note that $pull also pulls all elements that match, not just one.
This is admittedly a little confusing.
Related
I have a Mongo collection (using Meteor) structured like so:
{
"_id" : "xx",
"name" : "xx",
"handle" : "xx",
"tweets" : [
{
"published" : true,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
{
"published" : true,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
{
"published" : false,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
]
}
I only want to display the published tweets. I am using a template helper to retrieve and filter:
return Tweets.find({
handle:handle,
"tweets.published": true
});
I cannot seem to get the nested filter on 'published' to work. All tweets are displayed, using the above code. I have tried many different permutations of the "tweets.published": true. What is the correct way to filter out the unpublished tweets?
Since the tweets field is an array of objects, not just one object, your method will not work.
First you should use the handle to find the correct document:
return Tweets.find({
handle: handle,
});
This must then be combined with a fields, to select tweets which should be returned.
return Tweets.find({
handle: handle,
}, {
fields: { tweets: { $elemMatch: { published: true } } }
});
the $elemMatch part specifies the tweet must be publised. More information on the mongo page.
EDIT
If the snippet must run on the client (in your case), you have other options. You can use a server publication with my snippet to only give published tweets to the client.
Alternatively, give the transform option to the find request.
return Tweets.find({
handle: handle
}, {
transform: doc => {
doc.tweets = doc.tweets.filter(tweet => tweet.published);
return doc;
}
});
This is how my collection structure looks like:
{
"_id" : ObjectId("57589d2a9108dace306602b8"),
"IDproject" : NumberLong(53),
"email" : "john.doe#gmail.com",
"dc" : ISODate("2016-06-06T22:33:13.000Z")
}
{
"_id" : ObjectId("57589d2a9108dace306602b8"),
"IDproject" : NumberLong(53),
"email" : "david.doe#gmail.com",
"dc" : ISODate("2016-06-07T22:33:13.000Z")
}
{
"_id" : ObjectId("57589d2a9108dace306602b8"),
"IDproject" : NumberLong(53),
"email" : "elizabeth.doe#gmail.com",
"dc" : ISODate("2016-06-078T22:33:13.000Z")
}
As you can see, there are two customers added on June 7th and one on June 6th. I would like to group and sum these results for the last 30 days.
It should looks something like this:
{
"dc" : "2016-06-05"
"total" : 0
}
{
"dc" : "2016-06-06"
"total" : 1
}
{
"dc" : "2016-06-07"
"total" : 2
}
As, you can see, there are no records on June 6th, so it's zero. It should be zero for June 5th, etc.
That would be the case #1, and the case #2 are following results:
{
"dc" : "2016-06-05"
"total" : 0
}
{
"dc" : "2016-06-06"
"total" : 1
}
{
"dc" : "2016-06-07"
"total" : 3
}
I've tried this:
db.getCollection('customer').aggregate([
{$match : { IDproject : 53}},
{ $group: { _id: "$dc", total: { $sum: "$dc" } } }, ]);
But seems complicated. I'm first time working with noSQL database.
Thanks.
Here's how you will get daily counts (the common idiom for row count is {$sum: 1}).
However, you cannot obtain zeros for days that are lacking data – because there is no data that would give the grouping key for these days. You must handle these cases in PHP by generating a list of desided dates and then looking if there's data for that each date.
db.getCollection('customer').aggregate([
{$match : { IDproject : 53}},
{$group: {
_id: {year: {$year: "$dc"}, month: {$month: "$dc"}, day: {$dayOfMonth: "$dc"}}},
total: {$sum: 1}
}},
]);
Note that MongoDB only operates in the UTC timezone; there are no aggregation pipeline operators that can convert timestamps to local timezones reliably. The $year, $month and $dayOfMonth operators give the date in UTC which may not be the same as in the local timezone. Solutions include:
saving timestamps in the local timezone (= lying to MongoDB that they are in UTC),
saving the timezone offset with the timestamp,
saving the local year, month and dayOfMonth with the timestamp.
is it possible to query only the first (or last or any single?) day of the month of a mongo date field.
i use the $date aggregation operators regularly but within a $group clause.
basically i have field that is already aggregated (averaged) for each day of the month. i want to select only one of these days (with the value as a representative of the entire month.)
following is a sample of a record set from jan 1, 2014 to feb 1, 2015 with price as the daily price and 28day_avg as the trailing monthly average for 28 days.
{ "date" : ISODate("2014-01-01T00:00:00Z"), "_id" : ObjectId("533b3697574e2fd08f431cff"), "price": 59.23, "28day_avg": 54.21}
{ "date" : ISODate("2014-01-02T00:00:00Z"), "_id" : ObjectId("533b3697574e2fd08f431cff"), "price": 58.75, "28day_avg": 54.15}
...
{ "date" : ISODate("2015-02-01T00:00:00Z"), "_id" : ObjectId("533b3697574e2fd08f431cff"), "price": 123.50, "28day_avg": 122.25}
method 1.
im currently running an aggregation using $month data (and summing the price) but one issue is im seeking to retrieve the underlying date value ISODate("2015-02-01T00:00:00Z") versus the 0,1,2 value that comes with several of the date aggregations (that loop at the first of the week, month, year). mod(28) on a date?
method 2
i'd like to simply pluck out a single record of the 28day_avg as representative of the period. the 1st of the month would be adequate
the desired output is...
_id: ISODate("2015-02-01T00:00:00Z"), value: 122.25,
_id: ISODate("2015-01-01T00:00:00Z"), value: 120.78,
_id: ISODate("2014-12-01T00:00:00Z"), value: 118.71,
...
_id: ISODate("2014-01-01T00:00:00Z"), value: 53.21,
of course, the value will vary from method 1 to method 2 but that is fine. one is 28 days trailing while the other will account for 28, 30, 31 day months...dont care about that so much.
A non-agg is ok but also doesnt work. aka {"date": { "$mod": [ 28, 0 ]} }
To pick the first of the month for each month (method 2), use the following aggregation:
db.test.aggregate([
{ "$project" : { "_id" : "$date", "day" : { "$dayOfMonth" : "$date" }, "28day_avg" : 1 } },
{ "$match" : { "day" : 1 } }
])
You can't use an index for the match, so this is not efficient. I'd suggest adding another field to each document that holds the $dayOfMonth value, so you can index it and do a simple find:
{
"date" : ISODate("2014-01-01T00:00:00Z"),
"price" : 59.23,
"28day_avg" : 54.21,
"dayOfMonth" : 1
}
db.test.ensureIndex({ "dayOfMonth" : 1 })
db.test.find({ "dayOfMonth" : 1 }, { "_id" : 0, "date" : 1, "28day_avg" : 1 })
This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 8 years ago.
The title is a little bit hard to understand the first time, so here is an example. I use mongodb and have data like this :
id: String
timestamp: String
steps: [{
action: 1,
timestamp: String
}, {
action: 2,
timestamp: String
}, ...
]
I would like to add a new field to each step, let's say bot: false.
I tried an update like :
{ $set: { 'steps': { 'bot': false } } }
However this replaced the whole step, losing action/timestamp.
I also tried :
{ $set: { 'steps.bot': false } }
This didn't work either, because steps is an array.
I also looked at the $each modifier, however it doesn't seem to work with $set.
Any idea ?
Thanks
As per your question I think your documents structure like below
{
"_id" : ObjectId("54996f980e64b1a2fcb4824e"),
"id" : "1",
"timestamp" : "Tue, 23 Dec 2014 13:37:33 GMT",
"steps" : [
{
"action" : 1,
"timestamp" : "Tue, 23 Dec 2014 13:37:40 GMT"
},
{
"action" : 2,
"timestamp" : "Tue, 23 Dec 2014 13:37:40GMT"
}
]
}
And in above document you want to add new field like steps.bot:false so you should use following javascript to update nested documents
db.collectionName.find({
"steps":{"$exists":true}}).forEach(function(data){
for(var i=0;i<data.steps.length;i++) {
db.demo.update(
{
"_id": data._id,
"steps.action": data.steps[i].action
},
{
"$set": {
"steps.$.bob":
false
}
},true,true
);
}
})
I have the following schema:
{
"_id" : 27,
"n" : [{
"d" : new Date("Sat, 24 Dec 2011 17:03:00 GMT +04:00"),
"e" : ObjectId("4f0aef5346b3b88013000001"),
"f" : [26, 10, 16],
"k" : new Date("Mon, 09 Jan 2011 17:44:51 GMT +04:00"),
"t" : "f",
"u" : 10
}, {
"a" : ObjectId("4f0c208846b3b8140f000000"),
"d" : new Date("Tue, 10 Jan 2012 15:27:21 GMT +04:00"),
"p" : [ObjectId("4f0c209046b3b8340f000000"), ObjectId("4f0c209346b3b8340f000001"), ObjectId("4f0c209646b3b8340f000002"), ObjectId("4f0c209946b3b8340f000003")],
"t" : "p",
"u" : 10
}]
}
and this query removes all of the sub elements if at least one satisfy it
db.newsFeed.update({ "_id" : 27, },{
$pull : {
'n' : {
'd' : {
$lte : new Date(2012, 1, 1)
}
}
}
});
so the document becomes like this,
{
"_id" : 27,
"n" : []
}
what am I doing wrong, and more important what should I do to pull just some of the elements?
I think your query is fine, the problem is the date you're using.
Instead of:
new Date(2012, 1, 1)
Try:
ISODate("2012-01-01")
Try typing them in the shell by themselves and you'll get back these results:
> new Date(2012, 1, 1)
ISODate("2012-02-01T07:00:00Z")
> ISODate("2012-01-01")
ISODate("2012-01-01T00:00:00Z")
At least, that's what I see in my shell. Using the "new" returns a date that matches on both elements of your array, so they're both removed. Using ISODate directly creates the date object you're looking for and only matches on the first result.
The Date constructor takes a zero based month, so you want this if you want to use new:
new Date(2012, 0, 1)