Есть ли способ получить доступ к значению «предыдущая строка» в инструкции SELECT? - PullRequest
73 голосов
/ 02 апреля 2009

Мне нужно рассчитать разницу столбца между двумя строками таблицы. Есть ли способ сделать это напрямую в SQL? Я использую Microsoft SQL Server 2008.

Я ищу что-то вроде этого:

SELECT value - (previous.value) FROM table

Представляя, что «предыдущая» переменная ссылается на последнюю выбранную строку. Конечно, при таком выборе я получу n-1 ряд, выбранный в таблице с n строками, что, вероятно, на самом деле не то, что мне нужно.

Возможно ли это как-то?

Ответы [ 7 ]

52 голосов
/ 02 апреля 2009

В SQL нет встроенного понятия порядка, поэтому вам нужно упорядочить по некоторому столбцу, чтобы это имело смысл. Примерно так:

select t1.value - t2.value from table t1, table t2 
where t1.primaryKey = t2.primaryKey - 1

Если вы знаете, как упорядочить вещи, но не знаете, как получить предыдущее значение с учетом текущего (например, вы хотите упорядочить по алфавиту), то я не знаю, как это сделать в стандартном SQL, но большинство SQL реализации будут иметь расширения для этого.

Вот способ для SQL-сервера, который работает, если вы можете упорядочить строки так, чтобы каждая из них отличалась:

select  rank() OVER (ORDER BY id) as 'Rank', value into temp1 from t

select t1.value - t2.value from temp1 t1, temp1 t2 
where t1.Rank = t2.Rank - 1

drop table temp1

Если вам нужно разорвать связи, вы можете добавить столько столбцов, сколько необходимо в ORDER BY.

47 голосов
/ 02 июля 2015

Используйте функцию lag :

SELECT value - lag(value) OVER (ORDER BY Id) FROM table

Последовательности, используемые для идентификаторов, могут пропускать значения, поэтому Id-1 не всегда работает.

27 голосов
/ 30 декабря 2011
WITH CTE AS (
  SELECT
    rownum = ROW_NUMBER() OVER (ORDER BY columns_to_order_by),
    value
  FROM table
)
SELECT
  curr.value - prev.value
FROM CTE cur
INNER JOIN CTE prev on prev.rownum = cur.rownum - 1
26 голосов
/ 02 апреля 2009

Oracle, PostgreSQL, SQL Server и многие другие движки СУБД имеют аналитические функции, называемые LAG и LEAD, которые делают именно эту вещь.

В SQL Server до 2012 года вам нужно будет сделать следующее:

SELECT  value - (
        SELECT  TOP 1 value
        FROM    mytable m2
        WHERE   m2.col1 < m1.col1 OR (m2.col1 = m1.col1 AND m2.pk < m1.pk)
        ORDER BY 
                col1, pk
        )
FROM mytable m1
ORDER BY
      col1, pk

, где COL1 - столбец, по которому вы заказываете.

Наличие индекса на (COL1, PK) значительно улучшит этот запрос.

9 голосов
/ 02 апреля 2009

LEFT СОЕДИНЯЕТ таблицу к себе, с условием соединения, таким образом, строка, соответствующая в объединенной версии таблицы, на одну строку выше, для вашего конкретного определения «предыдущая».

Обновление: сначала я подумал, что вы хотели бы сохранить все строки с NULL для условия, в котором не было предыдущей строки. Прочитав это снова, вы просто хотите, чтобы строки отбирались, поэтому вам следует использовать внутреннее соединение, а не левое соединение.


Обновление:

В более новых версиях Sql Server также есть функции управления окнами LAG и LEAD, которые также могут использоваться для этого.

2 голосов
/ 06 января 2013
select t2.col from (
select col,MAX(ID) id from 
(
select ROW_NUMBER() over(PARTITION by col order by col) id ,col from testtab t1) as t1
group by col) as t2
2 голосов
/ 02 апреля 2009

Выбранный ответ будет работать только в том случае, если в последовательности нет пробелов. Однако, если вы используете автоматически сгенерированный идентификатор, в последовательности могут быть пробелы из-за откатов вставок.

Этот метод должен работать, если у вас есть пробелы

declare @temp (value int, primaryKey int, tempid int identity)
insert value, primarykey from mytable order by  primarykey

select t1.value - t2.value from @temp  t1
join @temp  t2 
on t1.tempid = t2.tempid - 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...