#sql #oracle #hierarchical-data #gaps-and-islands
#sql #Oracle #иерархические данные #пробелы и острова
Вопрос:
У меня есть иерархические данные, которые связывают экземпляры объекта с помощью DATE_FROM
и DATE_TO
.
Пожалуйста, смотрите sqlfiddle.
Используя CONNECT_BY
, я могу определить количество смежных экземпляров для каждого объекта, т. Е. Длину «островов», что в основном то, что я хочу. Например, это дает ожидаемые длины островов для каждого объекта с DATE_FROM
в 2014 году:
-- QUERY 1
SELECT
T.ENTITY_ID,
MAX(LEVEL) MAX_LEVEL
FROM TEST T
WHERE EXTRACT(YEAR FROM T.DATE_FROM) = 2014
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_FROM = PRIOR T.DATE_TO
GROUP BY T.ENTITY_ID
Однако то, что я хотел бы сделать, это подсчитать строки на островах, где DATE_FROM
и DATE_TO
занимают некоторое минимальное количество дней. Я не хочу нарушать иерархию острова, когда делаю это.
Итак, я попробовал это, но это неправильно. Результаты не всегда соответствуют тому, что я ищу.
-- QUERY 2
SELECT
T.ENTITY_ID,
MAX(LEVEL) MAX_LEVEL,
SUM(
CASE WHEN PRIOR T.DATE_TO - PRIOR T.DATE_FROM > 183
THEN 1
ELSE 0
END
) LONG_TERM_COUNT
FROM TEST T
WHERE EXTRACT(YEAR FROM T.DATE_FROM) = 2014
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_FROM = PRIOR T.DATE_TO
GROUP BY T.ENTITY_ID
Что дает
----------- ----------- -----------------
| ENTITY_ID | MAX_LEVEL | LONG_TERM_COUNT |
----------- ----------- -----------------
| 1 | 4 | 3 |
| 2 | 5 | 4 |
----------- ----------- -----------------
но я ищу
----------- ----------- -----------------
| ENTITY_ID | MAX_LEVEL | LONG_TERM_COUNT |
----------- ----------- -----------------
| 1 | 4 | 4 |
| 2 | 5 | 4 |
----------- ----------- -----------------
Мне нужно решение Oracle. Спасибо за чтение.
Комментарии:
1. Я добавляю вознаграждение за это. Если какие-либо требования неясны, пожалуйста, дайте мне знать.
Ответ №1:
Условие WHERE вычисляется после ПОДКЛЮЧЕНИЯ BY, поэтому ваш запрос не начинается со строк из 2014. Он создает иерархию для каждой строки в вашей таблице, вы можете легко увидеть это, удалив WHERE и агрегацию:
SELECT
T.ENTITY_ID,
LEVEL,
T.DATE_TO,
T.DATE_FROM,
prior T.DATE_TO,
prior T.DATE_FROM
FROM TEST T
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_TO = PRIOR T.DATE_FROM
order by 1,2
Вам нужно использовать START WITH вместо WHERE-condition:
SELECT
T.ENTITY_ID,
LEVEL,
T.DATE_TO,
T.DATE_FROM,
prior T.DATE_TO,
prior T.DATE_FROM
FROM TEST T
START WITH EXTRACT(YEAR FROM T.DATE_FROM) = 2014
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_TO = PRIOR T.DATE_FROM
Итак, наконец, это:
SELECT
T.ENTITY_ID,
MAX(LEVEL) MAX_LEVEL, -- or COUNT(*)
SUM(
CASE WHEN T.DATE_TO - T.DATE_FROM > 183
THEN 1
ELSE 0
END
) LONG_TERM_COUNT
FROM TEST T
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_TO = PRIOR T.DATE_FROM
START WITH EXTRACT(YEAR FROM T.DATE_FROM) = 2014
GROUP BY T.ENTITY_ID
И вы можете получить неверный результат, если существуют две строки в 2014 году, поэтому вам нужно начать с последней строки в 2014 году:
SELECT
T.ENTITY_ID,
MAX(LEVEL) MAX_LEVEL,
SUM(
CASE WHEN T.DATE_TO - T.DATE_FROM > 183
THEN 1
ELSE 0
END
) LONG_TERM_COUNT
FROM TEST T
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_TO = PRIOR T.DATE_FROM
START WITH T.DATE_FROM =
(
SELECT MAX(T2.DATE_FROM)
FROM TEST T2
WHERE T.ENTITY_ID = T2.ENTITY_ID
AND T2.DATE_FROM >= DATE '2014-01-01'
AND T2.DATE_FROM <= DATE '2014-12-31'
)
GROUP BY T.ENTITY_ID
Ответ №2:
Ваш оператор sql верен. Но один сценарий, который необходимо учитывать, когда CASE WHEN T.DATE_TO - PRIOR T.DATE_FROM > 183
оператор становится null
, который не будет учитываться.
INSERT INTO TEST
VALUES (1,TO_DATE('20130101','YYYYMMDD'),TO_DATE('20140101','YYYYMMDD'));
INSERT INTO TEST
VALUES (1,TO_DATE('20140101','YYYYMMDD'),TO_DATE('20150101','YYYYMMDD'));
Из вашего примера данных, эквивалентного случаю:
CASE WHEN
TO_DATE('20140101','YYYYMMDD') - PRIOR TO_DATE('20140101','YYYYMMDD') > 183
Это дает null
значение;
Ответ №3:
Я не очень хорошо знаком с Oracle, но хорошим подходом могло бы быть использование RANK
Aggregate. Например:
SELECT
T.ENTITY_ID,
T.DATE_FROM,
RANK() OVER (PARTITION BY ENTITY_ID
ORDER BY T.DATE_TO DESC) "Rank"
FROM TEST T
WHERE EXTRACT(YEAR FROM T.DATE_FROM) <= 2014
Объединение T.ENTITY_ID = Prior T.ENTITY_ID
И Rank = (PRIOR.Rank 1)
может привести к решению. Как я уже сказал, это всего лишь предложение, как подойти.
Я попробовал немного больше, и это мое решение с подзапросом SQL Fiddle
SELECT
T.ENTITY_ID,
MAX(LEVEL) MAX_LEVEL,
(Select MAX("Rank") FROM
(
SELECT T2.ENTITY_ID AS ID, RANK() OVER (PARTITION BY T2.ENTITY_ID
ORDER BY T2.DATE_TO DESC) "Rank"
FROM TEST T2
WHERE EXTRACT(YEAR FROM T2.DATE_FROM) < 2014
) SubQ
WHERE ID = T.ENTITY_ID
) "LONG_TERM_COUNT"
FROM TEST T
WHERE EXTRACT(YEAR FROM T.DATE_FROM) = 2014
CONNECT BY
T.ENTITY_ID = PRIOR T.ENTITY_ID
AND T.DATE_FROM = PRIOR T.DATE_TO
GROUP BY T.ENTITY_ID
Комментарии:
1. Спасибо за ваше предложение, но я не думаю, что это действительно работает. Смотрите мой исправленный sqlfiddle , где я добавил одну строку в объект 1. Результат теперь неверен.