ruby-on-rails - Rails 关联和 Postgres 聚合函数
问题描述
在使用 Rails 5.2 在 Postgres 中创建聚合函数时,我无法成功调用关联列。我不断收到以下错误:
PG::GroupingError: ERROR: column "items.name" must appear in the GROUP BY clause or be used in an aggregate function
我尝试了几种解决方案,例如将items.name
列添加到我的 group 子句,但由于sale_selections: :item
.
我也尝试过使用DISTINCT ON (sale_selections.id) sale_selections.id, items.name, etc
. 但这给了我相同的错误结果。
是否可以在 select 方法中包含一列而无需将其添加到 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。但是,如果我将 1.77 添加item.name
到 group 方法中,我会在 Apple 和 Banana 中显示 1.77。
解决方案
选定的列需要至少在功能上依赖于分组(并且仅是分组的和严格模式下的聚合函数)。
假设您正在尝试计算每件商品的平均售价,请尝试:
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