我需要处理不少于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万,所以我需要一个性能良好的解决方案。
关于如何以最小的性能损失实现此目标的任何提示?
答案 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
答案 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;