【问题标题】:Rails associations and Postgres aggregate functionsRails 关联和 Postgres 聚合函数
【发布时间】:2019-03-18 21:16:47
【问题描述】:

在使用 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 列添加到我的组子句中,但由于sale_selections: :item 之间的belongs_to/has_many 关联,它返回了不希望的结果。

我也尝试过使用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。但是,如果我将 item.name 添加到 group 方法中,我会在 Apple 和 Banana 中显示 1.77。

【问题讨论】:

  • 一般来说,PostgreSQL 要求所有选定的列都属于“分组依据”或聚合函数。在这种情况下,您只需将名称添加到“分组依据”。这样做是正确的,它会给你想要的结果。如果它没有给出预期的结果,那么您的数据模型很可能是错误的。请包括细节。
  • 您能否上传您的模型结构(Sale、SaleSelection、Item)以及如何关联它们
  • 感谢@MichaelChaney 的反馈。将 items.name 添加到组中的问题是 items 有很多 sale_selections 并且它按每个 sale_selections.id 和每个 items.name 分组,哪种类型的结果加倍并且平均计算不匹配。当按 sale_selections.id 分组时,我得到了正确的结果,但根本无法引用项目名称。
  • 我已经用我的模型关联更新了这个问题。
  • 请提供数据示例并描述您要计算的内容

标签: ruby-on-rails postgresql ruby-on-rails-5


【解决方案1】:

选定的列需要至少在功能上依赖于分组(并且只能是严格模式下的分组和聚合函数)。

假设您要计算每件商品的平均售价,请尝试:

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

【讨论】:

  • 此方法提供所有项目的平均值,并为每个 item.name 显示它。所以所有记录都有相同的结果。例如,我有两个项目名称 Apple 和 Banana。苹果的平均价格应为 0.50 美元(售出 2 件商品),香蕉的平均价格应为 1.00 美元(售出 1 件商品)。以上结果给了我 0.50 美元,因为这似乎是所有项目的平均值,然后平均为 3。
  • @DollarChills 添加了可运行测试 - 代码确实会产生您在更新问题中所期望的结果
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 2014-11-04
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
  • 2012-10-14
相关资源
最近更新 更多