为什么 Postgres 不接受我的计数列?

Why does Postgres not accept my count column?

我正在使用以下模型构建一个 Rails 应用程序:

# vote.rb
class Vote < ApplicationRecord
  belongs_to :person
  belongs_to :show
  scope :fulfilled, -> { where(fulfilled: true) }
  scope :unfulfilled, -> { where(fulfilled: false) }
end

# person.rb
class Person < ApplicationRecord
  has_many :votes, dependent: :destroy

  def self.order_by_votes(show = nil)
    count = 'nullif(votes.fulfilled, true)'
    count = "case when votes.show_id = #{show.id} AND NOT votes.fulfilled then 1 else null end" if show
    people = left_joins(:votes).group(:id).uniq!(:group)
    people = people.select("people.*, COUNT(#{count}) AS people.vote_count")
    people.order('people.vote_count DESC')
  end
end

order_by_votes 背后的想法是根据未完成的选票数量对 People 进行排序,或者计算所有选票,或者只计算与给定 Show.[=20 相关联的选票=]

当我针对 SQLite 进行测试时,这似乎工作正常。但是当我切换到 Postgres 时,我得到了这个错误:

Error:
PeopleControllerIndexTest#test_should_get_previously_on_show:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:  column people.vote_count does not exist
LINE 1: ...s"."show_id" =  GROUP BY "people"."id" ORDER BY people.vot...
                                                             ^

如果我使用 @people.to_sql 转储 SQL,这就是我得到的:

SELECT people.*, COUNT(nullif(votes.fulfilled, true)) AS people.vote_count FROM "people" LEFT OUTER JOIN "votes" ON "votes"."person_id" = "people"."id" GROUP BY "people"."id" ORDER BY people.vote_count DESC

为什么这在 Postgres 上失败但在 SQLite 上有效?我应该怎么做才能让它在 Postgres 上运行?

(PS: 我用一个点命名了字段 people.vote_count,这样我就可以在我的视图中访问它,而无需执行另一个 SQL 查询来实际查看投票为视图中的每个人计数(不确定这是否有效)但即使我将字段简单命名为 vote_count.)

也会出现相同的错误

(PS2: 我最近添加了 .uniq!(:group) 因为 Rails 6.2 有一些弃用警告,但我找不到它的任何文档所以我不确定我做对了,没有那个部分仍然存在错误。)

您确定您没有从 PostgreSQL 的某个地方收到语法错误吗?如果你这样做:

select count(*) as t.vote_count from t ... order by t.vote_count

在 PostgreSQL 开始抱怨没有 t.vote_count 列之前,我收到一个语法错误。

没关系,解决方法是不要尝试将您的 vote_count 放在 people table:

people = people.select("people.*, COUNT(#{count}) AS vote_count")
...
people.order(vote_count: :desc)

你在那里不需要它,你仍然可以像 people 中的任何“正常”列一样引用 vote_count。 select 列表中的任何内容都将在结果模型实例中显示为访问器,无论它们是否为列,它们都不会显示在 #inspect 输出中(因为它是基于 table 的列),但您仍然调用访问器方法。

从历史上看,仅通过在示波器上使用 count 来获得正确的计数就存在相当多的 AR 问题(和错误),我不确定它们是否真的全部消失了。

这取决于范围(AR 版本、关系、组、排序、uniq 等)。 gem 必须在作用域上普遍使用的默认 count 调用并不是万能的解决方案。出于这个已知原因,Pagy 允许您将正确的计数传递给它的 pagy 方法,如 Pagy documentation.

中所述

Your scope might become complex and the default pagy collection.count(:all) may not get the actual count. In that case you can get the right count with some custom statement, and pass it to pagy.

@pagy, @records = pagy(collection, count: your_count)

Notice: pagy will efficiently skip its internal count query and will just use the passed :count variable.

所以...只需获取您自己计算的计数并将其传递给 pagy,它甚至不会尝试使用默认值。

编辑:我忘了说:你可能想试试 pagy arel extra 那个:

adds specialized pagination for collections from sql databases with GROUP BY clauses, by computing the total number of results with COUNT(*) OVER ().

感谢所有的评论和回答,我终于找到了一个解决方案,我认为这是解决这个问题的最佳方法。

首先,问题发生在我调用 pagy 时,它试图通过附加 .count(:all) 来计算我的范围。这就是导致错误的原因。解决方案是不在 select() 中创建“字段”并在 .order() 中使用它。

所以这是正确的代码:

def self.order_by_votes(show = nil)
  count = if show
            "case when votes.show_id = #{show.id} AND NOT votes.fulfilled then 1 else null end"
          else
            'nullif(votes.fulfilled, true)'
          end
  left_joins(:votes).group(:id)
                    .uniq!(:group)
                    .select("people.*, COUNT(#{count}) as vote_count")
                    .order(Arel.sql("COUNT(#{count}) DESC"))
end

这会根据未完成的投票数对人数进行排序,能够仅计算给定节目的投票数,并且它适用于 pagy()pagy_arel(),其中我的情况更合适,因此结果可以正确分页。