Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

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  
    11module ActiveRecord 
    22  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] 
    44    def self.included(base) 
    55      base.extend(ClassMethods) 
    66    end 
     
    105105      # * <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 
    106106      #   include the joined columns. 
    107107      # * <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. 
    108112      # 
    109113      # Examples: 
    110114      #   Person.calculate(:count, :all) # The same as Person.count 
     
    167171            end 
    168172          end 
    169173 
    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}" 
    171176 
    172177          # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 
    173178          sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround 
    174179 
    175180          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 
    177188          sql << " FROM #{connection.quote_table_name(table_name)} " 
    178189          if merged_includes.any? 
    179190            join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) 
     
    200211 
    201212          sql << " ORDER BY #{options[:order]} "       if options[:order] 
    202213          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] 
    204216          sql 
    205217        end 
    206218 
  • activerecord/test/cases/calculations_test.rb

    old new  
    1515    assert_equal 318, Account.sum(:credit_limit) 
    1616  end 
    1717 
     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 
    1825  def test_should_average_field 
    1926    value = Account.average(:credit_limit) 
    2027    assert_kind_of Float, value 
     
    215222        # empty options are valid 
    216223        Company.send(:validate_calculation_options, func) 
    217224        # 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|  
    219226          Company.send(:validate_calculation_options, func, opt => true) 
    220227        end 
    221228      end