Rails用范围扩展字段,PG不喜欢它

Kar*_*arl 8 postgresql activerecord ruby-on-rails

我有一个小部件模型.窗口小部件属于商店模型,属于区域模型,属于公司.在公司模型中,我需要找到所有相关的小部件.简单:

class Widget < ActiveRecord::Base
  def self.in_company(company)
    includes(:store => {:area => :company}).where(:companies => {:id => company.id})
  end
end
Run Code Online (Sandbox Code Playgroud)

这将产生这个美丽的查询:

> Widget.in_company(Company.first).count

SQL (50.5ms)  SELECT COUNT(DISTINCT "widgets"."id") FROM "widgets" LEFT OUTER JOIN "stores" ON "stores"."id" = "widgets"."store_id" LEFT OUTER JOIN "areas" ON "areas"."id" = "stores"."area_id" LEFT OUTER JOIN "companies" ON "companies"."id" = "areas"."company_id" WHERE "companies"."id" = 1
 => 15088 
Run Code Online (Sandbox Code Playgroud)

但是,我后来需要在更复杂的范围内使用此范围.问题是AR通过选择单个字段来扩展查询,这些字段在PG中失败,因为所选字段必须在GROUP BY子句或聚合函数中.

这是更复杂的范围.

def self.sum_amount_chart_series(company, start_time)
  orders_by_day = Widget.in_company(company).archived.not_void.
                  where(:print_datetime => start_time.beginning_of_day..Time.zone.now.end_of_day).
                  group(pg_print_date_group).
                  select("#{pg_print_date_group} as print_date, sum(amount) as total_amount")

end

def self.pg_print_date_group
  "CAST((print_datetime + interval '#{tz_offset_hours} hours') AS date)"
end
Run Code Online (Sandbox Code Playgroud)

这是它在PG投掷的选择:

> Widget.sum_amount_chart_series(Company.first, 1.day.ago)

SELECT "widgets"."id" AS t0_r0, "widgets"."user_id" AS t0_r1,<...BIG SNIP, YOU GET THE IDEA...> FROM "widgets" LEFT OUTER JOIN "stores" ON "stores"."id" = "widgets"."store_id" LEFT OUTER JOIN "areas" ON "areas"."id" = "stores"."area_id" LEFT OUTER JOIN "companies" ON "companies"."id" = "areas"."company_id" WHERE "companies"."id" = 1 AND "widgets"."archived" = 't' AND "widgets"."voided" = 'f' AND ("widgets"."print_datetime" BETWEEN '2011-04-24 00:00:00.000000' AND '2011-04-25 23:59:59.999999') GROUP BY CAST((print_datetime + interval '-7 hours') AS date)
Run Code Online (Sandbox Code Playgroud)

哪个会生成此错误:

PGError:错误:列"widgets.id"必须出现在GROUP BY子句中或用于聚合函数LINE 1:SELECT"widgets"."id"AS t0_r0,"widgets"."user_id ...

如何重写Widget.in_company范围,以便AR不扩展选择查询以包含每个Widget模型字段?

Den*_*rdy 10

正如Frank解释的那样,PostgreSQL将拒绝任何不返回可重现行集的查询.

假设您有一个类似的查询:

select a, b, agg(c)
from tbl
group by a
Run Code Online (Sandbox Code Playgroud)

PostgreSQL将拒绝它,因为bgroup by语句中未指定.相比之下,在MySQL中运行它,它将被接受.但是,在后一种情况下,启动一些插入,更新和删除,并且磁盘页面上的行的顺序最终不同.

如果内存服务,实现细节是这样的,MySQL实际上将按a,b排序并返回集合中的第一个b.但就SQL标准而言,行为是未指定的 - 果然,PostgreSQL 在运行聚合函数之前并不总是排序.

这可能会导致bPostgreSQL中结果集的值不同.因此,除非你更具体,否则PostgreSQL会产生错误:

select a, b, agg(c)
from tbl
group by a, b
Run Code Online (Sandbox Code Playgroud)

Frank强调的是,在PostgreSQL 9.1中,if a是主键,而不是b未指定 - 当适用的主键意味着唯一的行时,计划程序已被教导忽略后续的分组字段.

特别是对于您的问题,您需要按照当前的方式指定您的组,以及您基于聚合的每个字段,即"widgets"."id", "widgets"."user_id", [snip]但不是类似的sum(amount),这些是聚合函数调用.

作为一个偏离主题的旁注,我不确定你的ORM /模型是如何工作的,但它生成的SQL并不是最佳的.许多左外连接似乎应该是内连接.这将导致计划员在适用的情况下选择适当的连接顺序.


Fra*_*ens 3

PostgreSQL 版本 9.1(目前为测试版)可能会解决您的问题,但前提是对主键存在功能依赖性。

从发行说明来看:

当在 GROUP BY 子句中指定主键时,允许查询目标列表中存在非 GROUP BY 列 (Peter Eisentraut)

其他一些数据库系统已经允许这种行为,并且由于主键,结果是明确的。

您可以运行测试,看看它是否可以解决您的问题。如果您可以等待生产版本,这可以在不更改代码的情况下解决问题。