在SP中使用SQL游标的另一种方法?

时间:2013-12-19 13:13:35

标签: sql sql-server-2008 cursor

我正在用SQL编写存储过程,我目前处于拥有3个嵌套游标的位置(这不是我想要的运行方式)。我很感激有关如何摆脱光标的任何建议是解决方案陷阱

我基本上有2个表(在应用程序的SELECT语句填充的SP期间都是临时的)

1 - 表,列和数据类型列表

Table1 | SURNAME  | VARCHAR
       | SEX      | VARCHAR
       | DOB      | DATETIME
------------------------------
Table2 | ADDRESS  | VARCHAR
------------------------------
Table3 | SALARY   | INT
------------------------------
Table4 | USERNAME | VARCHAR
       | PASSWORD | VARCHAR

2 - 一列用户号码

我的应用程序必须循环遍历每个用户编号,然后对于每个用户,我必须遍历不同的表(Table1,Table2,Table3,Table4)并查看该用户的该表中是否有任何数据(使用dyanmic) SQL所以我可以将表名作为参数传递)。如果有,那么我必须循环遍历与该表相关的每一列并构建动态SQL INSERT语句

所以对于2号用户...... 表1中的任何数据?不 - 跳过 表2中的任何数据?是 - 将行复制到临时表中,然后为ADDRESS值构建动态SQL 表3中的任何数据?不 - 跳过 表4中的任何数据?是 - 将行复制到临时表中,然后为USERNAME和PASSWORD列构建动态SQL

从功能的角度来看,光标方法工作正常,但性能有点慢。我已经确保2个源表尽可能紧密,在创建动态SQL时,我只将相关行复制到临时表中进行处理,我已经创建了我的游标FAST_FORWARD和READ_ONLY

我可以采取任何方法吗?

根据要求发布的原始代码:

IF OBJECT_ID('tempdb..##MembersToDelete') IS NOT NULL DROP TABLE ##MembersToDelete
IF OBJECT_ID('tempdb..##TempDataTable') IS NOT NULL DROP TABLE ##TempDataTable
DELETE FROM @CompSetTable
DELETE FROM DataRefreshDeletes
DELETE FROM DataRefreshInserts


--BUILD THE INDIVIDUAL SELECT STATEMENTS INTO A TEMP TABLE
SET @RowPosition = 1;
SET @inSelectFilter = @inSelectFilter + ';';
SET @inSelectFilter = REPLACE(@inSelectFilter,'/','''');
WHILE LEN(@inSelectFilter) > 0
BEGIN
    IF PATINDEX('%;%',@inSelectFilter) > 0
    BEGIN
        SET @SelectParameter = SUBSTRING(@inSelectFilter, 0, PATINDEX('%;%',@inSelectFilter))
        SET @inSelectFilter = SUBSTRING(@inSelectFilter, LEN(@SelectParameter + ';') + 1,LEN(@inSelectFilter))
        IF @RowPosition = 1
            BEGIN
            INSERT INTO @SelectParameterTable VALUES ('WHERE ' + @SelectParameter)
            END
        ELSE
            BEGIN
            INSERT INTO @SelectParameterTable VALUES (' AND ' + @SelectParameter)
            END
        SET @RowPosition = @RowPosition + 1        
    END
    ELSE
    BEGIN
        SET @SelectParameter = @inSelectFilter
        SET @inSelectFilter = NULL
    END
END

--BUILD THE COMPLETE DELETE STATEMENT
SET @SelectParameter = NULL;
SELECT @SelectParameter = COALESCE(@SelectParameter, '') + statementString FROM @SelectParameterTable 

--INSERT THE MEMBER NUMBERS INTO THE TEMP TABLE
IF OBJECT_ID('tempdb..##MembersToDelete') IS NOT NULL DROP TABLE ##MembersToDelete
SET @SelectParameter = 'SELECT MEMBNO INTO ##MembersToDelete FROM BASIC ' + @SelectParameter
BEGIN TRY
    EXECUTE sp_executesql @SelectParameter
END TRY
BEGIN CATCH
    Print 'The following statement could not be run - please check the syntax...'
    Print @SelectParameter
    GOTO cleanUpAndFinish
END CATCH
SELECT @MembersToDeleteCount = COUNT(*) FROM ##MembersToDelete

Print '##MembersToDelete TABLE BUILT - ' + CONVERT(VARCHAR(26), GETDATE(), 109)

--BUILD LIST OF COMPENDIA TABLES (ORDERED BY DSET)
DELETE FROM @CompSetTable
INSERT INTO @CompSetTable SELECT d.DSNAME, c.column_name, d.DSET, c.data_type FROM DICTIONARY d, INFORMATION_SCHEMA.COLUMNS c WHERE DNUM = -1 AND DSET < 250 AND c.table_name = d.DSNAME ORDER BY d.DSET

Print '@CompSetTable TABLE BUILT - ' + CONVERT(VARCHAR(26), GETDATE(), 109)
DECLARE setInsertCursor CURSOR GLOBAL FAST_FORWARD READ_ONLY FOR SELECT DISTINCT setName, setNumber FROM @CompSetTable ORDER BY setNumber

--WE NOW HAVE THE LIST OF MEMBER NUMBERS AND THE LIST OF TABLES TO BUILD THE DELETE STATEMENT
SELECT @MemberNumberString = COALESCE(@MemberNumberString + ', ', '') + LTRIM(STR(MEMBNO)) FROM ##MembersToDelete 

DECLARE setDeleteCursor CURSOR READ_ONLY SCROLL FOR SELECT DISTINCT setName, setNumber FROM @CompSetTable ORDER BY setNumber
OPEN setDeleteCursor
FETCH LAST FROM setDeleteCursor INTO @SetName, @SetNumber
    WHILE @@FETCH_STATUS = 0
        BEGIN
        INSERT INTO @DeleteStatementTable VALUES ('DELETE FROM ' + UPPER(@SetName) + ' WHERE MEMBNO IN (' + @MemberNumberString + ')')
        FETCH PRIOR FROM setDeleteCursor INTO @SetName, @SetNumber
        END
CLOSE setDeleteCursor
DEALLOCATE setDeleteCursor

Print '@DeleteStatementTable TABLE BUILT - ' + CONVERT(VARCHAR(26), GETDATE(), 109)

DECLARE memberInsertCursor CURSOR FOR SELECT MEMBNO FROM ##MembersToDelete
OPEN memberInsertCursor
FETCH NEXT FROM memberInsertCursor INTO @MemberNumber
WHILE @@FETCH_STATUS = 0
BEGIN
    --NOW BUILD THE INSERT STATEMENTS
    OPEN setInsertCursor
    FETCH NEXT FROM setInsertCursor INTO @SetName, @SetNumber
    WHILE @@FETCH_STATUS = 0
    BEGIN
            --CHECK IF MEMBER HAS ANY ROWS IN THIS SET - IF NOT, SKIP
            SET @ROWCOUNT = 0
            SELECT @COUNTSQL = N'SELECT @countOUT = COUNT(*) FROM ' + @SetName + ' WHERE MEMBNO = ' + LTRIM(STR(@MemberNumber))  
            EXEC sp_executesql @COUNTSQL, N'@countOUT INT OUTPUT', @countOUT=@ROWCOUNT OUTPUT;
            IF @ROWCOUNT = 0
                BEGIN
                                GOTO nextSet
                END

                                SET @VALUES = NULL;     

            --DROP TEMPORARY TABLE
            IF OBJECT_ID('tempdb..##TempDataTable') IS NOT NULL DROP TABLE ##TempDataTable

            --POPULATE TEMPORARY TABLE
            SET @SQL = 'SELECT * INTO ##TempDataTable FROM ' + @SetName + ' WHERE MEMBNO = ' + LTRIM(STR(@MemberNumber))
            EXECUTE sp_executesql @SQL

            --BUILD SELECT STATEMENT
            SET @INSERTSTRING = NULL
            SET @INSERTSTRING = CAST('' as nVarChar(MAX)) + 'SELECT ''INSERT INTO ' + @SetName + ' VALUES ('''

            DECLARE setColumnCursor CURSOR FAST_FORWARD READ_ONLY FOR SELECT columnName, dataType FROM @CompSetTable WHERE setName = @SetName
            OPEN setColumnCursor
            FETCH NEXT FROM setColumnCursor INTO @ColumnName, @DataType
            WHILE @@FETCH_STATUS = 0
            BEGIN       

                IF @DataType IN ('text','varchar','nvarchar','ntext','char')
                BEGIN
                    SET @INSERTSTRING = CAST('' as nVarChar(MAX)) + @INSERTSTRING + ''''' + ISNULL(' + @ColumnName + ',''NULL'') + '''''','''
                END
                ELSE IF @DataType IN ('int','decimal','smallint','numeric','tinyint','bigint','float')
                BEGIN
                    --SET @INSERTSTRING = @INSERTSTRING + ' + ' + @COLUMNNAMENULL + ' + '','''
                    SET @INSERTSTRING = CAST('' as nVarChar(MAX)) + @INSERTSTRING + ' + ISNULL(CONVERT(VARCHAR(MAX),' + @ColumnName + '),''NULL'')' + ' + '','''
                END
                ELSE IF @DataType IN ('datetime')
                BEGIN
                    --SET @INSERTSTRING = @INSERTSTRING + ' + ' + @COLUMNNAMENULL + ' + '','''
                    SET @INSERTSTRING = CAST('' as nVarChar(MAX)) + @INSERTSTRING + ''''' + ISNULL(CONVERT(VARCHAR(MAX),' + @ColumnName + '),''NULL'')' + ' + '''''','''
                END

                FETCH NEXT FROM setColumnCursor INTO @ColumnName, @DataType
            END
            CLOSE setColumnCursor
            DEALLOCATE setColumnCursor      

            SET @INSERTSTRING = @INSERTSTRING + '+'')'''
            SET @INSERTSTRING = @INSERTSTRING + ' FROM ##TempDataTable'

            INSERT INTO @InsertStatementTable EXECUTE sp_executesql @INSERTSTRING

        nextSet:
            FETCH NEXT FROM setInsertCursor INTO @SetName, @SetNumber
            END

FETCH NEXT FROM memberInsertCursor INTO @MemberNumber
END
CLOSE memberInsertCursor
DEALLOCATE memberInsertCursor

CLOSE setInsertCursor
DEALLOCATE setInsertCursor

3 个答案:

答案 0 :(得分:1)

也许我没有正确地回答你的问题,但是使用'insert ... select'哪个select加入所有这些表有什么问题?

答案 1 :(得分:1)

听起来你正在尝试实现优先权。我怀疑你是否真的需要原始表。如果没有,请使用not exists

insert into . . .
    select . . . 
    from Table1;

insert into . . .
    select . . .
    from Table2
    where not exists (select 1
                      from Table1
                      where Table1.userid = Table2.userid and
                            Table1.colname = Table2.colname
                     );

insert into . . .
    select . . .
    from Table3
    where not exists (select 1
                      from Table1
                      where Table1.userid = Table3.userid and
                            Table1.colname = Table3.colname
                     ) and
          not exists  (select 1
                      from Table2
                      where Table2.userid = Table3.userid and
                            Table2.colname = Table3.colname
                     );

如果您确实有条件地从每个表中选择列,那么您可能必须将上面的内容构造为动态SQL。

答案 2 :(得分:0)

如果在每个表中添加ID列并对其进行索引,则可以更快地进行选择。为ID列提供唯一索引,就是这样。你可以大大提高整个动作的速度。