আমি অনুরূপ সমস্যার দিকে নজর রেখেছি এবং এমন একটি উইন্ডো ফাংশন সমাধান খুঁজে পেতে সক্ষম হয়েছি যা ডেটা থেকে একক পাস করে। আমি মনে করি না এটি সম্ভব। উইন্ডো ফাংশনগুলির একটি কলামে সমস্ত মান প্রয়োগ করতে সক্ষম হওয়া প্রয়োজন। এটি রিসেটের গণনাগুলিকে খুব জটিল করে তোলে কারণ একটি রিসেট নীচের সমস্ত মানের মান পরিবর্তন করে।
সমস্যাটি সম্পর্কে চিন্তা করার একটি উপায় হ'ল আপনি যদি পূর্বের সঠিক সারি থেকে চলমান মোট বিয়োগ করতে পারেন তবে আপনি যদি কোনও প্রাথমিক চলমান মোট গণনা করেন তবে আপনি চান শেষ ফলাফলটি পেতে পারেন। উদাহরণস্বরূপ, আপনার নমুনা ডেটাতে 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
এবং তেমন ।
আমি লুপে টি-এসকিউএল দিয়ে এটি ব্যবহার করব। আমি কিছুটা দূরে সরে গিয়েছি এবং মনে করি আমার একটি সম্পূর্ণ সমাধান রয়েছে। 3 মিলিয়ন সারি এবং 500 টি গোষ্ঠীর জন্য কোডটি আমার ডেস্কটপে 24 সেকেন্ডের মধ্যে শেষ হয়েছে। আমি এসকিউএল সার্ভার 2016 বিকাশকারী সংস্করণ 6 টি ভিসিপিইউ দিয়ে পরীক্ষা করছি। আমি সমান্তরাল সন্নিবেশ এবং সমান্তরাল সম্পাদনার সুবিধা নিচ্ছি তাই আপনার যদি পুরানো সংস্করণে থাকে বা ডপ সীমাবদ্ধতা থাকে তবে আপনার কোডটি পরিবর্তন করতে হবে।
কোডটি নীচে যা আমি ডেটা উত্পন্ন করতে ব্যবহার করি। রেঞ্জগুলি চালু 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) একটি লুপে:
২ ক) প্রতিটি গ্রুপের জন্য, প্রথম সারিটি টেবিলের মধ্যে থাকা রিসেট_মূল্যের উপরে চলমান মোটের সাথে গণনা করুন এবং আইডি, চলমান মোট যা খুব বড় ছিল এবং টেম্প টেবিলের মধ্যে পূর্বের চলমান মোটটি খুব বড় store
2 খ) প্রথম টেম্প টেবিল থেকে সারণিগুলি মুছুন ফলাফলের টেম্প টেবিলের মধ্যে যা দ্বিতীয় টেম্প টেবিলের ID
চেয়ে কম বা সমান ID
। প্রয়োজন অনুসারে চলমান মোট সমন্বয় করতে অন্যান্য কলামগুলি ব্যবহার করুন।
3) মুছে ফেলার পরে আর সারিগুলি প্রক্রিয়া DELETE OUTPUT
করে না ফলাফলের টেবিলটিতে একটি অতিরিক্ত চালায় । এটি সেই গোষ্ঠীর শেষে থাকা সারিগুলির জন্য যা পুনরায় সেট করার মানটি অতিক্রম করে না।
আমি টি-এসকিউএল ধাপে ধাপে উপরের অ্যালগরিদমের একটি বাস্তবায়ন করব।
কয়েকটি টেম্প টেবিল তৈরি করে শুরু করুন। #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
সাধারণভাবে ইঙ্গিতটির অনুরাগী নয় , তবে এটি একটি সাধারণ ক্যোয়ারী এবং আমি যা চাইছিলাম তা পাওয়ার এটি ছিল দ্রুততম উপায়। প্রতিক্রিয়া সময়ের জন্য সত্যিই অপ্টিমাইজ করতে আমি চেয়েছিলাম যে সমান্তরাল নেস্টেড লুপটি ডওপ 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;