在Postgres查询中确定目标记录的偏移量

时间:2015-01-21 16:36:20

标签: sql json postgresql aggregate-functions paging

问题:我正在Postgres上构建一个包含以下参数的RESTful API:

  • 表格中特定记录的标识符<id>
  • 过滤和排序参数(可选)
  • count或页面尺寸(可选)

例如:/presidents/16?filter=!isAlive&sort=lastName,givenNames&count=5

API返回count(或可能更少)记录,其中包含<id>指定的记录以及返回的offsetcount条记录。

在上面的示例中,结果可能如下所示:

{
  "count": 5,
  "offset": 20,
  "records": [
    { "id": 17, "givenNames": "Andrew",    "lastName": "Johnson", "isAlive": false },
    { "id": 36, "givenNames": "Lyndon B.", "lastName": "Johnson", "isAlive": false },
    { "id": 35, "givenNames": "John F.",   "lastName": "Kennedy", "isAlive": false },
    { "id": 16, "givenNames": "Abraham",   "lastName": "Lincoln", "isAlive": false },
    { "id": 4,  "givenNames": "James",     "lastName": "Madison", "isAlive": false }
  ]
}

当前解决方案:我当前的方法是串行进行三次调用(将它们组合成一个嵌套查询,但效率问题仍然存在),我正在寻找更好的方法。

  1. 获取目标<id>引用的记录,在查询#2中使用。

    • e.g。 select * from presidents where id = 16
  2. 获取目标<id>

    的偏移量
    • e.g。 select count(*) from presidents where lastName < 'Lincoln' and givenNames < 'Abraham' and not isAlive order by lastName, givenNames, id
  3. 使用传递(或默认)offset和#2中的limit计算相应的countcount(*)后,检索记录页

    • e.g。 select * from presidents where not isAlive order by lastName, givenNames, id offset 20 limit 5

  4. 更新了SQL

    我在下面的答案中提到了@ErwinBrandstetter所提供的内容,这与我在寻找怪物陈述方面的内容非常接近,并将其改为:

    WITH prez AS (SELECT lastName, givenNames, id, isAlive FROM presidents WHERE not isAlive),
    cte AS (SELECT * FROM prez WHERE id = 16),
    start AS (SELECT (COUNT(*)/5)*5 as "offset" FROM prez WHERE (lastName, givenNames, id, isAlive) < (TABLE cte))
    SELECT row_to_json(sub2) AS js
    FROM  (
     SELECT (SELECT * FROM start) AS "offset"
           , count(*) AS "count"
           , array_agg(sub1) AS records
     FROM  (
        SELECT * from prez
        ORDER  BY lastName, givenNames, id
        OFFSET (SELECT * from start) LIMIT  5
        ) sub1
    ) sub2;
    

    SQL Fiddle

    Postgres是否有更简单的方法来确定给定查询的给定记录的偏移量?

    相关问题:

1 个答案:

答案 0 :(得分:2)

您正在寻找的一站式商店:

WITH cte AS (SELECT lastName, givenNames, id AS x FROM presidents WHERE id = 16)
SELECT row_to_json(sub2) AS js
FROM  (
   SELECT (SELECT count(*) FROM presidents
           WHERE (lastName, givenNames, id) < (TABLE cte)) AS "offset"
         , count(*) AS "count"
         , array_agg(sub1) AS records
   FROM  (
      SELECT id, givenNames, lastName, isAlive
      FROM   presidents
      WHERE (lastName, givenNames, id) >= (TABLE cte)
      ORDER  BY lastName, givenNames, id
      LIMIT  5
      ) sub1
  ) sub2;

SQL Fiddle.

原始查询#2 不正确

SELECT * FROM presidents
WHERE lastName < 'Lincoln'
AND   givenNames < 'Abraham'
AND   id < 16 ...

要保留您的排序顺序,必须:

SELECT * FROM presidents
WHERE (lastName, givenNames, id) < ('Lincoln', 'Abraham', 16) ...

比较行类型,而不是在各列上使用AND表达式,这会产生完全不同的结果。这就是ORDER BY有效运作的方式。

除了(lastName, givenNames, id)上的PRIMARY KEY之外,您应该id row_number()以使快速

变量与CTE中的WITH prez AS ( SELECT lastName, givenNames, id, isAlive , row_number() OVER (ORDER BY lastName, givenNames, id) AS rn FROM presidents WHERE NOT isAlive ) , x AS (SELECT ((rn-1)/5)*5 AS "offset" FROM prez WHERE id = 16) SELECT row_to_json(sub2) AS js FROM ( SELECT (SELECT "offset" FROM x) , count(*) AS "count" , array_agg(sub1) AS records FROM ( SELECT lastName, givenNames, id, isAlive FROM prez WHERE rn > (SELECT "offset" FROM x) ORDER BY rn LIMIT 5 ) sub1 ) sub2;

根据您的更新要求。

EXPLAIN ANALYZE

multicolumn index

使用{{1}}测试效果。