Что такое SQL для «следующий» и «предыдущий» в таблице? - PullRequest
11 голосов
/ 15 октября 2008

У меня есть таблица предметов, с каждым из которых связана дата. Если у меня есть дата, связанная с одним элементом, как я могу запросить базу данных с помощью SQL, чтобы получить «предыдущий» и «последующий» элементы в таблице?

Невозможно просто добавить (или вычесть) значение, поскольку даты не имеют регулярного разрыва между ними.

Одним из возможных приложений могут быть ссылки «предыдущий / следующий» в фотоальбоме или веб-приложении блога, где основные данные находятся в таблице SQL.

Я думаю, что есть два возможных случая:

Во-первых , где каждая дата уникальна:

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

1,3,8,19,67,45

Какой запрос (или запросы) будет давать 3 и 19, если в качестве параметра указано 8? (или строки 3,8,19). Обратите внимание, что не всегда нужно возвращать три строки - в конце последовательности одна будет отсутствовать.

Во-вторых , если существует отдельный уникальный ключ для упорядочения элементов, по какому запросу возвращается набор, окружающий дату? Ожидаемый заказ - по дате, затем по ключу.

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

(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8

Какой запрос для '8' возвращает набор:

2:3,3:8,4:8,16:8,5:19

или какой запрос генерирует таблицу:

key date prev-key next-key
1   1    null     2
2   3    1        3
3   8    2        4
4   8    3        16
5   19   16       10
10  19   5        11
11  67   10       15
15  45   11       null
16  8    4        5

Порядок таблиц не важен - только поля следующего и предварительного ключа.


У TheSoftwareJedi и Cade Roux есть решения, которые работают с наборами данных, которые я выложил вчера вечером. По второму вопросу оба набора данных для этого набора данных кажутся неудачными:

(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8

Ожидаемый порядок - по дате, а затем по ключу, поэтому один ожидаемый результат может быть:

2:3,3:8,4:8,16:8,5:19

и еще:

key date prev-key next-key
1   1    null     2
2   3    1        3
3   8    2        4
4   8    3        16
5   19   16       10
10  19   5        11
11  67   10       15
15  45   11       null
16  8    4        5

Порядок таблиц не важен - только поля следующего и предварительного ключа.

Ответы [ 7 ]

10 голосов
/ 15 октября 2008

Выберите максимум (элемент) из данных, где элемент <8 </p>

Union

Выберите мин (элемент) из данных, где элемент> 8

Но, как правило, более полезно думать о sql для операций, ориентированных на множество, а не итеративных операций.

9 голосов
/ 15 октября 2008

Самосоединение.

Для таблицы:

/*
CREATE TABLE [dbo].[stackoverflow_203302](
    [val] [int] NOT NULL
) ON [PRIMARY]
*/

С параметром @val

SELECT cur.val, MAX(prv.val) AS prv_val, MIN(nxt.val) AS nxt_val
FROM stackoverflow_203302 AS cur
LEFT JOIN stackoverflow_203302 AS prv
    ON cur.val > prv.val
LEFT JOIN stackoverflow_203302 AS nxt
    ON cur.val < nxt.val
WHERE cur.val = @val
GROUP BY cur.val

Вы можете сделать это хранимой процедурой с выходными параметрами или просто присоединить ее как коррелированный подзапрос к данным, которые вы извлекаете.

Без параметра для ваших данных результат будет:

val         prv_val     nxt_val
----------- ----------- -----------
1           NULL        3
3           1           8
8           3           19
19          8           45
45          19          67
67          45          NULL

Для измененного примера вы используете это как коррелированный подзапрос:

/*
CREATE TABLE [dbo].[stackoverflow_203302](
    [ky] [int] NOT NULL,
    [val] [int] NOT NULL,
    CONSTRAINT [PK_stackoverflow_203302] PRIMARY KEY CLUSTERED (
        [ky] ASC
    )
)
*/

SELECT cur.ky AS cur_ky
        ,cur.val AS cur_val
        ,prv.ky AS prv_ky
        ,prv.val AS prv_val
        ,nxt.ky AS nxt_ky
        ,nxt.val as nxt_val
FROM (
    SELECT cur.ky, MAX(prv.ky) AS prv_ky, MIN(nxt.ky) AS nxt_ky
    FROM stackoverflow_203302 AS cur
    LEFT JOIN stackoverflow_203302 AS prv
        ON cur.ky > prv.ky
    LEFT JOIN stackoverflow_203302 AS nxt
        ON cur.ky < nxt.ky
    GROUP BY cur.ky
) AS ordering
INNER JOIN stackoverflow_203302 as cur
    ON cur.ky = ordering.ky
LEFT JOIN stackoverflow_203302 as prv
    ON prv.ky = ordering.prv_ky
LEFT JOIN stackoverflow_203302 as nxt
    ON nxt.ky = ordering.nxt_ky

С выходом, как и ожидалось:

cur_ky      cur_val     prv_ky      prv_val     nxt_ky      nxt_val
----------- ----------- ----------- ----------- ----------- -----------
1           1           NULL        NULL        2           3
2           3           1           1           3           8
3           8           2           3           4           19
4           19          3           8           5           67
5           67          4           19          6           45
6           45          5           67          NULL        NULL

В SQL Server я предпочитаю делать подзапрос общим выражением таблицы. Это делает код более линейным, менее вложенным и более легким для отслеживания при большом количестве вложений (кроме того, для некоторых повторных объединений требуется меньше повторений).

1 голос
/ 28 ноября 2011

Если ваша СУБД поддерживает LAG и LEAD, это просто (Oracle, PostgreSQL, SQL Server 2012)

Они позволяют выбрать строку с любой стороны любой данной строки в одном запросе

1 голос
/ 15 октября 2008

Моя собственная попытка установить решение на основе TheSoftwareJedi.

Первый вопрос:

select date from test where date = 8
union all
select max(date) from test where date < 8
union all
select min(date) from test where date > 8
order by date;

Второй вопрос:

При отладке я использовал набор данных:

(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8,17:3,18:1

чтобы получить этот результат:

select * from test2 where date = 8
union all
select * from (select * from test2
                   where date = (select max(date) from test2 
                                     where date < 8)) 
    where key = (select max(key) from test2 
                    where date = (select max(date) from test2 
                                      where date < 8))
union all
select * from (select * from test2
                   where date = (select min(date) from test2 
                                     where date > 8)) 
    where key = (select min(key) from test2 
                    where date = (select min(date) from test2 
                                      where date > 8))
order by date,key;

В обоих случаях окончательный порядок по пункту строго не обязателен.

1 голос
/ 15 октября 2008
SELECT 'next' AS direction, MIN(date_field) AS date_key
FROM table_name
  WHERE date_field > current_date
GROUP BY 1 -- necessity for group by varies from DBMS to DBMS in this context
UNION
SELECT 'prev' AS direction, MAX(date_field) AS date_key
  FROM table_name
  WHERE date_field < current_date
GROUP BY 1
ORDER BY 1 DESC;

Производит:

direction  date_key
---------  --------
prev              3
next             19
1 голос
/ 15 октября 2008

Во-первых, это должно сработать (важен ORDER BY):

select min(a)
from theTable
where a > 8

select max(a)
from theTable
where a < 8

За второй вопрос, который я умоляю вас задать ...:

    select * 
    from theTable
    where date = 8

    union all

    select *
    from theTable
    where key = (select min(key) 
                 from theTable
                 where key > (select max(key)
                              from theTable
                              where date = 8)
                )

    union all

    select *
    from theTable
    where key = (select max(key) 
                 from theTable
                 where key < (select min(key)
                              from theTable
                              where date = 8)
                )

    order by key
0 голосов
/ 15 октября 2008

Попробуйте это ...

SELECT TOP 3 * FROM YourTable
WHERE Col >= (SELECT MAX(Col) FROM YourTable b WHERE Col < @Parameter)
ORDER BY Col
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...