I have been tasked with the creation of a custom stored procedure:
exec UPDATE_PROJECT_ORDER @PROJECTID=12, @UPDATEMODE=0
That will execute after a row in a table has been updated.
Unfortunately it has me really stumped (to the point that I’m not even sure if it is possible) I have been trying to simplify what I am doing and wondered if I could ask the board for assistance on just the code itself…
So the purpose of the stored procedure / code is to re-order a list of items generally in a sequential order, the re-ordering changes based on a passed in parameter which will either leave the value as it is set in the table by the user and order the rest of the list around it or renumber it as the next number in the sequence. I think we can assume that UPDATEMODE=0 is going to be default most of the time.
I have a table that looks like this:
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 1 | |
| 11 | 2 | |
| 12 | 3 | |
| 13 | 4 | |
---------------------------------------
If the user decides to give the record with ID 12 (ID is being passed in with the stored procedure) a higher priority, for example 1, then what should happen is the other records (10 / 11 / 13) should be sequentially re-ordered around it (2 / 3 / 4) which will affect how they are displayed in the front end, e.g.
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 2 | 1 |
| 11 | 3 | 2 |
| 12 | 1 | 3 |
| 13 | 4 | 4 |
---------------------------------------
Another example of this would be the position of record ID 12 is changed to from a 1 to a 7 so this data set:
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 2 | 1 |
| 11 | 3 | 2 |
| 12 | 1/7 | 3 |
| 13 | 4 | 4 |
---------------------------------------
The position data is re-ordered as:
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 1 | 2 |
| 11 | 2 | 3 |
| 12 | 7 | 1 |
| 13 | 3 | 4 |
---------------------------------------
As mentioned above – in the the stored procedure there is the requirement that another parameter be passed in (@UPDATEMODE which can be 0 or 1) that changes the behavior of the function allowing the user to specify what position they want and re-order the list around it versus making it the next number in the sequence, for example they update the priority in row 3 from value 1 to value 7
This data set row 12 position value = 1 but is changed to 7 with UPDATEMODE specified as 1:
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 2 | 1 |
| 11 | 3 | 2 |
| 12 | 1/7 | 3 |
| 13 | 4 | 4 |
---------------------------------------
Which would re-order the list as follows:
---------------------------------------
| ID | POSITION | OLD_POS |
---------------------------------------
| 10 | 1 | 2 |
| 11 | 2 | 3 |
| 12 | 4 | 1 |
| 13 | 3 | 4 |
---------------------------------------
In this example the stored procedure would be called as:
exec UPDATE_PROJECT_ORDER @PROJECTID=12, @UPDATEMODE=1
This is the SQL code I have been working with:
-- Declare variables
DECLARE @PROJECTID INTEGER
DECLARE @CURRENTPOSITION INTEGER
DECLARE @ROLLBACKPOSITION INTEGER
DECLARE @STARTPOSITION INTEGER
DECLARE @ENDPOSITION INTEGER
-- For testing hardcode a REQUEST ID
SET @PROJECTID = 12
-- Start Position value
SET @STARTPOSITION = 1
-- End Position value
SELECT @ENDPOSITION = COUNT(ID) FROM PROJECT WHERE PROJECT_ORDER IS NOT NULL
-- Update Rollback column with current value
UPDATE PROJECT SET OLD_POS = POSITION WHERE POSITION IS NOT NULL
DECLARE cursorProjectPositionUpdate CURSOR fast_forward
FOR
SELECT ID, POSITION, OLD_POS
FROM PROJECT
WHERE ID = @PROJECTID
AND POSITION IS NOT NULL
OPEN cursorProjectPositionUpdate
FETCH NEXT FROM cursorProjectPositionUpdate INTO @PROJECTID, @CURRENTPOSITION, @ROLLBACKPOSITION
WHILE @@FETCH_STATUS = 0
BEGIN
WHILE (@STARTPOSITION <= @ENDPOSITION)
IF @STARTPOSITION = 1
UPDATE PROJECT
SET POSITION = @STARTPOSITION
WHERE ID = @PROJECTID
AND OLD_POSITION = @ROLLBACKPOSITION
ELSE
UPDATE PROJECT
SET POSITION = @STARTPOSITION
WHERE OLD_POS = @ROLLBACKPOSITION
AND ID <> @PROJECTID
SET @STARTPOSITION = @STARTPOSITION + 1
FETCH NEXT FROM cursorProjectPositionUpdate INTO @PROJECTID, @CURRENTPOSITION, @ROLLBACKPOSITION
END
CLOSE cursorProjectPositionUpdate
DEALLOCATE cursorProjectPositionUpdate
I’ve used a cursor because there is a hard limit of 25 records max to be re-ordered so I’m not overly worried about performance. Although there maybe more than 25 records in the table which is why I have tried to exclude records with the AND POSITION IS NOT NULL clause so I hope this is acceptable.
The thought in my head is to count how many records I have total that have a position and then loop through setting the first to position 1 and then after that the rest to the next sequential order.
The biggest problem I am finding is on the matching of records, i.e. my WHERE clause, because the only thing being passed in by the stored procedure is the ID that I want to be set to position 1 so how do I know which one comes next… the logic is it should be the next lowest ID number working down the list.
This is being done in MS SQL Server.
I am trying to avoid creating any temporary tables so need to see if this can be done all in one.
Hopefully this makes some sort of sense to someone, I am open to solutions and will share as much information as I can.
Many thanks!
Additional
I have been thinking about this a little more based on the below answers, again I am trying to keep this as simple as possible to start with so I have the stored procedure which passed in a PROJECT ID value so how about if I loop through all of the values I have that do not match my PROJECT ID resetting them starting at 2 going up to my end point which is based on the number of records I have and once those are in sequence set the position of the passed in to project to 1. I know this doesn’t cater for my update mode option but I’m worried thats just adding to much complication.
So as far as code goes what are your thoughts on something like:
-- Declare variables
DECLARE @PROJECTID INTEGER
DECLARE @STARTPOSITION INTEGER
DECLARE @ENDPOSITION INTEGER
-- Hardcoded for testing
SET @PROJECTID = 25061
-- Start Position value
SET @STARTPOSITION = 2
-- End Position value
SELECT @ENDPOSITION = COUNT(ID) FROM PROJECT WHERE PROJECT_ORDER IS NOT NULL AND ID <> @PROJECTID
-- Update Rollback column with current value
UPDATE PROJECT SET PROJECT_ORDER_RB = PROJECT_ORDER WHERE PROJECT_ORDER IS NOT NULL
-- Loop other records
WHILE (@STARTPOSITION <= @ENDPOSITION)
UPDATE PROJECT SET PROJECT_ORDER = @STARTPOSITION WHERE ID <> @PROJECTID
AND PROJECT_ORDER IS NOT NULL AND (PROJECT_ORDER = 1 OR PROJECT_ORDER => @STARTPOSITION OR PROJECT_ORDER <= @ENDPOSITION)
SET @STARTPOSITION = @STARTPOSITION + 1
-- Finally set passed in Project to Position 1
UPDATE PROJECT SET PROJECT_ORDER = 1 WHERE ID = @PROJECTID
This can be done with a single update statement, without needing an OLD_POS column or anything similar, and without any Loops, Cursors or other non-set oriented artifacts: