My question is whether there''s a more idiomatic rails way of
structuring this query or of redoing the underlying models. First,
the ugly find_by_sql code, which is the method to generate an atom feed:
def atom
items_per_feed = 15
sql_query = "SELECT activities.*, users.real_name AS real_name,
accounts.last_scraped_at AS last_scraped_at,
accounts.total_miles as account_miles,
origins.name AS origin_name, origins.code AS origin_code,
destinations.name
AS destination_name, destinations.code AS destination_code,
airlines.name
AS airline_name, airlines.code AS airline_code, programs.name
AS program_name,
membership_levels.name AS membership_level FROM activities
LEFT OUTER JOIN accounts ON activities.account_id = accounts.id
LEFT OUTER JOIN
users on accounts.user_id = users.id LEFT OUTER JOIN
airports AS origins ON activities.origin_id = origins.id LEFT
OUTER JOIN airports
AS destinations ON activities.destination_id = destinations.id
LEFT OUTER JOIN
airlines ON activities.airline_id = airlines.id LEFT OUTER JOIN
programs ON
accounts.program_id = programs.id LEFT OUTER JOIN
membership_levels ON
accounts.membership_level_id = membership_levels.id
WHERE accounts.user_id = #{@session[:user].id}
ORDER BY activities.date DESC LIMIT #{items_per_feed}"
@activities = Activity.find_by_sql(sql_query)
end
Now, here''s a stripped down version of the models, which are for
keeping track of frequent flyer information:
class User < ActiveRecord::Base
has_many :accounts
end
class Account < ActiveRecord::Base
belongs_to :program
belongs_to :user
belongs_to :membership_level
has_many :activities
end
class Activity < ActiveRecord::Base
belongs_to :origin, :class_name => "Airport", :foreign_key =>
"origin_id"
belongs_to :destination, :class_name => "Airport", :foreign_key
=>
"destination_id"
belongs_to :account
belongs_to :airline
end
class Airport < ActiveRecord::Base
has_many :activities, :foreign_key => "origin_id"
has_many :activities, :foreign_key => "destination_id"
end
class Program < ActiveRecord::Base
belongs_to :airline
has_many :membership_levels
has_many :accounts
end
class MembershipLevel < ActiveRecord::Base
belongs_to :program
has_many :accounts
end
class Airline < ActiveRecord::Base
has_many :activities
has_many :programs
end
A few things of note. One of the complexities is that I''m joining
the airports table twice, once as origin and once as destination. I
don''t think there''s any support for this using the regular
find
method. Separately, I designed these models prior to the
availability of has many :through, but I now can''t decide whether and
how I should make use of that feature.
Obviously, I''ve gone through the trouble of making the above work.
However, I''d like any opinions on whether there''s a better way
to
accomplish the same outcome. Also, will this many joins bring my
database to its knees under load? Thanks in advance for your thoughts.
- dan
--
Dan Kohn <mailto:dan@dankohn.com>
<http://www.dankohn.com/> <tel:+1-415-233-1000>
Julian Gall
2006-Jul-16 06:13 UTC
[Rails] Re: How bad is it to have 7 joins in my find_by_sql?
If you''re using a database version that supports it, you could use a stored procedure. Put the whole select statement with all its joins into the stored procedure and call it with find_by_sql. e.g. See http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html This is perhaps not idiomatic for Rails but it has the advantage that the database engine can optimise the query once. Your Rails app then deals with a simple database entity called atom. Regarding performance, with the correct indexes on the key fields, it should be as fast as the complexity of your request allows. If there is really a problem, you could run the query for all users periodically and store the results in a temporary table for lookup by user id as required. But that is definitely not idiomatic for SQL! Julian -- Posted via http://www.ruby-forum.com/.
Dan Kohn
2006-Jul-16 17:36 UTC
[Rails] Re: How bad is it to have 7 joins in my find_by_sql?
Thanks. I''m planning on doing action caching on the atom feed and
using a sweeper, which should limit the queries to only be done when
there''s new info for that user.
- dan
--
Dan Kohn <mailto:dan@dankohn.com>
<http://www.dankohn.com/> <tel:+1-415-233-1000>
On Jul 15, 2006, at 11:13 PM, Julian Gall wrote:
> If you''re using a database version that supports it, you could use
a
> stored procedure. Put the whole select statement with all its joins
> into
> the stored procedure and call it with find_by_sql. e.g. See
> http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html
>
> This is perhaps not idiomatic for Rails but it has the advantage that
> the database engine can optimise the query once. Your Rails app then
> deals with a simple database entity called atom.
>
> Regarding performance, with the correct indexes on the key fields, it
> should be as fast as the complexity of your request allows. If
> there is
> really a problem, you could run the query for all users
> periodically and
> store the results in a temporary table for lookup by user id as
> required. But that is definitely not idiomatic for SQL!
>
> Julian
>
> --
> Posted via http://www.ruby-forum.com/.
> _______________________________________________
> Rails mailing list
> Rails@lists.rubyonrails.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
Ashley Moran
2006-Jul-16 19:12 UTC
[Rails] How bad is it to have 7 joins in my find_by_sql?
On Jul 15, 2006, at 11:48 pm, Dan Kohn wrote:> Obviously, I''ve gone through the trouble of making the above work. > However, I''d like any opinions on whether there''s a better way to > accomplish the same outcome. Also, will this many joins bring my > database to its knees under load? Thanks in advance for your > thoughts.Hi Dan The number of joins in your query doesn''t bother me too much, but... do they really all need to be left joins? I''ve found that database servers have a much harder time optimising queries with left joins than inner joins. If you can be sure that every row in table A has a corresponding row in table B, then you should always use an inner join. If it might not, double check why not, and also if it is actually meaningful to bring back the part of the result set for A without entries for B. Obviously the performance depends entirely on what system you are using (if you didn''t write it by hand, then judging by the SQL formatting I''m guessing it came from SQL Server''s Enterprise Manager!!!) Ashley