আমি কীভাবে উইন্ডোংয়ের ক্যোয়ারী লিখতে পারি যা পৃথক বালতি তৈরির জন্য একটি কলামের সমান?


11

আমার একটি টেবিল রয়েছে যার মধ্যে দশমিক মানগুলির কলাম রয়েছে:

id value size
-- ----- ----
 1   100  .02
 2    99  .38
 3    98  .13
 4    97  .35
 5    96  .15
 6    95  .57
 7    94  .25
 8    93  .15

আমার যা অর্জন করতে হবে তা বর্ণনা করা কিছুটা কঠিন, তাই দয়া করে আমাকে সহ্য করুন। আমি যা করার চেষ্টা করছি তা হ'ল sizeকলামটির একটি সামগ্রিক মান তৈরি করা যা পূর্ববর্তী সারিগুলি 1 অনুসারে 1 অনুযায়ী বৃদ্ধি পায়, যখন অনুসারে ক্রমবর্ধমান ক্রম অনুসারে value। ফলাফলটি এরকম কিছু দেখাচ্ছে:

id value size bucket
-- ----- ---- ------
 1   100  .02      1
 2    99  .38      1
 3    98  .13      1
 4    97  .35      1
 5    96  .15      2
 6    95  .57      2
 7    94  .25      2
 8    93  .15      3

আমার নিষ্পাপ প্রথম চেষ্টাটি ছিল একটি দৌড় SUMএবং তারপরে CEILINGএই মানটি রাখা, তবে এটি এমন কোনও মামলা পরিচালনা করে না যেখানে কয়েকটি রেকর্ডের sizeদুটি পৃথক বালতিতে মোট অবদান অবদান রয়েছে। নীচের উদাহরণ এটি স্পষ্ট করতে পারে:

id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
 1   100  .02       .02            1          .02      1
 2    99  .38       .40            1          .40      1
 3    98  .13       .53            1          .53      1
 4    97  .35       .88            1          .88      1
 5    96  .15      1.03            2          .15      2
 6    95  .57      1.60            2          .72      2
 7    94  .25      1.85            2          .97      2
 8    93  .15      2.00            2          .15      3

যেহেতু আপনি দেখতে পারেন, আমি যদি কেবল ব্যবহার ছিল CEILINGউপর crude_sumরেকর্ড # 8 বালতি 2. নির্ধারিত হবে এই দ্বারা ঘটিত হয় sizeদুই বালতি জুড়ে রেকর্ডের # 5 এবং # 8 বিভক্ত হচ্ছে। পরিবর্তে, আদর্শ সমাধান হ'ল প্রতিবার সমষ্টিটি 1 এ পৌঁছানোর পরে পুনরায় সেট করা, যা bucketকলামটি বাড়িয়ে তোলে এবং বর্তমান রেকর্ডের মান SUMথেকে শুরু করে একটি নতুন ক্রিয়াকলাপ শুরু করে size। যেহেতু রেকর্ডগুলির ক্রম এই ক্রিয়াকলাপের জন্য গুরুত্বপূর্ণ, আমি valueকলামটি অন্তর্ভুক্ত করেছি , যা উতরাই ক্রমে সাজানোর উদ্দেশ্যে।

আমার প্রাথমিক প্রচেষ্টায় ডেটা একাধিক পাস করা, একবার SUMঅপারেশন সম্পাদন করা , আরও একবার CEILINGইত্যাদি জড়িত ছিল etc. এখানে crude_sumকলামটি তৈরি করতে আমি কী করেছি তার উদাহরণ এখানে :

SELECT
  id,
  value,
  size,
  (SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
  table t1

যা UPDATEপরবর্তী সময়ে কাজ করার জন্য একটি টেবিলের মধ্যে মান সন্নিবেশ করানোর জন্য একটি অপারেশনে ব্যবহৃত হয়েছিল ।

সম্পাদনা করুন: আমি এটি ব্যাখ্যা করতে গিয়ে অন্য ছুরিকাঘাত নিতে চাই, তাই এখানে চলে যায়। প্রতিটি রেকর্ড একটি শারীরিক আইটেম কল্পনা করুন। এই আইটেমটির সাথে এর সাথে সম্পর্কিত একটি মান রয়েছে এবং একটির চেয়ে একটি দৈহিক আকার। আমার ভলিউম ক্ষমতা হুবহু 1 এর একটি বালতি রয়েছে এবং আমার নির্ধারণ করা দরকার যে এই কতগুলি বালতি আমার প্রয়োজন এবং প্রতিটি আইটেমটি আইটেমের মান অনুসারে যায়, সর্বোচ্চ থেকে নীচে যায় orted

একটি দৈহিক আইটেম একবারে দুটি স্থানে থাকতে পারে না, তাই এটি অবশ্যই একটি বালতিতে বা অন্যটিতে থাকতে হবে। এই কারণেই আমি মোট চলমান + CEILINGসমাধান করতে পারি না , কারণ এটি রেকর্ডগুলি তাদের আকার দুটি বালতিতে অবদান রাখতে দেয়।


আপনার প্রাথমিক প্রচেষ্টাটি কী কী অন্তর্ভুক্ত হয়েছে তা পরিষ্কার করার জন্য আপনার এসকিউএল যুক্ত করা উচিত।
mdahlman

আপনি যে বালতিটি কম্পিউটিং করছেন সে অনুযায়ী আপনি ডেটা একত্রিত করতে চলেছেন, বা বালটি নম্বরটি আপনি যে চূড়ান্ত উত্তরটি সন্ধান করছেন তা কি?
জন সেগেল

2
স্বীকারোক্তি। আমি সম্ভবত একটি ক্লায়েন্ট-সাইড অ্যাপ্লিকেশন সহ যাব কারণ এটি কার্সার লুপের বিপরীতে রেকর্ডগুলির আরও ভাল স্ট্রিমিং-কে সমর্থন করবে যা একবারে এক সারি নিয়ে আসে। আমি মনে করি যতক্ষণ না সমস্ত আপডেটগুলি ব্যাচগুলিতে করা হয় ততক্ষণ এটির যথাযথ পারফরম্যান্স করা উচিত।
জন সেগেল

1
অন্যরা ইতিমধ্যে উল্লেখ করেছে, distinct_countজিনিসগুলিতে বুকিংয়ের প্রয়োজনীয়তা বিষয়গুলিকে জটিল করে তোলে । এই জাতীয় উইন্ডোটিংয়ের কাজের জন্য অ্যারন বারট্র্যান্ডের এসকিউএল সার্ভারে আপনার বিকল্পগুলির দুর্দান্ত সংক্ষিপ্তসার রয়েছে । গণনা করার জন্য আমি "কুইকি আপডেট" পদ্ধতিটি ব্যবহার করেছি distinct_sum, যা আপনি এসকিউএল ফিডেলে এখানে দেখতে পাচ্ছেন , তবে এটি বিশ্বাসযোগ্য নয়।
নিক চামাস

1
@ জোনসিগেল আমাদের লক্ষ করা উচিত যে এক্স আইটেমগুলিকে ন্যূনতম সংখ্যক বালতিতে রাখার সমস্যাটি এসকিউএল ভাষার সারি অ্যালগরিদম দ্বারা সারি ব্যবহার করে দক্ষতার সাথে সমাধান করা যায় না। ০.7; ০.৮; ০.০ আকারের আইটেমগুলির জন্য ২ বালতি প্রয়োজন, তবে আইডি অনুসারে বাছাই করলে তাদের 3 বালতি প্রয়োজন।
স্টোলেগ

উত্তর:


9

আপনি কী ধরণের পারফরম্যান্স সন্ধান করছেন তা আমি নিশ্চিত নই, তবে সিএলআর বা বাহ্যিক অ্যাপ্লিকেশন যদি বিকল্প না হয় তবে একটি কার্সর যা বাকি রয়েছে তা সবই রয়েছে। আমার বয়স্ক ল্যাপটপে আমি নীচের সমাধানটি ব্যবহার করে প্রায় 100 সেকেন্ডের মধ্যে 1,000,000 সারি পেরিয়ে যাই। এটি সম্পর্কে দুর্দান্ত জিনিসটি এটি রৈখিকভাবে স্কেল করে, তাই আমি পুরো জিনিসটি চালাতে প্রায় 20 মিনিটের দিকে তাকিয়ে থাকব। একটি শালীন সার্ভারের সাহায্যে আপনি দ্রুত হবেন, তবে মাত্রার ক্রম নয়, এটি শেষ করতে এখনও কয়েক মিনিট সময় লাগবে। যদি এটি ওয়ান অফ প্রসেস হয় তবে আপনি সম্ভবত অলসতা বহন করতে পারেন। আপনার যদি এটি প্রতিবেদন হিসাবে বা নিয়মিত অনুরূপ হিসাবে চালনার দরকার হয় তবে আপনি একই সারণীতে মানগুলি সঞ্চয় করতে এবং আনতে নতুন সারি যুক্ত হওয়ার সাথে সাথে আপডেট করতে পারেন, যেমন ট্রিগার হিসাবে।

যাইহোক, এখানে কোড:

IF OBJECT_ID('dbo.MyTable') IS NOT NULL DROP TABLE dbo.MyTable;

CREATE TABLE dbo.MyTable(
 Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
 v NUMERIC(5,3) DEFAULT ABS(CHECKSUM(NEWID())%100)/100.0
);


MERGE dbo.MyTable T
USING (SELECT TOP(1000000) 1 X FROM sys.system_internals_partition_columns A,sys.system_internals_partition_columns B,sys.system_internals_partition_columns C,sys.system_internals_partition_columns D)X
ON(1=0)
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;

--SELECT * FROM dbo.MyTable

DECLARE @st DATETIME2 = SYSUTCDATETIME();
DECLARE cur CURSOR FAST_FORWARD FOR
  SELECT Id,v FROM dbo.MyTable
  ORDER BY Id;

DECLARE @id INT;
DECLARE @v NUMERIC(5,3);
DECLARE @running_total NUMERIC(6,3) = 0;
DECLARE @bucket INT = 1;

CREATE TABLE #t(
 id INT PRIMARY KEY CLUSTERED,
 v NUMERIC(5,3),
 bucket INT,
 running_total NUMERIC(6,3)
);

OPEN cur;
WHILE(1=1)
BEGIN
  FETCH NEXT FROM cur INTO @id,@v;
  IF(@@FETCH_STATUS <> 0) BREAK;
  IF(@running_total + @v > 1)
  BEGIN
    SET @running_total = 0;
    SET @bucket += 1;
  END;
  SET @running_total += @v;
  INSERT INTO #t(id,v,bucket,running_total)
  VALUES(@id,@v,@bucket, @running_total);
END;
CLOSE cur;
DEALLOCATE cur;
SELECT DATEDIFF(SECOND,@st,SYSUTCDATETIME());
SELECT * FROM #t;

GO 
DROP TABLE #t;

এটি টেবিলটি MyTable ফোঁটায় এবং পুনরায় তৈরি করে, এটি 1000000 সারি দিয়ে পূরণ করে এবং তারপরে কাজে যায়।

গণনা চালানোর সময় কার্সার প্রতিটি সারিটি একটি টেম্প টেবিলে অনুলিপি করে। শেষে সিলেক্ট করা ফলাফল গণনা করে। আপনি যদি চারপাশের ডেটাটি অনুলিপি না করে তবে পরিবর্তে কোনও স্থানের আপডেট করেন তবে আপনি কিছুটা দ্রুত হতে পারেন।

আপনার কাছে এসকিউএল ২০১২ এ আপগ্রেড করার বিকল্প থাকলে আপনি নতুন উইন্ডো-স্পুল সমর্থিত চলন্ত উইন্ডো সমষ্টিগুলিতে দেখতে পারেন, এটি আপনাকে আরও ভাল পারফরম্যান্স দেয়।

পার্শ্ব নোটে, যদি আপনার অনুমতি-সেট = নিরাপদে কোনও সমাবেশ ইনস্টল করা থাকে তবে আপনি সমাবেশের চেয়ে স্ট্যান্ডার্ড টি-এসকিউএল সহ কোনও সার্ভারের সাথে আরও খারাপ জিনিস করতে পারেন, তাই আমি সেই বাধা অপসারণের জন্য কাজ চালিয়ে যাব - আপনার ভাল ব্যবহার রয়েছে সিএলআর সত্যিই আপনাকে সাহায্য করবে যেখানে এখানে ক্ষেত্রে।


এটি কার্যকর করা কতটা সহজ ছিল এবং প্রয়োজন হওয়ার সাথে সাথে আমি এটিকে কীভাবে সহজেই পরিবর্তন ও ডিবাগ করতে পারি তার কারণে আমি এটিকে মেনে নিয়েছি। @ নিকচ্যামাসের উত্তরটিও সঠিক এবং সম্ভবত আরও দক্ষতার সাথে চালিত হয়েছে, সুতরাং আমি অনুমান করি যে একই রকমের সমস্যার বিরুদ্ধে অন্য যে কেউ আসছেন তার পক্ষে এটি অগ্রাধিকারের বিষয়।
21

9

এসকিউএল সার্ভার ২০১২-তে নতুন উইন্ডোটিং ফাংশনগুলি অনুপস্থিত, পুনরুক্তি সিটিই ব্যবহার করে জটিল উইন্ডোটিং সম্পন্ন করা যায়। আমি লক্ষ লক্ষ সারির বিরুদ্ধে এটি কতটা ভাল পারফর্ম করবে তা অবাক করি।

নীচের সমাধানে আপনি বর্ণিত সমস্ত মামলা কভার করে। আপনি এটি এসকিউএল ফিডল এ এখানে কর্মে দেখতে পাবেন ।

-- schema setup
CREATE TABLE raw_data (
    id    INT PRIMARY KEY
  , value INT NOT NULL
  , size  DECIMAL(8,2) NOT NULL
);

INSERT INTO raw_data 
    (id, value, size)
VALUES 
   ( 1,   100,  .02) -- new bucket here
 , ( 2,    99,  .99) -- and here
 , ( 3,    98,  .99) -- and here
 , ( 4,    97,  .03)
 , ( 5,    97,  .04)
 , ( 6,    97,  .05)
 , ( 7,    97,  .40)
 , ( 8,    96,  .70) -- and here
;

এবার গভীর নিঃশ্বাস নিন। এখানে দুটি কী সিটিই রয়েছে, প্রত্যেকটির সংক্ষিপ্ত মন্তব্যের আগে। বাকিগুলি কেবলমাত্র "ক্লিনআপ" সিটিই রয়েছে, উদাহরণস্বরূপ, আমরা তাদেরকে স্থান দেওয়ার পরে ডান সারিগুলি টানতে।

-- calculate the distinct sizes recursively
WITH distinct_size AS (
  SELECT
      id
    , size
    , 0 as level
  FROM raw_data

  UNION ALL

  SELECT 
      base.id
    , CAST(base.size + tower.size AS DECIMAL(8,2)) AS distinct_size
    , tower.level + 1 as level
  FROM 
                raw_data AS base
    INNER JOIN  distinct_size AS tower
      ON base.id = tower.id + 1
  WHERE base.size + tower.size <= 1
)
, ranked_sum AS (
  SELECT 
      id
    , size AS distinct_size
    , level
    , RANK() OVER (PARTITION BY id ORDER BY level DESC) as rank
  FROM distinct_size  
)
, top_level_sum AS (
  SELECT
      id
    , distinct_size
    , level
    , rank
  FROM ranked_sum
  WHERE rank = 1
)
-- every level reset to 0 means we started a new bucket
, bucket AS (
  SELECT
      base.id
    , COUNT(base.id) AS bucket
  FROM 
               top_level_sum base
    INNER JOIN top_level_sum tower
      ON base.id >= tower.id
  WHERE tower.level = 0
  GROUP BY base.id
)
-- join the bucket info back to the original data set
SELECT
    rd.id
  , rd.value
  , rd.size
  , tls.distinct_size
  , b.bucket
FROM 
             raw_data rd
  INNER JOIN top_level_sum tls
    ON rd.id = tls.id
  INNER JOIN bucket   b
    ON rd.id = b.id
ORDER BY
  rd.id
;

এই সমাধানটি অনুমান করে যে idএকটি ফাঁকবিহীন ক্রম। যদি তা না হয়, শুরুতে অতিরিক্ত সিটিই যোগ করে আপনার নিজস্ব ফাঁকবিহীন ক্রম উত্পন্ন করতে হবে যা ROW_NUMBER()পছন্দসই ক্রম অনুসারে সারিগুলিকে সংখ্যা দেয় (যেমন ROW_NUMBER() OVER (ORDER BY value DESC))।

কল্পিতভাবে, এটি বেশ ভার্জোজ।


1
এই দ্রবণটি সেই ক্ষেত্রে মোকাবেলা করছে বলে মনে হয় না যেখানে কোনও সারিতে একাধিক বালতিতে তার আকার অবদান রাখতে পারে। একটি ঘূর্ণায়মান সমষ্টি সহজ যথেষ্ট, কিন্তু আমি যে সমষ্টি প্রয়োজন আমার প্রশ্নে প্রতিবার এটি ছুঁয়েছে 1. দেখুন শেষ উদাহরণ টেবিল রিসেট করতে এবং তুলনা crude_sumসঙ্গে distinct_sumএবং তাদের যুক্ত bucketকলাম দেখতে আমি কি মানে।
জাইক্স

2
@ জাইকস - আমি আমার আপডেট সমাধান দিয়ে এই কেসটিকে সম্বোধন করেছি।
নিক চ্যামাস

দেখে মনে হচ্ছে এটি এখন কাজ করা উচিত। আমি এটি পরীক্ষা করার জন্য এটি আমার ডাটাবেসে একীভূত করার কাজ করব।
জাইক্স

@ জাইকস - কেবল কৌতূহলী, এখানে পোস্ট করা বিভিন্ন সমাধান আপনার বৃহত ডেটা সেটের বিপরীতে কীভাবে সম্পাদন করে? আমি আন্দাজ করছি আন্দ্রে এর দ্রুততম।
নিক চামাস

5

এটি মূর্খ সমাধানের মতো অনুভব করে এবং এটি সম্ভবত ভাল স্কেল হবে না, তাই আপনি যদি এটি ব্যবহার করেন তবে সাবধানতার সাথে পরীক্ষা করুন। যেহেতু মূল সমস্যাটি বালতিতে থাকা "স্থান" থেকে এসেছে, তাই আমাকে প্রথমে ডেটাগুলিতে মিলিত করতে একটি ফিলার রেকর্ড তৈরি করতে হয়েছিল।

with bar as (
select
  id
  ,value
  ,size
  from foo
union all
select
  f.id
  ,value = null
  ,size = 1 - sum(f2.size) % 1
  from foo f
  inner join foo f2
    on f2.id < f.id
  group by f.id
    ,f.value
    ,f.size
  having cast(sum(f2.size) as int) <> cast(sum(f2.size) + f.size as int)
)
select
  f.id
  ,f.value
  ,f.size
  ,bucket = cast(sum(b.size) as int) + 1
  from foo f
  inner join bar b
    on b.id <= f.id
  group by f.id
    ,f.value
    ,f.size

http://sqlfiddle.com/#!3/72ad4/14/0


1
+1 আমি মনে করি যদি উপযুক্ত সূচকগুলি থাকে তবে এর সম্ভাব্যতা রয়েছে।
জন সেগেল

3

নীচেরটি একটি পুনরাবৃত্ত সিটিই সমাধান রয়েছে, যদিও আমি এটি @ নিক এর পরামর্শের চেয়ে আরও সোজা বলেছি । এটি আসলে @ সেবাস্তিয়ানের কার্সারের কাছাকাছি , কেবল আমি মোট রানের পরিবর্তে চলমান পার্থক্য ব্যবহার করেছি। (প্রথমে আমি এমনকি ভেবেছিলাম যে @ নিকের উত্তরটি আমি এখানে যে পরামর্শ দিচ্ছি তার সাথে সামঞ্জস্য হতে চলেছে, এবং এটি শিখার পরে যে আমি আসলে আমার প্রস্তাব দেওয়ার সিদ্ধান্ত নিয়েছিলাম সেটাই তার চেয়ে আলাদা প্রশ্ন))

WITH rec AS (
  SELECT TOP 1
    id,
    value,
    size,
    bucket        = 1,
    room_left     = CAST(1.0 - size AS decimal(5,2))
  FROM atable
  ORDER BY value DESC
  UNION ALL
  SELECT
    t.id,
    t.value,
    t.size,
    bucket        = r.bucket + x.is_new_bucket,
    room_left     = CAST(CASE x.is_new_bucket WHEN 1 THEN 1.0 ELSE r.room_left END - t.size AS decimal(5,2))
  FROM atable t
  INNER JOIN rec r ON r.value = t.value + 1
  CROSS APPLY (
    SELECT CAST(CASE WHEN t.size > r.room_left THEN 1 ELSE 0 END AS bit)
  ) x (is_new_bucket)
)
SELECT
  id,
  value,
  size,
  bucket
FROM rec
ORDER BY value DESC
;

দ্রষ্টব্য: এই ক্যোরিয়াকে ধরে নেওয়া হয়েছে যে valueকলামটিতে কোনও ফাঁক ছাড়াই অনন্য মান রয়েছে। যদি এটি না হয় তবে আপনাকে অ্যাঙ্করটির ক্রমবর্ধমান ক্রমের ভিত্তিতে গণনা করা র‌্যাঙ্কিং কলামটি প্রবর্তন valueকরতে হবে এবং valueঅ্যাঙ্কারের সাথে পুনরাবৃত্ত অংশে যোগ দেওয়ার পরিবর্তে এটি পুনরাবৃত্ত সিটিইতে ব্যবহার করতে হবে ।

এই জিজ্ঞাসার জন্য একটি SQL বেহালার ডেমো খুঁজে পাওয়া যেতে পারে এখানে


এটি আমি যা লিখেছি তার চেয়ে অনেক ছোট। চমৎকার কাজ. আপনি বালতিতে বাকী ঘরটি গণনা না করে গণনা করার কোনও কারণ আছে?
নিক চাম্মাস

হ্যাঁ, এখানে অবশ্যই পোস্টিং শেষ হওয়া সংস্করণটির জন্য এটি যদি যথেষ্ট অর্থ দেয় তা নিশ্চিত নয় not যাই হোক, কারণে যে এটা আরো একটি একক মান (সঙ্গে একটি একক মান তুলনা প্রাকৃতিক / সহজ মনে ছিল sizeসঙ্গে room_leftহিসাবে (একটি অভিব্যক্তি সঙ্গে একটি একক মান তুলনা থেকে ভিন্ন) 1সঙ্গে running_size+ + size)। আমি is_new_bucketপ্রথমে পতাকা ব্যবহার করিনি তবে কয়েকটি CASE WHEN t.size > r.room_left ...পরিবর্তে ("বেশ কয়েকটি" কারণ আমিও মোট আকার গণনা করছিলাম (এবং প্রত্যাবর্তন করছিলাম), তবে তারপরে সরলতার জন্য এটির বিরুদ্ধে চিন্তা করেছি), তাই আমি ভেবেছিলাম এটি আরও মার্জিত হবে d ঐ দিকে.
অ্যান্ড্রি এম
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.