Ticket #9440: calculations_with_distinct_records_with_tests.diff
| File calculations_with_distinct_records_with_tests.diff, 4.5 kB (added by jcoglan, 5 months ago) |
|---|
-
activerecord/lib/active_record/calculations.rb
old new 1 1 module ActiveRecord 2 2 module Calculations #:nodoc: 3 CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, : limit, :offset, :include]3 CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :distinct_records, :limit, :offset, :include] 4 4 def self.included(base) 5 5 base.extend(ClassMethods) 6 6 end … … 105 105 # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not 106 106 # include the joined columns. 107 107 # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ... 108 # * <tt>:distinct_records</tt>: Set this to true if you want to calculate over distinct records but the values you are summing are not 109 # necessarily unique. You should use this if you are using <tt>:conditions</tt> based on tables included using <tt>:include</tt> or 110 # <tt>:joins</tt>. Those LEFT OUTER JOIN statements can lead to duplicates of the base record in the result set, and using 111 # this option avoids such problems. 108 112 # 109 113 # Examples: 110 114 # Person.calculate(:count, :all) # The same as Person.count … … 167 171 end 168 172 end 169 173 170 sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 174 calculation_column_name = options[:distinct_records] ? 'calculation_column' : column_name 175 sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{calculation_column_name}) AS #{aggregate_alias}" 171 176 172 177 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 173 178 sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround 174 179 175 180 sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] 176 sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround 181 if options[:distinct_records] 182 select_columns = ["#{table_name}.#{primary_key}"] 183 select_columns << column_name unless (select_columns + [primary_key]).include?(column_name) 184 sql << " FROM (SELECT DISTINCT #{select_columns * ', '} AS #{calculation_column_name}" 185 else 186 sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround 187 end 177 188 sql << " FROM #{connection.quote_table_name(table_name)} " 178 189 if merged_includes.any? 179 190 join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) … … 200 211 201 212 sql << " ORDER BY #{options[:order]} " if options[:order] 202 213 add_limit!(sql, options, scope) 203 sql << ')' if use_workaround 214 sql << ')' if use_workaround || options[:distinct_records] 215 sql << ' AS calculation_table' if options[:distinct_records] 204 216 sql 205 217 end 206 218 -
activerecord/test/cases/calculations_test.rb
old new 15 15 assert_equal 318, Account.sum(:credit_limit) 16 16 end 17 17 18 def test_should_sum_field_without_repeated_records 19 assert_equal 7, Author.sum("#{Author.table_name}.id", :include => :posts) 20 assert_equal 3, Author.sum("#{Author.table_name}.id", :include => :posts, :distinct_records => true) 21 assert_equal 12, Post.sum(:author_id, :include => :comments) 22 assert_equal 7, Post.sum(:author_id, :include => :comments, :distinct_records => true) 23 end 24 18 25 def test_should_average_field 19 26 value = Account.average(:credit_limit) 20 27 assert_kind_of Float, value … … 215 222 # empty options are valid 216 223 Company.send(:validate_calculation_options, func) 217 224 # these options are valid for all calculations 218 [:select, :conditions, :joins, :order, :group, :having, :distinct ].each do |opt|225 [:select, :conditions, :joins, :order, :group, :having, :distinct, :distinct_records].each do |opt| 219 226 Company.send(:validate_calculation_options, func, opt => true) 220 227 end 221 228 end