Чи є спосіб отримати значення “попереднього рядка” в операторі SELECT?


93

Мені потрібно обчислити різницю стовпця між двома рядками таблиці. Чи є спосіб зробити це безпосередньо в 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

Відповіді:


61

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
який передбачає, що первинні ключі генеруються послідовно, а рядки ніколи не видаляються, а вибір не має жодного іншого пункту замовлення та та і та ...
MartinStettner,

Мартін прав. Хоча це може спрацювати в деяких випадках, вам дійсно потрібно точно визначити, що ви маєте на увазі під "попереднім" у діловому сенсі, бажано, не покладаючись на сформований ідентифікатор.
Tom H

Ви праві, я додав вдосконалення за допомогою розширення SQL Server.
RossFabricant,

2
У відповідь на "Це нормально, замовлення не є проблемою" ... Тоді чому б вам просто не відняти значення арбітражу у вашому запиті, адже це те, що ви робите, якщо не вважаєте замовлення?
JohnFx,

79

Використовуйте функцію відставання :

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

Послідовності, що використовуються для ідентифікаторів, можуть пропускати значення, тому Id-1 не завжди працює.


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

10
@KromStern Не тільки рішення PostgreSQL. Функції вікна SQL були введені в стандарт SQL: 2003 .
Ганс Гінцель,

Функція LAG може приймати три параметра: LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). Кількість рядків, які потрібно затримати за замовчуванням, дорівнює 1, але ви можете вказати це та значення за замовчуванням, яке потрібно вибрати, коли відставати неможливо, оскільки ви перебуваєте на початку набору.
vaindil

29

Механізми 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

Сценарій Hana SQL також підтримує LAG та LEAD.
mik

Просто щоб додати ще один коментар глядачам, які приїхали сюди і шукають робити це у Вулику. Він також має функції LAG та LEAD. Документація тут: cwiki.apache.org/confluence/display/Hive/…
Jaime Caffarel

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, то як ми можемо це зробити? Тому що це працює лише для верхніх 2 рядків кожної групи, а не для решти рядків цієї групи. Для цього я використовував запуск цього коду в циклі while, але, здається, це дуже повільно. Будь-який інший підхід, який ми могли б мати за цим сценарієм? І це теж лише в SQL Server 2008?
Hemant Sisodia

10

ЗЛІВА ПРИЄДНУЙТЕ таблицю до себе, з умовою об’єднання опрацьовано, щоб рядок, що відповідає об’єднаній версії таблиці, був на один рядок попереднім, для вашого конкретного визначення поняття „попередній”.

Оновлення: Спочатку я думав, що ви хочете зберегти всі рядки з NULL для умови, коли попереднього рядка не було. Читаючи його ще раз, ви просто хочете, щоб рядки вибракувались, тому вам слід робити внутрішнє об’єднання, а не ліве.


Оновлення:

Новіші версії Sql Server також мають функції LAG та LEAD Windowing, які також можуть бути використані для цього.



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.