Хобрук: Ваш путь к мастерству в программировании

Найти пересечение между диапазонами дат в PostgreSQL

У меня есть записи с двумя датами check_in и check_out, я хочу знать диапазоны, когда несколько человек зарегистрировались одновременно.

Итак, если у меня есть следующие проверки/выписки:

  • Человек А: 1PM - 6PM
  • Человек Б: 3PM - 10PM
  • Человек С: 9PM - 11PM

Я хотел бы получить 3PM - 6PM (совпадение лиц A и B) и 9PM - 10PM (совпадение лиц B и C).

Я могу написать алгоритм, чтобы сделать это за линейное время с помощью кода, возможно ли сделать это с помощью реляционного запроса за линейное время с помощью PostgreSQL?

Он должен иметь минимальный отклик, что означает отсутствие перекрывающихся диапазонов. Поэтому, если бы был результат, который давал диапазон 6PM - 9PM и 8PM - 10PM, это было бы неправильно. Вместо этого он должен возвращать 6PM - 10pm.


  • Ваша версия Postgres, ваше точное определение таблицы (полный скрипт CREATE TABLE, включая все ограничения или то, что вы получаете с \d tbl в psql) и некоторые примеры данных были бы неплохо иметь. 10.01.2016
  • да, версия поможет нам ответить, в последних выпусках добавлены новые функции диапазона дат, которые могут быть применимы. 10.01.2016
  • Я предполагаю, что решение будет включать оконные функции и, возможно, рекурсивные CTE. 10.01.2016

Ответы:


1

Предположения

Решение сильно зависит от точного определения таблицы, включая все ограничения. Из-за отсутствия информации в вопросе я приму эту таблицу:

CREATE TABLE booking (
  booking_id serial PRIMARY KEY
, check_in   timestamptz NOT NULL
, check_out  timestamptz NOT NULL
, CONSTRAINT valid_range CHECK (check_out > check_in)
);

Таким образом, никаких значений NULL, только допустимые диапазоны с включенной нижней и исключающей верхней границей, и нам все равно, кто регистрируется.

Также предполагается текущая версия Postgres, по крайней мере, 9.2.

Запрос

Один из способов сделать это только с SQL, используя UNION ALL и оконные функции:

SELECT ts AS check_id, next_ts As check_out
FROM  (
   SELECT *, lead(ts) OVER (ORDER BY ts) AS next_ts
   FROM  (
      SELECT *, lag(people_ct, 1 , 0) OVER (ORDER BY ts) AS prev_ct
      FROM  (
         SELECT ts, sum(sum(change)) OVER (ORDER BY ts)::int AS people_ct
         FROM  (
            SELECT check_in AS ts, 1 AS change FROM booking
            UNION ALL
            SELECT check_out, -1 FROM booking
            ) sub1
         GROUP  BY 1
         ) sub2
      ) sub3
   WHERE  people_ct > 1 AND prev_ct < 2 OR  -- start overlap
          people_ct < 2 AND prev_ct > 1     -- end overlap
   ) sub4
WHERE  people_ct > 1 AND prev_ct < 2;

Скрипт SQL.

Объяснение

  • В подзапросе sub1 выведите таблицу check_in и check_out в одном столбце. check_in добавляет один к толпе, check_out вычитает один.

  • В sub2 суммируйте все события за один и тот же момент времени и вычислите текущее количество с помощью оконной функции: это оконная функция sum() по агрегату sum() - и приведите к integer или мы получим numeric из этого:

       sum(sum(change)) OVER (ORDER BY ts)::int
    
  • В sub3 посмотрите на количество предыдущей строки

  • В sub4 сохраняйте только строки, в которых начинаются и заканчиваются перекрывающиеся временные диапазоны, и перетаскивайте конец временного диапазона в ту же строку с lead().

  • Наконец, оставьте только строки, в которых начинаются временные диапазоны.


Чтобы оптимизировать производительность, я просматривал таблицу один раз в функции plpgsql, как показано в этом связанном ответе на dba.SE:

10.01.2016

2

Идея состоит в том, чтобы разделить время на периоды и сохранить их как битовые значения с заданной степенью детализации.

  • 0 - в одном зерне никто не проверяется
  • 1 - кто-то проверен в одно зерно

Предположим, что гранулярность составляет 1 час, а период — 1 день.

  • 000000000000000000000000 означает, что в этот день никто не проверялся
  • 000000000000000000000110 означает, что кто-то проверен между 21 и 23
  • 000000000000011111000000 означает, что кто-то проверен в возрасте от 13 до 18 лет.
  • 000000000000000111111100 означает, что кто-то проверен в возрасте от 15 до 22 лет.

После этого мы выполняем двоичное ИЛИ для каждого значения в диапазоне и получаем ответ.

  • 000000000000011111111110

Это можно сделать за линейное время. Вот пример из Oracle, но его можно легко преобразовать в PostgreSQL.

with rec (checkin, checkout)
as ( select 13, 18 from dual 
   union all 
    select 15, 22 from dual 
   union all 
    select 21, 23 from dual )
,spanempty ( empt)
 as ( select '000000000000000000000000' from dual) ,
 spanfull( full)
 as ( select '111111111111111111111111' from dual)
, bookingbin( binbook) as ( select  substr(empt, 1, checkin) || 
        substr(full, checkin, checkout-checkin) || 
        substr(empt, checkout, 24-checkout) 
 from rec 
 cross join spanempty
 cross join spanfull ),
 bookingInt (rn, intbook) as 
 ( select rownum, bin2dec(binbook) from bookingbin),
 bitAndSum (bitAndSumm) as (
 select sum(bitand(b1.intbook, b2.intbook)) from bookingInt b1 
 join bookingInt b2 
 on b1.rn = b2.rn -1 ) ,
 SumAll (sumall) as (
 select sum(bin2dec(binbook)) from bookingBin  )
select lpad(dec2bin(sumall - bitAndSumm), 24, '0')
from SumAll, bitAndSum

Результат:

000000000000011111111110
10.01.2016
Новые материалы

Получение стоковых обновлений с помощью Python
Для начинающего финансового аналитика Введение Описание: Этот проект Python создает скрипт для получения текущих обновлений акций с финансового веб-сайта Yahoo. Для этого проекта мы..

Это все, что вам нужно знать о Kotlin в 2022 году
Добро пожаловать! Kotlin — это язык программирования, популярность которого, кажется, растет, его действительно можно использовать для создания чего угодно, и если вы хотите узнать о Kotlin,..

Текстовый графический интерфейс с Lanterna на Java
Мой опыт работы с компьютерами (и текстовыми графическими пользовательскими интерфейсами) начался еще в восьмидесятых, когда я был ребенком, на дне рождения друга. Это был «новенький» Amstrad..

Перезарядите свой мозг: умопомрачительный потенциал мозговых компьютерных интерфейсов
Способность читать свои мысли и управлять объектами разумом долгое время были предметом человеческого любопытства, ограниченного областью научной фантастики… то есть до сих пор? С технологией,..

Основы C# — Нулевой оператор объединения (??)
Оператор ?? называется null-coalescing operator . Этот оператор используется для предоставления значения по умолчанию, если значение операнда в левой части оператора равно null ...

Сравнение номеров версий в C++ с использованием синтаксического анализа строк
Номера версий обычно используются для обозначения развития или обновлений программного обеспечения или любого другого продукта. При работе с номерами версий в C++ может быть полезно сравнить две..

В мир искусственного интеллекта…
ИИ — это новое топливо в современном мире. Куда бы вы ни обратились, с кем бы вы ни разговаривали — они, как правило, упоминают об ИИ хотя бы раз в ходе разговора. ИИ гудит повсюду. У каждого..