【问题标题】:SQL 2005: Optimize upsert-like Stored Procedure using Cursor, possible?SQL 2005:使用游标优化类似 upsert 的存储过程,可能吗?
【发布时间】:2017-10-18 09:31:32
【问题描述】:

好的,这是我第二次尝试解决这个问题。

我想知道是否有一种可能的方法来优化为存储过程创建的游标,用于迭代具有两个联合的大选择语句。稍后,存储过程开始将值插入到临时表中,并根据“不存在”的选择语句检查每个值。

或者更好的是,是否可以使用 select 语句创建所有这些并可能加入。

插入过程需要很长时间才能完成,我会重新考虑选择数据会快得多。

这是一个 SQL 示例:

declare @ID1 varchar(40) ,
        @ID2 varchar(20) ,
        @State varchar(20) ,
        @isActive bit

Declare CuTable SCROLL INSENSITIVE cursor for
    Select 
        Cast(ID1 as Varchar(20)) AS ID1, 
        Cast(ID2 as Varchar(20)) AS ID2,  
        'AT' AS [State], 
        CASE When (isAvtiveDate > { fn CURDATE() }) or isAvtiveDate is null Then 1 else 0 end AS isAvtive
    From 
        server1.db.dbo.table1 
    Inner Join 
        server1.db.dbo.table2 on ID2 = ID1
    Where ID3 = 1 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')

    UNION

    Select
        Cast(ID1 as Varchar(20)) AS ID1, 
        Cast(ID2 as Varchar(20)) AS ID2, 
        'AP' AS [State], 
        CASE When (isActiveDate > { fn CURDATE() }) or isActiveDate is null Then 1 else 0 end AS isActive
    From 
        server1.db.dbo.table1 
    Inner Join 
        server1.db.dbo.table2 on ID2 = ID1
    Where 
        ID3 = 2 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
UNION
Select
           Cast(ID1 as Varchar(20)) AS ID1, 
           Cast(ID2 as Varchar(20)) AS ID2,
           'AH' AS [State], 
           CASE When (isActiveDate > { fn CURDATE() }) or isActiveDate is null Then 1 else 0 end AS isActive
From server1.db.dbo.table1 inner join server1.db.dbo.table2 on ID2 = ID1
           inner join server1.db.dbo.table13 on ID2 = ID4
Where ID3 = 5 and toDate is null and fromDate is not null AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')


Open CuTable
Fetch Next From CuTable Into  @ID1, @ID2, @[State], @isActive

While @@Fetch_Status = 0

Begin
    Insert Into StagingTable (ID1, ID2, [State], isActive) 

           --Values 
           Select @ID1, @ID2, @[State], @isActive
           where not exists(select * from StagingTable  where ID1 = @ID1 and ID2 = @ID2)


Fetch Next From CuTable Into @ID1, @ID2, @[State], @isActive

End

close CuTable
deallocate CuTable

注意:我使用的是 SQL SERVER 2005

关于 Leonidas199x 评论线程的更新:

【问题讨论】:

    标签: sql-server stored-procedures sql-server-2005 cursor upsert


    【解决方案1】:

    根本不需要游标,因为数据不是动态变化的。您应该能够使用基于集合的方法来做到这一点。下面是一个使用 CTE 的示例,使用左连接仅插入暂存表中不存在的那些:

    ;WITH CTE AS
    (
    SELECT      CAST(ID1 as Varchar(20)) AS ID1, 
                CAST(ID2 as Varchar(20)) AS ID2,  
                'AT' AS [State], 
                CASE When (isAvtiveDate > { fn CURDATE() }) or isAvtiveDate is null Then 1 else 0 end AS isAvtive
    FROM        server1.db.dbo.table1 
    INNER JOIN  server1.db.dbo.table2 on ID2 = ID1
    WHERE       ID3 = 1 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
    
    UNION
    
    SELECT      CAST(ID1 as Varchar(20)) AS ID1, 
                CAST(ID2 as Varchar(20)) AS ID2, 
                'AP' AS [State], 
                CASE WHEN (isActiveDate > { fn CURDATE() }) or isActiveDate is null Then 1 else 0 end AS isActive
    FROM        server1.db.dbo.table1 
    INNER JOIN  server1.db.dbo.table2 on ID2 = ID1
    WHERE       ID3 = 2 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
    
    UNION
    
    SELECT
                Cast(ID1 as Varchar(20)) AS ID1, 
                Cast(ID2 as Varchar(20)) AS ID2,
                'AH' AS [State], 
                CASE When (isActiveDate > { fn CURDATE() }) or isActiveDate is null Then 1 else 0 end AS isActive
    FROM        server1.db.dbo.table1 
    INNER JOIN  server1.db.dbo.table2   ON ID2 = ID1
    INNER JOIN  server1.db.dbo.table13  ON ID2 = ID4
    WHERE       ID3 = 5 and toDate is null and fromDate is not null AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
    )
    INSERT INTO StagingTable 
            (
                ID1, 
                ID2, 
                [State], 
                isActive
            ) 
    SELECT      DISTINCT 
                CT.ID1, 
                CT.ID2, 
                CT.[State], 
                CT.isActive
    FROM        CTE             AS  CT
    LEFT JOIN   StagingTable    AS  ST  ON  ST.ID1 = CT.ID1 AND ST.ID2 = CT.ID2
    WHERE       ST.ID1 IS NULL 
    AND         ST.ID2 IS NULL;
    

    鉴于需要像游标一样检查每一行,我将使用以下内容,使用临时表检查插入临时表时标识的每组 ID1 和 ID2 是唯一的,然后插入临时表中的临时表:

    /*Create temp table*/
    IF OBJECT_ID('tempdb..#tmpData') IS NOT NULL DROP TABLE #tmpData
    GO
    CREATE TABLE #tmpData
            (
                ID1             VARCHAR(20) ,
                ID2             VARCHAR(20) ,
                [State]         VARCHAR(2)  ,
                IsActiveData    BIT
            )
    
    /*Insert into the temp table, with each insert join back to the temp table to ensure ID1 and ID2 are not already inserted*/
    INSERT INTO #tmpData
            (
                ID1                     ,
                ID2                     ,
                [State]                 ,
                IsActiveData
            )
    SELECT      CAST(ID1 as Varchar(20))    AS ID1, 
                CAST(ID2 as Varchar(20))    AS ID2,  
                'AT'                        AS [State], 
                CASE WHEN (isAvtiveDate > { fn CURDATE() }) or isAvtiveDate is null Then 1 else 0 end AS isAvtive
    FROM        server1.db.dbo.table1 
    INNER JOIN  server1.db.dbo.table2 on ID2 = ID1
    WHERE       ID3 = 1 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
    
    INSERT INTO #tmpData
            (
                ID1                     ,
                ID2                     ,
                [State]                 ,
                IsActiveData
            )
    SELECT      CAST(T1.ID1 as VARCHAR(20)) AS ID1, 
                CAST(T2.ID2 as VARCHAR(20)) AS ID2, 
                'AP'                        AS [State], 
                CASE WHEN (isActiveDate > { fn CURDATE() }) or isActiveDate is null Then 1 else 0 end AS isActive
    FROM        server1.db.dbo.table1       AS  T1
    INNER JOIN  server1.db.dbo.table2       AS  T2  ON T2.ID2 = T1.ID1
    LEFT JOIN   #tmpData                    AS  T   ON  T.ID1 = T1.ID1 AND T.ID2 = T2.ID2
    WHERE       ID3 = 2 AND isActiveDate <= ISNULL(isActiveDate,'2020-01-01')
    AND         T.ID1 IS NULL
    AND         T.ID2 IS NULL
    
    INSERT INTO #tmpData
            (
                ID1                     ,
                ID2                     ,
                [State]                 ,
                IsActiveData
            )
    SELECT
                Cast(T1.ID1 as Varchar(20)) AS ID1, 
                Cast(T2.ID2 as Varchar(20)) AS ID2,
                'AH' AS [State], 
                CASE When (isActiveDate > { fn CURDATE() }) OR isActiveDate IS NULL Then 1 else 0 end AS isActive
    FROM        server1.db.dbo.table1       AS  T1
    INNER JOIN  server1.db.dbo.table2       AS  T2  ON  T2.ID2  = T1.ID1
    INNER JOIN  server1.db.dbo.table13      AS  T13 ON  T2.ID2  = T13.ID4
    LEFT JOIN   #tmpData                    AS  T   ON  T.ID1   = T1.ID1 AND T.ID2 = T2.ID2
    WHERE       ID3             =   5 
    AND         toDate          IS NULL 
    AND         fromDate        IS NOT NULL 
    AND         isActiveDate    <= ISNULL(isActiveDate,'2020-01-01')
    AND         T.ID1           IS NULL
    AND         T.ID2           IS NULL
    
    
    
    
    /*Insert into the staging table from the temp table ensuring only records that are not already in there are inserted.*/
    INSERT INTO StagingTable 
            (
                ID1, 
                ID2, 
                [State], 
                isActive
            ) 
    SELECT      CT.ID1, 
                CT.ID2, 
                CT.[State], 
                CT.isActive
    FROM        #tmpData        AS  CT
    LEFT JOIN   StagingTable    AS  ST  ON  ST.ID1 = CT.ID1 AND ST.ID2 = CT.ID2
    WHERE       ST.ID1 IS NULL 
    AND         ST.ID2 IS NULL;
    

    【讨论】:

    • 感谢您抽出宝贵时间,我一定会检查一下。 :)
    • 好吧,它可以将数据复制到临时表,但它与带有游标的存储过程的效果不同。我知道在较大的 Select 语句中有一个“ID1”,工会是“3098”。当我在使用游标运行存储过程后在原始结果中进行选择时,我只得到一条 ID1 = '3098' 的记录。使用您的方法,我得到了两条记录,即 ID1 ='3098' 的两条相同记录。所有行的结果也是大约 71511 行,应该是 71510 行。 @Leonidas199x
    • 我明白了,这是有道理的,因为光标每次都会检查,所以会暴露在它之前插入的内容。应该通过在插入的选择查询上放置 DISTINCT 来解决(更新的答案),只要数据相同,当然。
    • 我真的很困惑。刚刚看到您对查询所做的更新,尝试了不同的确切位置,我仍然得到 71511 条记录,重复项不会被排除。出于某种原因,所有列都在 varchar 中,这与比较功能似乎不起作用的原因有关吗?
    • 不应该。理想情况下需要查看数据。 UNION 不会删除任何唯一记录,所以我假设欺骗来自同一个 SELECT 语句。记录完全一样吗?
    猜你喜欢
    • 1970-01-01
    • 2020-02-04
    • 2018-04-03
    • 1970-01-01
    • 1970-01-01
    • 2013-01-24
    • 2018-12-08
    • 1970-01-01
    • 2010-09-08
    相关资源
    最近更新 更多