Is it possible to do date arithmetic in an active record query without using custom sql ?

Hi,

I currently have a large array of model objects (around 5000 this
could grow to be in the 100k range) which have a date attribute. I
want to filter this objects if one of their date attribute falls into
an array of years selected by the user. I wrote the code to do this
in ruby land:

def FilterUtils.apply_year_filter readings, years
    readings.each {|reading|
      if( reading.timestamp != nil && years != nil &&
years.index(reading.timestamp.year) == nil )
        readings.delete(reading)
      end
    }
    readings
  end

but its too slow so I'm going to do it through a :conditions query on
the .find(:all) call rather than fetching them all back and filtering
through them afterwards.

As far as I can tell there's no built in date queries in the active
record interface. I've done about an hour of searching and no dice.

I'm currently writing the custom sql to do this in our postgres db but
was just curious if there was an efficient way to do this without
tying the project to postgres.

Thanks
Barry

this kind of thing are better to left in the db server since is much faster. and by the why you can do date arithmetic but dont do it that way since you have a lot of records returned

Hi,

I currently have a large array of model objects (around 5000 this
could grow to be in the 100k range) which have a date attribute. I
want to filter this objects if one of their date attribute falls into
an array of years selected by the user. I wrote the code to do this
in ruby land:

def FilterUtils.apply_year_filter readings, years
readings.each {|reading|
if( reading.timestamp != nil && years != nil &&
years.index(reading.timestamp.year) == nil )
readings.delete(reading)
end
}
readings
end

but its too slow so I'm going to do it through a :conditions query on
the .find(:all) call rather than fetching them all back and filtering
through them afterwards.

As far as I can tell there's no built in date queries in the active
record interface. I've done about an hour of searching and no dice.

You can do timestamp comparisons in ActiveRecord queries, so something like
:conditions => [ 'timestamp >= ? and timestamp <= ?', timestamp1, timestamp2 ]

Colin

voidstar wrote:

def FilterUtils.apply_year_filter readings, years
    readings.each {|reading|
      if( reading.timestamp != nil && years != nil &&
years.index(reading.timestamp.year) == nil )
        readings.delete(reading)
      end
    }
    readings
  end

I realize that you're not actually planning to use this code (thank
goodness), but I just wanted to point out that you're mutating the array
your iterating over. This is a VERY bad idea generally speaking.

Thanks Robert, yeah that's a silly thing to do :slight_smile:

Colin I'll try doing the :conditions => [ 'timestamp >= ? and
timestamp <= ?', timestamp1, timestamp2 ] and see how it performs
thanks I should have thought of doing that.

radhames yeah it makes a lot more sense to do it in the db, just wrote
some code quickly to do it in ruby land to see if it was possible

I'll post up the finished code when I'm done for future reference if
anyone else finds themselves in the same pickle

Here's the finished method thanks again Colin

def FilterUtils.get_readings_with_filters_applied reading_class,
meter_ids, timestamp_col_name, years,

    all_conditions = Array.new

    all_conditions += ["meter_id IN (?)", meter_ids] unless meter_ids
== nil || meter_ids.size <= 0

    years.each{ |year_int|
      year = Date.civil(year_int, 1, 1)
      year_start = year.beginning_of_year
      year_end = year.end_of_year
      all_conditions += [ "#{timestamp_col_name} >= ? and
#{timestamp_col_name} <= ?", year_start, year_end ]
    }

    readings = reading_class.all(:conditions => all_conditions)

  end

voidstar wrote:

Here's the finished method thanks again Colin

def FilterUtils.get_readings_with_filters_applied reading_class,
meter_ids, timestamp_col_name, years,

    all_conditions = Array.new

    all_conditions += ["meter_id IN (?)", meter_ids] unless meter_ids
== nil || meter_ids.size <= 0

    years.each{ |year_int|
      year = Date.civil(year_int, 1, 1)
      year_start = year.beginning_of_year
      year_end = year.end_of_year
      all_conditions += [ "#{timestamp_col_name} >= ? and
#{timestamp_col_name} <= ?", year_start, year_end ]
    }

    readings = reading_class.all(:conditions => all_conditions)

  end

When selecting based on a timestamp range one typically wants the range
to be inclusive on the lower end and exclusive on the upper end. Being
inclusive on both ends is probably not what you want.

A convenient (and nicely readable) syntax for that would be something
like:

Rails 2.x:
:conditions => { timestamp_col_name => year_start...next_year_start }

Rails 3.x
.where({ timestamp_col_name => year_start...next_year_start })

This would produce something like:
... WHERE 'timestamp_col_name' >= '2010-01-01' AND 'timestamp_col_name'
< '2011-01-01'

If you where to look at the year_start to year_end I'm afraid you would
be missing the last day. Your range would be midnight of 2010-01-01 to
midnight 2010-12-31. For example, looking at 2010-12-31 11:00 a.m. that
would, incorrectly, fall outside of your range. You need to include all
times up to, but not including, midnight 2011-01-01 in order to include
all times of that last day.

Robert Walker wrote:

Rails 2.x:
:conditions => { timestamp_col_name => year_start...next_year_start }

CORRECTION:
conditions => { :timestamp_col_name => year_start...next_year_start }
                ^
                 \__ Forgot to make this a symbol

Ah thank you Robert I'd missed that condition in my tests (lazy!)
thanks

Couldn't you do something like:

reading_class.all(:conditions => ['YEAR(timestamp_field) IN (?)',
years_list])

This is sort of a bad idea on some DBs, but Postgres will allow you to
add an index on the *computed* value if things get slow...

--Matt Jones