为啥 Postgres 不接受我的计数列?
Posted
技术标签:
【中文标题】为啥 Postgres 不接受我的计数列?【英文标题】:Why does Postgres not accept my count column?为什么 Postgres 不接受我的计数列? 【发布时间】:2021-11-14 11:42:33 【问题描述】:我正在使用以下模型构建 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...
^
如果我使用@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 有一些弃用警告,但我找不到任何文档,所以我不确定我做对了,没有那个错误仍然存在部分。)
【问题讨论】:
列不存在可能是迁移未运行的症状(或者您在运行后编辑迁移以添加列的情况)。您在数据库引擎之间切换的事实很可能只是混淆了一些东西。如果您确实编辑了迁移,请运行 db:rollback,然后再次运行 db:migrate。 不,没有迁移。该列在数据库中不存在,它是在 SQL 语句中创建的:SELECT ... AS people.vote_count
。 SQLite 接受这一点,但 Postgres 不接受。 ://
哦,抱歉,我没有仔细阅读。就像下面提到的 Mu 一样,Postgres 中的别名必须只是一个列名,但它没有帮助的原因是因为 Pagy 在计算集合大小时是您的所有 SELECT 子句的blowing away。这会破坏别名,因此 Postgres 会感到困惑。作为一个快速修复,我会尝试people.order("COUNT(#count) DESC")
以避免使用别名。
是的,我已将其固定为在末尾附加 count(:all)
。如果我尝试更详细的计数(只计算未完成的选票,或者只计算给定节目的选票),那么我会得到ActiveRecord::UnknownAttributeReference
。不幸的是,我不得不将雄心缩小到只做left_joins(:votes).group(:id).uniq!(:group).order('COUNT(votes.id)')
。也许以后我可以找到如何对不同类型的选票进行正确的排序。 ://
罗伯特,佩吉并没有“吹嘘”任何事情。它在您传递给它的任何范围内调用count(:all)
。获取集合计数的非常标准的方法。但是,如果在你数数的时候瞄准镜爆炸了,那么……肯定是瞄准镜有问题……而且这与佩吉无关。
【参考方案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
输出中(因为它是基于表的列生成的)但您调用访问器方法。
【讨论】:
我删除了people.
部分,但我仍然收到错误消息。我已经追踪到 Pagy 的错误。当我调用pagy(@people, ...)
对我的收藏进行分页时,它似乎出现了。如果我只是按原样检查集合,它看起来很好。
听起来您在 Pagy 中发现了一个错误。抱歉,我对佩吉一无所知,所以不能多说。【参考方案2】:
从历史上看,仅通过在示波器上使用 count
来获得正确的计数就存在不少 AR 问题(和错误),我不确定它们是否真的都消失了。
这取决于范围(AR 版本、关系、组、排序、uniq 等)。一个 gem 必须在作用域上通用使用的默认 count
调用并不是一个万能的解决方案。由于这个已知原因,Pagy 允许您将正确的计数传递给它的 pagy
方法,如 Pagy documentation 中所述。
您的范围可能会变得复杂,默认页面
collection.count(:all)
可能无法获得实际计数。在这种情况下,您可以使用一些自定义语句获得正确的计数,并将其传递给 pagy。
@pagy, @records = pagy(collection, count: your_count)
注意:pagy 将有效地跳过其内部计数查询,而只会使用传递的
:count
变量。
所以...只需获取您自己的计算计数并将其传递给pagy
,它甚至不会尝试使用默认值。
编辑:我忘了提:你可能想试试pagy arel extra:
通过
COUNT(*) OVER ()
计算结果总数,为带有GROUP BY
子句的sql 数据库集合添加专门的分页。
【讨论】:
谢谢,这真的很有帮助。赞成。但我认为我有办法成功地制作我的范围,完全按照我想要的方式,以一种与 Pagy 兼容的方式。如果我成功了,我会把它作为问题的答案发布,但这也很好。【参考方案3】:感谢所有的 cmets 和答案,我终于找到了一个我认为是解决这个问题的最佳方法的解决方案。
首先,当我调用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()
,这在我的情况下要好得多适合,所以结果可以正确分页。
【讨论】:
相当复杂的解决方案!很高兴知道 Pagy 与这个问题无关 :)以上是关于为啥 Postgres 不接受我的计数列?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在我的 Postgres 函数中使用 IF 语句时出现语法错误?