Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AR Calculations group by multiple fields #19

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 19 additions & 12 deletions activerecord/lib/active_record/calculations.rb
Expand Up @@ -187,7 +187,7 @@ def construct_calculation_sql(operation, column_name, options) #:nodoc:
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround

sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
elsif scope && scope[:from] && !use_workaround
Expand All @@ -211,8 +211,8 @@ def construct_calculation_sql(operation, column_name, options) #:nodoc:
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])

if options[:group]
group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
sql << " GROUP BY #{options[group_key]} "
group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
sql << " GROUP BY #{options[group_key].join(',')} "
end

if options[:group] && options[:having]
Expand All @@ -239,24 +239,31 @@ def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
end

def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
group_attr = options[:group].to_s
association = reflect_on_association(group_attr.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_field = associated ? association.primary_key_name : group_attr
group_alias = column_alias_for(group_field)
group_column = column_for group_field
sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
group_attr = options[:group].to_s.scan(/[^\(\), ]+\([^\(\)]+\)|[^\(^), ]+/) # split fields by comma except if within SQL function
association = reflect_on_association(group_attr.to_s.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.primary_key_name : group_attr)
group_aliases = []
group_columns = {}

group_fields.each do |field|
group_aliases << column_alias_for(field)
group_columns[column_alias_for(field)] = column_for(field)
end

sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
calculated_data = connection.select_all(sql)
aggregate_alias = column_alias_for(operation, column_name)

if association
key_ids = calculated_data.collect { |row| row[group_alias] }
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
key_records = association.klass.base_class.find(key_ids)
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
end

calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
key = type_cast_calculated_value(row[group_alias], group_column)
key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
key = key.first if key.size == 1
key = key_records[key] if associated
value = row[aggregate_alias]
all[key] = type_cast_calculated_value(value, column, operation)
Expand Down
5 changes: 5 additions & 0 deletions activerecord/test/cases/calculations_test.rb
Expand Up @@ -58,6 +58,11 @@ def test_should_group_by_field
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end

def test_should_group_by_multiple_fields
c = Account.count(:all, :group => 'firm_id, credit_limit')
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
end

def test_should_group_by_summed_field
c = Account.sum(:credit_limit, :group => :firm_id)
assert_equal 50, c[1]
Expand Down