Иерархические запросы START WITH с поведением предложения where

Я наткнулся на запрос во время работы и не мог понять, как именно он работает. Что делает запрос, так это ищет всех родителей человека, который является его родителем сегодня.

Хитрость заключается в том, что у каждой родительско-дочерней связи есть срок, в течение которого она действительна.

Возьмите этот набор данных в качестве эталона:

GrandParent является родителем Отца с 01-01-2012 по 02-02-2015

Отец является родителем ребенка с 01.01.2012 по 02.02.2011.

Ребенок - это просто человек самого низкого уровня

NewFather является родителем ребенка с 01 января 2012 г. по 02 февраля 2014 г.

теперь список родителей, действительных на сегодняшний день для Child, должен состоять только из NewFather

чтобы получить список, ранее мы использовали этот SQL:

SELECT connect_by_root per_id2 AS per_id2,
       per_id1,
       LEVEL                   AS per_level,
       n.entity_name
FROM   ci_per_per pp,
       ci_per_name N
WHERE  N.per_id = per_id1
       AND start_dt <= SYSDATE
       AND ( end_dt IS NULL
              OR end_dt >= SYSDATE )
START WITH per_id2 = :personID
CONNECT BY NOCYCLE PRIOR per_id1 = per_id2;

где personID — связанная переменная

этот запрос не работал, потому что поведение предложения where таково, что оно сначала получает все записи, а затем проверяет условия отсутствия соединения (проверки даты начала и даты окончания). Это приводит к тому, что он дает список родителей как NewFather, GrandParent, что совершенно НЕПРАВИЛЬНО!

Таким образом, запрос был изменен на следующее:

SELECT connect_by_root per_id2 AS per_id2,
       per_id1,
       LEVEL                   AS per_level,
       n.entity_name
FROM   ci_per_per pp,
       ci_per_name N
WHERE  N.per_id = per_id1
       AND start_dt <= SYSDATE
       AND ( end_dt IS NULL
              OR end_dt >= SYSDATE )
START WITH per_id2 = (SELECT per_id
                      FROM   ci_acct_per
                      WHERE  per_id = :personID
                             AND pp.start_dt <= SYSDATE
                             AND ( pp.end_dt IS NULL
                                    OR pp.end_dt >= SYSDATE ))
CONNECT BY NOCYCLE PRIOR per_id1 = per_id2;

Теперь то, что я не понимаю, это:

как может условие where в предложении start with влиять на поведение запроса таким образом?

Еще одна вещь, которая мне не нравится в этом запросе, заключается в том, что он использует совершенно не связанную таблицу с именем ci_acct_per, в которой просто есть столбец per_id для каждого человека в ci_per_per.

Можем ли мы сделать лучше? Доступен ли более чистый подход для исправления исходного запроса?

ОБНОВЛЕНИЕ

Этот запрос работает, только если мы путешествуем выше по иерархии, а не если мы ищем детей. Однако этот запрос никогда не ищет потомков и не должен этого делать.


person MozenRath    schedule 14.02.2013    source источник


Ответы (2)


Я не уверен, что правильно вас понял, но почему бы и нет:

SELECT connect_by_root per_id2 AS per_id2,
       pp.per_id1,
       LEVEL                   AS per_level,
       n.entity_name
FROM   (select * 
        from ci_per_per
       where start_dt <= SYSDATE
       AND ( end_dt IS NULL
              OR end_dt >= SYSDATE )) pp join
       ci_per_name N on N.per_id = pp.per_id1         
START WITH per_id2 = 1
CONNECT BY NOCYCLE PRIOR pp.per_id1 = pp.per_id2;

Вот демонстрация sqlfiddle


Обновление Благодаря @user1395 примеру:

Трудно объяснить, как работает этот странный запрос, потому что он не...

Что действительно происходит, так это то, что предложение START WITH использует per_id2, который является «отцом» столбца, поэтому, если их несколько (один не имеет отношения к sysdate), вам все равно нужно не начать с него .
Другими словами, оно начинается не с "ребенка", а с "детских" отцов - "отец" и "новыйотец".

Итак, либо используйте @user1395 предложение иметь логику даты как в предложении connect by, чтобы остановить, когда отец не имеет значения, так и в предложении start with, чтобы сделать доступным только соответствующего отца, или удалить всех нерелевантных отцов в первую очередь (как в моем прежнее предложение) или «начать с» «ребенка», а не его отцов:

select * from (
SELECT connect_by_root per_id1 AS per_id2,
       per_id1,
       LEVEL                   AS per_level,
       n.entity_name
FROM   ci_per_per pp,
       ci_per_name N
WHERE  N.per_id = per_id1       
START WITH per_id1 = 1
CONNECT BY NOCYCLE PRIOR per_id1 = per_id2 AND start_dt <= SYSDATE
       AND ( end_dt IS NULL
              OR end_dt >= SYSDATE ))
where per_id1 <> per_id2;

Еще одна демонстрация sqlfiddle

person A.B.Cade    schedule 14.02.2013
comment
теперь я помню, что предлагал что-то подобное человеку, который написал новый запрос, прежде чем он его написал, а затем он придумал этот странный запрос, но я все это забыл! Тем не менее, я все равно хотел бы знать, как работает его запрос! - person MozenRath; 14.02.2013
comment
Понятия не имею... это еще более странно - это также работает sqlfiddle.com/#! 4/e9e68/10 - person A.B.Cade; 14.02.2013
comment
хахахаха! Я предполагаю, что реализация пункта where для основного sql ошибочна! Или подождите в соответствии с приведенным выше ответом, возможно, так реализовано START WITH! - person MozenRath; 14.02.2013

Вы можете использовать логику даты внутри предложения connect by.

SELECT connect_by_root per_id2 AS per_id2,
       per_id1,
       LEVEL                   AS per_level,
       n.entity_name
FROM   ci_per_per pp,
       ci_per_name N
WHERE  N.per_id = per_id1
START WITH per_id2 = :personID AND 
                         SYSDATE BETWEEN start_dt AND NVL(end_dt,SYSDATE)
CONNECT BY NOCYCLE PRIOR per_id1 = per_id2 AND 
                         SYSDATE BETWEEN start_dt AND NVL(end_dt,SYSDATE);

Это остановит восхождение, когда на текущую дату нет действительных родителей.

person user1395    schedule 14.02.2013
comment
на самом деле мне нужны все столбцы, которые я использую в своем запросе, просто ошибка влияет только на столбцы, которые вы предложили - person MozenRath; 14.02.2013
comment
более того, ваш подход работает только для 1 уровня иерархии, а не для многоуровневого!!! - person MozenRath; 14.02.2013
comment
Это то, что я думал, что ты хотел. Меня смутили ваши утверждения: запрос ищет всех родителей человека, который сегодня является его родителем. Теперь список родителей, действительный сегодня для Child, должен состоять только из NewFather. Это приводит к тому, что он дает список родителей как NewFather, GrandParent, что совершенно НЕПРАВИЛЬНО! - Я почему-то думал, что GrandParent был родителем NewFather, извините. - person user1395; 14.02.2013
comment
не парься братан! просто измените свой ответ соответствующим образом или удалите свой ответ ради сайта :) - person MozenRath; 14.02.2013
comment
@ user1395, вы не использовали логику даты в предложении connect by, но в предложении start with точно так же, как в моем странном примере. Обратите внимание: если вы удалите логику из предложения start with, вы получите неправильные результаты... sqlfiddle.com/#!4/e9e68/42 - person A.B.Cade; 14.02.2013
comment
@A.B.Cade Спасибо, я видел, что вы сделали, и также добавил предложение «начать с» - я думаю, что логика даты в моем примере необходима как в «начать с», так и в «подключиться», если есть три уровня и нет действительной связи даты на втором уровне. Я изменил ваш пример sqlfiddle (дедушка является родителем нового отца и имеет своего собственного родителя, но дедушка-новый отец не является допустимой датой), см. http://www.sqlfiddle.com/#!4/d3973/1 - person user1395; 14.02.2013
comment
@ user1395, мммм ... теперь это все объясняет ... странный запрос в OP тоже не работает! теперь это имеет смысл - person A.B.Cade; 14.02.2013