ฉันดูปัญหาที่คล้ายกันและไม่เคยพบวิธีแก้ปัญหาฟังก์ชั่นหน้าต่างที่ส่งผ่านข้อมูลเพียงครั้งเดียว ฉันไม่คิดว่ามันเป็นไปได้ ฟังก์ชั่นหน้าต่างจะต้องสามารถนำไปใช้กับค่าทั้งหมดในคอลัมน์ นั่นทำให้การคำนวณรีเซ็ตเช่นนี้ยากมากเนื่องจากการรีเซ็ตหนึ่งครั้งจะเปลี่ยนค่าสำหรับค่าทั้งหมดต่อไปนี้
วิธีหนึ่งที่จะคิดเกี่ยวกับปัญหาคือคุณสามารถรับผลลัพธ์สุดท้ายที่คุณต้องการหากคุณคำนวณผลรวมการวิ่งพื้นฐานตราบเท่าที่คุณสามารถลบผลรวมการวิ่งออกจากแถวก่อนหน้าที่ถูกต้อง ยกตัวอย่างเช่นในข้อมูลตัวอย่างของค่าสำหรับid
4 running total of row 4 - the running total of row 3
เป็น ค่าสำหรับid
6 คือrunning total of row 6 - the running total of row 3
เนื่องจากการรีเซ็ตยังไม่เกิดขึ้น ค่าสำหรับid
7 คือrunning total of row 7 - the running total of row 6
เป็นต้น
ฉันจะเข้าใกล้สิ่งนี้กับ T-SQL ในวง ฉันถูกพาตัวไปเล็กน้อยและคิดว่าฉันมีทางออกเต็มรูปแบบ สำหรับ 3 ล้านแถวและ 500 กลุ่มรหัสเสร็จใน 24 วินาทีบนเดสก์ท็อปของฉัน ฉันกำลังทดสอบกับ SQL Server 2016 Developer Edition ที่มี 6 vCPU ฉันใช้ประโยชน์จากการแทรกแบบขนานและการประมวลผลแบบขนานโดยทั่วไปดังนั้นคุณอาจจำเป็นต้องเปลี่ยนรหัสหากคุณใช้รุ่นที่เก่ากว่าหรือมีข้อ จำกัด DOP
ด้านล่างรหัสที่ฉันใช้ในการสร้างข้อมูล ช่วงบนVAL
และRESET_VAL
ควรใกล้เคียงกับข้อมูลตัวอย่างของคุณ
drop table if exists reset_runn_total;
create table reset_runn_total
(
id int identity(1,1),
val int,
reset_val int,
grp int
);
DECLARE
@group_num INT,
@row_num INT;
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
SET @group_num = 1;
WHILE @group_num <= 50000
BEGIN
SET @row_num = 1;
WHILE @row_num <= 60
BEGIN
INSERT INTO reset_runn_total WITH (TABLOCK)
SELECT 1 + ABS(CHECKSUM(NewId())) % 10, 8 + ABS(CHECKSUM(NewId())) % 8, @group_num;
SET @row_num = @row_num + 1;
END;
SET @group_num = @group_num + 1;
END;
COMMIT TRANSACTION;
END;
อัลกอริทึมมีดังนี้:
1) เริ่มต้นด้วยการแทรกแถวทั้งหมดด้วยผลรวมการทำงานมาตรฐานลงในตารางอุณหภูมิ
2) ในวง:
2a) สำหรับแต่ละกลุ่มให้คำนวณแถวแรกด้วยผลรวมสะสมที่อยู่เหนือค่า reset_value ที่เหลืออยู่ในตารางและเก็บรหัสผลรวมสะสมที่มีขนาดใหญ่เกินไปและผลรวมการทำงานก่อนหน้าที่มีขนาดใหญ่เกินไปในตารางอุณหภูมิ
2b) ลบแถวออกจากตาราง temp แรกลงในผลลัพธ์ temp table ที่ID
น้อยกว่าหรือเท่ากับID
ในตาราง temp ที่สอง ใช้คอลัมน์อื่น ๆ เพื่อปรับผลรวมสะสมที่ต้องการ
3) หลังจากการลบไม่มีการประมวลผลแถวอีกต่อไปให้เรียกใช้เพิ่มเติมDELETE OUTPUT
ในตารางผลลัพธ์ นี่สำหรับแถวท้ายกลุ่มที่ไม่เกินค่ารีเซ็ต
ฉันจะดำเนินการตามขั้นตอนวิธีการหนึ่งใน T-SQL ทีละขั้นตอน
เริ่มต้นด้วยการสร้างตารางชั่วคราวสักสองสามตาราง #initial_results
เก็บข้อมูลดั้งเดิมพร้อมผลรวมการทำงานมาตรฐาน#group_bookkeeping
ได้รับการอัปเดตแต่ละวงเพื่อหาว่าแถวไหนที่สามารถเคลื่อนย้ายได้และ#final_results
มีผลลัพธ์พร้อมการปรับยอดรวมการทำงานสำหรับการรีเซ็ต
CREATE TABLE #initial_results (
id int,
val int,
reset_val int,
grp int,
initial_running_total int
);
CREATE TABLE #group_bookkeeping (
grp int,
max_id_to_move int,
running_total_to_subtract_this_loop int,
running_total_to_subtract_next_loop int,
grp_done bit,
PRIMARY KEY (grp)
);
CREATE TABLE #final_results (
id int,
val int,
reset_val int,
grp int,
running_total int
);
INSERT INTO #initial_results WITH (TABLOCK)
SELECT ID, VAL, RESET_VAL, GRP, SUM(VAL) OVER (PARTITION BY GRP ORDER BY ID) RUNNING_TOTAL
FROM reset_runn_total;
CREATE CLUSTERED INDEX i1 ON #initial_results (grp, id);
INSERT INTO #group_bookkeeping WITH (TABLOCK)
SELECT DISTINCT GRP, 0, 0, 0, 0
FROM reset_runn_total;
ฉันสร้างดัชนีคลัสเตอร์บนตารางชั่วคราวหลังจากนั้นการแทรกและการสร้างดัชนีสามารถทำได้แบบขนาน สร้างความแตกต่างอย่างมากในเครื่องของฉัน แต่อาจไม่ได้อยู่ที่คุณ การสร้างดัชนีในตารางต้นฉบับดูเหมือนจะไม่ช่วย แต่ก็สามารถช่วยในเครื่องของคุณได้
รหัสด้านล่างทำงานในลูปและอัปเดตตารางการทำบัญชี สำหรับแต่ละกลุ่มเราจำเป็นต้องได้รับการหาค่าสูงสุดID
ที่ควรย้ายไปไว้ในตารางผลลัพธ์ เราต้องการผลรวมสะสมจากแถวนั้นเพื่อให้เราสามารถลบออกจากผลรวมการเริ่มต้น grp_done
คอลัมน์ถูกกำหนดเป็น 1 เมื่อไม่มีการทำงานใด ๆ grp
ที่ต้องทำสำหรับ
WITH UPD_CTE AS (
SELECT
#grp_bookkeeping.GRP
, MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) max_id_to_update
, MIN(#group_bookkeeping.running_total_to_subtract_next_loop) running_total_to_subtract_this_loop
, MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN initial_running_total ELSE NULL END) additional_value_next_loop
, CASE WHEN MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) IS NULL THEN 1 ELSE 0 END grp_done
FROM #group_bookkeeping
INNER JOIN #initial_results IR ON #group_bookkeeping.grp = ir.grp
WHERE #group_bookkeeping.grp_done = 0
GROUP BY #group_bookkeeping.GRP
)
UPDATE #group_bookkeeping
SET #group_bookkeeping.max_id_to_move = uv.max_id_to_update
, #group_bookkeeping.running_total_to_subtract_this_loop = uv.running_total_to_subtract_this_loop
, #group_bookkeeping.running_total_to_subtract_next_loop = uv.additional_value_next_loop
, #group_bookkeeping.grp_done = uv.grp_done
FROM UPD_CTE uv
WHERE uv.GRP = #group_bookkeeping.grp
OPTION (LOOP JOIN);
จริงๆไม่ใช่แฟนของLOOP JOIN
คำใบ้โดยทั่วไป แต่นี่เป็นคำถามง่าย ๆ และเป็นวิธีที่เร็วที่สุดในการได้รับสิ่งที่ฉันต้องการ ในการปรับให้เหมาะสมที่สุดสำหรับเวลาตอบสนองฉันต้องการการรวมลูปซ้อนกันแบบซ้อนแทนที่จะรวมการผสาน DOP 1
โค้ดด้านล่างนี้ทำงานในลูปและย้ายข้อมูลจากตารางเริ่มต้นไปยังตารางผลลัพธ์สุดท้าย สังเกตเห็นการปรับเปลี่ยนให้เป็นผลรวมการรันเริ่มต้น
DELETE ir
OUTPUT DELETED.id,
DELETED.VAL,
DELETED.RESET_VAL,
DELETED.GRP ,
DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
INTO #final_results
FROM #initial_results ir
INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP AND ir.ID <= tb.max_id_to_move
WHERE tb.grp_done = 0;
เพื่อความสะดวกของคุณด้านล่างคือรหัสเต็ม:
DECLARE @RC INT;
BEGIN
SET NOCOUNT ON;
CREATE TABLE #initial_results (
id int,
val int,
reset_val int,
grp int,
initial_running_total int
);
CREATE TABLE #group_bookkeeping (
grp int,
max_id_to_move int,
running_total_to_subtract_this_loop int,
running_total_to_subtract_next_loop int,
grp_done bit,
PRIMARY KEY (grp)
);
CREATE TABLE #final_results (
id int,
val int,
reset_val int,
grp int,
running_total int
);
INSERT INTO #initial_results WITH (TABLOCK)
SELECT ID, VAL, RESET_VAL, GRP, SUM(VAL) OVER (PARTITION BY GRP ORDER BY ID) RUNNING_TOTAL
FROM reset_runn_total;
CREATE CLUSTERED INDEX i1 ON #initial_results (grp, id);
INSERT INTO #group_bookkeeping WITH (TABLOCK)
SELECT DISTINCT GRP, 0, 0, 0, 0
FROM reset_runn_total;
SET @RC = 1;
WHILE @RC > 0
BEGIN
WITH UPD_CTE AS (
SELECT
#group_bookkeeping.GRP
, MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) max_id_to_move
, MIN(#group_bookkeeping.running_total_to_subtract_next_loop) running_total_to_subtract_this_loop
, MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN initial_running_total ELSE NULL END) additional_value_next_loop
, CASE WHEN MIN(CASE WHEN initial_running_total - #group_bookkeeping.running_total_to_subtract_next_loop > RESET_VAL THEN ID ELSE NULL END) IS NULL THEN 1 ELSE 0 END grp_done
FROM #group_bookkeeping
CROSS APPLY (SELECT ID, RESET_VAL, initial_running_total FROM #initial_results ir WHERE #group_bookkeeping.grp = ir.grp ) ir
WHERE #group_bookkeeping.grp_done = 0
GROUP BY #group_bookkeeping.GRP
)
UPDATE #group_bookkeeping
SET #group_bookkeeping.max_id_to_move = uv.max_id_to_move
, #group_bookkeeping.running_total_to_subtract_this_loop = uv.running_total_to_subtract_this_loop
, #group_bookkeeping.running_total_to_subtract_next_loop = uv.additional_value_next_loop
, #group_bookkeeping.grp_done = uv.grp_done
FROM UPD_CTE uv
WHERE uv.GRP = #group_bookkeeping.grp
OPTION (LOOP JOIN);
DELETE ir
OUTPUT DELETED.id,
DELETED.VAL,
DELETED.RESET_VAL,
DELETED.GRP ,
DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
INTO #final_results
FROM #initial_results ir
INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP AND ir.ID <= tb.max_id_to_move
WHERE tb.grp_done = 0;
SET @RC = @@ROWCOUNT;
END;
DELETE ir
OUTPUT DELETED.id,
DELETED.VAL,
DELETED.RESET_VAL,
DELETED.GRP ,
DELETED.initial_running_total - tb.running_total_to_subtract_this_loop
INTO #final_results
FROM #initial_results ir
INNER JOIN #group_bookkeeping tb ON ir.GRP = tb.GRP;
CREATE CLUSTERED INDEX f1 ON #final_results (grp, id);
/* -- do something with the data
SELECT *
FROM #final_results
ORDER BY grp, id;
*/
DROP TABLE #final_results;
DROP TABLE #initial_results;
DROP TABLE #group_bookkeeping;
END;