Finding records either side of a reference

I have a simple problem that I am sure has a simple answer, but my
brain will not produce the answer.
I have a table (segments) that has a start_time field. Given a
specific time I want to find the last segment with a start_time <=
time and the first one with start_time > time, so
Segment.order(:start_time).where( "start_time <= ?", time).last
and
Segment.order(:start_time).where( "start_time > ?", time).first
I feel sure it must be possible to do this in one query, but I can't see how.

Any suggestions gratefully received.

Colin

Nothing leaps to mind re doing it in one query, given your
requirements. Depending how much flexibility you have, it might be
close enough to order by absolute value of difference between
start_time and your desired time, and take the first two.

If you really do need the first before or matching, and the first
strictly after, maybe you can make those two queries more efficient,
which I would figure is probably your overall goal in this question.
I'm not sure if the database will send Rails *all* the matching
records and let Rails apply the .last and .first. If it does, then
using .limit(1) like this should make the DB do the filtering before
sending the records back to Rails:

first_before_or_it = Segment.where( "start_time <= ?", time).
        order(:start_time => :desc).limit(1).last
first_after_it = Segment.where( "start_time > ?", time).
        order(:start_time => :asc).limit(1).last

(Note the opposite ordering.) Ideally one would think ActiveRecord
would apply the .last and .first do do the limiting for you in the
database, but I can't be bothered to go check right now. :slight_smile:

-Dave

I want to find the last segment with a start_time <=
time and the first one with start_time > time, so
Segment.order(:start_time).where( "start_time <= ?", time).last
and
Segment.order(:start_time).where( "start_time > ?", time).first
I feel sure it must be possible to do this in one query, but I can't see how.

Nothing leaps to mind re doing it in one query, given your
requirements. Depending how much flexibility you have, it might be
close enough to order by absolute value of difference between
start_time and your desired time, and take the first two.

I think you may be right, that there is sensible way to do it in one
query. I can't use the abs technique as the time stamps are
essentially random so that might give me two samples before or after
rather than surrounding the event.

If you really do need the first before or matching, and the first
strictly after, maybe you can make those two queries more efficient,
which I would figure is probably your overall goal in this question.
I'm not sure if the database will send Rails *all* the matching
records and let Rails apply the .last and .first. If it does, then
using .limit(1) like this should make the DB do the filtering before
sending the records back to Rails:

In fact there is no great need to change the code I have, it just
offends me having to have the two similar queries one after the other,
so it is purely an aesthetic issue really. Often when I see code like
that it means I am not doing it the best way, but perhaps not in this
case. I suppose it comes down to the fact that in sql one cannot say
"give me the records starting with the last one that meets this
criterion".

first_before_or_it = Segment.where( "start_time <= ?", time).
        order(:start_time => :desc).limit(1).last
first_after_it = Segment.where( "start_time > ?", time).
        order(:start_time => :asc).limit(1).last

(Note the opposite ordering.) Ideally one would think ActiveRecord
would apply the .last and .first do do the limiting for you in the
database, but I can't be bothered to go check right now. :slight_smile:

Yes, ActiveRecord does exactly that.

Thanks

Colin