Как выбрать даты из двойных, но с объединенными данными? - PullRequest
7 голосов
/ 07 февраля 2020

У меня проблема SQL, которую я не способен решить.

Прежде всего, скрипка с SQL: http://sqlfiddle.com/#! 4 / fe7b07 / 2

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

I should have been an painter, not database admin

Временные шкалы, охватывающие 2020-01-01 до 2020-01-31, блоки являются датами в базе данных. Так что это будет простой вывод SELECT * FROM days.

Теперь я хочу заполнить этот вывод несколькими днями. Они будут варьироваться от timeline_begin до MIN(date_from); и от MAX(date_from) до timeline_end.

Я отмечу эти красные на следующем рисунке:

enter image description here

Оранжевый span тоже не нужно добавлять, но если ваше решение тоже это сделает, это тоже будет хорошо.

Хорошо, пока все хорошо.

Для этого я создал SELECT * FROM minmax, который выберет MIN(date_from) и MAX(date_from) для каждого id_othertable. Маги c по-прежнему не задействованы.

Я борюсь за то, чтобы создавать эти дни для каждого id_othertable, а также объединять данные, которые у них есть (в этой скрипке это просто поле some_info) .

Я пытался написать это в запросе SELECT * FROM days_before, но я просто не могу заставить его работать. Я читал о магической функции CONNECT BY, которая будет построчно создавать даты, но не могу соединить свои данные из предыдущей таблицы. Каждый раз, когда я присоединяюсь к информации, я получаю только одну строку за id_othertable, а не все те даты, которые мне нужны.

Таким образом, идеальное решение, которое я ищу, это иметь три выборочные запросы:

  1. SELECT * FROM days, которые выбирают даты из базы данных
  2. SELECT * FROM days_before, которые будут показывать даты до MIN(date_from) запроса 1
  3. SELECT * FROM days_after для дат после MAX(date_from) запроса 1

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

Я надеюсь, что смогу объяснить моя проблема достаточно хороша Если вам нужна какая-либо информация или дальнейшие объяснения, пожалуйста, не стесняйтесь спрашивать.


РЕДАКТИРОВАТЬ 1: я создал пастинный с некоторыми примерами данных: https://pastebin.com/jskrStpZ

Имейте в виду, что только первый запрос содержит актуальную информацию из базы данных, остальные два создали данные. Кроме того, этот пример выходных данных содержит данные только для id_othertable = 1, поэтому фактический запрос также должен содержать информацию для id_othertable = 2, 3.

EDIT 2: просто для пояснения, поле date_to является простым date_from + 1 day.

Ответы [ 3 ]

4 голосов
/ 07 февраля 2020

Если у вас есть денормализованная дата, это довольно просто:

with bas as (
    select 1 id_other_table, to_date('2020-01-05', 'YYYY-MM-DD') date_from, to_date('2020-01-06', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 1 id_other_table, to_date('2020-01-06', 'YYYY-MM-DD') date_from, to_date('2020-01-07', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 1 id_other_table, to_date('2020-01-07', 'YYYY-MM-DD') date_from, to_date('2020-01-08', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 1 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 1 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 1 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
    union all select 2 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'my' some_info from dual
    union all select 2 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'my' some_info from dual
    union all select 2 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'my' some_info from dual
    union all select 3 id_other_table, to_date('2020-01-20', 'YYYY-MM-DD') date_from, to_date('2020-01-21', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
    union all select 3 id_other_table, to_date('2020-01-21', 'YYYY-MM-DD') date_from, to_date('2020-01-22', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
    union all select 3 id_other_table, to_date('2020-01-22', 'YYYY-MM-DD') date_from, to_date('2020-01-23', 'YYYY-MM-DD') date_to, 'friend' some_info from dual)
, ad as (select trunc(sysdate,'YYYY') -1 + level all_dates from dual connect by level <= 31)
select distinct some_info,all_dates from bas,ad where (some_info,all_dates) not in (select some_info,date_from from bas)

Если у вас есть более длинные диапазоны дат или вы считаете, что время, когда запросу требуется другое решение, будет полезным. Но это сложнее отладить. Потому что довольно сложно получить оранжевый временной интервал

2 голосов
/ 07 февраля 2020

Если вы хотите, чтобы даты за id отсутствовали в базе данных, вы можете использовать функцию LEAD analyti c:

WITH dates ( id, date_from, date_to ) AS (
  SELECT id_othertable,
         DATE '2020-01-01',
         MIN( date_from )
  FROM   some_dates
  WHERE  date_to > DATE '2020-01-01'
  AND    date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
  GROUP BY id_othertable
UNION ALL
  SELECT id_othertable,
         date_to,
         LEAD( date_from, 1, ADD_MONTHS( DATE '2020-01-01', 1 ) )
           OVER ( PARTITION BY id_othertable ORDER BY date_from )
  FROM   some_dates
  WHERE  date_to > DATE '2020-01-01'
  AND    date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
)
SELECT id,
       date_from,
       date_to
FROM   dates
WHERE  date_from < date_to
ORDER BY id, date_from;

, поэтому для тестовых данных:

CREATE TABLE some_dates ( id_othertable, date_from, date_to, some_info ) AS
SELECT 1, DATE '2020-01-05', DATE '2020-01-06', 'hello1' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-06', DATE '2020-01-07', 'hello2' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-07', DATE '2020-01-08', 'hello3' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-10', DATE '2020-01-13', 'hello4' FROM DUAL UNION ALL
SELECT 2, DATE '2020-01-10', DATE '2020-01-13', 'my'     FROM DUAL UNION ALL
SELECT 3, DATE '2020-01-20', DATE '2020-01-23', 'friend' FROM DUAL UNION ALL
SELECT 4, DATE '2019-12-31', DATE '2020-01-05', 'before' FROM DUAL UNION ALL
SELECT 4, DATE '2020-01-30', DATE '2020-02-02', 'after'  FROM DUAL UNION ALL
SELECT 5, DATE '2019-12-31', DATE '2020-01-10', 'only_before' FROM DUAL UNION ALL
SELECT 6, DATE '2020-01-15', DATE '2020-02-01', 'only_after'  FROM DUAL UNION ALL
SELECT 7, DATE '2019-12-31', DATE '2020-02-01', 'exlude_all'  FROM DUAL;

это выводит:

ID | DATE_FROM  | DATE_TO   
-: | :--------- | :---------
 1 | 2020-01-01 | 2020-01-05
 1 | 2020-01-08 | 2020-01-10
 1 | 2020-01-13 | 2020-02-01
 2 | 2020-01-01 | 2020-01-10
 2 | 2020-01-13 | 2020-02-01
 3 | 2020-01-01 | 2020-01-20
 3 | 2020-01-23 | 2020-02-01
 4 | 2020-01-05 | 2020-01-30
 5 | 2020-01-10 | 2020-02-01
 6 | 2020-01-01 | 2020-01-15

дБ <> Fiddle здесь

Если вы хотите затем выберите следующие дни:

WHERE day_from = DATE '2020-01-01'

и, аналогично, если вы хотите, чтобы дни после этого включили фильтр:

WHERE day_to = ADD_MONTHS( DATE '2020-01-01', 1 )

Если вы хотите указать дату и номер начала месяцев, затем используйте именованные параметры привязки:

WITH dates ( id, date_from, date_to ) AS (
  SELECT id_othertable,
         :start_date,
         MIN( date_from )
  FROM   some_dates
  WHERE  date_to > :start_date
  AND    date_from < ADD_MONTHS( :start_date, :number_months )
  GROUP BY id_othertable
UNION ALL
  SELECT id_othertable,
         date_to,
         LEAD( date_from, 1, ADD_MONTHS( :start_date, :number_months ) )
           OVER ( PARTITION BY id_othertable ORDER BY date_from )
  FROM   some_dates
  WHERE  date_to > :start_date
  AND    date_from < ADD_MONTHS( :start_date, :number_months )
)
SELECT id,
       date_from,
       date_to
FROM   dates
WHERE  date_from < date_to
ORDER BY id, date_from;
2 голосов
/ 07 февраля 2020

Выберите весь диапазон, используя генератор connect by. Присоединяйтесь к вашему столу, разделенному по id.

select date_from, nvl(date_to, date_from +1) date_to, id_othertable, some_info
  from (
    select date '2020-01-01' + level - 1 as date_from 
      from dual 
      connect by level <= date '2020-01-31' - date '2020-01-01' ) gen 
  natural left join some_dates partition by (id_othertable) 

sqlfiddle

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...