# Re-aggregate the population, never add up already grouped cells (multi-value axes overlap). class CountsPivotTotals def initialize(population, axes, row_headers, column_headers) @population = population @axes = axes @rows = row_headers.group_by { _1[:values].length } @columns = column_headers.group_by { _1[:values].length } end def call queries = @rows.flat_map do |row_depth, headers| @columns.filter_map do |column_depth, columns| next if row_depth == @axes.rows.length && column_depth == @axes.columns.length indexes = (0...row_depth).to_a + (0...column_depth).map { @axes.rows.length + _1 } expressions = indexes.map { @axes.expressions[_1] } scope = @population.scope.except(:select, :order) joins = @axes.joins(indexes.map { @axes.all[_1].fetch('field') }) scope = scope.joins(joins) unless joins.empty? if row_depth.positive? tuples = headers.map { _1[:values] }.uniq predicate = tuples.map { Array.new(row_depth) { |i| "(#{ expressions[i] }) <=> ?" }.join(' AND ') }.map { "(#{ _1 })" }.join(' OR ') scope = scope.where(predicate, *tuples.flatten) end if column_depth.positive? tuples = columns.map { _1[:values] }.uniq predicate = tuples.map { Array.new(column_depth) { |i| "(#{ expressions[row_depth + i] }) <=> ?" }.join(' AND ') }.map { "(#{ _1 })" }.join(' OR ') scope = scope.where(predicate, *tuples.flatten) end grouped = expressions.each_with_index.map { |expression, index| "#{ expression } AS v#{ index }" } # Fixed-width result coordinates, independent of NULL and of translated labels. padding = (expressions.length...@axes.all.length).map { "NULL AS v#{ _1 }" } fields = ["#{ row_depth } AS row_depth", "#{ column_depth } AS column_depth"] + grouped + padding + ['COUNT(DISTINCT v.id) AS count'] scope = scope.group(*expressions.map { Arel.sql(_1) }) if expressions.any? scope.optimizer_hints('MAX_EXECUTION_TIME(10000)') .select(Arel.sql(fields.join(', '))).to_sql end end return [] if queries.empty? sql = queries.map { "(#{ _1 })" }.join(' UNION ALL ') @population.definition.model.connection.select_all( "SELECT /*+ MAX_EXECUTION_TIME(10000) */ * FROM (#{ sql }) totals").map do |record| row_depth = record['row_depth'].to_i column_depth = record['column_depth'].to_i { row: (0...row_depth).map { record["v#{ _1 }"] }, column: (0...column_depth).map { record["v#{ row_depth + _1 }"] }, count: record['count'].to_i } end end end