Rails ассоциации и агрегатные функции Postgres - PullRequest
0 голосов
/ 19 марта 2019

Я в недоумении, пытаясь успешно вызвать связанный столбец при создании агрегатной функции в Postgres с использованием Rails 5.2. Я продолжаю получать следующую ошибку:

PG::GroupingError: ERROR:  column "items.name" must appear in the GROUP BY clause or be used in an aggregate function

Я пробовал пару решений, таких как добавление столбца items.name в мое предложение группы, но он возвращает нежелательный результат из-за связи own_to / has_many между sale_selections: :item.

Я также пытался использовать DISTINCT ON (sale_selections.id) sale_selections.id, items.name, etc. Но это дает мне тот же результат ошибки.

Можно ли включить столбец в метод выбора без необходимости добавлять его в предложение group и при этом иметь возможность ссылаться на него? Или мне нужно найти другое решение?

Мой запрос

@search = Sale.joins(sale_selections: :item)
.select('sale_selections.id, items.name, AVG(sale.price) as price)
.group('sale_selections.id').where('sale.price IS NOT NULL')

Мой взгляд

<% @search.each do |s| %>
  <%= s.name %> <br />
  <%= s.sale %>
<% end %>

Модельные ассоциации

class Sale < ApplicationRecord
  has_many :sale_selections, dependent: :destroy
end

class Item < ApplicationRecord
  has_many :sale_selections
end

class SaleSelection < ApplicationRecord
  belongs_to :Sale
  belongs_to :Item
end

Обновление

Выполнение следующего запроса не дает правильных результатов из-за связи между sale_selections: :item.

@search = Sale.joins(sale_selections: :item)
.select('items.id, items.name, AVG(sale.price) as price)
.group('items.id').where('sale.price IS NOT NULL')

Предоставляет одинаковую среднюю цену для всех элементов в запросе вместо правильных значений, которые дает группировка по sale_selections.id.

Продается стол

| id | price |
| 1  |  2.50 |
| 2  |  1.50 |
| 3  |  1.30 |

Таблица наименований

| id |   name  |
| 1  |   Apple |
| 2  |  Banana |

Таблица выбора продаж

| id | sale_id | item_id |
| 1  |   1     |    1    |
| 2  |   2     |    2    |
| 2  |   3     |    2    |

Таким образом, мой средний результат для яблока должен быть 2,50, а для банана - 1,40. Но если я добавлю item.name к групповому методу, я получу 1.77 как в Apple, так и в Banana.

1 Ответ

0 голосов
/ 19 марта 2019

Выбранные столбцы должны по крайней мере функционально зависеть от сгруппированных по ним (и быть только сгруппированными и агрегатными функциями в строгом режиме).

Предполагая, что вы пытаетесь рассчитать среднюю цену продажи для каждого предмета, попробуйте:

Item.select("items.id, items.name, AVG(sales.price) as average_sale_price").
  joins(sale_selections: :sale).
  where("sales.price is not null").
  group("items.id")

Обновление. Пример выполнения:

require "bundler/inline"
gemfile(true) do
  source "https://rubygems.org"
  gem "activerecord", "5.2.2.1"
  gem "sqlite3", "~> 1.3.6"
end

require "active_record"
require "minitest/autorun"
require "logger"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
  create_table(:items, force: true){|t| t.string  :name }
  create_table(:sales, force: true) {|t| t.decimal :price }
  create_table :sale_selections, force: true do |t|
    t.integer :sale_id
    t.integer :item_id
  end
end

class Sale < ActiveRecord::Base
  has_many :sale_selections, dependent: :destroy
end

class Item < ActiveRecord::Base
  has_many :sale_selections
end

class SaleSelection < ActiveRecord::Base
  belongs_to :sale
  belongs_to :item
end

class SomeTest < Minitest::Test
  def test_stuff
    apple, banana = %w[Apple Banana].map{|i| Item.create! name: i}
    [apple, banana, banana].zip([2.5, 1.5, 1.3]).each{|item, price|
      SaleSelection.create item:item, sale: Sale.create(price: price)
    }

    res = Item.select("items.id, items.name, AVG(sales.price) as average_sale_price").
      joins(sale_selections: :sale).
      where("sales.price is not null").
      group("items.id")

    res.each{|r| puts "#{r.name} avg is #{r.average_sale_price}" }

    avg = res.map{|r| [r.id, r.average_sale_price]}.to_h
    assert_equal 1.4, avg[banana.id]
    assert_equal 2.5, avg[apple.id]
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...