Oracle Self-Join для нескольких возможных совпадений столбцов - CONNECT BY?

У меня есть запрос от ----. Пытаюсь решить это с помощью CONNECT BY, но не могу получить нужные мне результаты.


Таблица (упрощенная):

create table CSS.USER_DESC (
    USER_ID          VARCHAR2(30) not null,
    NEW_USER_ID      VARCHAR2(30),
    GLOBAL_HR_ID     CHAR(8)
)

-- USER_ID       is the primary key
-- NEW_USER_ID   is a self-referencing key
-- GLOBAL_HR_ID  is an ID field from another system

Есть два источника пользовательских данных (фиды данных) ... Я должен следить за ошибками в любом из них при обновлении информации.


Сценарии:

  1. Пользователю дается новый идентификатор пользователя ... Старая запись устанавливается соответствующим образом и деактивируется (обычно это переименование для подрядчиков, которые переходят на полный рабочий день)
  2. Пользователь уходит и возвращается через некоторое время. HR не может отправить нам старый идентификатор пользователя, чтобы мы могли подключить учетные записи.
  3. Система облажалась и не установила новый идентификатор пользователя на старую запись.
  4. Данные могут быть плохими по сотне других причин


Мне нужно знать, что это один и тот же пользователь, и я не могу полагаться на имя или другие поля ... они различаются между совпадающими записями:

ROOTUSER    NUMROOTS   NODELEVEL  ISLEAF    USER_ID    NEW_USER_ID  GLOBAL_HR_ID   USERTYPE      LAST_NAME         FIRST_NAME  
-----------------------------------------------------------------------------------------------------------------------------
EX0T1100    2          1          0         EX0T1100   EX000005                    CONTRACTOR    VON DER HAAVEN    VERONICA    
EX0T1100    2          2          1         EX000005                00126121       EMPLOYEE      HAAVEN, VON DER   VERONICA    
GL110456    1          1          1         GL110456                00126121       EMPLOYEE      VONDERHAAVEN      VERONICA    


EXOT1100 и EX000005 правильно соединены полем NEW_USER_ID. Переименование произошло до того, как появились глобальные идентификаторы HR, поэтому у EX0T1100 его нет. EX000005 был назначен новый идентификатор пользователя, «GL110456», и эти два связаны только с одним и тем же глобальным идентификатором HR.

Очистка данных - это не вариант.


Запрос на данный момент:

select connect_by_root cud.user_id RootUser, 
       count(connect_by_root cud.user_id) over (partition by connect_by_root cud.user_id) NumRoots, 
       level NodeLevel, connect_by_isleaf IsLeaf, --connect_by_iscycle IsCycle,
       cud.user_id, cud.new_user_id, cud.global_hr_id,
       cud.user_type_code UserType, ccud.last_name, cud.first_name
from   css.user_desc cud
where  cud.user_id in ('EX000005','EX0T1100','GL110456')
-- Using this so I don't get sub-users in my list of root users...
-- It complicates the matches with GLOBAL_HR_ID, however
start with cud.user_id not in (select cudsub.new_user_id 
                               from   css.user_desc cudsub 
                               where  cudsub.new_user_id is not null)
connect by nocycle (prior new_user_id = user_id);


Я пробовал разные CONNECT BY предложения, но ни один из них не совсем правильный:

-- As a multiple CONNECT BY
connect by nocycle (prior global_hr_id = global_hr_id)
connect by nocycle (prior new_user_id  = user_id)

-- As a compound CONNECT BY
connect by nocycle ((prior new_user_id = user_id)
                     or (prior global_hr_id = global_hr_id 
                         and user_id != prior user_Id))


Объединение двух запросов CONNECT BY не работает ... Я не понимаю выравнивания.

Вот то, что я хотел бы увидеть ... Меня устраивает набор результатов, который я должен отделить и использовать в качестве подзапроса. Я также согласен с любым из трех идентификаторов пользователей в столбце ROOTUSER ... Мне просто нужно знать, что это одни и те же пользователи.

ROOTUSER    NUMROOTS   NODELEVEL  ISLEAF    USER_ID    NEW_USER_ID  GLOBAL_HR_ID   USERTYPE      LAST_NAME         FIRST_NAME  
-----------------------------------------------------------------------------------------------------------------------------
EX0T1100    3          1          0         EX0T1100   EX000005                    CONTRACTOR    VON DER HAAVEN    VERONICA    
EX0T1100    3          2          1         EX000005                00126121       EMPLOYEE      HAAVEN, VON DER   VERONICA    
EX0T1100    3         (2 or 3)    1         GL110456                00126121       EMPLOYEE      VONDERHAAVEN      VERONICA    


Идеи?


Обновить

Николас, ваш код очень похож на правильный трек ... в настоящий момент lead(user_id) over (partition by global_hr_id) получает ложные срабатывания, когда global_hr_id равно нулю. Например:

USER_ID   NEW_USER_ID   CHAINNEWUSER   GLOBAL_HR_ID   LAST_NAME   FIRST_NAME
FP004468                FP004469                      AARON       TIMOTHY
FP004469                                              FOONG       KOK WAH

Мне часто хотелось рассматривать нули как отдельные записи в разделе, но я никогда не находил способа заставить ignore nulls работать. Это сделал то, что я хотел:

decode(global_hr_id,null,null,lead(cud.user_id ignore nulls) over (partition by global_hr_id order by user_id)

... но должен быть способ получше. Мне еще не удалось завершить запрос на полномасштабных пользовательских данных (около 40 000 пользователей). И global_hr_id, и new_user_id проиндексированы.


Обновить

Запрос возвращается примерно через 750 секунд ... длинный, но управляемый. Он возвращает 93 тыс. Записей, потому что у меня нет хорошего способа отфильтровать попадания уровня 2 из корня - у вас есть start with global_hr_id is null, но, к сожалению, это не всегда так. Мне нужно еще подумать о том, как их отфильтровать.

Раньше я пробовал добавлять более сложные предложения начала с предложениями, но обнаружил, что по отдельности они выполняются ‹1 секунду ... вместе они занимают 90 минут>.‹

Еще раз спасибо за вашу помощь ...


person James King    schedule 14.10.2012    source источник


Ответы (1)


Вы предоставили образец данных только для одного пользователя. Лучше было бы немного больше. В любом случае, давайте посмотрим на что-то вроде этого.

SQL> with user_desc(USER_ID, NEW_USER_ID,  GLOBAL_HR_ID)as(
  2    select 'EX0T1100',  'EX000005',  null          from dual union all
  3    select 'EX000005',   null,       00126121      from dual union all
  4    select 'GL110456',   null,       00126121      from dual
  5  )
  6  select connect_by_root(user_id) rootuser
  7       , count(connect_by_root(user_id)) over(partition by connect_by_root(user_id)) numroot
  8       , level nodlevel
  9       , connect_by_isleaf
 10       , user_id
 11       , new_user_id
 12       , global_hr_id
 13    from (select user_id
 14               , coalesce(new_user_id, usr) new_user_id1
 15               , new_user_id
 16               , global_hr_id
 17            from ( select user_id
 18                        , new_user_id
 19                        , global_hr_id
 20                        , decode(global_hr_id,null,null,lead(user_id) over (partition by global_hr_id order by user_id)) usr
 21                    from user_desc
 22                 )
 23         )
 24  start with global_hr_id is null
 25  connect by prior new_user_id1 = user_id
 26  ;

Результат:

ROOTUSER    NUMROOT   NODLEVEL CONNECT_BY_ISLEAF USER_ID  NEW_USER_ID GLOBAL_HR_ID
-------- ---------- ---------- ----------------- -------- ----------- ------------
EX0T1100          3          1                 0 EX0T1100 EX000005    
EX0T1100          3          2                 0 EX000005                   126121
EX0T1100          3          3                 1 GL110456                   126121
person Nick Krasnov    schedule 15.10.2012
comment
Ого. Я так недостоин. Я играю с этим сейчас ... У меня заканчивается временное табличное пространство, которое я пытаюсь выполнить, но я думаю, что могу повозиться с этим, чтобы заставить его работать. Дам вам знать, спасибо! - person James King; 15.10.2012
comment
PS: Трудно придумать полностью репрезентативный набор данных ... если я смогу заставить это дело работать, я думаю, что большинство других подпадут под эту линию. Проверим обязательно. - person James King; 15.10.2012
comment
Разместил обновление с дополнительной информацией ... продолжаю играть с тем, что вы мне дали. - person James King; 15.10.2012
comment
Я не знаю, опередил ли я вас в этом или наоборот :) Но я считаю, что ваш ответ означает, что на самом деле нет лучшего способа. - person James King; 15.10.2012
comment
Я добавил сообщение о награждении после принятия, потому что этот ответ был чрезвычайно полезным и поучительным. Узнал кучу новых трюков Oracle :) Спасибо! - person James King; 19.10.2012