PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Намерете и сумирайте диапазони от дати с припокриващи се записи в postgresql

demo:db<>fiddle (използва стария набор от данни с припокриващата се A-B-част)

Отказ от отговорност: Това работи за дневни интервали, а не за времеви отпечатъци. Изискването за ts дойде по-късно.

SELECT
    s.acts,
    s.sum,
    MIN(a.start) as start,
    MAX(a.end) as end
FROM (
    SELECT DISTINCT ON (acts)
        array_agg(name) as acts,
        SUM(count)
    FROM
        activities, generate_series(start, "end", interval '1 day') gs
    GROUP BY gs
    HAVING cardinality(array_agg(name)) > 1
) s
JOIN activities a
ON a.name = ANY(s.acts)
GROUP BY s.acts, s.sum
  1. generate_series генерира всички дати между началото и края. Така че всяка дата, на която съществува дейност, получава един ред с конкретния count
  2. Групиране на всички дати, обобщаване на всички съществуващи дейности и сумата от броя им
  3. HAVING филтрира датите, на които съществува само една дейност
  4. Тъй като има различни дни с едни и същи дейности, имаме нужда само от един представител:Филтрирайте всички дубликати с DISTINCT ON
  5. Свържете този резултат с оригиналната таблица, за да получите началото и края. (обърнете внимание, че „край“ е запазена дума в Postgres, по-добре е да намерите друго име на колона!). Беше по-удобно да ги загубите преди, но е възможно да получите тези данни в подзаявката.
  6. Групирайте това присъединяване, за да получите най-ранната и най-късната дата за всеки интервал.

Ето версия за клеймото за време:

demo:db<>fiddle

WITH timeslots AS (
    SELECT * FROM (
        SELECT
            tsrange(timepoint, lead(timepoint) OVER (ORDER BY timepoint)),
            lead(timepoint) OVER (ORDER BY timepoint)     -- 2
        FROM (
            SELECT 
                unnest(ARRAY[start, "end"]) as timepoint  -- 1 
            FROM
                activities
            ORDER BY timepoint
        ) s
    )s  WHERE lead IS NOT NULL                            -- 3
)
SELECT 
    GREATEST(MAX(start), lower(tsrange)),                 -- 6
    LEAST(MIN("end"), upper(tsrange)),
    array_agg(name),                                      -- 5
    sum(count)
FROM 
    timeslots t
JOIN activities a
ON t.tsrange && tsrange(a.start, a.end)                   -- 4
GROUP BY tsrange
HAVING cardinality(array_agg(name)) > 1

Основната идея е да се идентифицират възможните времеви интервали. Така че вземам всяко известно време (начало и край) и ги поставям в сортиран списък. Така че мога да взема познатите часове за първото теглене (17:00 от начало А и 18:00 от начало B) и да проверя кой интервал е в него. След това го проверявам за 2-ри и 3-ти, след това за 3-ти, 4-ти и така нататък.

В първия времеви интервал само A пасва. Във втория от 18-19 също B се вписва. В следващия слот 19-20 също C, от 20 до 20:30 A вече не пасва, само B и C. Следващият е 20:30-22, където само B пасва, накрая 22-23 D се добавя към B и не на последно място само D се вписва в 23-23:30.

Така че вземам този списък с време и го свързвам с таблицата с дейности, където интервалите се пресичат. След това има само групиране по времеви интервал и обобщаване на вашия брой.

  1. това поставя и двете ts на ред в един масив, чиито елементи се разширяват в един ред на елемент с unnest . Така че получавам всички времена в една колона, която може просто да се подреди
  2. използване на водещата прозоречна функция позволява да се вземе стойността на следващия ред в текущия. Така че мога да създам диапазон от времево клеймо от тези две стойности с tsrange
  3. Този филтър е необходим, защото последният ред няма „следваща стойност“. Това създава NULL стойност, която се интерпретира от tsrange като безкрайност. Така че това би създало невероятно грешен времеви интервал. Така че трябва да филтрираме този ред.
  4. Съединете времевите интервали спрямо оригиналната таблица. && операторът проверява дали два типа диапазони се припокриват.
  5. Групиране по отделни времеви интервали, обобщаване на имената и броя. Филтрирайте времевите интервали само с една дейност, като използвате HAVING клауза
  6. Малко трудно е да получите точната начална и крайна точка. Така че началните точки са или максимумът на началото на дейността, или началото на времеви интервал (който може да бъде получен чрез lower ). напр. Вземете интервала 20-20:30:Започва в 20h, но нито B, нито C имат начална точка там. Подобно на крайния час.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Функцията отнема вечно да работи за голям брой записи

  2. Двусмислена препратка към колона в INSERT ... ON CONFLICT DO UPDATE

  3. Postgres COUNT брой стойности на колони с INNER JOIN

  4. ГРЕШКА:липсва памет на машина с 32 GB RAM и без суап файл

  5. Формата за търсене не се насочва към правилния контролер в Rails 5.1