Есть ли способ получить доступ к значению «предыдущей строки» в операторе SELECT?


96

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

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

SELECT value - (previous.value) FROM table

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

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


6
Ну просто добавляю для комментария, полезного для новых зрителей дальше. SQL 2012 теперь имеет LAG и LEAD :) См. Эту ссылку blog.sqlauthority.com/2013/09/22/…
KD

Ответы:


65

В 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.


Это нормально, порядок не является проблемой, я просто удалил его из примера, чтобы было проще, я попробую.
Эдвин Джарвис

7
который предполагает, что первичные ключи генерируются последовательно, а строки никогда не удаляются, а в select нет других предложений о порядке, а также и ...
MartinStettner

Мартин прав. Хотя в некоторых случаях это может сработать, вам действительно нужно точно определить, что вы подразумеваете под словом «предыдущий» в бизнес-смысле, желательно не полагаясь на сгенерированный идентификатор.
Tom H

Вы правы, я добавил улучшение с помощью расширения SQL Server.
RossFabricant 02

2
В ответ на «Все в порядке, порядок не является проблемой» ... Тогда почему бы вам просто не вычесть произвольное значение в своем запросе, поскольку именно это вы делаете, если не учитываете порядок?
JohnFx 02

85

Используйте функцию задержки :

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

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


1
Это решение PostgreSQL. Вопрос по MSSQL. MSSQL имеет такую ​​функцию в версиях 2012+ ( msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx )
Kromster 02 дек.15,

10
@KromStern Не только решение PostgreSQL. Функции окна SQL были представлены в стандарте SQL: 2003 .
Ханс Гинзель

Функция LAG может принимать три параметра: LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). По умолчанию количество строк для задержки равно 1, но вы можете указать это и значение по умолчанию, которое нужно выбрать, когда невозможно отставать, поскольку вы находитесь в начале набора.
vaindil 02

30

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)значительно улучшит этот запрос.


14
SQL Server 2012 теперь также имеет LAG и LEAD.
ErikE 06

Скрипт Hana SQL также поддерживает LAG и LEAD.
mik

Просто чтобы добавить еще один комментарий зрителям, которые пришли сюда и хотели сделать это в Hive. Он также имеет функции LAG и LEAD. Документация здесь: cwiki.apache.org/confluence/display/Hive/…
Хайме Каффарель,

27
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

Он работает правильно, если в запросе нет группировки, но что, если мы хотим вычесть значения из предыдущего значения только внутри группы, скажем, того же EmployeeID, тогда как мы можем это сделать? Coz, запускающий это, работает только для верхних 2 строк каждой группы, а не для остальных строк в этой группе. Для этого я использовал этот код в цикле while, но это кажется очень медленным. Какой другой подход мы могли бы в этом сценарии? И то тоже только в SQL Server 2008?
Hemant Sisodia

10

LEFT JOIN таблица к самой себе, с отработанным условием соединения, так что строка, соответствующая в объединенной версии таблицы, находится на одну строку раньше, для вашего конкретного определения "previous".

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


Обновить:

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


3
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

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

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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.