I have a file system app that uses a SQL Server 2008 R2 to keep track of files. When I remove a file, I want to update parents in the path hierarchy to reflect their new size. I am using @fid for the current FILE_ID, @size as the FILE_SIZE, and @pid as the FILE_ID of the parent in the hierarchy.
Here is the loop I am using:
SELECT @pid=PARENT_ID FROM FILES WHERE FILE_ID=@fid;
WHILE @pid<>0
BEGIN
UPDATE FILES
SET
FILE_SIZE =
-- Avoid potential situation where new file size might incorrectly drop below 0
CASE
WHEN FILE_SIZE-@size>=0 THEN FILE_SIZE-@size
ELSE 0
END
WHERE FILE_ID=@pid;
SET @fid=@pid;
SELECT @pid=PARENT_ID FROM FILES WHERE FILE_ID=@fid;
END
When I run this, the sizes are not updating. If I replace the UPDATE with a SELECT, it looks like it should be working correctly. What is going on? Why are the sizes not getting updated? Is there a better way to do this?
To add some context, this snip-it is actually running inside another loop so multiple files can be deleted in a batch. Here is the code in this context:
-- Declarations
DECLARE @fid int, @size int, @pid int;
DECLARE c CURSOR FOR
SELECT
FILE_ID, FILE_SIZE
FROM
FILES
OPEN c;
-- Initialize variables
FETCH NEXT FROM c
INTO @fid, @size;
-- Main loop
WHILE @@FETCH_STATUS = 0
BEGIN
-- Statements to delete the file --
-- Loop to update sizes --
SELECT @pid=PARENT_ID FROM FILES WHERE FILE_ID=@fid;
WHILE @pid<>0
BEGIN
UPDATE FILES
SET
FILE_SIZE =
CASE
WHEN FILE_SIZE-@size>=0 THEN FILE_SIZE-@size
ELSE 0
END
WHERE FILE_ID=@pid;
SET @fid=@pid;
SELECT @pid=PARENT_ID FROM FILES WHERE FILE_ID=@fid;
END
FETCH NEXT FROM c
INTO @fid, @size;
END
CLOSE c;
DEALLOCATE c;
There is definately a better way to do this. The approach you outline is procedural and iterative and great for languages like C# and visual basic, however, for the most efficeint solution in SQL Cursors are the LAST trick you should reach for.
This should work
WITH FileList
AS
(
-- Select all the files that have no children
SELECT f.FILE_ID
,f.FILE_SIZE
,f.PARENT_ID
,0 AS CHILD_ID
,0 AS Depth
FROM FILES f
LEFT JOIN
FILES f1 ON f.FILE_ID=f1.PARENT_ID
WHERE f1.PARENT_ID IS NULL
UNION ALL
-- Then recursively select thrir parents
SELECT f.FILE_ID
,fl.FILE_SIZE
,f.PARENT_ID
,fl.FILE_ID
,fl.Depth + 1
FROM FileList fl
INNER JOIN
FILES f ON f.FILE_ID=fl.PARENT_ID
)
-- With this data update the file size with the sum of all leaf nodes
UPDATE FILES
SET FILE_SIZE = (SELECT SUM(FILE_SIZE)
FROM FileList fl
WHERE fl.FILE_ID=FILES.FILE_ID
GROUP BY FILE_ID)
One command, no itteration - should be orders of magnitude quicker and you are getting the accurate filesizes propagated all the way up from the leaves each time.
I wanted to post the result, in case it helps. I hope this is the right way to do it.
I needed to restrict the update to just the affected path, so I ended up using something like this, based on the suggestion from Dale M:
-- Since I already have @size and @pid, I use them here
WITH FileList
AS
(
-- Select the parent of the delete target
SELECT f.FILE_ID
,f.PARENT_ID
,f.FILE_SIZE-@size AS FILE_SIZE
FROM FILES f
WHERE f.FILE_ID=@pid
UNION ALL
-- Then recursively select its parents
SELECT f.FILE_ID
,f.PARENT_ID
,f.FILE_SIZE-@size AS FILE_SIZE
FROM FileList fl
INNER JOIN
FILES f ON f.FILE_ID=fl.PARENT_ID
)
-- Update FILES with the size, already adjusted in the CTE above
UPDATE FILES
SET FILE_SIZE=fl.FILE_SIZE
FROM FILES
INNER JOIN
FileList fl ON fl.FILE_ID=FILES.FILE_ID;
This is nice because I can start to break this script down into stored procedures. Thanks again to Dale M!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.