Rails simple sql comparison with another attribute - sql

I think this should be quite simple. I want to do something like:
User.where("created_at < ?", :updated_at)
updated_at is another attribute of the User
Thanks in advance!

You can do the following:
john = User.where(username: 'john').first
users_updated_before_john = User.where('created_at < ?', john.updated_at)
After you commented:
I'm trying to do something like User.where("created_at < ?", Date.today - :updated_at) And I want the attribute created_at be compared with the updated_at for each user, not with the a static one.
Here is how you do it: (PostgreSQL 9.1 syntax!)
User.where('users.created_at::date < (users.updated_at::date - integer '5')')
The documentation about Date/Time functions in PostgreSQL (9.1):
http://www.postgresql.org/docs/9.1/static/functions-datetime.html

Related

How to select each model which has the maximum value of an attribute for any given value of another attribute?

I have a Work model with a video_id, a user_id and some other simple fields. I need to display the last 12 works on the page, but only take 1 per user. Currently I'm trying to do it like this:
def self.latest_works_one_per_user(video_id=nil)
scope = self.includes(:user, :video)
scope = video_id ? scope.where(video_id: video_id) : scope.where.not(video_id: nil)
scope = scope.order(created_at: :desc)
user_ids = works = []
scope.each do |work|
next if user_ids.include? work.user_id
user_ids << work.user_id
works << work
break if works.size == 12
end
works
end
But I'm damn sure there is a more elegant and faster way of doing it especially when the number of works gets bigger.
Here's a solution that should work for any SQL database with minimal adjustment. Whether one thinks it's elegant or not depends on how much you enjoy SQL.
def self.latest_works_one_per_user(video_id=nil)
scope = includes(:user, :video)
scope = video_id ? scope.where(video_id: video_id) : scope.where.not(video_id: nil)
scope.
joins("join (select user_id, max(created_at) created_at
from works group by created at) most_recent
on works.user_id = most_recent.user_id and
works.created_at = most_recent.created_at").
order(created_at: :desc).limit(12)
end
It only works if the combination of user_id and created_at is unique, however. If that combination isn't unique you'll get more than 12 rows.
It can be done more simply in MySQL. The MySQL solution doesn't work in Postgres, and I don't know a better solution in Postgres, although I'm sure there is one.

Highscore excerpt (10 better, 10 worse scores) in a single SQL query

Database schema:
User(id, name)
Highscore(id, points, user_id)
When a User has submitted his highscore, I want him to see the 10 better and 10 worse highscores (to display his placement). Is it possible to accomplish this in a single SQL / ActiveModel query?
This is as far as I've gotten:
Highscore.where("points < ?", highscore.points).order(points: :desc).limit(10).merge(Highscore.where("points >= ?", highscore.points).order(:points).limit(10))
but merge does something else than I thought it did. So this doesn't work.
Doing merge or multiple where calls will AND the conditions together. I haven't had any luck getting UNION to work in ActiveRecord, but you can get the same result with subqueries and Arel.
It would look something like this:
highscores = Highscore.arel_table
bottom_scores_query = Highscore.select(:id).where('points < ?', highscore.points).order(points: :desc).limit(10).to_sql
top_scores_query = Highscore.select(:id).where("points >= ?", highscore.points).order(:points).limit(10).to_sql
Highscore.where(highscores[:id].in(Arel.sql(bottom_scores_query)).or(highscores[:id].in(Arel.sql(top_scores_query)))
This solution uses Arel to do two subqueries to get the high scores where the id is in the top 10 high scores, or where the id is in the bottom 10 high scores.
Refactored into scopes, this would look something like:
class Highscore < ActiveRecord::Base
scope :top_scores, ->(highscore) { where("points >= ?", highscore.points).order(:points).limit(10) }
scope :bottom_scores, ->(highscore) { where('points < ?', highscore.points).order(points: :desc).limit(10) }
scope :summarized, ->(highscore) {
top_scores_query = top_scores(highscore).select(:id).to_sql
bottom_scores_query = bottom_scores(highscore).select(:id).to_sql
where(arel_table[:id].in(Arel.sql(bottom_scores_query)).or(arel_table[:id].in(Arel.sql(top_scores_query))))
}
end
And then you could get them by calling Highscore.summarized(highscore).

Rails select register from the previous day

I'm trying to get the model instances with one day of history. I'm using postgreSQL.
What i want is likeness this:
element = Element.find(:first , :conditions => ["name LIKE ? AND created_at < ---ONE_DAY_CONDITION---)
What is the best way to solve it using a SQL query?
Thanks!
You mean 1.day.ago?
element = Element.find(:first,
:conditions => ["name LIKE ? AND created_at > ?", whatever, 1.day.ago])

Making a Safe sql query

The code below is from a Sinatra app (that uses DataMappe), which I am trying to convert to a Rails 3 application. It is a class method in the Visit class.
def self.count_by_date_with(identifier,num_of_days)
visits = repository(:default).adapter.query("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")
dates = (Date.today-num_of_days..Date.today)
results = {}
dates.each { |date|
visits.each { |visit| results[date] = visit.count if visit.date == date }
results[date] = 0 unless results[date]
}
results.sort.reverse
end
My problem is with this part
visits = repository(:default).adapter.query("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")
Rails (as far as I know) doesn't have this repository method, and I would expect a query to be called on an object of some sort, such as Visit.find
Can anyone give me a hint how this would best be written for a Rails app?
Should I do
Visit.find_by_sql("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")
Model.connection.execute "YOUR SQL" should help you. Something like
class Visit < Activerecord::Base
class << self
def trigger(created_at,identifier,num_of_days)
sql = "SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)"
connection.execute sql
end
end
end
I know you already accepted an answer, but you asked for the best way to do what you asked in Rails. I'm providing this answer because Rails does not recommend building conditions as pure query strings.
Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, Client.where("first_name LIKE '%#{params[:first_name]}%'") is not safe.
Fortunately, Active Record is incredibly powerful and can build very complex queries. For instance, your query can be recreated with four method calls while still being easy to read and safe.
# will return a hash with the structure
# {"__DATE__" => __COUNT__, ...}
def self.count_by_date_with(identifier, num_of_days)
where("link_identifier = ?", identifier)
.where(:created_at => (num_of_days.to_i.days.ago)..(1.day.from_now))
.group('date(created_at)')
.count
end
Active Record has been built to turn Ruby objects into valid SQL selectors and operators. What makes this so cool is that Rails can turn a Ruby Range into a BETWEEN operator or an Array into an IN expression.
For more information on Active Record check out the guide. It explains what Active Record is capable of and how to use it.

Complex SQL Query in Rails - User.where(hash)

My starting point is basically Ryan Bates Railscast.
I have User model that I need to do some queries on. The model has a couple hourly rate attributes as follows:
#User migration
...
t.decimal :hour_price_high
t.decimal :hour_price_low
...
I have the query working in the User.where(Array) format where Array is formatted
["users.hour_price_high <= ? OR users.hour_price_low >= ? OR users.hour_price_low <= ? AND users.hour_price_high >= ?", hour_price_high, hour_price_low, hour_price_low, hour_price_high]
#This is simply a search of two ranges. Search for $40/h - $60/h.
#It will return an User who charge any overlapping price range. Such as one who charges $45/h - $65/h, etc.
I simply wish to convert this into Ruby syntax in the where statement.
My problem is how to represent the OR.
#This isn't the full query shown above..
User.where(:high_price_high => hour_price_low..hour_price_high, :hour_price_low => hour_price_low..hour_price_high)
Produces this SQL:
=> "SELECT `users`.* FROM `users` WHERE (`users`.`hour_price_high` BETWEEN 45 AND 60) AND (`users`.`hour_price_low` BETWEEN 45 AND 60)"
Which, of course, is wrong because of the AND. I need it to be:
=> "SELECT `users`.* FROM `users` WHERE (`users`.`hour_price_high` BETWEEN 45 AND 60) OR (`users`.`hour_price_low` BETWEEN 45 AND 60)"
How can I make this be an OR statement without busting out the old truth table from my sophomore E.E. classes?
Thanks!
When you chain several where methods or have a few arguments in one where method, then you always get AND between them, so when you want to have OR you need to use this syntax:
User.where("users.hour_price_high <= ? OR users.hour_price_low >= ? OR users.hour_price_low <= ? AND users.hour_price_high >= ?", hour_price_high, hour_price_low, hour_price_low, hour_price_high)
Note: please watch http://railscasts.com/episodes/202-active-record-queries-in-rails-3 in order to get more information about active record queries.