Writing an activerecord statement to query a datetime column - ruby-on-rails-4

I have a profiles table with a column name videoconfavailability which is a datetime type. I am trying to make an Ajax button_tag to search all of the videoconfavailability 1 hour from Time.now and 1 hour before Time.now.
so far I have this line here, is there a NOT clause to filter out other conditions?
Profile.where("videoconfavailability <= ? AND videoconfavailability >= ?", Time.now + 1.hour , Time.now - 1.hour )
The end goal here is to have ALL of the time available 1 hour before current time and 1 hour after current time.
Is this going to work?

It looks good to me - maybe you can explain the issue you are having with it!
In terms of using it you might consider creating a scope tied to your Profile model. If you need to use it with different time ranges perhaps it could accept arguments. If not then just hardcode it to make it easier to use again and again with repeating yourself.
scope :available_within_time, -> (start, end) { where("videoconfavailability <= ? AND videoconfavailability >= ?", start, end )
As it's written in your question perhaps consider making use of the Rails time helpers to make things even more concise.
1.hour.ago
1.hour.from_now
If you want to filter out negative matches to check some other condition you can chain a .where.not(QUERY) onto your existing query.

Related

Annotate one part of a range to a new field

So we've been using a DateTimeRangeField in a booking model to denote start and end. The rationale for this might not have been great —separate start and end fields might have been better in hindsight— but we're over a year into this now and there's no going back.
It's generally been fine except I need to annotate just the end datetime onto a related model's query. And I can't work out the syntax.
Here's a little toy example where I want a list of Employees with end of their last booking annotated on.
class Booking(models.Model):
timeframe = DateTimeRangeField()
employee = models.ForeignKey('Employee')
sq = Booking.objects.filter(employee=OuterRef('pk')).values('timeframe')
Employee.objects.annotate(last_on_site=Subquery(sq, output_field=DateTimeField()))
That doesn't work because the annotated value is the range, not the single value. I've tried a heap of modifiers (egs __1 .1 but nothing works).
Is there a way to get just the one value? I guess you could simulate this without the complication of the subquery just doing a simple values lookup. Booking.objects.values('timeframe__start') (or whatever). That's essentially what I'm trying to do here.
Thanks to some help in IRC, it turns out you can use the RangeStartsWith and RangeEndsWith model transform classes directly. These are the things that are normally just registered to provide you with a __startswith filter access to range values, but directly they can pull back the value.
In my example, that means just modifying the annotation slightly:
from django.contrib.postgres.fields.ranges import RangeEndsWith
sq = Booking.objects.filter(employee=OuterRef('pk')).values('timeframe')
Employee.objects.annotate(last_on_site=RangeEndsWith(Subquery(sq[:1])))

How can I use the F() object to do this with the Django ORM?

I encountered a model like this:
class Task(models.Model):
timespan = models.IntegerField(null=True, blank=True)
class Todo(models.Model):
limitdate = models.DateTimeField(null=True, blank=True)
task = models.ForeignKey(Task)
I need to extract all Todos with a limitdate that is lower or equal to today's date + a timespan defined in the related Task model.
Something like (dummy example):
today = datetime.datetime.now()
Todo.objects.filter(limitdate__lte=today + F('task__timespan'))
Now, I can do that with a loop but I'm looking for a way to do it with F(), and I can't find one.
I'm starting to wonder if I can do that with F(). Maybe I should use extra ?
Please note that I don't have the luxury of changing the model code.
The main issue is that DB does not support date + integer and its hard to write ORM query to date + integer::interval, for PostgreSQL for example, where integer is the value of the task_timespan column, in days count.
However, as
limitdate <= today + task__timespan equals to
limitdate - today <= task__timespan
We could transform the query to
Todo.objects.filter(task__timespan__gte=F('limitdate') - today).distinct()
thus the SQL becomes something like integer >= date - date, that should work in PostgreSQL because date - date outputs interval which could be compared w/ integer days count.
In other DBs such as SqLite, it's complicated because dates need to be cast w/ julianday() at first...and I think you need to play w/ extra() or even raw() to get the correct SQL.
Also, as Chris Pratt suggests, if you could use timestamp in all relative fields, the query task might become easier because of less limited add and subtract operations.
P.S. I don't have env to verify it now, you could try it first.
The problem is that there's no TIMESPAN type on a database. So, F cannot return something that you can actually work with in this context. I'm not sure what type of field you actually used in your database, but the only way I can think of to do this is to the store the timespan as an integer consisting of seconds, add that to "today" as a timestamp, and then convert it back into a datetime which you can use to compare with limitdate. However, I'm unsure if Django will accept such complex logic with F.

Django object filter - price behaving strangely, eg 170 treated as 17 etc

I have a simple object filter that uses price__lt and price__gt. This works on a property on my product model called price, which is a CharField [string] (decimal saw the same errors, and caused trouble with aggregation so reverted to string).
It seems that when passing in these values to the filter, they are treated in a strange way, eg 10 is treated as 100. for example:
/products/price/10-200/ returns products priced 100-200. the filters are being passed in as filterargs: FILTER ARGS: {'price__lt': '200', 'price__gt': '10'} . This also breaks in the sense that price/0-170 will NOT return products priced at 18.50; it is treating the 170 as 'less than 18' for some reason.
any idea what would cause this, and how to fix it? Thanks!
The problem, as Jeff suggests, is that price is a CharField and thus is being compared using character-by-character string comparison logic, i.e. any string of any length starting with 1 will be less than any string of any length starting with 2, etc.
I'm curious what problems you were having with having price be an IntegerField, as that would seem to be the straightforward solution, but if you need to keep price as a CharField, here's a (hacky) way to make the query work:
lt = 200
gt = 10
qs = Product.objects.extra(select={'int_price': 'cast(price as int)'},
where=['int_price < %s', 'int_price > %s'],
params=[lt, gt])
qs.all() # the result
This uses the extra method of Django's QuerySet class, which you can read about in the docs here. In a nutshell, it computes an integer version of the string price using SQL's cast expression and then filters with integers based on that.

How do I tell if a coldfusion query is caching?

I have set caching on a query that is getting run a lot of times.. the query itself is not all that slow, but it get's run many times for each request, so figured caching may help. I have enabled caching, but doesn't really seem to be making a difference.. how do I tell if my query is being cached or not?
I'm setting caching with : q.setCachedWithin("#createTimespan(0, 1, 0, 0)#");
Here is my full query preperation:
q = New Query();
q.setSQL("SELECT * FROM guest_booking WHERE room_id = :roomID and check_in <= :iDate and check_out > :iDate and status != 0");
q.setName("checkAvailability");
q.setCachedWithin("#createTimespan(0, 1, 0, 0)#");
q.addParam(name="iDate", value="#createODBCDate(arguments.date)#", cfsqltype="cf_sql_date");
q.addParam(name="roomID", value="#createODBCDate(arguments.room_id)#", cfsqltype="cf_sql_integer");
qResult = q.execute().getresult();
Debug output is showing:
checkAvailability (Datasource=accom_crm, Time=16ms, Records=1) in C:\ColdFusion9\CustomTags\com\adobe\coldfusion\base.cfc # 16:15:56.056
SELECT * FROM guest_booking WHERE room_id =
?
and check_in <=
?
and check_out >
?
and status != 0
Query Parameter Value(s) -
Parameter #1(cf_sql_integer) = 56
Parameter #2(cf_sql_date) = {ts '2011-11-14 00:00:00'}
Parameter #3(cf_sql_date) = {ts '2011-11-14 00:00:00'}
Many thanks in advance..
Jason
EDIT AFTER SHAWN'S ANSWER BELOW
Have changed the following two lines of query preperation:
query name is now different for differ query.. created dynamically from paramaters passed in
q.setName("check#arguments.room_id##DateFormat(arguments.date,'ddmmyy')#");
createTimeSpan removed from quotes, so not passed in as a string.
q.setCachedWithin(createTimespan(0, 1, 0, 0));
I have also tried sending through an unprepared query (not using addparam(), but just rendering the variables straight in the query string), but made no difference..
EDIT 2 AFTER SHAWN'S 3rd EDITANWSER BELOW
Shawn.. nice pickup on edit 3!!! you have isolated where the problem is. (anyone reading this, quick, up vote Shawn's answer, he found a needle in a hay stack)
Passing the dates in as params does not cache..e.g..
q.setSQL("SELECT booking_id FROM guest_booking WHERE room_id = :roomID and check_in <= :iDate and check_out > :iDate and status != 0");
q.addParam(name="iDate", value="#createODBCDate(arguments.date)#", cfsqltype="cf_sql_date");
Just passing it in as a variable does not cache..e.g..
q.setSQL("SELECT booking_id FROM guest_booking WHERE room_id = :roomID and check_in <= #createODBCDate(arguments.date)# and check_out > #createODBCDate(arguments.date)# and status != 0");
BUT hard coding the dates DOES cache..e.g..
q.setSQL("SELECT booking_id FROM guest_booking WHERE room_id = :roomID and check_in <= {ts '2011-12-16 00:00:00'} and check_out > {ts '2011-12-16 00:00:00'} and status != 0");
This is all good, but clearly I can't hard code the dates... The dates will change for each day obviously, but even where I run the same query with the same dates being passed in dynamically (query syntax being exactly the same), the query won't cache if the dates are passed in as variables.. only if they are hard coded into the query.. wierd.. will keep playing and see what I can find.
Thank you Shawn for pinpointing the problem !!!
If it was correctly cached, your debug output would include just a tiny bit of additional info, to the tune of:
checkAvailability (Datasource=accom_crm, Time=0ms, Records=1, Cached Query)
Something is preventing your query from being cached.
I notice your call to .setCachedWithin() has a string being passed to it-or rather, you're making it a string by qualifying it with quotes, and using # signs.
Try passing the actual value returned from CreateTimeSpan(), without converting it to a string, like so:
q.setCachedWithin(createTimeSpan(0, 1, 0, 0));
-- edit --
Some other tidbits to note about query caching:
The name of the query must be the same.
The SQL statement (in all its parameterized forms) must be the same.
The datasource must be the same.
If used, the username & password must be the same.
The DBTYPE must be the same.
All these attributes must remain the same from call to call in order for ColdFusion to consider it a cache-able query. You mentioned above that you tried taking the addParam() calls out, but still had no luck...
...try using a static query name, rather than one with variables--see if you get any further
q.setName("checkTestQuery");
-- 2nd edit --
Another often overlooked issue is the ColdFusion server's clock. Make sure the CFServer's date/time is set correctly. This may sound silly, but I've seen many a "production" server whose clock was completely off, and not set to the correct timezone, let alone time...and time, of course, is of great significance within the context of caching.
-- 3rd edit --
After re-reading and reviewing everything, I'm going to recommend you take one more look at the 2nd point I made above regarding the SQL statement needing to be the same, and your WHERE clause being dependent upon a variable that is influenced by date/time, which implicitly could change on every request.
...and, since the SQL statement must remain the same in order to be cached, CF discards any attempt to cache it.
Try restructuring the SQL statement temporarily, without the WHERE clause looking for those date variables...and see what it produces.
Shawn's advice is mostly good, but the easiest way to check if the query is being cached is to update the underlying data and see if requerying gives you the previously-cached (all going well) data, or reflects the updates. If it reflects the updates, then it's not being cached...
Note that you don't need to do that horsing around with the query name (as per your update). CF will work out by itself when the parameters are different, and cache separate result sets.

App Engine GQL: querying a date range

What would be the App Engine equivalent of this Django statement?
return Post.objects.get(created_at__year=bits[0],
created_at__month=bits[1],
created_at__day=bits[2],
slug__iexact=bits[3])
I've ended up writing this:
Post.gql('WHERE created_at > DATE(:1, :2, :3) AND created_at < DATE(:1, :2, :4) and slug = :5',
int(bit[0]), int(bit[1]), int(bit[2]), int(bit[2]) + 1, bit[3])
But it's pretty horrific compared to Django. Any other more Pythonic/Django-magic way, e.g. with Post.filter() or created_at.day/month/year attributes?
How about
from datetime import datetime, timedelta
created_start = datetime(year, month, day)
created_end = created_start + timedelta(days=1)
slug_value = 'my-slug-value'
posts = Post.all()
posts.filter('created_at >=', created_start)
posts.filter('created_at <', created_end)
posts.filter('slug =', slug_value)
# You can iterate over this query set just like a list
for post in posts:
print post.key()
You don't need 'relativedelta' - what you describe is a datetime.timedelta. Otherwise, your answer looks good.
As far as processing time goes, the nice thing about App Engine is that nearly all queries have the same cost-per-result - and all of them scale proportionally to the records returned, not the total datastore size. As such, your solution works fine.
Alternately, if you need your one inequality filter for something else, you could add a 'created_day' DateProperty, and do a simple equality check on that.
Ended up using the relativedelta library + chaining the filters in jQuery style, which although not too Pythonic yet, is a tad more comfortable to write and much DRYer. :) Still not sure if it's the best way to do it, as it'll probably require more database processing time?
date = datetime(int(year), int(month), int(day))
... # then
queryset = Post.objects_published()
.filter('created_at >=', date)
.filter('created_at <', date + relativedelta(days=+1))
...
and passing slug to the object_detail view or yet another filter.
By the way you could use the datetime.timedelta. That lets you find date ranges or date deltas.