【问题标题】:SQL Server cursor - cannot assign variable properly [closed]SQL Server游标 - 无法正确分配变量[关闭]
【发布时间】:2018-05-30 18:54:51
【问题描述】:

我使用 SQL Server 2016。我需要将用户从旧系统转移到新系统。但是在从遗留系统中引入所有用户之前,我需要检查他们是否已经存在于我们的新系统中。这种确定是基于每个系统中用户的电子邮件地址的比较来完成的。

所以如果新系统中不存在用户,我需要将这样的记录从旧系统插入到新系统中。但如果用户确实存在,我需要使用旧系统中的数据更新新系统中的某些列。

为了完成它,我使用带有SCOPE_IDENTITY() 的光标。

我还需要在每次游标迭代中记录新插入的和现有的更新记录。问题是我的代码没有区分新记录和现有记录,因此它将所有用户 ID 插入日志表中两次。一次是用户 ID (@ContactID) 为 NULL(新系统中不存在),另一次是用户 ID (@ContactID) 不是 NULL - 存在于新系统中。

在下面的示例中,我有 1 条记录在新系统中不存在,而 3 条记录已经匹配。所以在 IF (@CoantactID IS NULL) 我的意思是只插入新插入到 MyDB.dbo.Contact 表记录中,在 IF (@CoantactID IS NOT NULL) 中插入到 Log 表中只有匹配的树记录(现有) 在新系统中。

但问题是,即使在 this IF (@ContactID IS NULL) 中,它也会将所有四个 ContactID 都插入到日志表中,然后在 IF (@ContactID IS NOT NULL) 中再次插入

;USE [MyDB];
GO

-- exec dbo.sp_UserMigration_Users_Copy
DROP PROCEDURE IF EXISTS dbo.sp_UserMigration_Users_Copy
GO

;USE [MyDB];
GO

CREATE  PROCEDURE dbo.sp_UserMigration_Users_Copy
 @UserID UNIQUEIDENTIFIER = NULL
 --,@ContactID INT

AS 

DECLARE @Email nvarchar(100),
        @ContactID INT;
-- @UserID UNIQUEIDENTIFIER,

DECLARE @SysID INT
SET @SysId = 17511; -- system contactID

---- creating log table 
--CREATE TABLE #T 
--(
-- UserID UNIQUEIDENTIFIER NOT NULL,
-- Email NVARCHAR(50) NOT NULL 
--);

SELECT * INTO #T 
FROM MyDB.dbo.User
WHERE UserID IN (   
                        '0604C514',
                        'C1FDAF34', 
                        '23BABE2D',
                        'EBA21D10'
                    ); 

IF NOT EXISTS (select * from MyDB.sys.tables where name = N'UserIDContactIDMigrationLog')

    CREATE TABLE MyDB.dbo.UserIDContactIDMigrationLog
        (
            UserID UNIQUEIDENTIFIER /* CONSTRAINT [PK_UserIDContactIDMigrationLog_UserID] PRIMARY KEY */ NOT NULL,  -- somehow inserts duplicate UserIDs 
            ContactId INT /*UNIQUE CONSTRAINT [UNIQUE_ContactId] */ NOT NULL,
            CreatedDt DATETIME2 NULL,
            UpdatedDt DATETIME2 NULL,
            UpdatedFlag BIT /*CONSTRAINT [DF_MigratedFlag] DEFAULT(0) */ NULL
        );

-- -----------------------------------------------------
-- Cursor: For all contacts to be migrated
-- -----------------------------------------------------
IF (SELECT CURSOR_STATUS('global','user_cursor')) >= -1
 BEGIN
  IF (SELECT CURSOR_STATUS('global','user_cursor')) > -1
   BEGIN
    CLOSE user_cursor
   END
 DEALLOCATE user_cursor
END

DECLARE user_cursor CURSOR
    FOR 
        SELECT a.UserID, LTRIM(RTRIM(a.Email)) AS Email
        FROM #T a WITH (NOLOCK)      
        --WHERE a.UserID = @UserID

-- begin cursor loop
OPEN user_cursor
FETCH NEXT FROM user_cursor INTO @UserID, @Email

WHILE @@FETCH_STATUS = 0
BEGIN

    SELECT @ContactID = (
        SELECT c.ContactId
        FROM MyDB.dbo.Contact c 
        LEFT OUTER JOIN MyDB.dbo.Login l ON c.ContactId = l.ContactId  
        WHERE (LTRIM(RTRIM(c.ContactEmailTx)) = @Email 
                OR LTRIM(RTRIM(l.ContactLoginNameTx)) = @Email 
                --OR l.ContactLoginNameTx LIKE @Email+'%'  -- this concept does not work in case of leading space at the ContactLoginNameTx so need to use the code below 
                OR SUBSTRING(LTRIM(RTRIM(l.ContactLoginNameTx)), -1, CHARINDEX('.INACTIVE.', l.ContactLoginNameTx)) = @Email)
        AND (c.ContactEmailTx != '' OR c.ContactEmailTx IS NOT NULL OR l.ContactLoginNameTx != '' OR l.ContactLoginNameTx IS NOT NULL)
                        )

-- check if a contact is new
    IF (@ContactID IS NULL)
    BEGIN
        -- -----------------------------------------------------
        -- MyDB.dbo.Contact
        -- -----------------------------------------------------
        INSERT INTO [MyDB].[dbo].[Contact]
            (   
                [ContactFirstNameTx],
                [ContactLastNameTx],
                [ContactEmailTx],
                [ContactTitleTx],
                [ContactCreateDt],
                [ContactCreateByID],
                [ContactCreateLoginTypeID]
            )
        SELECT 
                [FirstName],
                [LastName],
                [Email],
                [Title],
                --[AccountId],
                [UserCreatedDate],
                @SysId AS [ContactCreateByID],  -- as as flag for import [ContactCreateLoginTypeID]
                -10 AS [ContactCreateLoginTypeID]
        FROM #T o WITH (NOLOCK) 
        WHERE UserID = @UserID 

        SET @ContactID = SCOPE_IDENTITY()
    END
--   select * from  [MyDB].[dbo].[Contact] where contactid in ( 1051364, 466440, 560466, 618576)
    -- -----------------------------------------------------
    --MyDB.dbo.Login
    -- -----------------------------------------------------
    INSERT INTO [MyDB].[dbo].[Login]          
    (   
        ContactID,
        [ContactLoginNameTx], -- V.C. this is email. the field is has a unique constraint
        [ContactLoginActiveIn],
        [ContactLoginCreateDt],
        [ContactLoginCreateByID],
        [ContactLoginCreateLoginTypeID],
        [ContactLoginPasswordLastChangeDt],
        [ContactLoginLastLoginDt],
        [ContactLoginPasswordExclusionIn] -- this is not nullable and BIT
    )
    SELECT 
        @ContactID AS ContactID,
        [Email],
        CASE WHEN [UserStatusID] IN (1, 3) THEN 1 ELSE 0 END AS [ContactLoginActiveIn], -- v.c. in Onvia 1 is Active, 2 - Inactive, 3 - Invited, 4 - Inactive By System, 5 - Pending Registration
        [UserCreatedDate],
        @SysId AS ContactLoginCreateByID,  -- as as flag for Onvia import[ContactCreateLoginTypeID]
        -10 AS [ContactLoginCreateLoginTypeID],
        [UserLastPasswordChangedDate],
        [UserLastLoginDate],
        0 as ContactLoginPasswordExclusionIn -- this is not nullable and BIT
    FROM #T o WITH (NOLOCK) 
    WHERE UserID = @UserID
    AND NOT EXISTS (select * from MyDB.dbo.Login z where z.ContactID = @ContactID)

    -- -----------------------------------------------------
    -- Activate any inactive users 
    -- -----------------------------------------------------

    IF EXISTS (select * from MyDB.dbo.Login where ContactLoginActiveIn = 0 and ContactID = @ContactID)
        AND NOT EXISTS (select * from MyDB.dbo.Login where ContactLoginActiveIn = 1 and ContactID = @ContactID)
    BEGIN
        UPDATE [MyDB].[dbo].[Login]
        SET ContactLoginNameTx = @Email,
            ContactLoginActiveIn = (select case when [UserStatusID] IN (1, 3) then 1 else 0 end 
                                        from #T o where o.UserID = @UserID) 
        WHERE ContactID = @ContactID
    END

    -- -----------------------------------------------------
    --MyDB.dbo.ContactPhoneNumber
    -- -----------------------------------------------------
    INSERT INTO [MyDB].[dbo].[PhoneNumber]        
    (   
        ContactID,
        PhoneNumberTypeID,
        ContactPhoneNumberValueTx 
    )

    SELECT 
        @ContactID AS ContactID,
        1 AS PhoneNumberTypeID,
        ISNULL([PhoneNumber1],'') AS ContactPhoneNumberValueTx
    FROM #T o WITH (NOLOCK) 
    WHERE o.UserID = @UserID 
    AND NOT EXISTS (select * from [MyDB].[dbo].[PhoneNumber] z where z.ContactID = @ContactID);

    -- -----------------------------------------------------
    --MyDB.dbo.ContactOrg
    -- -----------------------------------------------------
    INSERT INTO [MyDB].[dbo].[Org]      
    (   
        ContactID,
        OrgID
    )
    SELECT 
        @ContactID AS ContactID,
        b.OrgID AS OrgID
    FROM #T o WITH (NOLOCK) 
    INNER JOIN ImportQueue.dbo.OnviaAccountIDOrgIDMigrationLog b WITH (NOLOCK) ON o.AccountId = b.OnviaAccountID
    WHERE o.UserID = @UserID 
    AND NOT EXISTS (select * from [MyDB].[dbo].[Org] z where z.ContactID = @ContactID);

    -- -----------------------------------------------------
    ---- MyDB.dbo.UserIDContactIDMigrationLog
    ---- log output for new users
    -- -----------------------------------------------------

    INSERT INTO MyDB.dbo.UserIDContactIDMigrationLog
    (
        UserID,
        ContactId,
        CreatedDt,
        UpdatedFlag
    )
    VALUES 
    (
        @UserID,
        @ContactID,
        GETDATE(),
        0 
    )

        -- -----------------------------------------------------
        --  EXISTING MATCHED USERS
        -- -----------------------------------------------------
            -- check if a contact is existing
    IF (@ContactID IS NOT NULL)
    BEGIN
        -- -----------------------------------------------------
        -- MyDB.dbo.Contact
        -- -----------------------------------------------------
        UPDATE MyDB.dbo.Contact
        SET ContactFirstNameTx =
                CASE 
                    WHEN ContactFirstNameTx NOT IN ('', NULL) THEN c.FirstName
                    ELSE ContactFirstNameTx
                END,
            ContactLastNameTx = 
                CASE 
                    WHEN ContactLastNameTx IN ('', NULL) THEN c.LastName
                    ELSE ContactLastNameTx
                END,
            ContactEmailTx = 
                CASE 
                    WHEN ContactEmailTx IN ('', NULL) THEN c.Email
                    ELSE ContactEmailTx
                END,
            ContactTitleTx =
                CASE 
                    WHEN ContactTitleTx IN ('', NULL) THEN c.Title
                    ELSE ContactTitleTx
                END,
            [ContactCreateDt] =
                CASE 
                    WHEN ContactCreateDt IN ('', NULL) THEN c.UserCreatedDate
                    ELSE ContactCreateDt
                END,
            [ContactModifyByID] = @SysId,
            [ContactModifyLoginTypeID] = - 10

        FROM #T c
            WHERE UserID = @UserID 
            AND ContactID = @ContactID;
    -- -----------------------------------------------------
    ---- MyDB.dbo.UserIDContactIDMigrationLog
    ---- log output for existing matched users
    -- -----------------------------------------------------

            INSERT INTO MyDB.dbo.UserIDContactIDMigrationLog 
             (
                 UserID,
                 ContactId,
                 UpdatedDt,
                 UpdatedFlag
             )

            VALUES 
             (
                @UserID,
                @ContactID,
                GETDATE(),
                1 
             );

        FETCH NEXT FROM user_cursor INTO @UserID, @Email

        -- end cursor loop

    END
END

CLOSE user_cursor
        DEALLOCATE user_cursor

GO

【问题讨论】:

  • 请发布示例数据以及您当前的代码和您获得的结果 - 也称为minimal reproducible example。您可能也不需要在此处使用光标 - 我希望您还没有接受这种方法?
  • 在我的示例中,我有 1 条记录在新系统中不存在,3 条记录匹配。所以在 IF (@CoantactID IS NULL) 我的意思是只插入新插入到 MyDB.dbo.Contact 表记录中,在 IF (@CoantactID IS NOT NULL) 中插入到 Log 表中只有匹配的树记录(现有) 在新系统中。
  • 但问题是,即使在 this IF (@ContactID IS NULL) 中,它也会将所有四个 ContactID 插入到日志表中,然后在 IF (@ContactID IS NOT NULL) 中再次插入。
  • 旁注:您应该为您的存储过程使用sp_ 前缀。微软有reserved that prefix for its own use (see Naming Stored Procedures),你确实会在未来某个时候冒着名称冲突的风险。 It's also bad for your stored procedure performance。最好只是简单地避免sp_ 并使用其他东西作为前缀 - 或者根本不使用前缀!
  • 您可能也想看看这里,因为您对临时表使用了 nolock。它实际上并没有做更多的事情,只是让您输入更多内容。 blogs.sentryone.com/aaronbertrand/nolock-temp-tables

标签: loops variables scope cursor sql-server-2016


【解决方案1】:

试试下面的代码,而不是游标。

- exec dbo.sp_UserMigration_Users_Copy
    DROP PROCEDURE IF EXISTS dbo.sp_UserMigration_Users_Copy
    GO

    ;USE [MyDB];
    GO

    CREATE  PROCEDURE dbo.sp_UserMigration_Users_Copy
     @UserID UNIQUEIDENTIFIER = NULL
     --,@ContactID INT

    AS 

    DECLARE @Email nvarchar(100),
            @ContactID INT;


    DECLARE @SysID INT
    SET @SysId = 17511; 

    CREATE TABLE #T 
    (
    ID INT IDENTITY(1,1) NOT NULL,
     UserID NVARCHAR(50)NOT NULL,
     Email NVARCHAR(50) NOT NULL 
    );
    INSERT INTO #T 
    SELECT * 
    FROM MyDB.dbo.User
    WHERE UserID IN (   
                            '0604C514',
                            'C1FDAF34', 
                            '23BABE2D',
                            'EBA21D10'
                        ); 

    IF NOT EXISTS (select * from MyDB.sys.tables where name = N'UserIDContactIDMigrationLog')

        CREATE TABLE MyDB.dbo.UserIDContactIDMigrationLog
            (
                UserID UNIQUEIDENTIFIER /* CONSTRAINT [PK_UserIDContactIDMigrationLog_UserID] PRIMARY KEY */ NOT NULL,  -- somehow inserts duplicate UserIDs 
                ContactId INT /*UNIQUE CONSTRAINT [UNIQUE_ContactId] */ NOT NULL,
                CreatedDt DATETIME2 NULL,
                UpdatedDt DATETIME2 NULL,
                UpdatedFlag BIT /*CONSTRAINT [DF_MigratedFlag] DEFAULT(0) */ NULL
            );


    DECLARE @COUNT INT 
    DECLARE @I INT = 1
    DECLARE @UserID NVARCHAR(50) ;
    DECLARE @Email NVARCHAR(50)
    SELECT @COUNT = COUNT(*) FROM #T
    WHILE(@I <= @COUNT)

    BEGIN 
            SELECT @UserID = UserID, @Email= LTRIM(RTRIM(Email)) FROM #T WHERE ID = @I   


        SELECT @ContactID = (
            SELECT c.ContactId
            FROM MyDB.dbo.Contact c 
            LEFT OUTER JOIN MyDB.dbo.Login l ON c.ContactId = l.ContactId  
            WHERE (LTRIM(RTRIM(c.ContactEmailTx)) = @Email 
                    OR LTRIM(RTRIM(l.ContactLoginNameTx)) = @Email 
                    --OR l.ContactLoginNameTx LIKE @Email+'%'  -- this concept does not work in case of leading space at the ContactLoginNameTx so need to use the code below 
                    OR SUBSTRING(LTRIM(RTRIM(l.ContactLoginNameTx)), -1, CHARINDEX('.INACTIVE.', l.ContactLoginNameTx)) = @Email)
            AND (c.ContactEmailTx != '' OR c.ContactEmailTx IS NOT NULL OR l.ContactLoginNameTx != '' OR l.ContactLoginNameTx IS NOT NULL)
                            )

    -- check if a contact is new
        IF (@ContactID IS NULL)
        BEGIN
            -- -----------------------------------------------------
            -- MyDB.dbo.Contact
            -- -----------------------------------------------------
            INSERT INTO [MyDB].[dbo].[Contact]
                (   
                    [ContactFirstNameTx],
                    [ContactLastNameTx],
                    [ContactEmailTx],
                    [ContactTitleTx],
                    [ContactCreateDt],
                    [ContactCreateByID],
                    [ContactCreateLoginTypeID]
                )
            SELECT 
                    [FirstName],
                    [LastName],
                    [Email],
                    [Title],
                    --[AccountId],
                    [UserCreatedDate],
                    @SysId AS [ContactCreateByID],  -- as as flag for Onvia import[ContactCreateLoginTypeID]
                    -10 AS [ContactCreateLoginTypeID]
            FROM #T o WITH (NOLOCK) 
            WHERE UserID = @UserID 

            SET @ContactID = SCOPE_IDENTITY()
        END
    --   select * from  [MyDB].[dbo].[Contact] where contactid in ( 1051364, 466440, 560466, 618576)
        -- -----------------------------------------------------
        --MyDB.dbo.Login
        -- -----------------------------------------------------
        INSERT INTO [MyDB].[dbo].[Login]          
        (   
            ContactID,
            [ContactLoginNameTx], -- V.C. this is email. the field is has a unique constraint
            [ContactLoginActiveIn],
            [ContactLoginCreateDt],
            [ContactLoginCreateByID],
            [ContactLoginCreateLoginTypeID],
            [ContactLoginPasswordLastChangeDt],
            [ContactLoginLastLoginDt],
            [ContactLoginPasswordExclusionIn] -- this is not nullable and BIT
        )
        SELECT 
            @ContactID AS ContactID,
            [Email],
            CASE WHEN [UserStatusID] IN (1, 3) THEN 1 ELSE 0 END AS [ContactLoginActiveIn], -- v.c. in Onvia 1 is Active, 2 - Inactive, 3 - Invited, 4 - Inactive By System, 5 - Pending Registration
            [UserCreatedDate],
            @SysId AS ContactLoginCreateByID,  -- as as flag for Onvia import[ContactCreateLoginTypeID]
            -10 AS [ContactLoginCreateLoginTypeID],
            [UserLastPasswordChangedDate],
            [UserLastLoginDate],
            0 as ContactLoginPasswordExclusionIn -- this is not nullable and BIT
        FROM #T o WITH (NOLOCK) 
        WHERE UserID = @UserID
        AND NOT EXISTS (select * from MyDB.dbo.Login z where z.ContactID = @ContactID)

        -- -----------------------------------------------------
        -- Activate any inactive users 
        -- -----------------------------------------------------

        IF EXISTS (select * from MyDB.dbo.Login where ContactLoginActiveIn = 0 and ContactID = @ContactID)
            AND NOT EXISTS (select * from MyDB.dbo.Login where ContactLoginActiveIn = 1 and ContactID = @ContactID)
        BEGIN
            UPDATE [MyDB].[dbo].[Login]
            SET ContactLoginNameTx = @Email,
                ContactLoginActiveIn = (select case when [UserStatusID] IN (1, 3) then 1 else 0 end 
                                            from #T o where o.UserID = @UserID) 
            WHERE ContactID = @ContactID
        END

        -- -----------------------------------------------------
        --MyDB.dbo.ContactPhoneNumber
        -- -----------------------------------------------------
        INSERT INTO [MyDB].[dbo].[PhoneNumber]        
        (   
            ContactID,
            PhoneNumberTypeID,
            ContactPhoneNumberValueTx 
        )

        SELECT 
            @ContactID AS ContactID,
            1 AS PhoneNumberTypeID,
            ISNULL([PhoneNumber1],'') AS ContactPhoneNumberValueTx
        FROM #T o WITH (NOLOCK) 
        WHERE o.UserID = @UserID 
        AND NOT EXISTS (select * from [MyDB].[dbo].[PhoneNumber] z where z.ContactID = @ContactID);

        -- -----------------------------------------------------
        --MyDB.dbo.ContactOrg
        -- -----------------------------------------------------
        INSERT INTO [MyDB].[dbo].[Org]      
        (   
            ContactID,
            OrgID
        )
        SELECT 
            @ContactID AS ContactID,
            b.OrgID AS OrgID
        FROM #T o WITH (NOLOCK) 
        INNER JOIN ImportQueue.dbo.OnviaAccountIDOrgIDMigrationLog b WITH (NOLOCK) ON o.AccountId = b.OnviaAccountID
        WHERE o.UserID = @UserID 
        AND NOT EXISTS (select * from [MyDB].[dbo].[Org] z where z.ContactID = @ContactID);

        -- -----------------------------------------------------
        ---- MyDB.dbo.UserIDContactIDMigrationLog
        ---- log output for new users
        -- -----------------------------------------------------

        INSERT INTO MyDB.dbo.UserIDContactIDMigrationLog
        (
            UserID,
            ContactId,
            CreatedDt,
            UpdatedFlag
        )
        VALUES 
        (
            @UserID,
            @ContactID,
            GETDATE(),
            0 
        )

            -- -----------------------------------------------------
            --  EXISTING MATCHED USERS
            -- -----------------------------------------------------
                -- check if a contact is existing
        IF (@ContactID IS NOT NULL)
        BEGIN
            -- -----------------------------------------------------
            -- MyDB.dbo.Contact
            -- -----------------------------------------------------
            UPDATE MyDB.dbo.Contact
            SET ContactFirstNameTx =
                    CASE 
                        WHEN ContactFirstNameTx NOT IN ('', NULL) THEN c.FirstName
                        ELSE ContactFirstNameTx
                    END,
                ContactLastNameTx = 
                    CASE 
                        WHEN ContactLastNameTx IN ('', NULL) THEN c.LastName
                        ELSE ContactLastNameTx
                    END,
                ContactEmailTx = 
                    CASE 
                        WHEN ContactEmailTx IN ('', NULL) THEN c.Email
                        ELSE ContactEmailTx
                    END,
                ContactTitleTx =
                    CASE 
                        WHEN ContactTitleTx IN ('', NULL) THEN c.Title
                        ELSE ContactTitleTx
                    END,
                [ContactCreateDt] =
                    CASE 
                        WHEN ContactCreateDt IN ('', NULL) THEN c.UserCreatedDate
                        ELSE ContactCreateDt
                    END,
                [ContactModifyByID] = @SysId,
                [ContactModifyLoginTypeID] = - 10

            FROM #T c
                WHERE UserID = @UserID 
                AND ContactID = @ContactID;
        -- -----------------------------------------------------
        ---- MyDB.dbo.UserIDContactIDMigrationLog
        ---- log output for existing matched users
        -- -----------------------------------------------------

                INSERT INTO MyDB.dbo.UserIDContactIDMigrationLog 
                 (
                     UserID,
                     ContactId,
                     UpdatedDt,
                     UpdatedFlag
                 )

                VALUES 
                 (
                    @UserID,
                    @ContactID,
                    GETDATE(),
                    1 
                 );




        END
 SET @I = @I+1;
    END

【讨论】:

  • 非常感谢。但我不确定“ID”列来自哪里? SELECT @UserID = UserID, @Email= LTRIM(RTRIM(Email)) FROM #T WHERE ID = I。#T 中唯一的标识列是 UserID
  • ID 列在 #t 表中。它是一个标识列。
  • 所以我在#T 表中添加了一个新列。但是 SELECT @UserID = OnviaUserID, @Email= LTRIM(RTRIM(Email)) FROM #T WHERE ID = I is hang
  • 如果您执行包括此代码及以上代码(但不在以下代码)在内的所有内容,您将看到它挂起 BEGIND SELECT @UserID = OnviaUserID, @Email= LTRIM(RTRIM(Email)) FROM #T WHERE ID =我结束
  • 这意味着@I 没有增加。
猜你喜欢
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 2014-09-10
  • 2021-03-14
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多