54

I'm using mongodb and I store datetime in my database in this way

for a date "17-11-2011 18:00" I store:

date = datetime.datetime(2011, 11, 17, 18, 0)
db.mydatabase.mycollection.insert({"date" : date})

I would like to do a request like that

month = 11
db.mydatabase.mycollection.find({"date.month" : month})

or

day = 17
db.mydatabase.mycollection.find({"date.day" : day})

anyone knows how to do this query?

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
kschaeffler
  • 4,083
  • 7
  • 33
  • 41
  • My approach got some performance problems, use only if you have no other choice ( refer my post and comments). Instead DrColossos answer is a best way to do that... please accept his as answer – RameshVel Nov 16 '11 at 07:39
  • This clearly is not a duplicate. If the way the syntax was described in this question was actually to be implemented in MongoDB some day, we would never find it in the answers to the other question, as they ask for two entirely different things (comparing date properties vs. finding within timespan). The solutions happen to be the same, but they do not have to, and could theoretically (and maybe *will* some day) vary. – phil294 Aug 14 '19 at 21:59

7 Answers7

62

Dates are stored in their timestamp format. If you want everything that belongs to a specific month, query for the start and the end of the month.

var start = new Date(2010, 11, 1);
var end = new Date(2010, 11, 30);

db.posts.find({created_on: {$gte: start, $lt: end}});
//taken from http://cookbook.mongodb.org/patterns/date_range/
DrColossos
  • 12,656
  • 3
  • 46
  • 67
  • 1
    Is it the only way? Is there any possibility to only retrieve the month of my date object in my database? – kschaeffler Nov 15 '11 at 13:15
  • 2
    @DrColossos, there is a another way to do this using $where – RameshVel Nov 15 '11 at 13:17
  • 1
    @DrColossos, seems your approach is better than mine.. $where expression has got some performance problems, pls check my answer for more info... – RameshVel Nov 16 '11 at 07:28
  • you need to plus one day to the `end` to make sure all 31 days are included. Btw, `month=11` is December, not November. The Op seems to refer to Nov. – zs2020 Nov 11 '13 at 22:52
  • it doesn't work when filtering by a specific day. ie: birthdates. – KikoV Feb 12 '14 at 21:51
  • 9
    Wow, does this suck or what...if you want to query by specific day, you have to be careful about the days and the end of the month....month can have 28,29, 30 or 31 days...why couldn't they make something like: date: { $year: 2014, $month:4 }? This isn't a query language, but programming. – dakt Apr 16 '14 at 11:38
  • @dakt you are right there is a problem with this answer. it does not show results for the last day of the month and when month has more than 30 days. check my answer! – Ujjwal Ojha May 04 '16 at 09:23
  • @dakt In mongodb we have now Date Aggregation Operators, by which we can search like date: { $year: 2014, $month:4 }, check my answer below – Puneet Singh Feb 15 '17 at 07:27
  • @DrColossos in your specific example, shouldnt this be `end = new Date(2010, 12, 01)`? – phil294 Aug 14 '19 at 22:15
34

You cannot straightly query mongodb collections by date components like day or month. But its possible by using the special $where javascript expression

db.mydatabase.mycollection.find({$where : function() { return this.date.getMonth() == 11} })

or simply

db.mydatabase.mycollection.find({$where : 'return this.date.getMonth() == 11'})

(But i prefer the first one)

Check out the below shell commands to get the parts of date

>date = ISODate("2011-09-25T10:12:34Z")
> date.getYear()
111
> date.getMonth()
8
> date.getdate()
25

EDIT:

Use $where only if you have no other choice. It comes with the performance problems. Please check out the below comments by @kamaradclimber and @dcrosta. I will let this post open so the other folks get the facts about it.

and check out the link $where Clauses and Functions in Queries for more info

RameshVel
  • 64,778
  • 30
  • 169
  • 213
  • 3
    this is a convenient idea, however the use of $where is really discouraged if you want to have good performance : it takes a global lock. See @DrColossos answer for a better way to do this. – kamaradclimber Nov 15 '11 at 14:03
  • @kamaradclimber, yeah thats true, i am aware that this is slower than the native operators, since no indexes used. But i am not sure about the global lock.. are you sure..? any links that confirms the claim very helpful.... – RameshVel Nov 15 '11 at 14:12
  • I've not found any trace of lock in the docs. Here is a link for performance loss : http://www.mongodb.org/display/DOCS/Server-side+Code+Execution#Server-sideCodeExecution-%7B%7B%24where%7D%7DClausesandFunctionsinQueries. I think I got the lock idea from the db.eval paragraph so let's just forget it :-) – kamaradclimber Nov 15 '11 at 16:39
  • The answer from DrColossos is still much much faster – kamaradclimber Nov 15 '11 at 16:41
  • 3
    It's not a lock, per se, but each `mongod` instance only has one javascript context in which to execute any javascript (`$where`, map-reduce jobs, etc). Queries will yield periodically, so one query won't fully block another, but multiple queries or other javascript work will be slower. This is why `$where` is not recommended. – dcrosta Nov 15 '11 at 18:45
  • @dcrosta, thanks for the clarification.. i ll let this answer open, so other folks will know the facts. and i ll ask OP to accept the DrColossos answer... – RameshVel Nov 16 '11 at 07:27
  • @RameshVel , I have try above answer but it gives me the MongoError: $where not allowed in this atlas tier, how can I solve this error – Ravindra S. Patil Jun 28 '21 at 06:46
13

Use the $expr operator which allows the use of aggregation expressions within the query language. This will give you the power to use the Date Aggregation Operators in your query as follows:

month = 11
db.mydatabase.mycollection.find({ 
    "$expr": { 
        "$eq": [ { "$month": "$date" }, month ] 
    } 
})

or

day = 17
db.mydatabase.mycollection.find({ 
    "$expr": { 
        "$eq": [ { "$dayOfMonth": "$date" }, day ] 
    } 
})

You could also run an aggregate operation with the aggregate() function that takes in a $redact pipeline:

month = 11
db.mydatabase.mycollection.aggregate([
    {
        "$redact": {
            "$cond": [
                { "$eq": [ { "$month": "$date" }, month ] },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

For the other request

day = 17
db.mydatabase.mycollection.aggregate([
    {
        "$redact": {
            "$cond": [
                { "$eq": [ { "$dayOfMonth": "$date" }, day ] },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

Using OR

month = 11
day = 17
db.mydatabase.mycollection.aggregate([
    {
        "$redact": {
            "$cond": [
                { 
                    "$or": [ 
                        { "$eq": [ { "$month": "$date" }, month ] },
                        { "$eq": [ { "$dayOfMonth": "$date" }, day ] }
                    ] 
                },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

Using AND

var month = 11,
    day = 17;
db.collection.aggregate([
    {
        "$redact": {
            "$cond": [
                { 
                    "$and": [ 
                        { "$eq": [ { "$month": "$createdAt" }, month ] },
                        { "$eq": [ { "$dayOfMonth": "$createdAt" }, day ] }
                    ] 
                },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

The $redact operator incorporates the functionality of $project and $match pipeline and will return all documents match the condition using $$KEEP and discard from the pipeline those that don't match using the $$PRUNE variable.

chridam
  • 100,957
  • 23
  • 236
  • 235
13

how about storing the month in its own property since you need to query for it? less elegant than $where, but likely to perform better since it can be indexed.

RubyTuesdayDONO
  • 2,350
  • 2
  • 25
  • 37
12

If you want to search for documents that belong to a specific month, make sure to query like this:

// Anything greater than this month and less than the next month
db.posts.find({created_on: {$gte: new Date(2015, 6, 1), $lt: new Date(2015, 7, 1)}});

Avoid quering like below as much as possible.

// This may not find document with date as the last date of the month
db.posts.find({created_on: {$gte: new Date(2015, 6, 1), $lt: new Date(2015, 6, 30)}});

// don't do this too
db.posts.find({created_on: {$gte: new Date(2015, 6, 1), $lte: new Date(2015, 6, 30)}});
Ujjwal Ojha
  • 1,340
  • 10
  • 17
  • 1
    Great principle in general. Date searches should almost always have an inclusive start date and an exclusive end date. – Rob H May 13 '16 at 12:58
7

You can find record by month, day, year etc of dates by Date Aggregation Operators, like $dayOfYear, $dayOfWeek, $month, $year etc.

As an example if you want all the orders which are created in April 2016 you can use below query.

db.getCollection('orders').aggregate(
   [
     {
       $project:
         {
           doc: "$$ROOT",
           year: { $year: "$created" },
           month: { $month: "$created" },
           day: { $dayOfMonth: "$created" }
         }
     },
     { $match : { "month" : 4, "year": 2016 } }
   ]
)

Here created is a date type field in documents, and $$ROOT we used to pass all other field to project in next stage, and give us all the detail of documents.

You can optimize above query as per your need, it is just to give an example. To know more about Date Aggregation Operators, visit the link.

Puneet Singh
  • 3,477
  • 1
  • 26
  • 39
-1

You can use MongoDB_DataObject wrapper to perform such query like below:

$model = new MongoDB_DataObject('orders');

$model->whereAdd('MONTH(created) = 4 AND YEAR(created) = 2016');

$model->find();

while ($model->fetch()) {
    var_dump($model);
}

OR, similarly, using direct query string:

$model = new MongoDB_DataObject();

$model->query('SELECT * FROM orders WHERE MONTH(created) = 4 AND YEAR(created) = 2016');

while ($model->fetch()) {
    var_dump($model);
}
CEDA
  • 55
  • 4