SQL Server是遍历数百万行的更好方法

SQL Server是遍历数百万行的更好方法,sql,sql-server,sap,database-cursor,Sql,Sql Server,Sap,Database Cursor,我正在处理SAP时间表数据,因此有数百万行。我试图做的是从SAP表中选择数据并将其插入MS SQL Server上的表中 因此,我想插入原始记录,然后如果对原始记录进行了更新(以带有refcounter的新SAP记录的形式),我想在表中找到原始记录并进行更新,保留原始计数器值 因此,我已经用游标成功地完成了这项工作(我知道不是最好的),但是对于数百万条记录,我想知道是否有更快的方法,因为我正在运行游标的第4天。有没有比下面更好的方法: BEGIN CREATE TABLE CATSDB

我正在处理SAP时间表数据,因此有数百万行。我试图做的是从SAP表中选择数据并将其插入MS SQL Server上的表中

因此,我想插入原始记录,然后如果对原始记录进行了更新(以带有
refcounter
的新SAP记录的形式),我想在表中找到原始记录并进行更新,保留原始计数器值

因此,我已经用游标成功地完成了这项工作(我知道不是最好的),但是对于数百万条记录,我想知道是否有更快的方法,因为我正在运行游标的第4天。有没有比下面更好的方法:

BEGIN
    CREATE TABLE CATSDB 
        (
            [COUNTER] nvarchar(12),
            REFCOUNTER nvarchar(12),
            PERNR nvarchar(8),
            WORKDATE nvarchar(8),
            CATSHOURS decimal(7, 3),
            APDAT nvarchar(8),
            LAETM nvarchar(6),
            CATS_STATUS nvarchar(2),
            APPR_STATUS nvarchar(2)
        )   

    INSERT INTO CATSDB
            (
                [COUNTER],REFCOUNTER,PERNR,WORKDATE,CATSHOURS,APDAT,LAETM,CATS_STATUS,APPR_STATUS
            )
        VALUES
            ('000421692670',NULL,'00000071','20190114','6.00','20190204','174541','30','30'),
            ('000421692671',NULL,'00000071','20190114','3.00','20190204','174541','30','30'),
            ('000421692672',NULL,'00000071','20190115','6.00','00000000','000000','60','20'),
            ('000421692673',NULL,'00000071','20190115','3.00','00000000','000000','60','20'),
            ('000421692712','000421692672','00000071','20190115','0.00','20190115','111007','30','30'),
            ('000421692713','000421692673','00000071','20190115','0.00','20190115','111007','30','30'),
            ('000429718015',NULL,'00000072','20190313','7.00','00000000','000000','60','20'),
            ('000429718016',NULL,'00000072','20190313','1.50','20190315','164659','30','30'),
            ('000429718017',NULL,'00000072','20190313','1.00','20190315','164659','30','30'),
            ('000430154143',NULL,'00000072','20190313','2.00','00000000','000000','60','20'),
            ('000430154142','000429718015','00000072','20190313','5.00','00000000','000000','60','20'),
            ('000430154928','000430154142','00000072','20190313','4.50','20190315','164659','30','30'),
            ('000430154929','000430154143','00000072','20190313','2.50','20190315','164659','30','30'),
            ('000429774620',NULL,'00000152','20190314','1.00','00000000','000000','60','20'),
            ('000429774619',NULL,'00000152','20190314','1.00','00000000','000000','60','20'),
            ('000429802106','000429774620','00000152','20190314','2.00','00000000','000000','60','20'),
            ('000429802105','000429774619','00000152','20190314','3.00','00000000','000000','60','20'),
            ('000429840242','000429802106','00000152','20190314','4.00','20190315','143857','30','30'),
            ('000429840241','000429802105','00000152','20190314','5.00','20190315','143857','30','30')

    CREATE TABLE [TBL_COUNTER]
        (
            [COUNTER] [varchar](12) NOT NULL,
            [REFCOUNTER] [varchar](12) NULL
        )   

    CREATE TABLE TEMP
        (
            [COUNTER] [nvarchar](12) NOT NULL,
            [REFCOUNTER] [nvarchar](12) NULL,
            [PERNR] [nvarchar](8) NULL,
            [WORKDATE] [nvarchar](8) NULL,
            [CATSHOURS] [decimal](7, 3) NULL,
            [APDAT] [nvarchar](8) NULL,
            [LAETM] [nvarchar](6) NULL,
            [CATS_STATUS] [nvarchar](2) NULL,
            [APPR_STATUS] [nvarchar](2) NULL
        )       
END

BEGIN
    DECLARE     @COUNTER nvarchar(12),  
                @REFCOUNTER nvarchar(12),   
                @PERNR nvarchar(8), 
                @WORKDATE nvarchar(8),  
                @CATSHOURS decimal(7, 3),
                @APDAT nvarchar(8),
                @LAETM nvarchar(6),
                @CATS_STATUS nvarchar(2),
                @APPR_STATUS nvarchar(2)

    DECLARE @orig_counter nvarchar(12)
END

BEGIN
    DECLARE curs CURSOR FOR
        SELECT 
                [COUNTER],
                REFCOUNTER,
                PERNR,
                WORKDATE,
                CATSHOURS,
                APDAT,
                LAETM,
                CATS_STATUS,
                APPR_STATUS
        FROM 
                CATSDB
END

BEGIN
    OPEN curs
END

BEGIN
    FETCH NEXT FROM curs INTO
        @COUNTER,
        @REFCOUNTER,
        @PERNR,
        @WORKDATE,
        @CATSHOURS,
        @APDAT,
        @LAETM,
        @CATS_STATUS,
        @APPR_STATUS
END

BEGIN
    WHILE @@FETCH_STATUS = 0
        BEGIN
            BEGIN
                IF NOT EXISTS (SELECT * FROM TBL_COUNTER WHERE [COUNTER] = @COUNTER)
                    BEGIN
                        INSERT INTO TBL_COUNTER
                                ([COUNTER]
                                ,REFCOUNTER)
                            VALUES
                                (@COUNTER
                                ,@REFCOUNTER)
                    END
            END
            BEGIN
                IF NOT EXISTS (SELECT * FROM TEMP WHERE [COUNTER] = @COUNTER)
                    BEGIN
                            --If REFCOUNTER is populated, get the original COUNTER value, then update that row with the new values. Otherwise insert new record
                            IF @REFCOUNTER <> '' AND @REFCOUNTER IS NOT NULL
                                BEGIN
                                    BEGIN
                                        WITH n([COUNTER], REFCOUNTER) AS 
                                            (
                                                SELECT 
                                                        cnt.[COUNTER], 
                                                        cnt.REFCOUNTER 
                                                FROM 
                                                        TBL_COUNTER cnt
                                                WHERE 
                                                        cnt.[COUNTER] = @REFCOUNTER
                                            UNION ALL
                                                SELECT 
                                                        nplus1.[COUNTER], 
                                                        nplus1.REFCOUNTER 
                                                FROM 
                                                        TBL_COUNTER as nplus1, 
                                                        n
                                                WHERE 
                                                        n.[COUNTER] = nplus1.REFCOUNTER
                                            )
                                        SELECT @orig_counter = [COUNTER] FROM n WHERE REFCOUNTER = '' OR REFCOUNTER IS NULL
                                    END
                                    BEGIN
                                        UPDATE TEMP
                                           SET 
                                               [REFCOUNTER] = @REFCOUNTER
                                              ,[PERNR] = @PERNR 
                                              ,[WORKDATE] = @WORKDATE                                               
                                              ,[CATSHOURS] = @CATSHOURS                                                                                    
                                              ,[APDAT] = @APDAT                                        
                                              ,[LAETM] = @LAETM
                                              ,[CATS_STATUS] = @CATS_STATUS
                                              ,[APPR_STATUS] = @APPR_STATUS                                        
                                            WHERE [COUNTER] = @orig_counter
                                    END
                                END
                            ELSE
                                BEGIN
                                    INSERT INTO TEMP
                                               ([COUNTER]
                                               ,[REFCOUNTER]                                               
                                               ,[PERNR]                                               
                                               ,[WORKDATE]                                               
                                               ,[CATSHOURS]                                             
                                               ,[APDAT]                                              
                                               ,[LAETM]
                                               ,[CATS_STATUS]                                               
                                               ,[APPR_STATUS])                                              
                                         VALUES
                                               (@COUNTER
                                               ,@REFCOUNTER                                              
                                               ,@PERNR                                               
                                               ,@WORKDATE                                             
                                               ,@CATSHOURS                                               
                                               ,@APDAT                                               
                                               ,@LAETM                                               
                                               ,@CATS_STATUS                                               
                                               ,@APPR_STATUS)                                               
                                END
                    END

            FETCH NEXT FROM curs INTO
                @COUNTER,
                @REFCOUNTER,
                @PERNR,
                @WORKDATE,
                @CATSHOURS,
                @APDAT,
                @LAETM,
                @CATS_STATUS,
                @APPR_STATUS
        END
    END
END

BEGIN
    CLOSE curs
    DEALLOCATE curs
END

我需要补充一点。这有两个阶段。第一个阶段是,我将从2019年提取所有数据,以初始加载我的表。然后每周,我将从原始数据源中提取数据,以获取新记录和上次运行时更改的记录。所以我不会每周都有完整的链条。需要有一种方法可以在没有完整数据集的情况下返回原始计数器值,这就是为什么我有计数器表。我很抱歉没有说得更清楚。我工作忙得不可开交,没能像计划的那样专注于此。我正在尝试所有这些不同的技术。

我相信,下面的查询将帮助您开始,而且它是实现目标的非常有效的方法

创建它是为了在中心位置维护SQL Server的历史信息,并执行以下活动,您必须在相应的脚本块中包含/替换表结构

  • 创建
    temp
    表格
  • 使用
    OPENQUERY
    通过
    Lined server
    (源代码)从多个服务器收集信息,并加载到
    Temp
    表中
  • Temp
    表上创建索引
  • 将数据加载到包含3个场景的中心表(目的地)(如脚本中的注释所示)
  • 注意:根据您的场景替换了脚本


    看起来它可以通过一个简单的递归查询来完成。拥有合适的索引也很重要

    样本数据

    这就是问题中示例数据的外观。只有很少的相关栏目。 最好包括多组/多个变更链,而不是一个。只有这些样本数据将使您更难验证所提供的解决方案是否正确

    +-----------+---------------------+-----------+------------+
    |   BELNR   |     CHARGE_HOLD     |  COUNTER  | REFCOUNTER |
    +-----------+---------------------+-----------+------------+
    | 417548605 | T4-GS023ABC2 0150#* | 420202428 | NULL       |
    | 417549506 | T4-GS023-ABC2       | 420203329 | 420202428  |
    | 417553156 | JGS023001    0010#* | 420206979 | 420203329  |
    | 417557221 | T4-GS023-ABC2       | 420211044 | 420206979  |
    | 417581675 | JGS023001    0010#* | 420235498 | 420211044  |
    | 417677969 | JGS023001    0010#* | 420331792 | 420235498  |
    +-----------+---------------------+-----------+------------+
    
    查询的主要递归部分

    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    SELECT * FROM CTE;
    
    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    ,CTE_rn
    AS
    (
        SELECT
            *
            ,ROW_NUMBER() OVER (PARTITION BY OriginalCOUNTER ORDER BY Lvl DESC) AS rn
        FROM CTE
    )
    SELECT *
    FROM CTE_rn
    WHERE rn = 1
    --OPTION (MAXRECURSION 0)
    ;
    
    中间结果

    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    | Lvl | OriginalBELNR | OriginalCHARGE_HOLD | OriginalCOUNTER | OrginalREFCOUNTER | NewBELNR  |   NewCHARGE_HOLD    | NewCOUNTER | NewREFCOUNTER |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    |   1 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417548605 | T4-GS023ABC2 0150#* |  420202428 | NULL          |
    |   2 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417549506 | T4-GS023-ABC2       |  420203329 | 420202428     |
    |   3 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417553156 | JGS023001    0010#* |  420206979 | 420203329     |
    |   4 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417557221 | T4-GS023-ABC2       |  420211044 | 420206979     |
    |   5 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417581675 | JGS023001    0010#* |  420235498 | 420211044     |
    |   6 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417677969 | JGS023001    0010#* |  420331792 | 420235498     |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    | Lvl | OriginalBELNR | OriginalCHARGE_HOLD | OriginalCOUNTER | OrginalREFCOUNTER | NewBELNR  |   NewCHARGE_HOLD    | NewCOUNTER | NewREFCOUNTER | rn |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    |   6 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417677969 | JGS023001    0010#* |  420331792 |     420235498 |  1 |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    
    您可以看到,我们已经获取了链的起始行(其中
    RefCounter为NULL
    ),并将其带到整个更改链中

    现在我们只需要选择最后一次更改的行,即对于每个起始行,具有最大的
    Lvl
    。一种方法是使用带有适当分区的
    ROW\u NUMBER
    函数

    最终查询

    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    SELECT * FROM CTE;
    
    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    ,CTE_rn
    AS
    (
        SELECT
            *
            ,ROW_NUMBER() OVER (PARTITION BY OriginalCOUNTER ORDER BY Lvl DESC) AS rn
        FROM CTE
    )
    SELECT *
    FROM CTE_rn
    WHERE rn = 1
    --OPTION (MAXRECURSION 0)
    ;
    
    如果链的长度超过100,则应向查询中添加
    选项(MAXRECURSION 0)
    ,因为默认情况下SQL Server将递归深度限制为100

    结果

    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    | Lvl | OriginalBELNR | OriginalCHARGE_HOLD | OriginalCOUNTER | OrginalREFCOUNTER | NewBELNR  |   NewCHARGE_HOLD    | NewCOUNTER | NewREFCOUNTER |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    |   1 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417548605 | T4-GS023ABC2 0150#* |  420202428 | NULL          |
    |   2 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417549506 | T4-GS023-ABC2       |  420203329 | 420202428     |
    |   3 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417553156 | JGS023001    0010#* |  420206979 | 420203329     |
    |   4 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417557221 | T4-GS023-ABC2       |  420211044 | 420206979     |
    |   5 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417581675 | JGS023001    0010#* |  420235498 | 420211044     |
    |   6 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417677969 | JGS023001    0010#* |  420331792 | 420235498     |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+
    
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    | Lvl | OriginalBELNR | OriginalCHARGE_HOLD | OriginalCOUNTER | OrginalREFCOUNTER | NewBELNR  |   NewCHARGE_HOLD    | NewCOUNTER | NewREFCOUNTER | rn |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    |   6 |     417548605 | T4-GS023ABC2 0150#* |       420202428 | NULL              | 417677969 | JGS023001    0010#* |  420331792 |     420235498 |  1 |
    +-----+---------------+---------------------+-----------------+-------------------+-----------+---------------------+------------+---------------+----+
    
    效率

    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    SELECT * FROM CTE;
    
    WITH
    CTE
    AS
    (
        SELECT
            1 AS Lvl,
            CATSDB.BELNR AS OriginalBELNR,
            CATSDB.CHARGE_HOLD AS OriginalCHARGE_HOLD,
            CATSDB.[COUNTER] AS OriginalCOUNTER,
            CATSDB.REFCOUNTER AS OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
        WHERE
            REFCOUNTER IS NULL
    
        UNION ALL
    
        SELECT
            CTE.Lvl + 1 AS Lvl,
            CTE.OriginalBELNR,
            CTE.OriginalCHARGE_HOLD,
            CTE.OriginalCOUNTER,
            CTE.OrginalREFCOUNTER,
            CATSDB.BELNR AS NewBELNR,
            CATSDB.CHARGE_HOLD AS NewCHARGE_HOLD,
            CATSDB.[COUNTER] AS NewCOUNTER,
            CATSDB.REFCOUNTER AS NewREFCOUNTER
        FROM
            CATSDB
            INNER JOIN CTE ON CATSDB.REFCOUNTER = CTE.NewCOUNTER
    )
    ,CTE_rn
    AS
    (
        SELECT
            *
            ,ROW_NUMBER() OVER (PARTITION BY OriginalCOUNTER ORDER BY Lvl DESC) AS rn
        FROM CTE
    )
    SELECT *
    FROM CTE_rn
    WHERE rn = 1
    --OPTION (MAXRECURSION 0)
    ;
    
    为了使它有效地工作,我们需要在
    REFCOUNTER
    列上有一个索引。此外,该查询假定
    REFCOUNTER
    为空,而不是
    '
    。如果混合使用空字符串和空字符串,请统一数据,否则索引将不起作用。此索引是您需要的最小索引

    理想情况下,您应该在
    REFCOUNTER
    列上有一个聚集索引,因为查询总是从表中选择所有列

    CREATE CLUSTERED INDEX [IX_RefCounter] ON [dbo].[CATSDB]
    (
        [REFCOUNTER] ASC
    )
    
    如果无法更改原始表的索引,我建议将所有数百万行复制到临时表中,并为该临时表创建此聚集索引

    我对这个聚集索引有一个很好的计划


    您可以做几件事来提高性能:

    将计数器和REFCOUNTER从nvarchar转换为数据类型int,对int的操作要比字符快得多。 不要使用游标,您仍然可以使用while循环一次处理一条记录

    DECLARE @CCOUNTER int = 0
    WHILE (1 = 1)
    BEGIN
        /* SELECT @COUNTER = MIN(COUNTER) > @COUNTER FROM CATSDB */
        /* IF @@ROWCOUNT != 1 THEN BREAK OUT OF THE WHILE LOOP, WE ARE DONE */
        /* SELECT RECORD FOR THIS @COUNTER FROM CATSDB */
        /* DO THE PROCESSING FOR THIS RECORD */
    END
    

    有一种叫做sql批量复制的方法,我不知道它对解决您的问题有什么帮助,但请尝试一下。

    最有效的方法是通过BCP

    您可以将所有数据BCP到SQL Server中的一个临时表中,然后运行插入和更新。此外,在检查记录是否不存在以确定这是插入还是更新时,“如果不存在(选择*FROM TEMP,其中[COUNTER]=@COUNTER)”的开销非常大

    执行此操作的更高效方法的示例: (表名TBL_源TBL_目的地TBL_更新,以及TBL_插入


    更新将在#tbl_更新中捕获,并在#tbl_插入中插入

    请参见基于少量样本数据和给定输出,我们的脚本无法100%正常和优化,其中需要更新的数据数以百万计

    我对我的脚本有信心,在充分理解需求之后,它可以朝着这个方向改进

    首先,我想知道为什么数据类型是
    nvarchar
    ,如果可能的话,将其设置为
    varchar,int,datetime

    如果您可以更改数据类型,那么它将对性能产生奇迹般的影响

    此外,没有应为聚集索引的标识列

    从性能的角度来看,这两点很重要

    在我的例子中

    CREATE TABLE CATSDB 
            (
                id int identity ,
                [COUNTER] nvarchar(12),
                REFCOUNTER nvarchar(12),
                PERNR nvarchar(8),
                WORKDATE nvarchar(8),
                CATSHOURS decimal(7, 3),
                APDAT nvarchar(8),
                LAETM nvarchar(6),
                CATS_STATUS nvarchar(2),
                APPR_STATUS nvarchar(2)
            )   
    
    ALTER TABLE CATSDB
    ADD CONSTRAINT PK_CATSDB_ID PRIMARY KEY CLUSTERED(ID)
    
    CREATE NONCLUSTERED INDEX FICATSDB_REFCOUNTER ON CATSDB(REFCOUNTER,[COUNTER]);
    
    
    
    
    IF OBJECT_ID('tempdb..#TEMP', 'U') IS NOT NULL
        DROP TABLE #TEMP;
    
    CREATE TABLE #TEMP
    (UpdateID      INT,
     FINDID        INT
     PRIMARY KEY,
     [COUNTER]     [NVARCHAR](12) NOT NULL,
     [REFCOUNTER]  [NVARCHAR](12) NULL,
     [PERNR]       [NVARCHAR](8) NULL,
     [WORKDATE]    [NVARCHAR](8) NULL,
     [CATSHOURS]   [DECIMAL](7, 3) NULL,
     [APDAT]       [NVARCHAR](8) NULL,
     [LAETM]       [NVARCHAR](6) NULL,
     [CATS_STATUS] [NVARCHAR](2) NULL,
     [APPR_STATUS] [NVARCHAR](2) NULL
    );
    
    WITH CTE
         AS (SELECT a.id,
                    a.[COUNTER],
                    a.REFCOUNTER,
                    a.id AS Findid
             FROM dbo.CATSDB A
    
             UNION ALL
             SELECT b.id,
                    a.[COUNTER],
                    a.[refCOUNTER],
                    a.id
             FROM dbo.CATSDB A
                  INNER JOIN CTE b ON(a.REFCOUNTER = b.[COUNTER])
             WHERE a.id >= b.Findid),
         CTE1
         AS (SELECT id,
                    MAX(Findid) Findid
             FROM CTE
             GROUP BY id)
    
         INSERT INTO #TEMP
         (UpdateID,
          FINDID,
          [COUNTER],
          [REFCOUNTER],
          [PERNR],
          [WORKDATE],
          [CATSHOURS],
          [APDAT],
          [LAETM],
          [CATS_STATUS],
          [APPR_STATUS]
         )
                SELECT c1.ID,
                       c1.FINDID,
                       a.COUNTER,
                       a.REFCOUNTER,
                       a.PERNR,
                       a.WORKDATE,
                       a.CATSHOURS,
                       a.APDAT,
                       a.LAETM,
                       a.CATS_STATUS,
                       a.APPR_STATUS
                FROM dbo.CATSDB A
                     INNER JOIN CTE1 c1 ON a.id = c1.Findid;
    
    BEGIN TRY
        BEGIN TRAN;
    
        UPDATE A
          SET
              [REFCOUNTER] = b.REFCOUNTER,
              [PERNR] = b.PERNR,
              [WORKDATE] = b.WORKDATE,
              [CATSHOURS] = b.CATSHOURS,
              [APDAT] = b.APDAT,
              [LAETM] = b.LAETM,
              [CATS_STATUS] = b.CATS_STATUS,
              [APPR_STATUS] = b.APPR_STATUS
        FROM CATSDB A
             INNER JOIN #TEMP B ON a.id = b.UpdateID;
    
        -- this is only test query
        SELECT c1.UpdateID AS UpdateID,
               a.*
        FROM dbo.CATSDB A
             INNER JOIN #TEMP c1 ON a.id = c1.Findid;
    
        IF(@@trancount > 0)
            ROLLBACK; -- commit
    END TRY
    BEGIN CATCH
        IF(@@trancount > 0)
            ROLLBACK;
    END CATCH;
    
    #温度应为永久表。

    依我看,您的表迫切需要标识列,它应该是标识和聚集索引

    你可以尝试,你可以改变它

    REFCOUNTER,COUNTER
    应为非聚集索引

    在优化查询之后,并且只有在适当的计划下,以上索引才能提高性能。