Найти предыдущие / следующие строки по порядку при запросе определенного идентификатора - PullRequest
3 голосов
/ 29 февраля 2012

У меня есть таблица, такая как (упрощенная до крайности, чтобы сделать ее более понятной)

create table mytable (
  id integer not null,
  owner text not null,
  order_field_1 integer not null,
  order_field_2 integer not null
)

Я пытаюсь получать идентификаторы следующего и предыдущего элементов каждый раз, когда получаю строку из базы данных, чтобы разрешить навигацию. Строки упорядочены не по id, а по ORDER BY order_field_1 DESC, order_field_2 DESC.

При получении последних записей для владельца, у меня нет проблем, чтобы найти то, что я хочу, используя окно и привести / отставание

SELECT
  id,
  owner,
  lag(id) over w AS previous_id,
  lead(id) over w AS next_id
FROM
  mytable
WHERE
  owner = 'someuser'
WINDOW w AS (
  ORDER BY order_field_1 DESC, 
  order_field_2 DESC
)
ORDER BY 
  order_field_1 DESC, 
  order_field_2 DESC
LIMIT
  5

Это написано по памяти, но это суть, и она отлично работает.

Моя проблема в том, что когда я хочу получить определенную строку, используя владельца и идентификатор, но я все еще хочу найти предыдущий и следующий идентификаторы, я больше не могу использовать оконную функцию, так как где возвращается только одна строка, и мое текущее решение сделать подзапрос для получения обоих идентификаторов навигации не очень хорошая производительность

Например, (я поставил только предыдущий идентификатор, так как он следующий для следующего)

SELECT
  m1.id,
  m1.owner,
  (
    SELECT 
      m2.id 
    FROM 
      mytable m2 
    WHERE 
      m2.owner = m1.owner 
      AND m2.id != m1.id 
      AND (
        m2.order_field_1 < m1.order_field_1 
        OR (
          m2.order_field_1 = m1.order_field_1 
          AND m2.order_field_2 <= m1.order_field_2
        ) 
      ORDER BY 
        m2.order_field_1 DESC, 
        m2.order_field_2 DESC
      LIMIT
        1
  ) AS previous_id
FROM
  mytable m1
WHERE
  owner = 'someuser'
  AND id = 12345

Итак, я выбираю свою строку, затем выбираю первую строку от того же пользователя с другим идентификатором, то есть либо с более низким полем порядка_1, либо с тем же, но с более низким полем порядка_2.

Это не очень эффективно, и я получаю плохие результаты, и мне интересно, есть ли у кого-нибудь идеи о том, как я мог бы это улучшить?

Пример набора данных:

id |    owner | order_field_1 | order_field_2
 1 | someuser |             4 |             2
 2 | someuser |             2 |             8
 3 | someuser |             4 |             3
 4 | someuser |             3 |             2
 5 | someuser |             4 |             6
 6 | someuser |             4 |             5

Заказанный:

id |    owner | order_field_1 | order_field_2
 5 | someuser |             4 |             6
 6 | someuser |             4 |             5
 3 | someuser |             4 |             3
 1 | someuser |             4 |             2
 4 | someuser |             3 |             2
 2 | someuser |             2 |             8

Если я выберу владельца = 'someuser' и id = 3, предыдущий_ид должен быть 1, следующий_ид должен быть 6.

Если я выберу владельца = 'someuser' и id = 1, предыдущий_ид должен быть 4, следующий_ид должен быть 3.

Заранее спасибо за любую помощь

Ответы [ 2 ]

2 голосов
/ 29 февраля 2012

С оконными функциями и CTE

намного дешевле иметь уже WHERE owner = 'someuser' в CTE:

WITH t AS (
    SELECT id
          ,owner
          ,lag(id)  over w AS previous_id
          ,lead(id) over w AS next_id
    FROM   mytable
    WHERE  owner = 'someuser'
    WINDOW w AS (ORDER BY order_field_1 DESC, order_field_2 DESC)
    )
SELECT *
FROM   t
WHERE  id = 3

Кроме того, как только вы выбираетев одной строке нет необходимости в ORDER BY в финале SELECT.

__

Старая школа с подзапросами

Это довольно уродливо, но можетбудет быстрее, если в owner будет много строк.Вам придется протестировать ...

SELECT id
     , owner
     ,(SELECT id
       FROM   tbl p
       WHERE  p.owner = t.owner -- same owner
       AND    p.id   <> t.id    -- different id
       AND    p.order_field_1 <= t.order_field_1
       AND    p.order_field_2 <= t.order_field_2
       ORDER  BY order_field_1 DESC
               , order_field_2 DESC
       LIMIT  1) AS previous_id

     ,(SELECT id
       FROM   tbl n
       WHERE  n.owner = t.owner
       AND    n.id   <> t.id
       AND    n.order_field_1 >= t.order_field_1
       AND    n.order_field_2 >= t.order_field_2
       ORDER  BY order_field_1
               , order_field_2
       LIMIT  1) AS next_id
FROM   tbl t
WHERE  owner = 'someuser'
AND    id = 3

Этот вариант работает и для более старых версий PostgreSQL.
Ключ к производительности - это, конечно, правильные индексы.

1 голос
/ 29 февраля 2012

Как насчет нахождения значений запаздывания и опережений до применения предложения WHERE?

WITH T as (
  SELECT
    id,
    owner,
    lag(id) over w AS previous_id,
    lead(id) over w AS next_id
  FROM
    mytable
  WINDOW w AS (
    ORDER BY order_field_1 DESC, 
    order_field_2 DESC
  )
)
  SELECT * FROM T
  WHERE
    owner = 'someuser' AND id = 3
  ORDER BY 
    order_field_1 DESC, 
    order_field_2 DESC
...