0

我正在使用以下模型构建 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.

当我对 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" = $1 GROUP BY "people"."id" ORDER BY people.vot...
                                                             ^

如果我使用 转储 SQL @people.to_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" 按 people.vote_count DESC 排序

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

(PS:我用一个点命名了字段people.vote_count,所以我可以在我的视图中访问它,而无需执行另一个 SQL 查询来实际查看视图中每个人的投票计数(不确定这是否有效)但我得到了即使我简单地命名该字段,也会出现同样的错误vote_count。)

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

4

3 回答 3

1

从历史上看,仅在示波器上使用就出现了很多 AR 问题(和错误),count但我不确定它们是否真的全部消失了。

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

您的范围可能会变得复杂,并且默认页面collection.count(:all)可能无法获得实际计数。在这种情况下,您可以使用一些自定义语句获得正确的计数,并将其传递给 pagy。

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

注意:pagey 将有效地跳过其内部计数查询,而只会使用传递的:count变量。

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

编辑:我忘了提:你可能想试试pagy arel 额外的:

通过GROUP BY使用COUNT(*) OVER ().

于 2021-09-22T07:00:03.657 回答
1

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

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

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

无论如何,解决方案是不要试图把你vote_count放在people桌子上:

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

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

于 2021-09-20T21:29:36.577 回答
0

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

首先,当我调用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()在我的情况下更适合,因此可以对结果进行适当的分页.

于 2021-09-22T20:03:28.650 回答