Чи є спосіб отримати значення “попереднього рядка” в операторі 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.