63

I would like to know if there is a way to use an order by clause when updating a table. I am updating a table and setting a consecutive number, that's why the order of the update is important. Using the following sql statement, I was able to solve it without using a cursor:

DECLARE @Number INT = 0

UPDATE Test
SET @Number = Number = @Number +1

now what I'd like to to do is an order by clause like so:

 DECLARE @Number INT = 0

 UPDATE Test
 SET @Number = Number = @Number +1
 ORDER BY Test.Id DESC

I've read: How to update and order by using ms sql The solutions to this question do not solve the ordering problem - they just filter the items on which the update is applied.

Take care, Martin

Community
  • 1
  • 1
jerleth
  • 643
  • 1
  • 5
  • 5

9 Answers9

76

No.

Not a documented 100% supported way. There is an approach sometimes used for calculating running totals called "quirky update" that suggests that it might update in order of clustered index if certain conditions are met but as far as I know this relies completely on empirical observation rather than any guarantee.

But what version of SQL Server are you on? If SQL2005+ you might be able to do something with row_number and a CTE (You can update the CTE)

With cte As
(
SELECT id,Number,
ROW_NUMBER() OVER (ORDER BY id DESC) AS RN
FROM Test
)
UPDATE cte SET Number=RN
Martin Smith
  • 438,706
  • 87
  • 741
  • 845
  • Sure, but we can target lesser versions by using a subquery. – spender Aug 09 '10 at 10:45
  • @spender what version supports row_number but not ctes? – Martin Smith Aug 09 '10 at 10:46
  • 3
    ah. you got me there! ;) I only came into the SqlServer world at 2005, where CTEs were shiny and new. I always thought row_number had been around a little longer. – spender Aug 09 '10 at 10:49
  • Thanks a lot for the detailed & quick answer! I am on a Sql2008. In this case, I think I'll take a step back, drop the nice update statement and use a slower cursor implementation as speed is not a primary concern in this application. – jerleth Aug 09 '10 at 12:18
  • 1
    @Martin - Sorry if I wasn't clear. The `Row_number()` approach will give you the correct results for the example in your question. It doesn't rely upon any particular update order. Obviously if your actual needs are a bit different and you are calculating running totals or something this won't be suitable. – Martin Smith Aug 09 '10 at 13:01
  • [here](http://stackoverflow.com/a/655031/2218697) **another answer** which worked for me. – Shaiju T Jan 30 '16 at 15:45
26

You can not use ORDER BY as part of the UPDATE statement (you can use in sub-selects that are part of the update).

UPDATE Test 
SET Number = rowNumber 
FROM Test
INNER JOIN 
(SELECT ID, row_number() OVER (ORDER BY ID DESC) as rowNumber
FROM Test) drRowNumbers ON drRowNumbers.ID = Test.ID
Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
6

Edit

Following solution could have problems with clustered indexes involved as mentioned here. Thanks to Martin for pointing this out.

The answer is kept to educate those (like me) who don't know all side-effects or ins and outs of SQL Server.


Expanding on the answer gaven by Quassnoi in your link, following works

DECLARE @Test TABLE (Number INTEGER, AText VARCHAR(2), ID INTEGER)
DECLARE @Number INT

INSERT INTO @Test VALUES (1, 'A', 1)
INSERT INTO @Test VALUES (2, 'B', 2)
INSERT INTO @Test VALUES (1, 'E', 5)
INSERT INTO @Test VALUES (3, 'C', 3)
INSERT INTO @Test VALUES (2, 'D', 4)

SET @Number = 0

;WITH q AS (
    SELECT  TOP 1000000 *
    FROM    @Test
    ORDER BY
            ID
)            
UPDATE  q
SET     @Number = Number = @Number + 1
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
  • 2
    That probably will work if id is the clustered index but isn't guaranteed to work. See the discussion here http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx – Martin Smith Aug 09 '10 at 10:44
  • 1
    I have to say, I'm not too keen on the side-effect method of performing updates. – spender Aug 09 '10 at 10:51
  • @Martin Smith, thanks for the link. I was thinking about deleting the answer but it might as well stay, if not alone for the link you've provided. – Lieven Keersmaekers Aug 09 '10 at 11:04
  • 1
    You might also be interested in this article http://www.sqlservercentral.com/articles/T-SQL/68467/ – Martin Smith Aug 09 '10 at 11:06
4

The row_number() function would be the best approach to this problem.

UPDATE T
    SET T.Number = R.rowNum
    FROM Test T
    JOIN (
        SELECT T2.id,row_number() over (order by T2.Id desc) rowNum from Test T2
    ) R on T.id=R.id 
spender
  • 117,338
  • 33
  • 229
  • 351
  • I couldn't get your solution to work - no matter which way I order the numbers are always assigned in the same direction. – jerleth Aug 09 '10 at 12:13
  • The order of assignment of the numbers is assigned by the over clause of row_number (i.e. row_number() over (order by T2.Id desc)). What are you trying? – spender Aug 09 '10 at 13:55
2

update based on Ordering by the order of values in a SQL IN() clause

Solution:

DECLARE @counter int
SET @counter = 0

;WITH q  AS
        (
select * from Products WHERE ID in (SELECT TOP (10) ID FROM Products WHERE  ID IN( 3,2,1) 
ORDER BY ID DESC)
        )
update q set Display= @counter, @counter = @counter + 1

This updates based on descending 3,2,1

Hope helps someone.

Shaiju T
  • 6,201
  • 20
  • 104
  • 196
2

I had a similar problem and solved it using ROW_NUMBER() in combination with the OVER keyword. The task was to retrospectively populate a new TicketNo (integer) field in a simple table based on the original CreatedDate, and grouped by ModuleId - so that ticket numbers started at 1 within each Module group and incremented by date. The table already had a TicketID primary key (a GUID).

Here's the SQL:

UPDATE Tickets SET TicketNo=T2.RowNo
FROM Tickets
INNER JOIN 
  (select TicketID, TicketNo, 
     ROW_NUMBER() OVER (PARTITION BY ModuleId ORDER BY DateCreated) AS RowNo from Tickets) 
  AS T2 ON T2.TicketID = Tickets.TicketID

Worked a treat!

Fireman Sam
  • 121
  • 1
  • 3
1

I ran into the same problem and was able to resolve it in very powerful way that allows unlimited sorting possibilities.

I created a View using (saving) 2 sort orders (*explanation on how to do so below).

After that I simply applied the update queries to the View created and it worked great.

Here are the 2 queries I used on the view:

1st Query:

Update  MyView
Set SortID=0


2nd Query:

DECLARE @sortID int
SET     @sortID = 0
UPDATE  MyView
SET     @sortID = sortID = @sortID + 1


*To be able to save the sorting on the View I put TOP into the SELECT statement. This very useful workaround allows the View results to be returned sorted as set when the View was created when the View is opened. In my case it looked like:

(NOTE: Using this workaround will place an big load on the server if using a large table and it is therefore recommended to include as few fields as possible in the view if working with large tables)

SELECT     TOP (600000) 
dbo.Items.ID, dbo.Items.Code, dbo.Items.SortID, dbo.Supplier.Date, 
dbo.Supplier.Code AS Expr1
FROM         dbo.Items INNER JOIN
                      dbo.Supplier ON dbo.Items.SupplierCode = dbo.Supplier.Code
ORDER BY dbo.Supplier.Date, dbo.Items.ID DESC



Running: SQL Server 2005 on a Windows Server 2003

Additional Keywords: How to Update a SQL column with Ascending or Descending Numbers - Numeric Values / how to set order in SQL update statement / how to save order by in sql view / increment sql update / auto autoincrement sql update / create sql field with ascending numbers

Augustine
  • 11
  • 2
0
SET @pos := 0;
UPDATE TABLE_NAME SET Roll_No = ( SELECT @pos := @pos + 1 ) ORDER BY First_Name ASC;

In the above example query simply update the student Roll_No column depending on the student Frist_Name column. From 1 to No_of_records in the table. I hope it's clear now.

sandeep autade
  • 261
  • 7
  • 17
  • 2
    while this may answer the question, you can improve the quality of your answer by editing it to include some explanation – dub stylee Feb 09 '18 at 20:10
0
IF OBJECT_ID('tempdb..#TAB') IS NOT NULL
BEGIN
    DROP TABLE #TAB
END

CREATE TABLE #TAB(CH1 INT,CH2 INT,CH3 INT)

DECLARE @CH2 INT = NULL , @CH3 INT=NULL,@SPID INT=NULL,@SQL NVARCHAR(4000)='', @ParmDefinition NVARCHAR(50)= '',
@RET_MESSAGE AS VARCHAR(8000)='',@RET_ERROR INT=0


SET @ParmDefinition='@SPID INT,@CH2 INT OUTPUT,@CH3 INT OUTPUT'

SET @SQL='UPDATE T
            SET CH1=@SPID,@CH2= T.CH2,@CH3= T.CH3
            FROM #TAB T WITH(ROWLOCK)
            INNER JOIN (
                        SELECT TOP(1)  CH1,CH2,CH3
                        FROM
                        #TAB WITH(NOLOCK)
                        WHERE CH1 IS NULL
                        ORDER BY CH2 DESC) V ON T.CH2= V.CH2 AND T.CH3= V.CH3' 

INSERT INTO #TAB
(CH2 ,CH3 )
SELECT 1,2 UNION ALL
SELECT 2,3 UNION ALL
SELECT 3,4

BEGIN TRY
    WHILE EXISTS(SELECT TOP 1 1 FROM #TAB WHERE CH1 IS NULL)
    BEGIN

        EXECUTE @RET_ERROR = sp_executesql @SQL, @ParmDefinition,@SPID =@@SPID,  @CH2=@CH2 OUTPUT,@CH3=@CH3 OUTPUT;  

        SELECT * FROM #TAB
        SELECT @CH2,@CH3

    END

END TRY
BEGIN CATCH

    SET @RET_ERROR=ERROR_NUMBER()
    SET @RET_MESSAGE =  '@ERROR_NUMBER : ' + CAST(ERROR_NUMBER()  AS VARCHAR(255)) + '@ERROR_SEVERITY  :' + CAST( ERROR_SEVERITY()  AS VARCHAR(255)) 
    + '@ERROR_STATE :' + CAST(ERROR_STATE() AS VARCHAR(255)) + '@ERROR_LINE :' + CAST( ERROR_LINE() AS VARCHAR(255)) 
    +  '@ERROR_MESSAGE :' + ERROR_MESSAGE()  ;

    SELECT @RET_ERROR,@RET_MESSAGE;

END CATCH