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

Determine a target record's offset in a Postgres query

问题: 我正在 Postgres 之上构建一个 RESTful API,其中包括以下参数:

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

API returns count(或可能更少)包含由 <id>offset 和 [=13= 指定的记录的记录] 返回的记录。

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

{
  "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。

    • 例如select * from presidents where id = 16
  2. 获取目标的偏移量<id>

    • 例如select count(*) from presidents where lastName < 'Lincoln' and givenNames < 'Abraham' and not isAlive order by lastName, givenNames, id
  3. 在计算出适当的 offsetlimit 之后,使用传递的(或默认的)count 和来自 #2 的 count(*),检索记录页。

    • 例如select * from presidents where not isAlive order by lastName, givenNames, id offset 20 limit 5

已更新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 中是否有更简单的方法来确定给定查询的给定记录的偏移量?

相关问题:

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

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-ing 表达式,这会产生完全不同的结果。这就是 ORDER BY 有效运作的方式。

除了 id 上的 PRIMARY KEY 之外,您还应该在 (lastName, givenNames, id) 上有一个 multicolumn index,才能使这个 快速

在 CTE 中具有 row_number() 的变体

根据您更新后的要求。

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;

SQL Fiddle.

使用 EXPLAIN ANALYZE 测试性能。