我有一个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"
。我该如何解决这个问题?
答案 0 :(得分:4)
......有更好的方法吗?
是。从PostgreSQL 9.1开始,表的主键在逻辑上涵盖了GROUP BY
子句中表的所有列。我引用release notes for version 9.1:
在主要时,允许查询目标列表中的非GROUP BY列 key在GROUP BY子句中指定(Peter Eisentraut)
以下语句......产生错误
PG ::错误:错误:表“users”缺少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
先验的需要。这应该快得多(除了正确) - 如果我对丢失的表定义的假设成立。
答案 1 :(得分: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)