Rails 3: Difference between Relation.count and Relation.all.count
Moin,
i stumbled upon a inconsistency in ActiveRecord.
I tried to get all used combinations of values in two columns of a large table. First idea:SELECT DISTINCT col1, col2 FROM table
Imagine a rails app that organizes meals as a model and each Meal has_many :noodles
Each Noodle has the attributes (and hence the DB table columns) color
and shape
.
My goal is to get the number of all present combinations of color
and shape
for a single meal.
Since AR does not provide a "distinct" method i used
my_meal.noodles.select("distinct color, shape")
and got (in the rails console stdout) a six line output of 8 Noodle objects (respectively their String representations). But:
>> my_meal.noodles.select("distinct color, shape").count
=> 1606
In fact my_meal
contains 1606 noodles.
If i convert the the Relation to an array and get the siz开发者_StackOverflowe of it or use .all.count
the result is correct.
So my question is, why does AR output 8 Objects but count all the DB lines?
A similar problem seems to be mentioned here but no answer is given.
Thanks and best regards, Tim
Okay, thanks to tadman for pushing me in the right direction.
I digged somewhat deeper (especially in the log files) and what i found is a little bit weird.
The problem was caused by the number of selected columns. If one selects only one column and counts the result
my_meal.noodles.select("distinct color").count
ActiveRecord creates the following SQL statement:
SELECT COUNT(distinct color) AS count_id FROM "NOODLES" WHERE ("NOODLES".meal_id = 295)
In case one selects two or more columns and applies count
to it
my_meal.noodles.select("distinct color, shape").count
ActiveRecord forgets about that select clause and creates:
SELECT COUNT(*) AS count_id FROM "NOODLES" WHERE ("NOODLES".meal_id = 295)
This may be right, since (SQL's) COUNT
allows only one or less columns as parameters. Add a group
before the count
and anything is fine:
my_meal.noodles.select("distinct color, shape").group("color, shape").count
SELECT COUNT(*) AS count_all, color, shape AS color_shape FROM "NOODLES" WHERE ("NOODLES".meal_id = 295) GROUP BY color, shape
Apart from this AS color_shape
it is exact what i expected. BUT... only it returns this:
>> my_meal.noodles.select("distinct color, shape").group("color, shape").count
=> {star=>309, circle=>111, spaghetti=>189, square=>194, triangle=>179, bowtie=>301, shell=>93, letter=>230}
>> my_meal.noodles.select("distinct color, shape").group("color, shape").count.class
=> ActiveSupport::OrderedHash
This weird return value is (apart from order which depends on the DB) identical with the result and return value of
my_meal.noodles.group("shape").count
Conclusion:
As pointed out here there is still a gap between relations (may they be mathematical or arel relations) and ActiveRecord::Relations.
I can see the advantages of pressing the result in the patterns of a model as often as possible (at least in the context of a Rails app).
However real relations are not the result of the combination of several operations but the result of the concatenation of those operations.
In general the chainability of ActiveRecord::Relations is a great thing but there are some design decisions i cannot follow.
If you can't depend on the assurance that each action returns a new relation to work with, it looses much of its inuitional appeal.
As for the solution of my problem, i will use the above-mentioned group
solution and some kind of dirty workaround for the count operation:
my_meal.noodles.select("distinct color, shape").group("color, shape").all.count
This compresses the results to an acceptable minimum before pulling them out of the database and creating expensive objects just to count them. Alternatively one could use a handwritten SQL query, but why have a Rails and do not use it, huh? ;-)
Thanks for your help,
Tim
精彩评论