批处理组

Batches over groups

我需要以不少于 N 行的批次处理 table 中的行。每个批次需要包含一整组行(组只是另一列),即当我 select 来自 table 的前 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 行。

第一批(id 从 1 到 5)仅涵盖 'g2' 组的一小部分,因此我需要扩展这批以涵盖第 1-10 行(我需要在单个中处理整个 g2批次)。

(顺便说一句,我不介意批量扩大 - 我需要确保每批至少涵盖一个完整的组)。

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

现在,table 有数十亿行,每个批处理大小约为 50K-100K,因此我需要一个性能良好的解决方案。

关于如何以最小的性能影响解决这个问题的任何提示?

我注意到的第一件事是您当前的代码假定标识列中没有间隙 - 但这是一个错误。标识列可能(并且经常)在数字中有间隙 - 因此您要做的第一件事是使用 row_number() over(order by id) 为所有记录获取连续的 运行 数字。

我添加的第二个东西是一个列,它为每个组提供一个数字 ID,按照与标识列相同的顺序排序 - 使用众所周知的技术来解决 gaps and islands 问题。

我使用了一个 table 变量来为源 table 上的每个 ID 存储此数据以用于此演示,但您可能想使用临时 table 并在相关列上添加索引以提高性能。

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

所以这是我使用的 table 变量:

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 

这是这篇table的内容:

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 循环来完成批处理。 在循环中,我使用 table 来计算每个批次的第一个和最后一个 id,以及该批次的最后一个行号。 然后我所要做的就是在原始 table:

的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.

看看下面是否有帮助:

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

在这个临时循环之后 table :

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;