web-dev-qa-db-fra.com

Existe-t-il un moyen d'accéder à la valeur de la "ligne précédente" dans une instruction SELECT?

Je dois calculer la différence d'une colonne entre deux lignes d'un tableau. Est-il possible de le faire directement en SQL? J'utilise Microsoft SQL Server 2008.

Je cherche quelque chose comme ça:

SELECT value - (previous.value) FROM table

Imaginant que la variable "précédente" fasse référence à la dernière ligne sélectionnée. Bien sûr, avec une sélection telle que celle-ci, je vais me retrouver avec n-1 lignes sélectionnées dans un tableau de n lignes, ce qui est peu probable, c’est exactement ce dont j’ai besoin.

Est-ce possible d'une certaine manière?

76
Edwin Jarvis

SQL n'a pas de notion intégrée d'ordre, vous devez donc classer par colonne pour que cela ait un sens. Quelque chose comme ça:

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

Si vous savez commander des choses sans savoir comment obtenir la valeur précédente en fonction de la valeur actuelle (par exemple, vous voulez commander par ordre alphabétique), je ne connais pas de moyen de le faire en SQL standard, mais la plupart des implémentations de SQL auront extensions pour le faire.

Voici un moyen pour le serveur SQL qui fonctionne si vous pouvez ordonner des lignes de manière à ce qu'elles soient distinctes:

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

Si vous avez besoin de briser l'égalité, vous pouvez ajouter autant de colonnes que nécessaire à ORDER BY.

53
RossFabricant

Utilisez la fonction lag :

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

Les séquences utilisées pour les identifiants peuvent ignorer des valeurs. Id-1 ne fonctionne donc pas toujours.

49
Hans Ginzel
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
27
Jeremy Stein

Oracle, PostgreSQL, SQL Server et de nombreux autres moteurs de SGBDR ont des fonctions analytiques appelées LAG et LEAD qui font exactement cela.

Dans SQL Server avant 2012, vous devez procéder comme suit:

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

, où COL1 est la colonne par laquelle vous commandez.

Avoir un index sur (COL1, PK) va grandement améliorer cette requête.

26
Quassnoi

LEFT JOIN la table à elle-même, la condition de jointure étant définie, la ligne correspondante dans la version jointe de la table est une ligne précédente, pour votre définition particulière de "précédent".

Mise à jour: Au début, je pensais que vous voudriez conserver toutes les lignes, avec NULL pour la condition dans laquelle il n'y avait pas de ligne précédente. En le relisant, vous souhaitez simplement que les lignes soient sélectionnées, vous devez donc créer une jointure interne plutôt que gauche.


Mise à jour:

Les versions les plus récentes de Sql Server disposent également des fonctions LAG et LEAD Windowing qui peuvent également être utilisées à cet effet.

9
Joel Coehoorn
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
user1920851

La réponse sélectionnée ne fonctionnera que s'il n'y a pas de lacunes dans la séquence. Toutefois, si vous utilisez un identifiant généré automatiquement, il est probable que la séquence présente des lacunes en raison des insertions qui ont été annulées.

Cette méthode devrait fonctionner si vous avez des lacunes

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
2
HLGEM