为啥 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 语句时出现语法错误?

为啥没有为 Postgres 视图启用行级安全性?

Asp.Net MVC 的ContentResult 返回字符串,jquery.post的回调函数不接受,为啥?

post方式发送json为啥后端接受不到数据

为啥我的 Postgres SQL 查询不使用索引

数组列上的 Postgres 全文搜索