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