পোস্টগ্রেস্কেল ক্যোয়ারিতে একাধিক ধরণের সামঞ্জস্যপূর্ণ রেঞ্জের প্রারম্ভিক এবং শেষ দক্ষতার সাথে নির্বাচন করুন


19

আমি একটি টেবিলের সাথে প্রায় এক বিলিয়ন সারি ডেটা পেয়েছি এবং একটি নাম্বার এবং পূর্ণসংখ্যা 1-288 এর মধ্যে রয়েছে। প্রদত্ত নামের জন্য , প্রতিটি অন্তর্লিপি অনন্য এবং পরিসরের প্রতিটি সম্ভাব্য পূর্ণসংখ্যার উপস্থিতি নেই - তাই ফাঁক রয়েছে।

এই ক্যোয়ারী একটি উদাহরণ কেস উত্পন্ন করে:

--what I have:
SELECT *
FROM ( VALUES ('foo', 2),
              ('foo', 3),
              ('foo', 4),
              ('foo', 10),
              ('foo', 11),
              ('foo', 13),
              ('bar', 1),
              ('bar', 2),
              ('bar', 3)
     ) AS baz ("name", "int")

আমি প্রতিটি নাম এবং ধারাবাহিক সংখ্যার ক্রমিকের জন্য সারি সহ একটি সন্ধানের সারণী তৈরি করতে চাই। এই জাতীয় প্রতিটি সারিতে থাকবে:

নাম - এর মান নাম কলাম
শুরু - সংলগ্ন ক্রমানুসারে প্রথম পূর্ণসংখ্যা
শেষ - সংলগ্ন ক্রমানুসারে চূড়ান্ত মান
বিঘত - শেষ - শুরুর +1

এই কোয়েরি উপরের উদাহরণের জন্য উদাহরণ আউটপুট উত্পন্ন করে:

--what I need:
SELECT * 
FROM ( VALUES ('foo', 2, 4, 3),
              ('foo', 10, 11, 2),
              ('foo', 13, 13, 1),
              ('bar', 1, 3, 3)
     ) AS contiguous_ranges ("name", "start", "end", span)

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

আগাম ধন্যবাদ!

সম্পাদনা:

আমার যুক্ত করা উচিত যে পিএল / পিজিএসকিউএল সমাধানগুলি স্বাগত ((দয়া করে কোনও অভিনব ট্রিকস ব্যাখ্যা করুন - আমি এখনও পিএল / পিজিএসকিউএল এ নতুন)।


আমি পর্যাপ্ত পরিমাণে টেবিলটি প্রক্রিয়া করার একটি উপায় খুঁজে পেতে পারি (সম্ভবত "নামটি এন বালতিগুলিতে হ্যাসিং করে, বা নামের প্রথম / শেষ অক্ষর গ্রহণ করে), যাতে একটি সাজানোর স্মৃতিতে ফিট হয়। সম্ভবত টেবিলে বেশ কয়েকটি টেবিল স্ক্যান করা ডিস্কে বাছাইয়ের চেয়ে আরও দ্রুত হবে। একবার আমার এটি হয়ে গেলে আমি উইন্ডোটিং ফাংশনগুলি ব্যবহার করে যাব। এছাড়াও, ডেটাতে নিদর্শনগুলি ব্যবহার করতে ভুলবেন না। সম্ভবত "নাম" এর বেশিরভাগেরই 288 মানের একটি গুন রয়েছে, সেক্ষেত্রে আপনি এই প্রক্রিয়াগুলি মূল প্রক্রিয়া থেকে বাদ দিতে পারেন। এলোমেলো

দুর্দান্ত - এবং সাইটে আপনাকে স্বাগতম। প্রদত্ত সমাধানগুলির সাথে কি আপনার ভাগ্য রয়েছে?
জ্যাক ডগলাস 16

ধন্যবাদ. আমি এই প্রশ্নটি পোস্ট করার পরে খুব শীঘ্রই প্রকল্পগুলি পরিবর্তন করেছি (এবং এর খুব শীঘ্রই, আমি চাকরি পরিবর্তন করেছি), তাই এই সমাধানগুলি পরীক্ষা করার সুযোগ আমার কাছে কখনও আসেনি। এ জাতীয় ক্ষেত্রে উত্তর নির্বাচন করার ক্ষেত্রে আমার কী করা উচিত?
স্টিউ

উত্তর:


9

কিভাবে ব্যবহার সম্পর্কে with recursive

পরীক্ষার দর্শন:

create view v as 
select *
from ( values ('foo', 2),
              ('foo', 3),
              ('foo', 4),
              ('foo', 10),
              ('foo', 11),
              ('foo', 13),
              ('bar', 1),
              ('bar', 2),
              ('bar', 3)
     ) as baz ("name", "int");

প্রশ্ন:

with recursive t("name", "int") as ( select "name", "int", 1 as span from v
                                     union all
                                     select "name", v."int", t.span+1 as span
                                     from v join t using ("name")
                                     where v."int"=t."int"+1 )
select "name", "start", "start"+span-1 as "end", span
from( select "name", ("int"-span+1) as "start", max(span) as span
      from ( select "name", "int", max(span) as span 
             from t
             group by "name", "int" ) z
      group by "name", ("int"-span+1) ) z;

ফলাফল:

 name | start | end | span
------+-------+-----+------
 foo  |     2 |   4 |    3
 foo  |    13 |  13 |    1
 bar  |     1 |   3 |    3
 foo  |    10 |  11 |    2
(4 rows)

এটি আপনার বিলিয়ন সারি টেবিলে কীভাবে সম্পাদন করে তা জানতে আগ্রহী হব।


পারফরম্যান্স যদি কোনও সমস্যা হয় তবে work_mem এর জন্য সেটিংসটি খেললে পারফরম্যান্স উন্নত হতে পারে।
ফ্রাঙ্ক হিকেন্স 15

7

আপনি উইন্ডোটিং ফাংশন দিয়ে এটি করতে পারেন। মূল ধারণাটি হ'ল বর্তমান সারির সামনে এবং পিছনে সারিগুলি টানতে ফাংশনগুলি ব্যবহার leadএবং lagউইন্ডোং করা। তারপরে আমরা যদি অনুক্রমের সূচনা বা শেষ হয় তবে আমরা গণনা করতে পারি:

create temp view temp_view as
    select
        n,
        val,
        (lead <> val + 1 or lead is null) as islast,
        (lag <> val - 1 or lag is null) as isfirst,
        (lead <> val + 1 or lead is null) and (lag <> val - 1 or lag is null) as orphan
    from
    (
        select
            n,
            lead(val, 1) over( partition by n order by n, val),
            lag(val, 1) over(partition by n order by n, val ),
            val
        from test
        order by n, val
    ) as t
;  
select * from temp_view;
 n  | val | islast | isfirst | orphan 
-----+-----+--------+---------+--------
 bar |   1 | f      | t       | f
 bar |   2 | f      | f       | f
 bar |   3 | t      | f       | f
 bar |  24 | t      | t       | t
 bar |  42 | t      | t       | t
 foo |   2 | f      | t       | f
 foo |   3 | f      | f       | f
 foo |   4 | t      | f       | f
 foo |  10 | f      | t       | f
 foo |  11 | t      | f       | f
 foo |  13 | t      | t       | t
(11 rows)

(আমি একটি ভিউ ব্যবহার করেছি যাতে যুক্তিটি নীচে অনুসরণ করা আরও সহজ হবে)) সুতরাং এখন আমরা জানি যে সারিটি কোনও শুরু বা শেষ if আমাদের এটিকে সারিবদ্ধভাবে ভেঙে ফেলতে হবে:

select
    n as "name",
    first,
    coalesce (last, first) as last,
    coalesce (last - first + 1, 1) as span
from
(
    select
    n,
    val as first,
    -- this will not be excellent perf. since were calling the view
    -- for each row sequence found. Changing view into temp table 
    -- will probably help with lots of values.
    (
        select min(val)
        from temp_view as last
        where islast = true
        -- need this since isfirst=true, islast=true on an orphan sequence
        and last.orphan = false
        and first.val < last.val
        and first.n = last.n
    ) as last
    from
        (select * from temp_view where isfirst = true) as first
) as t
;

 name | first | last | span 
------+-------+------+------
 bar  |     1 |    3 |    3
 bar  |    24 |   24 |    1
 bar  |    42 |   42 |    1
 foo  |     2 |    4 |    3
 foo  |    10 |   11 |    2
 foo  |    13 |   13 |    1
(6 rows)

আমার কাছে সঠিক দেখাচ্ছে :)


3

অন্য উইন্ডো ফাংশন সমাধান। দক্ষতা সম্পর্কে কোনও ধারণা নেই, আমি মৃত্যুদণ্ড কার্যকর করার পরিকল্পনাটি শেষে রেখেছি (যদিও এত কম সারি দিয়েও এটি সম্ভবত খুব বেশি দামের নয়)। আপনি যদি চারপাশে খেলতে চান: এসকিউএল-ফিডল পরীক্ষা

সারণী এবং ডেটা:

CREATE TABLE baz
( name VARCHAR(10) NOT NULL
, i INT  NOT NULL
, UNIQUE  (name, i)
) ;

INSERT INTO baz
  VALUES 
    ('foo', 2),
    ('foo', 3),
    ('foo', 4),
    ('foo', 10),
    ('foo', 11),
    ('foo', 13),
    ('bar', 1),
    ('bar', 2),
    ('bar', 3)
  ;

প্রশ্ন:

SELECT a.name     AS name
     , a.i        AS start
     , b.i        AS "end"
     , b.i-a.i+1  AS span
FROM
      ( SELECT name, i
             , ROW_NUMBER() OVER (PARTITION BY name ORDER BY i) AS rn
        FROM baz AS a
        WHERE NOT EXISTS
              ( SELECT * 
                FROM baz AS prev
                WHERE prev.name = a.name
                  AND prev.i = a.i - 1
              ) 
      ) AS a
    JOIN
      ( SELECT name, i 
             , ROW_NUMBER() OVER (PARTITION BY name ORDER BY i) AS rn
        FROM baz AS a
        WHERE NOT EXISTS
              ( SELECT * 
                FROM baz AS next
                WHERE next.name = a.name
                  AND next.i = a.i + 1
              )
      ) AS b
    ON  b.name = a.name
    AND b.rn  = a.rn
 ; 

অনুসন্ধান পরিকল্পনা

Merge Join (cost=442.74..558.76 rows=18 width=46)
Merge Cond: ((a.name)::text = (a.name)::text)
Join Filter: ((row_number() OVER (?)) = (row_number() OVER (?)))
-> WindowAgg (cost=221.37..238.33 rows=848 width=42)
-> Sort (cost=221.37..223.49 rows=848 width=42)
Sort Key: a.name, a.i
-> Merge Anti Join (cost=157.21..180.13 rows=848 width=42)
Merge Cond: (((a.name)::text = (prev.name)::text) AND (((a.i - 1)) = prev.i))
-> Sort (cost=78.60..81.43 rows=1130 width=42)
Sort Key: a.name, ((a.i - 1))
-> Seq Scan on baz a (cost=0.00..21.30 rows=1130 width=42)
-> Sort (cost=78.60..81.43 rows=1130 width=42)
Sort Key: prev.name, prev.i
-> Seq Scan on baz prev (cost=0.00..21.30 rows=1130 width=42)
-> Materialize (cost=221.37..248.93 rows=848 width=50)
-> WindowAgg (cost=221.37..238.33 rows=848 width=42)
-> Sort (cost=221.37..223.49 rows=848 width=42)
Sort Key: a.name, a.i
-> Merge Anti Join (cost=157.21..180.13 rows=848 width=42)
Merge Cond: (((a.name)::text = (next.name)::text) AND (((a.i + 1)) = next.i))
-> Sort (cost=78.60..81.43 rows=1130 width=42)
Sort Key: a.name, ((a.i + 1))
-> Seq Scan on baz a (cost=0.00..21.30 rows=1130 width=42)
-> Sort (cost=78.60..81.43 rows=1130 width=42)
Sort Key: next.name, next.i
-> Seq Scan on baz next (cost=0.00..21.30 rows=1130 width=42)

3

এসকিউএল সার্ভারে, আমি পূর্ববর্তী আইটেম নামে আরও একটি কলাম যুক্ত করব:

SELECT *
FROM ( VALUES ('foo', 2, NULL),
              ('foo', 3, 2),
              ('foo', 4, 3),
              ('foo', 10, 4),
              ('foo', 11, 10),
              ('foo', 13, 11),
              ('bar', 1, NULL),
              ('bar', 2, 1),
              ('bar', 3, 2)
     ) AS baz ("name", "int", "previousInt")

পূর্ববর্তী << এবং একটি এফকে সীমাবদ্ধতা (নাম, পূর্ববর্তী) উল্লেখ করে (নাম, পূর্ববর্তী), এবং জলরোধক ডেটা অখণ্ডতা নিশ্চিত করার জন্য আরও কয়েকটি বাধা রেখেছে তা নিশ্চিত করার জন্য আমি একটি চেক সীমাবদ্ধতা ব্যবহার করব। এটি সম্পন্ন, ফাঁক নির্বাচন করা তুচ্ছ:

SELECT NAME, PreviousInt, Int from YourTable WHERE PreviousInt < Int - 1;

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


1

আপনি তাবিবিটোসান পদ্ধতিটি সন্ধান করতে পারেন:

https://community.oracle.com/docs/DOC-915680
http://rwijk.blogspot.com/2014/01/tabibitosan.html
https://www.xaprb.com/blog/2006/03/22/find-contiguous-ranges-with-sql/

মূলত:

SQL> create table mytable (nr)
  2  as
  3  select 1 from dual union all
  4  select 2 from dual union all
  5  select 3 from dual union all
  6  select 6 from dual union all
  7  select 7 from dual union all
  8  select 11 from dual union all
  9  select 18 from dual union all
 10  select 19 from dual union all
 11  select 20 from dual union all
 12  select 21 from dual union all
 13  select 22 from dual union all
 14  select 25 from dual
 15  /

 Table created.

 SQL> with tabibitosan as
 2  ( select nr
 3         , nr - row_number() over (order by nr) grp
 4      from mytable
 5  )
 6  select min(nr)
 7       , max(nr)
 8    from tabibitosan
 9   group by grp
10   order by grp
11  /

   MIN(NR)    MAX(NR)
---------- ----------
         1          3
         6          7
        11         11
        18         22
        25         25

5 rows selected.

আমি এই কর্মক্ষমতা আরও ভাল বলে মনে করি:

SQL> r
  1  select min(nr) as range_start
  2    ,max(nr) as range_end
  3  from (-- our previous query
  4    select nr
  5      ,rownum
  6      ,nr - rownum grp
  7    from  (select nr
  8       from   mytable
  9       order by 1
 10      )
 11   )
 12  group by grp
 13* order by 1

RANGE_START  RANGE_END
----------- ----------
      1      3
      6      7
     11     11
     18     22
     25     25

0

মোটামুটি পরিকল্পনা:

  • প্রতিটি নামের জন্য সর্বনিম্ন নির্বাচন করুন (নাম অনুসারে গ্রুপ করুন)
  • প্রতিটি নামের জন্য সর্বনিম্ন 2 নির্বাচন করুন, যেখানে min2> min1 এবং বিদ্যমান নেই (উপশোক্তি: সেল মিন 2-1)।
  • সর্বাধিক সেল 1> মিনিট ভাল 1 যেখানে সর্বাধিক ভাল 1 <মিনিট ভাল 2।

কোনও আপডেট না হওয়া পর্যন্ত 2 থেকে পুনরাবৃত্তি করুন। সর্বাধিক মিনিট এবং সর্বনিম্ন মিনিটের বেশি গ্রুপিংয়ের সাথে গর্ডিয়ান সেখান থেকে জটিল হয়ে ওঠে। আমার ধারণা আমি কোনও প্রোগ্রামিং ভাষার জন্য যাব।

পিএস: কয়েকটি নমুনা মান সহ একটি দুর্দান্ত নমুনার টেবিলটি সূক্ষ্ম হবে, যা প্রত্যেকে ব্যবহার করতে পারে, তাই সকলেই স্ক্র্যাচ থেকে তার টেস্টডেটা তৈরি করে না।


0

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

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

কয়েকটি অন্যান্য নোট : নিম্নলিখিতটি এসকিউএল 3 এর কোড is এসকিউএলাইট উপভাষাটি পোস্টগ্রেস্কল থেকে উদ্ভূত, সুতরাং এটি খুব অনুরূপ এবং এমনকি অবারিত কাজ করতে পারে। আমি ওভারের ধারাগুলিতে ফ্রেমিং সীমাবদ্ধতা যুক্ত করেছি, যেহেতু lag()এবং lead()ক্রিয়াকলাপগুলিতে যথাক্রমে আগে এবং পরে কেবলমাত্র একটি একক-সারি উইন্ডো প্রয়োজন (সুতরাং পূর্ববর্তী সমস্ত সারিগুলির ডিফল্ট সেট রাখার প্রয়োজন ছিল না )। আমি নামগুলির পক্ষেও নির্বাচন করেছি firstএবং lastযেহেতু শব্দটি endসংরক্ষিত রয়েছে।

create temp view test as 
with cte(name, int) AS (
select * from ( values ('foo', 2),
              ('foo', 3),
              ('foo', 4),
              ('foo', 10),
              ('foo', 11),
              ('foo', 13),
              ('bar', 1),
              ('bar', 2),
              ('bar', 3) ))
select * from cte;


SELECT name,
       int AS first, 
       endpoint AS last,
       (endpoint - int + 1) AS span
FROM ( SELECT name, 
             int, 
             CASE WHEN prev <> 1 AND next <> -1 -- orphan
                  THEN int
                WHEN next = -1 -- start of range
                  THEN lead(int) OVER (PARTITION BY name 
                                       ORDER BY int 
                                       ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
                ELSE null END
             AS endpoint
        FROM ( SELECT name, 
                   int,
                   coalesce(int - lag(int) OVER (PARTITION BY name 
                                                 ORDER BY int 
                                                 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 
                            0) AS prev,
                   coalesce(int - lead(int) OVER (PARTITION BY name 
                                                  ORDER BY int 
                                                  ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),
                            0) AS next
              FROM test
            ) AS mark_boundaries
        WHERE NOT (prev = 1 AND next = -1) -- discard values within range
      ) as raw_ranges
WHERE endpoint IS NOT null
ORDER BY name, first

ফলাফল যেমনটি প্রত্যাশা করে ঠিক তেমনই অন্যান্য উত্তরগুলির মতো:

 name | first | last | span
------+-------+------+------
 bar  |     1 |    3 |   3
 foo  |     2 |    4 |   3
 foo  |    10 |   11 |   2
 foo  |    13 |   13 |   1
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.