0

I'm taking over some code that is trying to store a list of IDs at one time and I find this code to be running quite slow for the actions we are trying to complete. Plus, in certain occasions resulting in deadlocks due to high amounts ids.

USE [store]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[UpdateImagePriority]

   @separator CHAR(1),
   @filename  varchar(50),
   @parentId   int,
   @slaveIds varchar(8000)

   AS
   BEGIN      
      SET NOCOUNT ON
      DECLARE @SLAPriorityint
      DECLARE @separator_position INT 
      DECLARE @array_value VARCHAR(50)

      SET @slaveIds = @slaveIds + @separator
      SET @SLAPriority= 0

      WHILE PATINDEX('%' + @separator + '%', @slaveIds ) <> 0
           BEGIN

               SET @SLAPriority= @SLAPriority+ 1

               SELECT  @separator_position = PATINDEX('%' + @separator + '%',@slaveIds )
               SELECT  @array_value = LEFT(@slaveIds , @separator_position - 1)

               SELECT  Array_Value = @array_value

               SELECT  @slaveIds = STUFF(@slaveIds , 1, @separator_position, '')

               UPDATE image_info
               SET SLA_PRIORITY = @SLAPriority
               WHERE FILE=@filename and EXT_PAR_ID=@parentId   and SLA_ID=@array_value
           END
      SET NOCOUNT OFF
   END

This is a sample of what we would pass in:

e.g.

separator = ','
filename = 'burgerking'
parentId = '1859'
slaveIds = '15,16,19,20,21,25,28,29,30,38,99'

Any suggestions on how to improve the speed of this code.

Thanks in advance!

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
Miguel
  • 1,157
  • 1
  • 15
  • 28

3 Answers3

3

What you are looking for is a table-valued function to split your values into a table. Then all you need is a single UPDATE .. FROM .. JOIN statement.

CREATE PROCEDURE [dbo].[UpdateImagePriority]
   @separator CHAR(1),
   @filename  varchar(50),
   @parentId   int,
   @slaveIds varchar(8000)
AS

set @slaveIds = @slaveIds + @separator
;WITH SplitString AS
(
    SELECT
        1 ID,LEFT(@slaveIds,CHARINDEX(',',@slaveIds)-1) AS Part,RIGHT(@slaveIds,LEN(@slaveIds)-CHARINDEX(',',@slaveIds)) AS Remainder
    UNION ALL
    SELECT
        ID+1,LEFT(Remainder,CHARINDEX(',',Remainder)-1),RIGHT(Remainder,LEN(Remainder)-CHARINDEX(',',Remainder))
        FROM SplitString
        WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)>0
)
update i
SET SLA_PRIORITY = s.ID
from splitstring s
join image_info i on i.[FILE]=@filename and i.EXT_PAR_ID=@parentId and i.SLA_ID= s.Part
where s.Part > ''

For SQL Server 2000, or just to make the string splitting re-usable, I lifted this function from another question.

create function dbo.SplitString 
    (
        @str varchar(8000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p Id,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 8000 end) 
            AS Part
        from tokens
      )
GO

Then your SP becomes

CREATE PROCEDURE [dbo].[UpdateImagePriority]
   @separator CHAR(1),
   @filename  varchar(50),
   @parentId   int,
   @slaveIds varchar(8000)
AS

update i
SET SLA_PRIORITY = s.ID
from dbo.splitstring(@slaveIds,@separator) s
join image_info i on i.[FILE]=@filename and i.EXT_PAR_ID=@parentId and i.SLA_ID= s.Part
where s.Part > ''
GO
Community
  • 1
  • 1
RichardTheKiwi
  • 105,798
  • 26
  • 196
  • 262
  • Nice use of a CTE query to separate the comma string. But it would be better in a function so it is reusable... – James L. Oct 03 '12 at 03:44
  • You're right. I think of `no-trace` queries all the time but if he's creating a procedure, he could well create a function at the same time. – RichardTheKiwi Oct 03 '12 at 03:55
  • Hey Richard this is working fine in my local database but when I try to use it on the live database it returns with an error:"Msg 156, Level 15, State 1, Procedure UpdateImagePriority, Line 15 Incorrect syntax near the keyword 'WITH'.". I'm assuming its an issue with the version of SQL Server its running (8.0.2055)? – Miguel Oct 03 '12 at 16:31
  • *More information its running SQL 2000 and I don't think "with"s are in the supported list. – Miguel Oct 03 '12 at 17:04
0

Use a function to convert the array into an XML document, which is then easy to return as a single column table of integers:

use tempdb
go

create function dbo.ParseIDs(@ids varchar(max), @separator char(1)) returns @rtn table (
  id int
)
as
begin
  declare @xml xml

  set @xml = '<root><n>' + replace(@ids, @separator, '</n><n>') + '</n></root>'

  insert into @rtn
  select id.value('.', 'int')as id
  from @xml.nodes('/root/n') as records(id)

  return
end
go

declare @buf varchar(max) = '15,16,19,20,21,25,28,29,30,38,99'
select * from dbo.ParseIDs(@buf, ',')
go

drop function dbo.ParseIDs
go

This returns the following:

id
----
15
16
19
20
21
25
28
29
30
38
99

It would be easy to then to do something like this:

UPDATE image_info
SET SLA_PRIORITY = @SLAPriority
WHERE FILE=@filename and EXT_PAR_ID=@parentId   and SLA_ID in (
  select id from dbo.ParseIDs(@array_value, ',')
)

This might even be better:

UPDATE tbl
SET    tbl.SLA_PRIORITY = @SLAPriority
FROM   dbo.ParseIDs(@array_value, ',') as x
       inner join image_info tbl on x.id = tbl.SLA_ID
WHERE  tbl.FILE=@filename and tbl.EXT_PAR_ID=@parentId
James L.
  • 9,384
  • 5
  • 38
  • 77
0

Do a set based update:

    USE [store]
    GO
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    ALTER PROCEDURE [dbo].[UpdateImagePriority]

   @separator CHAR(1),
   @filename  varchar(50),
   @parentId   int,
   @slaveIds varchar(8000)

   AS
   BEGIN      
      SET NOCOUNT ON
      DECLARE @SLAPriorityint
      DECLARE @separator_position INT 
      DECLARE @array_value VARCHAR(50)

      SET @slaveIds = @slaveIds + @separator
      SET @SLAPriority= 0

      DECLARE @slaveIdTable Table
      (
        ids INT,
        SlaPriority INT
      )
      WHILE PATINDEX('%' + @separator + '%', @slaveIds ) <> 0
           BEGIN

               SET @SLAPriority= @SLAPriority+ 1

               SELECT  @separator_position = PATINDEX('%' + @separator + '%',@slaveIds )
               SELECT  @array_value = LEFT(@slaveIds , @separator_position - 1)

               -- get table of ids
               INSERT INTO @slaveIdTable (ids,SlaPriority) VALUES(@array_value,@SLAPriority);

               SELECT  @slaveIds = STUFF(@slaveIds , 1, @separator_position, '')

           END

    UPDATE ii
        SLA_PRIORITY = @SLAPriority
    FROM image_info ii
    JOIN @slaveIdTable st ON st.ids = st.SLA_ID
    WHERE
        st.[FILE] =  @filename AND
        st.EXT_PAR_ID = @parentId                   

      SET NOCOUNT OFF
   END

Richard's answer is a bit more elegant. Didn't see it while I was working on mine.

DanCaveman
  • 676
  • 1
  • 6
  • 22