多批处理

时间:2018-12-24 09:55:35

标签: sql-server performance tsql grouping batch-processing

我需要处理不少于N行的表中的行。每个批次都需要包含整个行组(组只是另一列),即当我从表中选择前N行进行处理时,我需要扩展N以覆盖该批次中的最后一组,而不是拆分最后一组批次之间。

样本数据:

CREATE TABLE test01 (id INT PRIMARY KEY CLUSTERED IDENTITY(1, 1) NOT NULL
                   , person_name NVARCHAR(100)
                   , person_surname NVARCHAR(100)
                   , person_group_code CHAR(2) NOT NULL);

INSERT INTO
    dbo.test01 (person_name
              , person_surname
              , person_group_code)
VALUES
  ('n1', 's1', 'g1')
, ('n2', 's2', 'g1')
, ('n3', 's3', 'g1')
, ('n4', 's4', 'g1')
, ('n5', 's5', 'g2')
, ('n6', 's6', 'g2')
, ('n7', 's7', 'g2')
, ('n8', 's8', 'g2')
, ('n9', 's9', 'g2')
, ('n10', 's10', 'g2')
, ('n11', 's11', 'g3')
, ('n12', 's12', 'g3')
, ('n13', 's13', 'g3')
, ('n14', 's14', 'g3');

我当前的尝试:

DECLARE @batch_start INT = 1
      , @batch_size INT = 5;
DECLARE @max_id INT = (SELECT MAX(id) FROM dbo.test01);

WHILE @batch_start <= @max_id
    BEGIN
        SELECT *
        FROM dbo.test01
        WHERE id BETWEEN @batch_start AND @batch_start + @batch_size - 1;

        SELECT @batch_start += @batch_size;
    END;

DROP TABLE dbo.test01;

在上面的示例中,我将14行分为3个批次:#1批次中有5行,#2批次中另外5行,最后一个批次中有4行。

第一个批次(从1到5的id)仅覆盖“ g2”组的一部分,因此我需要扩展此批次以覆盖第1-10行(我需要在单个批次中处理整个g2)。 / p>

(顺便说一下,我不介意批量升级-我需要确保每批至少覆盖一个完整的组)。

结果是,批次1将覆盖组g1和g2(10行),然后批次2将覆盖组g3(4行),并且根本没有批次3。

现在,该表有数十亿行,每批的大小约为5万至10万,所以我需要一个性能良好的解决方案。

关于如何以最小的性能损失实现此目标的任何提示?

2 个答案:

答案 0 :(得分:3)

我注意到的第一件事是,您当前的代码假定Identity列中没有空格-但这是一个错误。身份列可能(并且经常确实)在数字上存在缺口-因此,您要做的第一件事是使用row_number() over(order by id)获取所有记录的连续运行数字。

我作为列添加的第二件事是,使用众所周知的技术来解决gaps and islands问题,该列为按身份标识列相同顺序排列的每个组提供了数字ID。

出于演示目的,我已经使用表变量在源表上为每个id存储此数据,但是您可能希望使用临时表并在相关列上添加索引以提高性能。

我还将您的@batch_size变量重命名为@batch_min_size,并添加了一些其他变量。

这是我使用的表变量:

DECLARE @Helper As Table (Id int, Rn int, GroupId int)
INSERT INTO @Helper (Id, Rn, GroupId)
SELECT  Id, 
        ROW_NUMBER() OVER(ORDER BY ID) As Rn,
        ROW_NUMBER() OVER(ORDER BY ID) -
        ROW_NUMBER() OVER(PARTITION BY person_group_code ORDER BY ID) As GroupId        
FROM dbo.test01 

这是此表的内容:

Id  Rn  GroupId
1   1   0
2   2   0
3   3   0
4   4   0
5   5   4
6   6   4
7   7   4
8   8   4
9   9   4
10  10  4
11  11  10
12  12  10
13  13  10
14  14  10

我使用了while循环来做批处理。 在循环中,我使用此表来计算每个批次的第一个和最后一个ID,以及该批次的最后一个行号。 然后,我要做的就是在原始表的where子句中使用第一个和最后一个id:

DECLARE @batch_min_size int = 10
      , @batch_end int = 0
      , @batch_start int
      , @first_id_of_batch int
      , @last_id_of_batch int
      , @total_row_count int;

SELECT @total_row_count = COUNT(*) FROM @test01 

WHILE @batch_end < @total_row_count 
BEGIN

    SELECT @batch_start = @batch_end + 1;

    SELECT @batch_end = MAX(Rn)
         , @first_id_of_batch = MIN(Id)
         , @last_id_of_batch = MAX(Id) 
    FROM @Helper 
    WHERE Rn >= @batch_start 
    AND GroupId <= 
    (
        SELECT MAX(GroupId)
        FROM @Helper
        WHERE Rn <= @batch_start + @batch_min_size - 1 
    )


    SELECT id, person_name, person_surname, person_group_code
    FROM dbo.test01 
    WHERE Id >= @first_id_of_batch 
    AND Id <= @last_id_of_batch 

END

See a live demo on rextester.

答案 1 :(得分:0)

看看下面是否有帮助:

CREATE TABLE #Temp(g_record_count  int, groupname  varchar(50) )

insert into #Temp(g_record_count,groupname) SELECT MAX(id),person_group_code FROM dbo.test01 group by person_group_code

在循环访问此临时表之后:

DECLARE @rec_per_batch INT = 1
 WHILE @batch_start <= @max_id
BEGIN
    select min(g_record_count) into @rec_per_batch from #temp where  g_record_count>=@batch_size * @batch_start;

    SELECT *
    FROM dbo.test01
    WHERE id BETWEEN @batch_start AND  @rec_per_batch;

    SELECT @batch_start += @batch_size;
END;