3

我有一个 Ruby on Rails 2.3.x 应用程序,我正在尝试从我自己的 VPS 迁移到 Heroku,包括从 SQLite(开发)和 MySQL(生产)移植到 Postgres。

这是我正在使用的典型 Rails 调用:

spots = Spot.paginate(:all, :include => [:thing, :user, :store, {:thing => :tags}, {:thing => :brand}], :group => :thing_id, :order => order, :conditions => conditions, :page => page, :per_page => per_page)

问题 1:我收到很多错误,例如PG::Error: ERROR: column "spots.id" must appear in the GROUP BY clause or be used in an aggregate function. SQLite/MySQL 显然在这里更宽容。当然,我可以通过将指定字段添加到我的:group参数来轻松解决这些问题,但我觉得我的代码搞砸了。有没有更好的办法?

问题 2:如果我输入 Postgres 缺少的所有 GROUP BY 列,我最终会得到以下语句(仅:group已更改):

spots = Spot.paginate(:all, :include => [:thing, :user, :store, {:thing => :tags}, {:thing => :brand}], :group => 'thing_id,things.id,users.id,spots.id', :order => order, :conditions => conditions, :page => page, :per_page => per_page)

这又会产生以下 SQL 代码:

SELECT * FROM (SELECT DISTINCT ON ("spots".id) "spots".id, spots.created_at AS alias_0 FROM "spots"  
LEFT OUTER JOIN "things" ON "things".id = "spots".thing_id 
WHERE (spots.recommended_to_user_id = 1 OR spots.user_id IN (1) OR things.is_featured = 't')  
GROUP BY thing_id,things.id,users.id,spots.id) AS id_list 
ORDER BY id_list.alias_0 DESC LIMIT 16 OFFSET 0;

...产生错误PG::Error: ERROR: missing FROM-clause entry for table "users"。我该如何解决这个问题?

4

2 回答 2

4

问题一:

...有没有更好的办法?

是的。GROUP BY从 PostgreSQL 9.1 开始,表的主键在逻辑上涵盖了子句中表的所有列。我引用了9.1 版的发行说明

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

问题2:

以下语句...产生错误

PG::Error: 错误:缺少表“用户”的 FROM 子句条目

我该如何解决这个问题?

首先(一如既往!),我对您的查询进行了格式化以使其更易于理解。罪魁祸首大胆强调:

SELECT *
FROM  (
   SELECT DISTINCT ON (spots.id)
          spots.id, spots.created_at AS alias_0
   FROM   spots  
   LEFT   JOIN things ON things.id = spots.thing_id 
   WHERE (spots.recommended_to_user_id = 1 OR
          spots.user_id IN (1) OR
          things.is_featured = 't')  
   GROUP  BY thing_id, things.id, users.id, spots.id
   ) id_list 
ORDER  BY id_list.alias_0 DESC
LIMIT  16
OFFSET 0;

现在一切都很明显了,对吧?
好吧,不是全部。还有很多。DISTINCT ON在同GROUP BY一个查询中,它有它的用途,但不是在这里。从根本上简化为:

SELECT s.id, s.created_at AS alias_0
FROM   spots s
WHERE  s.recommended_to_user_id = 1 OR
       s.user_id = 1 OR
       EXISTS (
          SELECT 1 FROM things t
          WHERE  t.id = s.thing_id
          AND    t.is_featured = 't')
ORDER  BY s.created_at DESC
LIMIT  16;

EXISTS半连接避免了后期需要先验GROUP BY。如果我对丢失表定义的假设成立,这应该会快得多(除了正确之外)。

于 2012-11-10T04:28:30.757 回答
0

走“纯 SQL”路线为我打开了一罐蠕虫,所以我尝试保留 will_paginate gem 并调整Spot.paginate参数。事实证明,该:joins参数非常有用。

这目前对我有用:

spots = Spot.paginate(:all, :include => [:thing, {:thing => :tags}, {:thing => :brand}], :joins => [:user, :store, :thing], :group => 'thing_id,things.id,users.id,spots.id', :order => order, :conditions => conditions, :page => page, :per_page => per_page)
于 2013-01-02T15:33:56.297 回答