{"id":287,"date":"2010-11-23T00:00:06","date_gmt":"2010-11-23T00:00:06","guid":{"rendered":"http:\/\/www.sqlserver.fr\/blog\/?p=287"},"modified":"2026-05-02T14:31:24","modified_gmt":"2026-05-02T12:31:24","slug":"order-by-offset","status":"publish","type":"post","link":"https:\/\/www.sqlserver.fr\/blog\/order-by-offset\/","title":{"rendered":"ORDER BY + OFFSET"},"content":{"rendered":"<p>Pour marquer l\u2019arriv\u00e9e de la CTP de SQL Server 2011 (nom de code Denali), j\u2019ai choisi d\u2019\u00e9crire un petit mot sur une des nouveaut\u00e9s que cette version propose au niveau du codage T-SQL. Il s\u2019agit d\u2019un compl\u00e9ment de syntaxe pour la clause ORDER BY, qui permet par exemple la gestion directe d\u2019une pagination dans le rendu des r\u00e9sultats.<!--more--><br \/>\nAfin de mieux comprendre l\u2019utilit\u00e9 de cette syntaxe, j\u2019ai choisi d\u2019exprimer un besoin et de montrer comment ce besoin pourrait \u00eatre d\u00e9velopp\u00e9 avec une syntaxe compatible avec SQL Server 2000, SQL Server 2005 et enfin avec la nouvelle syntaxe SQL Server 2011.<br \/>\nLe test de syntaxe s\u2019appuiera sur la base de d\u00e9monstration AdventureWorks2008R2, que l\u2019on peut trouver ici : <a href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/tag\/adventureworks\">http:\/\/msftdbprodsamples.codeplex.com\/<\/a>. Il consiste \u00e0 obtenir retourner un listing des ventes avec Num\u00e9ro de commande, Date de commande et Acheteur. Jusqu\u2019ici, rien de bien sp\u00e9cial, mais rajoutons surtout que ce listing doit s\u2019afficher de mani\u00e8re pagin\u00e9e \u00e0 l\u2019\u00e9cran, et que nous cherchons en fait \u00e0 afficher les @NbElements premiers \u00e9l\u00e9ments \u00e0 par de l\u2019\u00e9l\u00e9ment num\u00e9ro @NoPremierElement, en triant suivant la date et le num\u00e9ro de commande.<\/p>\n<p>C\u00f4t\u00e9 SQL Server 2000, \u00e9tant donn\u00e9 diff\u00e9rentes contraintes li\u00e9es \u00e0 la syntaxe (par exemple le fait que la clause TOP ne pouvait pas recevoir de variable), on obtient un code assez lourd qui passe par une jolie petite boucle.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE PROCEDURE [dbo].[ps_Page2000]\r\n@NoPremierElement int,\r\n@NbElements int\r\nAS\r\nBEGIN\r\n\r\n\tDECLARE @SalesOrderID int\r\n\tDECLARE @Compteur int\r\n\tcreate table #Liste (SalesOrderID int, PurchaseOrderNumber nvarchar(25), OrderDate datetime, SalesPersonID int, Selection bit)\r\n\r\n\tINSERT INTO #Liste (SalesOrderID,PurchaseOrderNumber, OrderDate, SalesPersonID)\r\n\tSELECT soh.SalesOrderID,\r\n\t\t\tsoh.PurchaseOrderNumber,\r\n\t\t\tsoh.OrderDate,\r\n\t\t\tsoh.SalesPersonID\r\n\tFROM Sales.SalesOrderHeader soh\r\n\tWHERE SalesPersonID is not null\r\n\r\n\tSELECT @Compteur=0\r\n\r\n\tDECLARE Curseur CURSOR FOR\r\n\tSELECT l.SalesOrderID\r\n\tFROM #Liste l\r\n\tORDER BY l.OrderDate,l.PurchaseOrderNumber\r\n\tFOR UPDATE OF l.Selection\r\n\r\n    OPEN Curseur\r\n    FETCH NEXT FROM Curseur INTO @SalesOrderID\r\n\r\n    WHILE @@FETCH_STATUS = 0\r\n    BEGIN\r\n\r\n\t\tSELECT @Compteur=@Compteur+1\r\n\r\n\t\tIF @Compteur&gt;=@NoPremierElement+@NbElements\r\n\t\t\tBREAK;\r\n\r\n\t\tIF @Compteur&gt;=@NoPremierElement\r\n\t\t\tUPDATE #Liste SET Selection=1 where SalesOrderID=@SalesOrderID\r\n\r\n\t\tFETCH NEXT FROM Curseur INTO @SalesOrderID\r\n    END\r\n\r\n    CLOSE Curseur\r\n    DEALLOCATE Curseur\r\n\r\n\tSELECT l.PurchaseOrderNumber,\r\n\t\t\tl.OrderDate,\r\n\t\t\tp.LastName + ' ' + p.FirstName\r\n\tFROM #Liste l\r\n\tJOIN Person.Person p on p.BusinessEntityID=l.SalesPersonID\r\n\tWHERE l.Selection=1\r\n\tORDER BY l.OrderDate,l.PurchaseOrderNumber\r\n\r\nEND<\/pre>\n<p>Certes, le code n\u2019est pas sp\u00e9cialement optimis\u00e9, mais l\u2019id\u00e9e principale y est, et il fonctionne.<\/p>\n<p>SQL Server 2005 nous a apport\u00e9 pas mal de nouvelles fonctionnalit\u00e9s, dont notamment les fonctions de classement qui sont ici tr\u00e8s utiles. A noter qu\u2019il y a l\u00e0 encore d\u2019autres syntaxes possibles, celle que je propose ici a juste le m\u00e9rite de fonctionner\u2026<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE PROCEDURE [dbo].[ps_Page2005]\r\n@NoPremierElement int,\r\n@NbElements int\r\nAS\r\nBEGIN\r\n\r\n\tWITH Liste (SalesOrderID,PurchaseOrderNumber, OrderDate, SalesPersonID, Ordre) as\r\n\t\t(SELECT soh.SalesOrderID,\r\n\t\t\t\tsoh.PurchaseOrderNumber,\r\n\t\t\t\tsoh.OrderDate,\r\n\t\t\t\tsoh.SalesPersonID,\r\n\t\t\t\tROW_NUMBER() OVER (ORDER BY soh.OrderDate,soh.PurchaseOrderNumber)\r\n\t\tFROM Sales.SalesOrderHeader soh\r\n\t\tWHERE SalesPersonID is not null)\r\n\tSELECT l.PurchaseOrderNumber,\r\n\t\t\tl.OrderDate,\r\n\t\t\tp.LastName + ' ' + p.FirstName\r\n\tFROM Liste l\r\n\tJOIN Person.Person p on p.BusinessEntityID=l.SalesPersonID\r\n\tWHERE l.Ordre&gt;=@NoPremierElement\r\n\tand l.Ordre&lt;@NoPremierElement+@NbElements\r\n\tORDER BY l.Ordre;\r\n\r\nEND<\/pre>\n<p>Et en plus, c\u2019est d\u00e9j\u00e0 en une seule instruction, certes complexe, mais c\u2019est d\u00e9j\u00e0 bien plus lisible que le curseur de la version 2000 !<\/p>\n<p>Enfin, nous arrivons \u00e0 la fameuse nouvelle syntaxe de Denali, objet de cet article.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE PROCEDURE [dbo].[ps_Page2011]\r\n@NoPremierElement int,\r\n@NbElements int\r\nAS\r\nBEGIN\r\n\r\n\tSELECT soh.SalesOrderID,\r\n\t\t\tsoh.PurchaseOrderNumber,\r\n\t\t\tsoh.OrderDate,\r\n\t\t\tsoh.SalesPersonID\r\n\tFROM Sales.SalesOrderHeader soh\r\n\tJOIN Person.Person p on p.BusinessEntityID=soh.SalesPersonID\r\n\tWHERE soh.SalesPersonID is not null\r\n\tORDER BY soh.OrderDate,soh.PurchaseOrderNumber OFFSET @NoPremierElement ROWS FETCH NEXT @NbElements ROWS ONLY;\r\n\r\nEND<\/pre>\n<p>Voil\u00e0, plus besoin d\u2019y aller en plusieurs \u00e9tapes (ordonnancement + filtre), tout est fait d\u2019un coup.<br \/>\nA noter toutefois que, m\u00eame si l\u2019on obtient une syntaxe beaucoup plus r\u00e9duite et maintenable, il est toujours n\u00e9cessaire d\u2019avoir une pens\u00e9e pour les performances, \u00e9tant donn\u00e9 que la clause ORDER BY est ex\u00e9cut\u00e9e en dernier lieu, et donc le filtre aussi.<br \/>\n<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0013.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-288\" title=\"image001\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0013.jpg\" alt=\"\" width=\"605\" height=\"174\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0013.jpg 605w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0013-300x86.jpg 300w\" sizes=\"auto, (max-width: 605px) 100vw, 605px\" \/><\/a><br \/>\nSi l\u2019on cherche donc \u00e0 avoir un petit extrait r\u00e9sultat de jointures complexes, il peut parfois \u00eatre int\u00e9ressant d\u2019\u00e9viter les syntaxes \u00e0 une seule instruction. Mais \u00e7a, c\u2019est une autre histoire\u2026<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pour marquer l\u2019arriv\u00e9e de la CTP de SQL Server 2011 (nom de code Denali), j\u2019ai choisi d\u2019\u00e9crire un petit mot sur une des nouveaut\u00e9s que cette version propose au niveau du codage T-SQL. Il s\u2019agit d\u2019un compl\u00e9ment de syntaxe pour &hellip; <a href=\"https:\/\/www.sqlserver.fr\/blog\/order-by-offset\/\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-287","post","type-post","status-publish","format-standard","hentry","category-article_sql"],"_links":{"self":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/287","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/comments?post=287"}],"version-history":[{"count":10,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/287\/revisions"}],"predecessor-version":[{"id":1969,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/287\/revisions\/1969"}],"wp:attachment":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/media?parent=287"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/categories?post=287"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/tags?post=287"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}