2008-09-23 15 views
12

Próbuję uruchomić następujące polecenie SQL w Oracle, a to trwa wieki, aby uruchomić:Optymalizacja kwerendę wybierającą, która działa powoli na Oracle, która biegnie szybko na SQL Server

SELECT orderID FROM tasks WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL) 

Jeśli uruchomić tylko sub-część, która jest w klauzuli IN, który działa bardzo szybko w Oracle, czyli

SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

Dlaczego cała wypowiedź na taki długi czas w Oracle? W SQL Server cała instrukcja działa szybko.

Czy istnieje również prostsza/inna/lepsza instrukcja SQL, której powinienem użyć?

Nieco więcej szczegółów na temat problemu:

  • Każde zamówienie jest wykonana z wielu zadań
  • Każde zamówienie będzie alokowanych (jednego lub większej liczby jego zadania będzie musiał engineer1 i engineer2 ustawiony) lub zlecenie może być nieprzydzielone (wszystkie jego zadania mają wartości puste dla pól inżynierskich)
  • Próbuję znaleźć wszystkie identyfikatory zamówień, które są nieprzydzielone.

W przypadku, gdy robi różnicę, w tabeli jest ~ 120 tys. Wierszy, a 3 zadania na zamówienie, czyli ~ 40k różnych zamówień.

Odpowiedzi na odpowiedzi:

  • wolałbym SQL, który działa zarówno w SQL Server i Oracle.
  • Zadania zawierają tylko indeks w identyfikatorze zlecenia i identyfikatorze zadania.
  • Próbowałem wersję instrukcji NOT EXISTS, ale trwało to ponad 3 minuty, zanim ją anulowałem. Być może potrzebujesz wersji JOIN instrukcji?
  • Istnieje również tabela "zamówienia" z kolumną orderID. Ale próbowałem uprościć pytanie, nie dołączając go do oryginalnego oświadczenia SQL.

myślę, że w oryginalnym SQL sub-zapytania jest uruchamiany za każdym razem dla każdego wiersza w pierwszej części zestawienia SQL - mimo że jest statyczna i powinna wystarczy uruchomić raz?

Wykonywanie

ANALYZE TABLE tasks COMPUTE STATISTICS; 

wykonana moja oryginalna instrukcja SQL wykonać znacznie szybciej.

Chociaż nadal jestem ciekawy, dlaczego muszę to zrobić, i czy/kiedy będę musiał uruchomić go ponownie?

Statystyki dać kosztowego optymalizatora informacje Oracle że potrzebnych do określenia efektywności różnych planów wykonania: dla przykład liczba rowsin stół, średniej szerokości wierszy, najwyższa i najniższe wartości na kolumnę, liczba różnych wartości na kolumnę, klasterowanie współczynnik indeksów itp.

W niewielkiej bazie danych wystarczy ustawić pracę, aby gromadzić statystyki każdej nocy i pozostawiać ją w spokoju. W rzeczywistości jest to domyślnie poniżej 10g. W przypadku większych implementacji zwykle trzeba zmierzyć stabilność planów wykonania w odniesieniu do sposobu, w jaki zmieniają się dane , co jest trudnym zadaniem.

Oracle ma również funkcję o nazwie "dynamiczne próbkowanie", która jest używana do tabel próbek w celu ustalenia odpowiednich statystyk w czasie wykonywania. Jest on znacznie częściej wykorzystywany w magazynach danych, w których obciążenie związane z próbką jest większe od potencjalnego zwiększenia wydajności o dla długoterminowego zapytania o wartości .

+1

Nigdy nie zrozumiem, dlaczego programiści tak często umieszczają DISTINCT w swoich klauzulach IN. Czy 7 w (1, 1, 1, 1, 2, 2, 2, 7)? Czy 5? Odpowiedź nie zmienia się, jeśli moja lista jest (1, 2, 7). Kiedy uruchamiam to w Oracle, po prostu ignoruje wyraźne ... CBO zdaje sobie sprawę, że nie ma żadnej wartości. –

Odpowiedz

9

Często tego typu problem zniknie, jeśli analizować tabele zaangażowanych (tak Oracle ma lepszy pomysł na dystrybucję danych)

ANALYZE TABLE tasks COMPUTE STATISTICS; 
+0

Niewiarygodne, po uruchomieniu tego zajęło mi nieco mniej niż 1 sekundę, aby uruchomić moją oryginalną instrukcję SQL. – RickL

+5

To jest przestarzała składnia do zbierania statystyk. DBMS_STATS to bardziej niezawodny sposób. http://download.oracle.com/docs/cd/B19306_01/server.102/b14211/stats.htm#PFGRF30102 –

+3

Zgadzam się, to jest przestarzałe. Proszę unikać używania tego. Użyj DBMS_STATS i upewnij się, że dostajesz również swoje indeksy, możesz ustawić cascade => true, gdy zbierzesz statystyki dla tabeli. –

3

Klauzula "IN" jest znana w Oracle jako dość powolna. W rzeczywistości wewnętrzny optymalizator zapytań w Oracle nie radzi sobie z instrukcjami z "IN" całkiem dobrze. spróbuj użyć "EXISTS":

SELECT orderID FROM tasks WHERE orderID NOT EXISTS 
    (SELECT DISTINCT orderID FROM tasks WHERE 
     engineer1 IS NOT NULL AND engineer2 IS NOT NULL)`print("code sample");` 

Uwaga: Proszę sprawdzić, czy zapytanie tworzy takie same wyniki danych.

Edyta mówi: ooops, zapytanie nie jest dobrze sformułowane, ale ogólny pomysł jest poprawny. Oracle musi wykonać pełne skanowanie tabeli dla drugiej (wewnętrznej) kwerendy, zbudować wyniki, a następnie porównać je z pierwszym (zewnętrznym) zapytaniem, dlatego zwalnia. Spróbuj

SELECT orderID AS oid FROM tasks WHERE NOT EXISTS 
    (SELECT DISTINCT orderID AS oid2 FROM tasks WHERE 
     engineer1 IS NOT NULL AND engineer2 IS NOT NULL and oid=oid2) 

lub coś podobnego ;-)

+0

Przyszedłem do tego samego zapytania (zobacz moją odpowiedź poniżej), z wyjątkiem: * podzapytanie nie ma powodu, aby wybrać DISTINCT orderIDs. * upuść "orderID" pomiędzy WHERE i NOT EXISTS (błąd składniowy). * upuść 'print' ("próbka kodu"), oczywiście ;-) – Mac

+0

Kiedy próbuję drugiego zapytania, pojawia się błąd? ORA-00904: "OID2": nieprawidłowy identyfikator – RickL

+0

Użyj "AS oid2", który wstawiłem, nie było w zapytaniu wcześniej. – Georgi

0

nie jest zapytanie taka sama jak

SELECT orderID FROM tasks 
WHERE engineer1 IS NOT NULL OR engineer2 IS NOT NULL 

?

+0

Nie, nie jest. Zrobiłem ten sam błąd :-) Każde zamówienie ma wiele zadań, a jeśli do jednego z tych zadań przypisano inżyniera, zamówienie liczy się jako "przydzielone". –

0

Jak o:

SELECT DISTINCT orderID FROM tasks t1 WHERE NOT EXISTS (SELECT * FROM tasks t2 WHERE t2.orderID=t1.orderID AND (engineer1 IS NOT NULL OR engineer2 IS NOT NULL)); 

Nie jestem guru optymalizacji, ale może też pominąć niektóre indeksy w bazie danych Oracle.

+0

Próbowałem tego, ale nadal trwało ponad minutę później, kiedy anulowałem to. – RickL

0

Inną opcją jest użycie MINUS (z wyjątkiem MSSQL)

SELECT orderID FROM tasks 
MINUS 
SELECT DISTINCT orderID FROM tasks WHERE engineer1 IS NOT NULL 
AND engineer2 IS NOT NULL 
+0

Pomyślałem o tym również, ale myślę, że to zapytanie też nie jest najszybsze. – Georgi

+0

Tak, bądź ostrożny, instrukcja minus używa dużo pamięci. –

+0

Zwykle niejawny odrębny byłby niepotrzebny narzut, ale w tym przypadku prawdopodobnie byłby odpowiedni. Dwa skany stołu mogą być jednak nieskuteczne. –

-2

Sub-zapytań są "złe" z Oracle. Zasadniczo lepiej używać złączeń.

Oto artykuł na temat przepisywania podzapytania z przyłączyć: http://www.dba-oracle.com/sql/t_rewrite_subqueries_performance.htm

+0

Nic nie jest konieczne "złe". Różnica między zaletami różnych technik prawie zawsze sprowadza się do dystrybucji wartości określonego zestawu danych i obecności lub braku indeksów i ograniczeń. –

-1

Tutaj jest alternatywnym podejściem, które myślę, że daje to, co chcesz:

SELECT orderID 
FROM tasks 
GROUP BY orderID 
HAVING COUNT(engineer1) = 0 OR COUNT(engineer2) = 0 

Nie jestem pewien, czy chcesz "AND" lub "OR" w klauzuli HAVING. Wygląda na to, że zgodnie z logiką biznesową te dwa pola powinny być wypełnione albo oba mają wartość NULL; jeśli jest to gwarantowane, możesz zredukować ten stan do jedynie sprawdzenia inżyniera1.

Twoje pierwotne zapytanie powinno, jak sądzę, dać wiele wierszy na identyfikator zamówienia, podczas gdy moje da tylko jeden.Zgaduję, że to jest OK, ponieważ pobierasz tylko identyfikator orderID.

2

Niektóre pytania:

  • Ile wierszy są tam w zadaniach?
  • Jakie indeksy są na nim zdefiniowane?
  • Czy ostatnio przeanalizowano tabelę?

Innym sposobem, aby napisać tę samą kwerendę byłoby:

select orderid from tasks 
minus 
select orderid from tasks 
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

Jednak wolałbym oczekiwać zapytanie do obejmować "Zamówienia" tabeli:

select orderid from ORDERS 
minus 
select orderid from tasks 
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

lub

select orderid from ORDERS 
where orderid not in 
(select orderid from tasks 
    where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 
) 

lub

select orderid from ORDERS 
where not exists 
(select null from tasks 
    where tasks.orderid = orders.orderid 
    and engineer1 IS NOT NULL OR engineer2 IS NOT NULL 
) 
+0

Twoja druga propozycja to NIE to samo, ponieważ może istnieć wiele zadań na identyfikator zamówienia, niektóre są przypisane, a inne nie. Chce tylko identyfikatorów zleceń, dla których nie przypisano ŻADNEGO z zadań. –

+0

Twoja ostatnia propozycja będzie musiała mieć skorelowane podkwerendy (np. Dodaj "AND tasks.orderID = orders.orderID"). Nie możesz po prostu zmienić NOT IN na NOT EXISTS bez modyfikowania podkwerendy. –

+0

Oba punkty teraz poprawione –

2

Zgadzam się z TZQTZIO, nie otrzymuję zapytania.

Jeśli założymy, że zapytanie miało sens, możesz spróbować użyć EXISTS, ponieważ niektórzy sugerują i unikają IN. IN nie zawsze jest zły i są prawdopodobne przypadki, w których można wykazać, że faktycznie działa lepiej niż EXISTS.

Tytuł pytania nie jest bardzo pomocny. Mogę ustawić tę kwerendę w jednej bazie danych Oracle i sprawię, że będzie działać wolno i sprawi, że będzie działać szybko w innym. Istnieje wiele czynników określających, w jaki sposób baza danych rozwiązuje zapytanie, statystyki obiektów, statystyki schematu SYS i parametry, a także wydajność serwera. Sqlserver kontra Oracle nie jest tutaj problemem.

Dla osób zainteresowanych dostrajaniem zapytań i wydajnością oraz chcesz dowiedzieć się więcej, niektóre z wyszukiwanych terminów google to "dębowy stół oracle" i "oracle jonathan lewis".

3

chciałbym spróbować za pomocą łączy zamiast

SELECT 
    t.orderID 
FROM 
    tasks t 
    LEFT JOIN tasks t1 
     ON t.orderID = t1.orderID 
     AND t1.engineer1 IS NOT NULL 
     AND t1.engineer2 IS NOT NULL 
WHERE 
    t1.orderID IS NULL 

również oryginalny kwerendy prawdopodobnie będzie łatwiej zrozumieć, jeśli został określony jako:

SELECT orderID FROM orders WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL) 

(zakładając, że mają rozkaz tabelę z wszystkich zamówień na liście)

, które można następnie przepisać przy użyciu joins jako:

SELECT 
    o.orderID 
FROM 
    orders o 
    LEFT JOIN tasks t 
     ON o.orderID = t.orderID 
     AND t.engineer1 IS NOT NULL 
     AND t.engineer2 IS NOT NULL 
WHERE 
    t.orderID IS NULL 
+0

To nie działa, ponieważ powinno zwracać tylko zamówienia, w których * wszystkie * pola zadań inżynierskich nie są zerowe, ale spowoduje to zwrócenie zamówień, w których niektóre zadania są wykonywane przez inżyniera, a niektóre zadania nie są zerowe. Czy miałeś na myśli, że klauzula WHERE jest inna? – RickL

+0

To chyba zwraca ten sam wynik jak kwerendy, który został podany: SELECT idZamówienia OD zadań GDZIE NIE idZamówienia IN (SELECT DISTINCT idZamówienia FROM zadań GDZIE engineer1 IS NOT NULL AND engineer2 IS NOT NULL) które wierzę zwrotów: Wszystkie zlecenia, które nie mają żadnych zadań z obydwoma inżynierami przypisanymi – kristof

+0

Ale być może czegoś brakuje, jeśli jeden przydzielony inżynier nie wystarczy, aby wymienić zamówienie, następnie zmienić lewe sprzężenie, aby odczytać jako: LEWE DOŁĄCZ zadania T1 NA t.orderID = t1. orderID AND (t1.engineer1 NIE JEST NULL LUB t1.engineer2 NIE JEST NULL) Ale to by było inne od twojego pierwotnego zapytania. – kristof

-1

Jeśli nie masz indeksu nad kolumnami Inżynier1 i Inżynier2, zawsze będziesz generował skanowanie tabeli w programie SQL Server i to, co jest w Oracle.

Jeśli potrzebujesz tylko zleceń, które mają nieprzydzielone zadania, poniższe powinny działać dobrze na obu platformach, ale powinieneś rozważyć dodanie indeksów do tabeli zadań, aby poprawić wydajność zapytań.

SELECT DISTINCT orderID 
FROM tasks 
WHERE (engineer1 IS NULL OR engineer2 IS NULL) 
+0

Zazwyczaj pola inżyniera będą obcymi klawiszami, więc indeksy powinny tam być. – tzot

+0

@ ΤΖΩΤΖΙΟΥ Dzięki za głosowanie, ale czy przeczytałeś pytanie? "Zadania zawierają tylko indeks ID zlecenia i zadania" –

0

Jeśli zdecydujesz się utworzyć tabeli Zamówienia, chciałbym dodać flagę przydzielane do niej i utworzyć indeks bitmap.Podejście to zmusza również do modyfikacji logiki biznesowej w celu aktualizacji flagi, ale zapytania będą błyskawiczne. Zależy to od tego, jak krytyczne są zapytania dla aplikacji.

Jeśli chodzi o odpowiedzi, tym prostsze, tym lepiej. Zapomnij o podzapytaniach, sprzężeniach, oddzielnych i grupowych przecinkach, w ogóle nie są potrzebne!

1

Myślę, że kilka osób ma prawie prawidłowy kod SQL, ale brakuje mu połączenia pomiędzy zapytaniami wewnętrznymi i zewnętrznymi.
Spróbuj tego:

SELECT t1.orderID 
FROM tasks t1 
WHERE NOT EXISTS 
     (SELECT 1 
     FROM tasks t2 
     WHERE t2.orderID = t1.orderID 
     AND t2.engineer1 IS NOT NULL 
     AND t2.engineer2 IS NOT NULL) 
+0

Dzięki, spróbowałem tego i jest to poprawna składnia, ale nadal trwało ponad 3 minuty, kiedy ją anulowałem. – RickL

0

jaką część wiersze w tabeli spełniają warunek "inżynier1 NIE JEST NIŻEJ I inżynier2 NIE JEST NIŻE"?

To mówi (mniej więcej), czy warto spróbować użyć indeksu do pobrania powiązanych zamówień.

Innym sposobem napisać kwerendę w Oracle, które będzie obsługiwać niezindeksowane przypadków bardzo dobrze byłoby:

select distinct orderid 
from 
(
select orderid, 
     max(case when engineer1 is null and engineer2 is null then 0 else 1) 
      over (partition by orderid) 
      as max_null_finder 
from tasks 
) 
where max_null_finder = 0 
0

optymalizator Oracle ma dobrą pracę sprawozdań przetwarzanie MINUS. Jeśli ponownie napiszesz zapytanie za pomocą MINUS, prawdopodobnie uruchomi się dość szybko:

SELECT orderID FROM tasks 
MINUS 
SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL 
0

Nowe spojrzenie.

Iff:

  • z funkcji count() nie liczą się wartości NULL

i

  • Chcesz idZamówienia wszystkich zadań gdzie żaden zadania mają inżyniera1 lub inżyniera2 ustawiona na wartość

następnie to powinien robić to, co chcesz:

SELECT orderID 
FROM tasks 
GROUP BY orderID 
HAVING COUNT(engineer1) = 0 AND COUNT(engineer2) = 0 

Proszę przetestować.

1

"Chociaż nadal jestem ciekawy, dlaczego muszę to zrobić, i czy/kiedy będę musiał go uruchomić ponownie?"

Statystyki podają informacje optymalizacyjne oparte na kosztach firmy Oracle, niezbędne do określenia skuteczności różnych planów wykonania: na przykład liczba rzędów w tabeli, średnia szerokość wierszy, najwyższa i najniższa wartość na kolumnę, liczba odrębne wartości na kolumnę, współczynnik klastrowania indeksów itp.

W małej bazie danych można po prostu ustawić pracę, aby gromadzić statystyki każdej nocy i pozostawić ją w spokoju. W rzeczywistości jest to wartość domyślna poniżej 10g.W przypadku większych implementacji zazwyczaj trzeba wyważyć stabilność planów wykonania względem sposobu, w jaki zmieniają się dane, co jest trudnym zadaniem.

Oracle ma również funkcję nazywaną "próbkowaniem dynamicznym", która służy do próbkowania tabel w celu określenia odpowiednich statystyk w czasie wykonywania. Znacznie częściej używa się go w hurtowniach danych, w których obciążenie próbkowania przewyższa potencjalny wzrost wydajności w przypadku długotrwałych zapytań.

+0

Dzięki, skopiowałeś swoją odpowiedź na pytanie. – RickL

+0

Dave's na dynamicznym buforze do pobierania próbek –

+0

Uwielbiam dynamiczne próbkowanie. Nie wiem, że nie mówi się o tym częściej. –

0

zgadzam się z ΤΖΩΤΖΙΟΥ i wearejimbo że zapytanie powinno być ...

SELECT DISTINCT orderID FROM Tasks 
WHERE Engineer1 IS NULL OR Engineer2 IS NULL; 

nie wiem o SQL Server, ale ta kwerenda nie będzie mógł skorzystać z żadnych indeksów, ponieważ wartość null wiersze nie znajdują się w indeksach. Rozwiązaniem tego problemu byłoby ponowne napisanie zapytania w taki sposób, aby umożliwić utworzenie indeksu opartego na funkcjach, który obejmuje tylko wiersze o wartości pustej. Można to zrobić za pomocą NVL2, ale prawdopodobnie nie byłby przenośny dla SQL Server.

Uważam, że najlepszą odpowiedzią nie jest ta, która spełnia twoje kryteria, a to oznacza inne stwierdzenie dla każdej platformy, która jest najlepsza dla tej platformy.

+1

SQL Server zezwala tylko na jeden wiersz NULL w indeksie, z wyjątkiem tego, że tworzysz filtrowany indeks. W tym przypadku nie obejmuje wartości NULL. – usr

+0

Dzięki za informacje. –