পোস্টগ্রিসএসকিউএল পুনরাবৃত্তকারী বংশদ্ভুত গভীরতা


15

আমার পূর্বসূরীর বংশধরের গভীরতা গণনা করা দরকার। যখন কোনও রেকর্ড থাকে তখন object_id = parent_id = ancestor_idএটিকে মূল নোড (পূর্বপুরুষ) হিসাবে বিবেচনা করা হয়। আমি WITH RECURSIVEপোস্টগ্রিজ এসকিউএল 9.4 দিয়ে চলছে এমন একটি কোয়েরি পাওয়ার চেষ্টা করছি ।

আমি ডেটা বা কলামগুলি নিয়ন্ত্রণ করি না। ডেটা এবং টেবিল স্কিমা একটি বাহ্যিক উত্স থেকে আসে। টেবিলটি ক্রমাগত বাড়ছে । এখন প্রতিদিন 30k রেকর্ড দ্বারা। গাছের কোনও নোড অনুপস্থিত হতে পারে এবং এগুলি কোনও পর্যায়ে কোনও বাহ্যিক উত্স থেকে টেনে নেওয়া হবে। এগুলি সাধারণত created_at DESCক্রমে টানা হয় তবে ডেটাটি অ্যাসিক্রোনাস ব্যাকগ্রাউন্ড কাজের সাথে টানা হয়।

আমাদের প্রাথমিকভাবে এই সমস্যার একটি কোড সমাধান ছিল, তবে এখন 5M + সারি রয়েছে, এটি সম্পূর্ণ হতে প্রায় 30 মিনিট সময় নেয়।

সারণির সংজ্ঞা এবং পরীক্ষার ডেটা উদাহরণ:

CREATE TABLE objects (
  id          serial NOT NULL PRIMARY KEY,
  customer_id integer NOT NULL,
  object_id   integer NOT NULL,
  parent_id   integer,
  ancestor_id integer,
  generation  integer NOT NULL DEFAULT 0
);

INSERT INTO objects(id, customer_id , object_id, parent_id, ancestor_id, generation)
VALUES (2, 1, 2, 1, 1, -1), --no parent yet
       (3, 2, 3, 3, 3, -1), --root node
       (4, 2, 4, 3, 3, -1), --depth 1
       (5, 2, 5, 4, 3, -1), --depth 2
       (6, 2, 6, 5, 3, -1), --depth 3
       (7, 1, 7, 7, 7, -1), --root node
       (8, 1, 8, 7, 7, -1), --depth 1
       (9, 1, 9, 8, 7, -1); --depth 2

নোট যে object_idঅনন্য নয়, তবে সংমিশ্রণটি (customer_id, object_id)অনন্য।
এই জাতীয় একটি ক্যোয়ারী চালানো:

WITH RECURSIVE descendants(id, customer_id, object_id, parent_id, ancestor_id, depth) AS (
  SELECT id, customer_id, object_id, parent_id, ancestor_id, 0
  FROM objects
  WHERE object_id = parent_id

  UNION

  SELECT o.id, o.customer_id, o.object_id, o.parent_id, o.ancestor_id, d.depth + 1
  FROM objects o
  INNER JOIN descendants d ON d.parent_id = o.object_id
  WHERE
    d.id <> o.id
  AND
    d.customer_id = o.customer_id
) SELECT * FROM descendants d;

আমি generationকলামটি গণনা করা গভীরতা হিসাবে সেট করতে চাই । একটি নতুন রেকর্ড যুক্ত করা হলে, প্রজন্মের কলামটি -1 হিসাবে সেট করা থাকে। কিছু ঘটনা আছে যেখানে একটি parent_idএখনও টানা নাও পারে। যদি এটি parent_idবিদ্যমান না থাকে তবে এটি প্রজন্মের কলামটি -1 এ সেট করা উচিত।

চূড়ান্ত তথ্য দেখতে হবে:

id | customer_id | object_id | parent_id | ancestor_id | generation
2    1             2           1           1            -1
3    2             3           3           3             0
4    2             4           3           3             1
5    2             5           4           3             2
6    2             6           5           3             3
7    1             7           7           7             0
8    1             8           7           7             1
9    1             9           8           7             2

ক্যোয়ারির ফলাফলটি প্রজন্মের কলামটি সঠিক গভীরতায় আপডেট করা উচিত।

আমি থেকে কাজ শুরু করে তাই এই সম্পর্কিত প্রশ্নের উত্তর


সুতরাং আপনি updateআপনার পুনরাবৃত্তি সিটিই ফলাফল সঙ্গে টেবিলে চান ?
a_horse_with_no_name

হ্যাঁ, আমি প্রজন্মের কলামটি এর গভীরতা কত তা আপডেট করতে চাই would যদি কোনও পিতামাত না থাকে (অবজেক্টস.প্যারেন্ট_আইডি কোনও অবজেক্টের সাথে মেলে না।

সুতরাং ancestor_idইতিমধ্যে সেট করা আছে, সুতরাং আপনি কেবল CTE.dthth থেকে প্রজন্মকে নিয়োগ করতে হবে?

হ্যাঁ, অবজেক্ট_আইডি, প্যারেন্ট_আইডি এবং পূর্বপুরুষ_আইডি ইতিমধ্যে আমরা API থেকে প্রাপ্ত ডেটা থেকে সেট করে রেখেছি। গভীরতা যাই হোক না কেন আমি প্রজন্মের কলামটি সেট করতে চাই। অন্য একটি নোট, অবজেক্ট_আইডিটি অনন্য নয়, কারণ গ্রাহক_আইডি 1-তে অবজেক্ট_আইডি 1 থাকতে পারে এবং গ্রাহক_আইডি 2-তে অবজেক্ট_আইডি থাকতে পারে। টেবিলে প্রাথমিক আইডিটি অনন্য।

এটি কি এককালীন আপডেট বা আপনি ক্রমাগত ক্রমবর্ধমান টেবিলে যুক্ত হচ্ছেন? পরের কেসের মতো মনে হচ্ছে। একটি বড় পার্থক্য তোলে । এবং কেবলমাত্র রুট নোডগুলি (এখনও) বা গাছের কোনও নোড অনুপস্থিত থাকতে পারে?
এরউইন ব্র্যান্ডসেটেটার

উত্তর:


14

আপনার কাছে থাকা ক্যোয়ারীটি মূলত সঠিক। আপনার কেবলমাত্র সিটিইর দ্বিতীয় (পুনরাবৃত্ত হওয়া) অংশে ভুল রয়েছে:

INNER JOIN descendants d ON d.parent_id = o.object_id

এটা কাছাকাছি অন্য কোন উপায়ে হওয়া উচিত:

INNER JOIN descendants d ON d.object_id = o.parent_id 

আপনি তাদের পিতামাতার সাথে বস্তুগুলিতে যোগদান করতে চান (এটি ইতিমধ্যে পাওয়া গেছে)।

সুতরাং যে ক্যোয়ারী গভীরতার গণনা করে তা লেখা যেতে পারে (কিছুই পরিবর্তিত হয়নি, কেবল বিন্যাসে):

-- calculate generation / depth, no updates
WITH RECURSIVE descendants
  (id, customer_id, object_id, parent_id, ancestor_id, depth) AS
 AS ( SELECT id, customer_id, object_id, parent_id, ancestor_id, 0
      FROM objects
      WHERE object_id = parent_id

      UNION ALL

      SELECT o.id, o.customer_id, o.object_id, o.parent_id, o.ancestor_id, d.depth + 1
      FROM objects o
      INNER JOIN descendants d ON  d.customer_id = o.customer_id
                               AND d.object_id = o.parent_id  
      WHERE d.id <> o.id
    ) 
SELECT * 
FROM descendants d
ORDER BY id ;

আপডেটের জন্য, আপনি কেবল গত প্রতিস্থাপন SELECTসঙ্গে UPDATE, কোটে ফল যোগদান টেবিলের ফিরে:

-- update nodes
WITH RECURSIVE descendants
    -- nothing changes here except
    -- ancestor_id and parent_id 
    -- which can be omitted form the select lists
    ) 
UPDATE objects o 
SET generation = d.depth 
FROM descendants d
WHERE o.id = d.id 
  AND o.generation = -1 ;          -- skip unnecessary updates

পরীক্ষিত এসকিউএলফিডেলে

অতিরিক্ত মন্তব্যগুলি:

  • ancestor_idএবং parent_id(পূর্বপুরুষ সুস্পষ্ট, পিতা বা মাতা একটি আউট কেন চিন্তা করার চতুর বিট করা হয়), নির্বাচন তালিকা হতে যাতে আপনি তাদের মধ্যে রাখতে পারবেন না প্রয়োজন হয় নাSELECT ক্যোয়ারী যদি তুমি চাও কিন্তু আপনি নিরাপদে তাদের কাছ থেকে অপসারণ করতে পারেন UPDATE
  • (customer_id, object_id)একটি প্রার্থী মত মনে হয়UNIQUE বাধ্যতা। যদি আপনার ডেটা এটি মেনে চলে, তবে এই জাতীয় সীমাবদ্ধতা যুক্ত করুন। পুনরাবৃত্ত সিটিইতে সম্পাদিত যোগদানগুলি এটি অনন্য না হলে কোনও অর্থ হবে না (কোনও নোডে 2 জন বাবা-মা থাকতে পারে)।
  • আপনি যদি এই সীমাবদ্ধতা যোগ করেন তবে সেই সীমাবদ্ধতার (customer_id, parent_id)প্রার্থী হবেন FOREIGN KEYযে REFERENCES(অনন্য) (customer_id, object_id)। আপনি সম্ভবত না এফকে সীমাবদ্ধতাটি যুক্ত চান যদিও আপনার বর্ণনার মাধ্যমে আপনি নতুন সারি যুক্ত করছেন এবং কিছু সারি অন্যকে উল্লেখ করতে পারেন যা এখনও যোগ করা হয়নি।
  • ক্যোয়ারির দক্ষতার সাথে অবশ্যই সমস্যা আছে, যদি এটি একটি বড় টেবিলে সম্পাদিত হয়। প্রথম রানেই নয়, প্রায় পুরো টেবিলটি যাইহোক আপডেট করা হবে। তবে দ্বিতীয়বার, আপনি কেবলমাত্র নতুন সারিগুলি (এবং যেগুলি 1 ম রান দ্বারা ছোঁয়া হয়নি) আপডেটের জন্য বিবেচনা করা উচিত। সিটিই যেমন আছে তেমন একটি বড় ফলাফল তৈরি করতে হবে। চূড়ান্ত আপডেটে নিশ্চিত করুন যে সারি যে 1 ম রান আপডেট হয়েছে আবার আপডেট করা হবে না করতে হবে কিন্তু কোটে এখনও একটি ব্যয়বহুল অংশ।
    AND o.generation = -1

নীচে এই সমস্যাগুলি সমাধান করার চেষ্টা করা হয়েছে: যতটা সম্ভব সারি সারি বিবেচনা করার জন্য সিটিইতে উন্নতি করুন এবং সারিগুলি সনাক্ত করার (customer_id, obejct_id)পরিবর্তে ব্যবহার করুন (id)(সুতরাং idক্যোয়ারী থেকে সম্পূর্ণভাবে মুছে ফেলা হয়েছে It এটি প্রথম আপডেট বা পরবর্তী হিসাবে ব্যবহার করা যেতে পারে:

WITH RECURSIVE descendants 
  (customer_id, object_id, depth) 
 AS ( SELECT customer_id, object_id, 0
      FROM objects
      WHERE object_id = parent_id
        AND generation = -1

      UNION ALL

      SELECT o.customer_id, o.object_id, p.generation + 1
      FROM objects o
        JOIN objects p ON  p.customer_id = o.customer_id
                       AND p.object_id = o.parent_id
                       AND p.generation > -1
      WHERE o.generation = -1

      UNION ALL

      SELECT o.customer_id, o.object_id, d.depth + 1
      FROM objects o
      INNER JOIN descendants d ON  o.customer_id = d.customer_id
                               AND o.parent_id = d.object_id
      WHERE o.parent_id <> o.object_id
        AND o.generation = -1
    )
UPDATE objects o 
SET generation = d.depth 
FROM descendants d
WHERE o.customer_id = d.customer_id
  AND o.object_id = d.object_id
  AND o.generation = -1        -- this is not really needed

সিটিইর কীভাবে 3 টি অংশ রয়েছে তা দ্রষ্টব্য। প্রথম দুটি স্থিতিশীল অংশ। প্রথম অংশটি মূল নোডগুলি সন্ধান করে যা আগে আপডেট করা হয়নি এবং এখনও রয়েছে generation=-1যাতে সেগুলি অবশ্যই নতুন যুক্ত নোডগুলি থাকতে হবে। দ্বিতীয় অংশটি generation=-1পূর্ববর্তী আপডেট হওয়া প্যারেন্ট নোডগুলির বাচ্চাদের (সহ ) সন্ধান করে ।
তৃতীয়, পুনরাবৃত্ত অংশটি পূর্বের মতো প্রথম দুটি অংশের সমস্ত বংশধরকে সন্ধান করে।

এসকিউএলফিডল -২ এ পরীক্ষিত


3

@ টাইপ्यूबটি ইতিমধ্যে যথেষ্ট ব্যাখ্যা সরবরাহ করেছে, তাই আমি কী যুক্ত করতে হবে তা তাড়া করে নেব।

যদি এটি parent_idবিদ্যমান না থাকে তবে এটি প্রজন্মের কলামটি -1 এ সেট করা উচিত।

আমি এই, যাও recursively প্রয়োগ অর্থাৎ গাছ বাকি অনুমিত হয় অনুমান সবসময় হয়েছে generation = -1কোনো অনুপস্থিত নোড পরে।

যদি গাছের কোনও নোড অনুপস্থিত থাকতে পারে (তবুও) এর সাথে আমাদের সারিগুলি সন্ধান করতে generation = -1হবে ...
... মূল নোডগুলি
... বা এর সাথে কোনও পিতামাতাকে থাকতে হবে generation > -1
এবং সেখান থেকে গাছটি অতিক্রম করুন। এই নির্বাচনের চাইল্ড নোডগুলির অবশ্যই থাকতে হবে generation = -1

generationএক দ্বারা বাড়ানো পিতামাতার থেকে নিন বা মূল নোডগুলির জন্য 0 এ ফিরে যান:

WITH RECURSIVE tree AS (
   SELECT c.customer_id, c.object_id, COALESCE(p.generation + 1, 0) AS depth
   FROM   objects      c
   LEFT   JOIN objects p ON c.customer_id = p.customer_id
                        AND c.parent_id   = p.object_id
                        AND p.generation > -1
   WHERE  c.generation = -1
   AND   (c.parent_id = c.object_id OR p.generation > -1)
       -- root node ... or parent with generation > -1

   UNION ALL
   SELECT customer_id, c.object_id, p.depth + 1
   FROM   objects c
   JOIN   tree    p USING (customer_id)
   WHERE  c.parent_id  = p.object_id
   AND    c.parent_id <> c.object_id  -- exclude root nodes
   AND    c.generation = -1           -- logically redundant, but see below!
   )
UPDATE objects o 
SET    generation = t.depth
FROM   tree t
WHERE  o.customer_id = t.customer_id
AND    o.object_id   = t.object_id;

অপরিবর্তিত অংশটি SELECTএইভাবে একক , তবে যৌক্তিকভাবে @ ইয়ারকিউবের দুটি ইউনিয়নের সমতুল্য SELECT। কোনটি দ্রুত তা নিশ্চিত নয়, আপনাকে পরীক্ষা করতে হবে।
পারফরম্যান্সের জন্য আরও গুরুত্বপূর্ণ পয়েন্টটি হ'ল:

ইনডেক্স!

আপনি যদি বার বার কোনও বড় টেবিলটিতে সারি যুক্ত করেন তবে আংশিক সূচক যুক্ত করুন :

CREATE INDEX objects_your_name_idx ON objects (customer_id, parent_id, object_id)
WHERE  generation = -1;

বড় টেবিলে বারবার ছোট সংযোজনের জন্য - এটি এখন পর্যন্ত আলোচিত অন্যান্য সমস্ত উন্নতির চেয়ে পারফরম্যান্সের জন্য আরও অর্জন করবে।

আংশিক সূচক প্রযোজ্য কিনা তা জানতে ক্যোয়ারার পরিকল্পনাকারীকে বুঝতে সহায়তা করতে আমি সিটিইর পুনরাবৃত্ত অংশে সূচি শর্ত যুক্ত করেছি (এমনকি যুক্তিযুক্তভাবে অপ্রয়োজনীয়)।

উপরন্তু আপনি সম্ভবত এছাড়াও থাকা উচিত UNIQUEউপর বাধ্যতা (object_id, customer_id)যে @ypercube ইতিমধ্যে উল্লেখ করেছে। অথবা, যদি আপনি কোনও কারণে স্বতন্ত্রতা আরোপ করতে না পারেন (কেন?) পরিবর্তে একটি সরল সূচক যুক্ত করুন। সূচী কলামগুলির ক্রম বিটিডব্লিউ:


1
আপনার দ্বারা প্রস্তাবিত সূচকগুলি এবং প্রতিবন্ধকতাগুলিকে আমি যুক্ত করব yp ডেটা সন্ধান করে, আমি এমন কোনও কারণ দেখতে পাচ্ছি না যে তারা ঘটতে পারে নি (বিদেশী কী ব্যতীত প্যারেন্ট_আইডি এখনও সেট করা হয়নি)। আমি প্রজন্মের কলামটিও nullable হতে এবং ডিফল্ট সেট -1 এর পরিবর্তে NULL হিসাবে সেট করব। তারপরে আমার কাছে অনেকগুলি "-1" ফিল্টার থাকবে না এবং আংশিক সূচকগুলি প্রজন্মের শুভতা ইত্যাদি হতে পারে ইত্যাদি
ডিজিটি

@ ডিজিগিটি: আপনি যদি বাকী অংশটি মানিয়ে নেন তবে ঠিক ঠিক কাজ করা উচিত।
এরউইন ব্র্যান্ডসেটেটার

@ ইরভিন দুর্দান্ত আমি মূলত আপনার মত একই চিন্তা। একটি সূচক ON objects (customer_id, parent_id, object_id) WHERE generation = -1;এবং সম্ভবত অন্য ON objects (customer_id, object_id) WHERE generation > -1;। আপডেটটিতে সমস্ত সূচী সারিকে একটি সূচক থেকে অন্য সূচকে "স্যুইচ" করতে হবে, সুতরাং নিশ্চিত না যে এটি আপডেটের প্রাথমিক রানের জন্য ভাল ধারণা কিনা।
ypercubeᵀᴹ

পুনরাবৃত্ত অনুসন্ধানগুলির জন্য সূচীকরণ সত্যিই কঠিন হতে পারে।
ypercubeᵀᴹ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.