সিরিজের প্রতিটি তারিখকে কত তারিখের সীমা আবরণ করা হয়েছে তা গণনার দ্রুততম উপায়


12

আমার কাছে একটি টেবিল রয়েছে (পোস্টগ্রিসকিউএল 9.4 এ) যা দেখতে দেখতে এটি:

CREATE TABLE dates_ranges (kind int, start_date date, end_date date);
INSERT INTO dates_ranges VALUES 
    (1, '2018-01-01', '2018-01-31'),
    (1, '2018-01-01', '2018-01-05'),
    (1, '2018-01-03', '2018-01-06'),
    (2, '2018-01-01', '2018-01-01'),
    (2, '2018-01-01', '2018-01-02'),
    (3, '2018-01-02', '2018-01-08'),
    (3, '2018-01-05', '2018-01-10');

এখন আমি প্রদত্ত তারিখগুলি এবং প্রতিটি ধরণের জন্য গণনা করতে চাই, dates_rangesপ্রতিটি তারিখ থেকে কয়টি সারি নেমে আসে into জিরোস সম্ভবত বাদ দেওয়া যেতে পারে।

কাঙ্ক্ষিত ফলাফল:

+-------+------------+----+
|  kind | as_of_date |  n |
+-------+------------+----+
|     1 | 2018-01-01 |  2 |
|     1 | 2018-01-02 |  2 |
|     1 | 2018-01-03 |  3 |
|     2 | 2018-01-01 |  2 |
|     2 | 2018-01-02 |  1 |
|     3 | 2018-01-02 |  1 |
|     3 | 2018-01-03 |  1 |
+-------+------------+----+

আমি দুটি সমাধান নিয়ে এসেছি, একটি সঙ্গে LEFT JOINএবংGROUP BY

SELECT
kind, as_of_date, COUNT(*) n
FROM
    (SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates
LEFT JOIN
    dates_ranges ON dates.as_of_date BETWEEN start_date AND end_date
GROUP BY 1,2 ORDER BY 1,2

এবং এর সাথে একটি LATERAL, যা সামান্য দ্রুত:

SELECT
    kind, as_of_date, n
FROM
    (SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates,
LATERAL
    (SELECT kind, COUNT(*) AS n FROM dates_ranges WHERE dates.as_of_date BETWEEN start_date AND end_date GROUP BY kind) ss
ORDER BY kind, as_of_date

আমি ভাবছি এই কোয়েরিটি লেখার আরও ভাল উপায় কি? এবং 0 টি গণনার সাথে কীভাবে জোড়গুলি তারিখ-জাতীয় অন্তর্ভুক্ত করবেন?

বাস্তবে কয়েকটি স্বতন্ত্র ধরণের রয়েছে, পাঁচ বছর অবধি (1800 তারিখ) এবং dates_rangesটেবিলে 30 ডলার সারি (তবে এটি উল্লেখযোগ্যভাবে বৃদ্ধি পেতে পারে)।

কোনও সূচী নেই। আমার ক্ষেত্রে সুনির্দিষ্ট হওয়ার জন্য এটি subquery এর ফলাফল, তবে আমি প্রশ্নটি একটি ইস্যুতে সীমাবদ্ধ করতে চেয়েছিলাম, সুতরাং এটি আরও সাধারণ।


আপনি যদি টেবিলের রেঞ্জগুলি অ-ওভারল্যাপিং বা স্পর্শ করে থাকেন তবে আপনি কী করবেন। উদাহরণস্বরূপ যদি আপনার এমন একটি পরিসীমা থাকে যেখানে (ধরনের, শুরু, শেষ) = (1,2018-01-01,2018-01-15)এবং (1,2018-01-20,2018-01-25)আপনার কতগুলি ওভারল্যাপিং তারিখ রয়েছে তা নির্ধারণ করার সময় আপনি কি তা বিবেচনায় নিতে চান?
ইভান ক্যারল

আমিও বিভ্রান্ত হয়েছি কেন আপনার টেবিলটি ছোট? কেন নয় 2018-01-31বা 2018-01-30বা 2018-01-29প্রথম পরিসীমা তাদের সব আছে যখন এটি কি আপনি?
ইভান ক্যারল

@ ইভানক্রোলের তারিখগুলি generate_seriesবহিরাগত প্যারামিটারগুলি রয়েছে - তারা অগত্যা সমস্ত dates_rangesসারণিকে সারণীতে আবরণ করে না । প্রথম প্রশ্নের হিসাবে আমি মনে করি আমি এটি বুঝতে পারি না - সারিগুলি dates_rangesস্বাধীন, আমি ওভারল্যাপিং নির্ধারণ করতে চাই না।
বারটেকচ

উত্তর:


4

"অনুপস্থিত শূন্যগুলি" ঠিক আছে তবে নিম্নলিখিত ক্যোয়ারীও কাজ করে:

select *
from (
  select
    kind,
    generate_series(start_date, end_date, interval '1 day')::date as d,
    count(*)
  from dates_ranges
  group by 1, 2
) x
where d between date '2018-01-01' and date '2018-01-03'
order by 1, 2;

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

নিম্নলিখিত কোয়েরিটি যে কোনও সিরিজ যে কোনওভাবে ওভারল্যাপ হয় না তা মুছে ফেলে অপ্রয়োজনীয় কাজ এড়াতে চেষ্টা করে:

select
  kind,
  generate_series(greatest(start_date, date '2018-01-01'), least(end_date, date '2018-01-03'), interval '1 day')::date as d,
  count(*)
from dates_ranges
where (start_date, end_date + interval '1 day') overlaps (date '2018-01-01', date '2018-01-03' + interval '1 day')
group by 1, 2
order by 1, 2;

- এবং আমি overlapsঅপারেটর ব্যবহার করতে হবে ! নোট করুন যে interval '1 day'ওভারল্যাপ অপারেটর সময়কালকে ডানদিকে উন্মুক্ত বলে বিবেচনা করে (যা মোটামুটি যৌক্তিক কারণ একটি তারিখ প্রায়শই মধ্যরাতের সময় উপাদান সহ একটি টাইমস্ট্যাম্প হিসাবে বিবেচিত হয়) you


ভাল, আমি জানি generate_seriesনা যে এরকম ব্যবহার করা যেতে পারে। কয়েকটি পরীক্ষার পরে আমি নিম্নলিখিত পর্যবেক্ষণ আছে। আপনার ক্যোয়ারী নির্বাচিত ব্যাপ্তির দৈর্ঘ্যের সাথে সত্যই ভাল স্কেল করে - এতে কার্যত 3 বছর থেকে 10 বছরের মধ্যে কোনও পার্থক্য নেই। তবে সংক্ষিপ্ত সময়ের জন্য (1 বছর) আমার সমাধানগুলি দ্রুত - আমি অনুমান করছি যে এর কারণটি হ'ল কিছু সত্যই দীর্ঘ রেঞ্জ রয়েছে dates_ranges(যেমন ২০১০-২০০১), যা আপনার জিজ্ঞাসাটিকে ধীর করে দিচ্ছে। সীমাবদ্ধকরণ start_dateএবং end_dateঅভ্যন্তরীণ কোয়েরিটির ভিতরে যদিও সহায়তা করা উচিত। আমার আরও কয়েকটি পরীক্ষা করা দরকার।
বারটেকচি

6

এবং 0 টি গণনার সাথে কীভাবে জোড়গুলি তারিখ-জাতীয় অন্তর্ভুক্ত করবেন?

সমস্ত সংমিশ্রণের গ্রিড তৈরি করুন তারপরে LATERAL আপনার টেবিলটিতে যোগ দিন:

SELECT k.kind, d.as_of_date, c.n
FROM  (SELECT DISTINCT kind FROM dates_ranges) k
CROSS  JOIN (
   SELECT d::date AS as_of_date
   FROM   generate_series(timestamp '2018-01-01', timestamp '2018-01-03', interval '1 day') d
   ) d
CROSS  JOIN LATERAL (
   SELECT count(*)::int AS n
   FROM   dates_ranges
   WHERE  kind = k.kind
   AND    d.as_of_date BETWEEN start_date AND end_date
   ) c
ORDER  BY k.kind, d.as_of_date;

যতটা সম্ভব দ্রুত হওয়া উচিত।

আমার LEFT JOIN LATERAL ... on trueপ্রথমে ছিল , তবে সাবকিউরিতে একটি সমষ্টি রয়েছে c, তাই আমরা সর্বদা একটি সারি পাই এবং CROSS JOINপাশাপাশি ব্যবহার করতে পারি । পারফরম্যান্সে কোনও পার্থক্য নেই।

আপনার যদি সমস্ত প্রাসঙ্গিক ধরণের একটি টেবিল থাকে তবে সাবকিউরিটি সহ তালিকাটি তৈরি করার পরিবর্তে এটি ব্যবহার করুন k

Cast integerালাই optionচ্ছিক। অন্যথায় আপনি পাবেন bigint

সূচকগুলি বিশেষত মাল্টিকালম ইনডেক্সে সহায়তা করবে (kind, start_date, end_date)। যেহেতু আপনি একটি উপশহর তৈরি করছেন, এটি অর্জন করা সম্ভবও হতে পারে।

মত সেট ফিরে ফাংশন ব্যবহার করে generate_series()SELECTতালিকা সাধারণত হয় না যুক্তিযুক্ত Postgres সংস্করণে 10 আগে (যদি না আপনি জানেন আপনি ঠিক কি করছেন)। দেখা:

আপনার যদি কয়েকটি বা কম সারি সহ প্রচুর সংমিশ্রণ থাকে তবে এই সমতুল্য রূপটি আরও দ্রুত হতে পারে:

SELECT k.kind, d.as_of_date, count(dr.kind)::int AS n
FROM  (SELECT DISTINCT kind FROM dates_ranges) k
CROSS JOIN (
   SELECT d::date AS as_of_date
   FROM   generate_series(timestamp '2018-01-01', timestamp '2018-01-03', interval '1 day') d
   ) d
LEFT   JOIN dates_ranges dr ON dr.kind = k.kind
                           AND d.as_of_date BETWEEN dr.start_date AND dr.end_date
GROUP  BY 1, 2
ORDER  BY 1, 2;

SELECTতালিকার সেট-রিটার্নিং ফাংশনগুলির জন্য - আমি পড়েছি যে এটি পরামর্শ দেওয়া হয় না, তবে দেখে মনে হচ্ছে এটি ঠিক কাজ করে, যদি এর মধ্যে একটি মাত্র ফাংশন থাকে। আমি যদি নিশ্চিত যে কেবলমাত্র একজনই থাকবে তবে কি কিছু ভুল হতে পারে?
বারটেকসিএইচ

@ বারটেকসিএইচ: তালিকার একটি একক এসআরএফ SELECTপ্রত্যাশা অনুযায়ী কাজ করে। অন্য একটি যুক্ত করার বিরুদ্ধে সতর্ক করতে কোনও মন্তব্য যুক্ত করুন। অথবা FROMপোস্টগ্রাসের পুরানো সংস্করণ দিয়ে শুরু করতে এটি তালিকায় স্থানান্তর করুন । ঝুঁকি জটিলতা কেন? (এটি স্ট্যান্ডার্ড এসকিউএল এবং অন্যান্য আরডিবিএমএস থেকে আসা লোকদের বিভ্রান্ত করবে না))
এরউইন ব্র্যান্ডসেটেটার

1

daterangeপ্রকারটি ব্যবহার করে

PostgreSQL এর একটি রয়েছে daterange। এটি ব্যবহার করা বেশ সহজ। আপনার নমুনা ডেটা দিয়ে শুরু করে আমরা টেবিলের ধরণটি ব্যবহার করতে চলেছি।

BEGIN;
  ALTER TABLE dates_ranges ADD COLUMN myrange daterange;
  UPDATE dates_ranges
    SET myrange = daterange(start_date, end_date, '[]');
  ALTER TABLE dates_ranges
    DROP COLUMN start_date,
    DROP COLUMN end_date;
COMMIT;

-- Now you can create GIST index on it...
CREATE INDEX ON dates_ranges USING gist (myrange);

TABLE dates_ranges;
 kind |         myrange         
------+-------------------------
    1 | [2018-01-01,2018-02-01)
    1 | [2018-01-01,2018-01-06)
    1 | [2018-01-03,2018-01-07)
    2 | [2018-01-01,2018-01-02)
    2 | [2018-01-01,2018-01-03)
    3 | [2018-01-02,2018-01-09)
    3 | [2018-01-05,2018-01-11)
(7 rows)

আমি প্রদত্ত তারিখগুলি এবং প্রতিটি ধরণের জন্য গণনা করতে চাই, প্রতিটি তারিখ খেজুর_রেঞ্জ থেকে কয়টি সারি পড়ে।

এখন এটির অনুসন্ধানের জন্য আমরা প্রক্রিয়াটি বিপরীত করি এবং একটি তারিখের সিরিজ উত্পন্ন করি তবে এখানে ক্যোয়ারী নিজেই কন্টেন্ট ( @>) অপারেটরটি খতিয়ে দেখতে পারে যে তারিখগুলি পরিসীমাতে রয়েছে, একটি সূচক ব্যবহার করে।

নোট আমরা ব্যবহার করি timestamp without time zone(ডিএসটি বিপত্তি বন্ধ করতে)

SELECT d1.kind, day::date, count(d2.kind)
FROM dates_ranges AS d1
CROSS JOIN LATERAL generate_series(
  lower(myrange)::timestamp without time zone,
  upper(myrange)::timestamp without time zone,
  '1 day'
) AS gs(day)
INNER JOIN dates_ranges AS d2
  ON d2.myrange @> day::date
GROUP BY d1.kind, day;

যা সূচকে আইটেমাইজড ডে-ওভারল্যাপ।

পার্শ্ব বোনাস হিসাবে, ডেটরেঞ্জ টাইপের সাহায্যে আপনি এমন ব্যাপ্তিগুলির সন্নিবেশ বন্ধ করতে পারেন যা অন্যের সাথে ওভারল্যাপ করে একটি ব্যবহার করেEXCLUDE CONSTRAINT


আপনার প্রশ্নের সাথে কিছু ভুল হয়েছে, দেখে মনে হচ্ছে এটি একাধিকবার সারিগুলি গণনা করছে, এটি JOINআমার খুব বেশি অনুমান।
বারটেকচ

@ বারটেকসিএইচ না আপনার ওভারল্যাপিং সারি নেই, আপনি ওভারল্যাপিং রেঞ্জগুলি (প্রস্তাবিত) সরিয়ে বা ব্যবহার করে এটি পেতে পারেনcount(DISTINCT kind)
ইভান ক্যারল

তবে আমি ওভারল্যাপিং সারিগুলি চাই। উদাহরণস্বরূপ 1তারিখ উদাহরণস্বরূপ 2018-01-01থেকে প্রথম দুটি সারি মধ্যে dates_ranges, কিন্তু আপনার জিজ্ঞাসা দেয় 8
বারটেকচ

অথবাcount(DISTINCT kind) আপনি DISTINCTকীওয়ার্ডটি সেখানে যুক্ত করেছেন?
ইভান ক্যারল

দুর্ভাগ্যক্রমে DISTINCTকীওয়ার্ড সহ এটি এখনও প্রত্যাশার মতো কাজ করে না। এটি প্রতিটি তারিখের জন্য পৃথক ধরণের গণনা করে তবে আমি প্রতিটি তারিখের জন্য প্রতিটি ধরণের সমস্ত সারি গণনা করতে চাই।
বারটেকসিএইচ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.