Use of Cursor

  • Hi

    Having searched for 'Using an Update Cursor in SQL' and only receiving replies that state that an alternative to the CURSOR be used, I have now decided to ask for your opinions.

    Please examine the code below and advise if there is a set based way to improve it. I have a list of telephone numbers that I need to remove any non digits from.

    ----------- CREATE TABLE / VALUES ------------

    if exists (select * from information_schema.tables where [table_name] = 'CLI_CleanUp')

    begin

    drop table CLI_CleanUp

    end

    create table CLI_CleanUp

    (CLI varchar(20))

    insert into CLI_CleanUp (CLI) values ('123456789')

    insert into CLI_CleanUp (CLI) values ('123_456789')

    insert into CLI_CleanUp (CLI) values ('0123456789')

    insert into CLI_CleanUp (CLI) values ('1234567a89')

    insert into CLI_CleanUp (CLI) values ('1234567 89')

    insert into CLI_CleanUp (CLI) values ('#00000123456789')

    insert into CLI_CleanUp (CLI) values ('')

    insert into CLI_CleanUp (CLI) values (' ')

    insert into CLI_CleanUp (CLI) values ('000000fffff')

    ------------- REMOVE NON DIGITS FROM CLI ------------

    declare @temp varchar(20)

    DECLARE Clean_cursor CURSOR

    FOR SELECT CLI from CLI_CleanUp where patindex( '%[^0-9]%',CLI) <>0

    FOR UPDATE

    OPEN Clean_cursor

    FETCH NEXT from Clean_cursor into @temp;

    IF @@FETCH_STATUS <> 0

    PRINT 'TABLE IS EMPTY' ;

    WHILE @@FETCH_STATUS = 0

    BEGIN

    while patindex( '%[^0-9]%', @temp) <>0

    begin

    set @temp = stuff (@temp, patindex( '%[^0-9]%', @temp),1,'')

    update CLI_CleanUp set CLI = @temp where CURRENT OF Clean_cursor end

    FETCH NEXT from Clean_cursor into @temp;

    END

    CLOSE Clean_cursor;

    DEALLOCATE Clean_cursor;

    --------------- DELETE CLI THAT IS NOT BETWEEN 8 AND 11 DIGITS ------------

    delete

    --select *

    from CLI_CleanUp

    where LEN(CLI ) not between 8 and 11

    ----------------- OUTPUT RESULTS -------------------

    select * from CLI_CleanUp

    The cursor is used to remove non digits from one row at a time, as the telephone number has to be scanned and non digits removed one at a time. Please advise if this could be one use of

    CURSORs in SQL Server, or do I need to replace STUFF with another function?

  • How about this update statement?

    UPDATE CLI_CleanUp

    SET CLI = stuff (CLI, patindex( '%[^0-9]%', CLI),1,'')

    WHERE patindex( '%[^0-9]%',CLI) <>0

  • Chrissy321 (4/28/2011)


    How about this update statement?

    UPDATE CLI_CleanUp

    SET CLI = stuff (CLI, patindex( '%[^0-9]%', CLI),1,'')

    WHERE patindex( '%[^0-9]%',CLI) <>0

    This doesn't handle non-numeric values that aren't at the start of the string.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • How's this work for you?

    (You'll have to modify it to do the actual update; I'm just selecting the corrected data.)

    DECLARE @CLI_CleanUp table

    (CLI varchar(20))

    insert into @CLI_CleanUp (CLI) values ('123456789')

    insert into @CLI_CleanUp (CLI) values ('123_456789')

    insert into @CLI_CleanUp (CLI) values ('0123456789')

    insert into @CLI_CleanUp (CLI) values ('1234567a89')

    insert into @CLI_CleanUp (CLI) values ('1234567 89')

    insert into @CLI_CleanUp (CLI) values ('#00000123456789')

    insert into @CLI_CleanUp (CLI) values ('')

    insert into @CLI_CleanUp (CLI) values (' ')

    insert into @CLI_CleanUp (CLI) values ('000000fffff');

    -- For information on how a tally table works, please see the article

    -- 'The "Numbers" or "Tally" Table - What it is and how it replaces a loop'

    -- at http://www.sqlservercentral.com/articles/T-SQL/62867/

    ;WITH

    TENS (N) AS (SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL

    SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL

    SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0),

    TALLY (N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM TENS t1 CROSS JOIN TENS t2),

    -- add a row number for each item in the cli_cleanup table so that it can be used to recompile the string.

    cte AS (SELECT RowID = ROW_NUMBER() OVER (ORDER BY (SELECT 0)), CLI FROM @CLI_CleanUp),

    cte2 AS

    (

    -- split the data in each row apart into individual characters.

    -- keep the position number for when putting it back together

    SELECT cte.RowID, ca.N, ca.ColChar

    FROM cte

    CROSS APPLY (SELECT N, ColChar = SUBSTRING(cte.CLI, N, 1)

    FROM TALLY

    WHERE N <= 20) ca -- 20 is the size of the field, no need to go higher

    -- only get the numeric positions

    WHERE ca.ColChar LIKE '[0-9]%'

    )

    SELECT RowID,

    CleanData

    FROM cte

    -- put back together into the column order for this row.

    CROSS APPLY (SELECT CleanData = (SELECT '' + ColChar

    FROM cte2

    WHERE RowID = cte.RowID

    ORDER BY N

    FOR XML PATH(''),TYPE).value('.','varchar(max)')) ds

    WHERE CleanData IS NOT NULL

    ORDER BY cte.RowID

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Thanks Wayne.

    This seems to work. Will have to understand it and test it before I use it though :blush:. I've worked with CTE's but not a TALLY table. Thanks for the pointer :cool:, I never would have thought of this.

  • No problem. If you have any questions, just ask away. As the tagline in my signature states, don't use the code in production until you can understand what it's doing.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • WayneS (4/28/2011)


    How's this work for you?

    I know this post is a couple of weeks old BUT...

    The answer to the quoted question is "not so good" in this case. This is one of the few places where a scalar function with a WHILE loop will beat the tar out of a cteTally table and send the dust bunnies running for cover.

    Of course, I'd never ask anyone to just take my word for it so, here's the usual million rows of test data...

    --===== Conditionally drop temp tables to make reruns easier in SSMS

    IF OBJECT_ID('TempDB..#DirtyData','U') IS NOT NULL DROP TABLE #DirtyData;

    SELECT TOP (1000000)

    RowNum = IDENTITY(INT,1,1),

    CleanMe = CAST(NEWID() AS VARCHAR(36))

    INTO #DirtyData

    FROM sys.all_columns ac1,

    sys.all_columns ac2

    ;

    Here's the "clean string" function that uses a WHILE loop...

    CREATE FUNCTION dbo.CleanString

    --===== Declare obviously named parameters

    (

    @pString VARCHAR(8000),

    @KillPattern VARCHAR(256)

    )

    RETURNS VARCHAR(8000) WITH SCHEMABINDING AS

    BEGIN

    --===== Prime the pump

    DECLARE @Posit INT

    ;

    --===== See if there're any characters to kill

    SELECT @Posit = PATINDEX('%'+@KillPattern+'%',@pString)

    ;

    --===== If there are no characters to kill, skip the loop

    -- If there are, then kill them in the loop using STUFF

    WHILE @Posit > 0

    SELECT @pString = STUFF(@pString,@Posit,1,''),

    @Posit = PATINDEX('%'+@KillPattern+'%',@pString)

    ;

    RETURN @pString

    ;

    END

    ;

    Ok... let's first make sure it works correctly...

    --===== Prove that it works.

    -- Note that the pattern is WHICH CHARACTERS TO KILL. So, to kill everything

    -- but numeric digits, you have to tell it to NOT kill numeric digits

    SELECT top 100 CleanMe, CleanedString = dbo.CleanString(CleanMe,'[^0-9]')

    FROM #DirtyData

    And now for the million row speed test... a cteTally function can't touch this for speed...

    --drop table #MyHead

    Go

    --===== Speed Test

    SELECT CleanMe, CleanedString = dbo.CleanString(CleanMe,'[^0-9]')

    INTO #MyHead

    FROM #DirtyData

    ;

    Try that against anything you could write with a Tally Table or a cteTally. You will be amazed.

    Yeah... I know. It's strange that the proverbial leader of the "Anti-RBAR Alliance" would actually endorse any form of RBAR but sometimes there are exceptions and this is one of them.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • terrykzncs (4/28/2011)


    Thanks Wayne.

    This seems to work. Will have to understand it and test it before I use it though :blush:. I've worked with CTE's but not a TALLY table. Thanks for the pointer :cool:, I never would have thought of this.

    As you can tell by my article on the Tally Table, I'm normally the first to stand up and say, "USE A TALLY TABLE!!!". Not this time, though. Please see the post immediately above this one.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

Viewing 8 posts - 1 through 7 (of 7 total)

You must be logged in to reply to this topic. Login to reply