3

我有一个带有author_id字段的书籍表。

我想获取一组书籍,其中仅包含每位作者的一本书。具有最新updated_at字段的那个。

像 Postgres 这样的直接方法的问题Books.all.group('author_id')是它需要在其GROUP BY块中的所有请求字段。(见https://stackoverflow.com/a/6106195/1245302

但是我需要为每个作者获取所有 Book 对象,最近的一个,忽略所有其他字段。在我看来,DBMS 有足够的数据来准确找到我想要的行,至少我可以自己做到这一点,而无需任何其他字段GROUP BY。:)

是否有任何简单的 Rails 3 + Postgres(版本 < 9)或独立于 SQL 实现的方法来获得它?

更新 Postgres 的不错的解决方案:

books.unscoped.select('DISTINCT ON(author_id) *').order('author_id').order('updated_at DESC') 

但!仍然存在问题 - 结果author_id首先排序,但我需要updated_at在相同的author_id-s 内排序(例如,查找前 10 位最近的书籍作者)。

而且 Postgres 不允许您更改查询中的ORDER BY参数顺序DISTINCT:(

4

2 回答 2

1

我不了解 Rails,但希望向您展示您想要的 SQL 将帮助您找到一种生成正确 SQL 的方法。

SELECT DISTINCT ON (author_id) *
  FROM Books
  ORDER BY author_id, updated_at DESC;

DISTINCT ON (author_id)部分不应与结果列列表的一部分混淆——它只是说每个 author_id 将有一行。子句中的列表DISTINCT ON必须是ORDER BY此类查询中子句的前导部分,并且保留的行是根据子句的其余部分首先排序的行ORDER BY

对于大量行,这种编写查询的方式通常比任何基于GROUP BY或窗口函数的解决方案快得多,通常快一个数量级或更多。不过,它是 PostgreSQL 扩展;所以它不应该用在旨在可移植的代码中。

如果您想在另一个查询中使用此结果集(例如,查找 10 个最近更新的作者),有两种方法可以做到这一点。您可以使用子查询,如下所示:

SELECT *
  FROM (SELECT DISTINCT ON (author_id) *
          FROM Books
          ORDER BY author_id, updated_at DESC) w
  ORDER BY updated_at DESC
  LIMIT 10;

您也可以使用 CTE,如下所示:

WITH w AS (
  SELECT DISTINCT ON (author_id) *
    FROM Books
    ORDER BY author_id, updated_at DESC)
SELECT * FROM w
  ORDER BY updated_at DESC
  LIMIT 10;

关于 CTE 的通常建议在这里成立:仅在没有其他方式编写查询或需要通过引入优化障碍来强制规划器时使用它们。这些计划非常相似,但通过 CTE 扫描传递中间结果会增加一点开销。在我的小型测试集上,CTE 表格慢了 17%。

于 2012-04-13T19:30:05.893 回答
0

这是迟到的,但在回答有关覆盖/重置默认顺序的问题时,请使用.reorder(nil).order(:whatever_you_want_instead)

(我无法发表评论,所以现在发布作为答案)

于 2017-03-17T19:18:44.860 回答