2012-03-15 4 views
7

Zauważyłem dziwne zachowanie PEŁNEGO ZEWNĘTRZNEGO DOŁĄCZU w Oracle 11. Dołączyłem do tabel ze schematu HR, szczególnie PRACOWNICY i DEPARTAMENTY.Dziwne zachowanie pełnego połączenia zewnętrznego w Oracle - jak można to wyjaśnić?

Na przykład poniższa kwerenda zwraca 123 rzędów:

SELECT * FROM employees e 
    FULL JOIN departments d ON e.department_id = d.department_id 

Jednak to, co jest trudne do zrozumienia - gdy kładę zbiór poszczególnych kolumn w klauzuli SELECT, kwerenda zwróci 122 wiersze (a brakuje wiersz jest dla pracownika, który nie ma przypisanego dział - ten, który jest dodatkowo wrócił z lewej przyłączyć w porównaniu do sprzężenia wewnętrznego):

SELECT first_name, last_name, department_name FROM employees e 
    FULL JOIN departments d on e.department_id = d.department_id 

Nawet kiedy liczyć wierszy zwraca 122 (COUNT(*)) !!! CO SIĘ DZIEJE? Jaka jest różnica między SELECT * i SELECT COUNT(*)?

wyjaśniania plan SELECT * ...:

SELECT STATEMENT          122 
    VIEW     VW_FOJ_0      122 
    HASH JOIN       FULL OUTER  122 
     Access Predicates 
     E.DEPARTMENT_ID = D.DEPARTMENT_ID 
     TABLE ACCESS  DEPARTMENTS  FULL   27 
     TABLE ACCESS  EMPLOYEES  FULL   107 

i dla SELECT COUNT(*) ...:

SELECT STATEMENT            1 
    SORT          AGGREGATE   1 
    VIEW    VW_FOJ_0       122 
     HASH JOIN       FULL OUTER  122 
     Access Predicates 
      E.DEPARTMENT_ID = D.DEPARTMENT_ID 
     INDEX   DEPT_ID_PK   FAST FULL SCAN 27 
     INDEX   EMP_DEPARTMENT_IX FAST FULL SCAN 107 
+2

Co się dzieje, jeśli użyć 'unia dla tych kolumn ? czy otrzymasz coś innego, gdy używasz 'union all'?co otrzymasz, jeśli policzysz z 'group by first_name, last_name, department_name'? –

+0

'WYBIERZ * OD pracowników e Dział PEŁNA JOIN d na e.department_id = d.department_id' zwraca 123 wiersze i' SELECT count (*) OD pracowników e FULL JOIN departamentów d na e.department_id = d.department_id' zwraca 122 wiersze? –

+0

Tak, dokładnie - dlatego opublikowałem to pytanie. –

Odpowiedz

5

optymalizator nie powinien być wybór do użyj indeksu na EMP.DEPT_ID w drugim zapytaniu, ponieważ może on mieć wartości NULL. Właśnie to powoduje wykluczenie jednego wiersza z wyników.

Jedyne wyjaśnienie, którego nie potrafię sobie teraz wyobrazić, to to, że w jakiś sposób utworzyliście ograniczenia w trybie WYŁĄCZENIE NABYTU, aby optymalizator uważał, że pole nie może zawierać wartości NULL. W takim przypadku prawidłowe byłoby użycie indeksu z niepoprawnymi informacjami w wiązaniach. Jednak wygląda na to, że opcja RELY nie jest dostępna dla ograniczeń NOT NULL, więc nie widzę, jak może to być problem. Należy jednak uważnie przyjrzeć się wszystkim ograniczeniom na tabelach.

Poza tym na stronie Oracle występuje zaskakująco dużo błędów dotyczących złych wyników z pełnych zewnętrznych połączeń. Możesz uderzać w jednego z nich. W sporo takich przypadkach rozwiązaniem jest wyłączenie „native” pełna sprzężenia zewnętrzne, które możesz zrobić dla swojej bieżącej sesji z tym stwierdzeniem:

alter session set "_optimizer_native_full_outer_join"=off; 
+0

+1: wygląda na najbardziej wiarygodne wytłumaczenie. Ciekawe byłoby znać wersję DB. –

1

(nie mogę napisać to w komentarzu)

Wyniki upodobnić się do planów realizacyjnych.

Plan wykonania count (*) używa indeksu EMP_DEPARTMENT_IX, który zawiera wszystkie dept_ids z tabeli Employess. Ale indeksy nie zawierają wartości null. Tak więc ten plan wykonania "straci" emps z pustym id_ departamentu.

Jednakże, należy wyjaśnić, dlaczego Oracle wybrać ten plan wykonania w przypadku

select first_name, last_name, department_name 

i

select count(*) 

w opozycji do

select * 
+1

Tak, zgadzam się. Jest to jednak niespójność w mojej opinii. Pełne sprzężenie zewnętrzne nie działa tak jak powinno we wszystkich przypadkach z wyjątkiem wyboru *. –

+0

@luckyjaca Czy możesz zrobić więcej testów z 'select emp_id',' select first_name', 'wybierz e.department_id',' wybierz d.department_id', 'wybierz d.department_name', etc? –

+0

heh ... Testowałem tak, jak sugerowałeś. Niemniej jednak wszystkie z nich zwróciło 122 wiersze - jedynym sposobem uzyskania 123 wierszy jest zaznaczenie wszystkich kolumn za pomocą gwiazdek. W końcu udało mi się jednak zwrócić 123 wiersze za pomocą podpowiedzi: '/ * + no_index (e) * /'. Wystarczy, że wszystkie zapytania będą działać - albo "COUNT (*)" albo "first_name, last_name, department_name". –