To takie proste: zapytanie uruchamiane w ciągu kilku sekund w SQL Developer łączącym się z Oracle 11g zajmuje 15-25 minut w SSRS 2008 R2. Nie próbowałem innych wersji SSRS. Do tej pory robię wszystkie wykonanie raportu od VS 2008.Zapytanie działa szybko w Oracle SQL Developer, ale powoli w SSRS 2008 R2
Używam dostawcy OLE DB "OraOLEDB.Oracle.1", który w przeszłości wydawał się dawać mi lepsze wyniki niż korzystanie z dostawcy Oracle.
Oto co udało mi się ustalić do tej pory:
• Opóźnienie jest w fazie realizacji DataSet i nie ma nic wspólnego z wynikiem ustalonym lub czas renderingu. (Sprawdzając, wybierając ten sam zestaw wierszy bezpośrednio z tabeli, do której wstawiłem).
• Sam SSRS nie jest zawieszany. To naprawdę czeka na Oracle, gdzie jest opóźnienie (udowodnione przez zakończenie sesji DB ze strony Oracle, co spowodowało natychmiastowy błąd w SSRS o zabiciu sesji).
• Próbowałem bezpośrednich zapytań z parametrami w postaci: Parametr. Bardzo wczesne wersje mojego zapytania, które były prostsze, działały dobrze w przypadku bezpośrednich zapytań, ale wydawało się, że po pewnym stopniu złożoności zapytanie zacznie trwać wiecznie od SSRS.
• Następnie przełączyłem się do wykonywania SP, który wstawia wyniki zapytania do tabeli lub globalnej tabeli tymczasowej. Pomogło to na krótką chwilę, ale nie było to bezpośrednie zapytanie, ale wygląda na to, że zwiększona złożoność zapytań lub ich długość ostatecznie złamała tę metodę. Uwaga: uruchamianie SP działającego w tabela, ponieważ opcja "Użyj pojedynczej transakcji" zaznaczone w opcji DataSource, DataSet są następnie uruchamiane w kolejności ich pojawiania się w pliku rdl. Zestawy danych, które nie zwracają żadnych pól, są nadal uruchamiane, o ile wszystkie ich parametry są spełnione.
• Po prostu wypróbowałem funkcję zwracającą tabelę i to wciąż nie przyniosło poprawy, mimo że bezpośrednie wywołania z dosłownymi parametrami w SQL Developer powracają w ciągu 1-5 sekund.
• Baza danych, o której mowa, nie ma statystyk. Jest to część produktu stworzonego przez sprzedawcę, a nie mieliśmy czasu ani wpisania się do zarządzania, aby tworzyć/aktualizować statystyki. Grałem z podpowiedź DYNAMIC_SAMPLING, aby obliczać statystyki w locie i otrzymałem lepszy plan wykonania: bez statystyk optymalizator kosztowy źle wykorzystywał łączenie LOOP zamiast sprzężenia HASH, powodując podobne, wielokrotne czasy wykonania. W ten sposób wstawiam wskazówki do kwerendy, aby wymusić kolejność łączenia, a także spowodować, że użyje strategicznego sprzężenia hash, przywracając czas wykonania z powrotem do zaledwie kilku sekund. Nie wróciłem i spróbowałem bezpośrednich zapytań w SSRS, korzystając z tych wskazówek dotyczących wykonywania.
• Uzyskałem pomoc od Oracle DBA, która utworzyła ślad (lub jakikolwiek inny odpowiednik Oracle) i był w stanie zobaczyć uruchamiane rzeczy, ale do tej pory nie znalazł niczego przydatnego. Niestety, jego czas jest ograniczony i nie byliśmy w stanie naprawdę zagłębić się, aby dowiedzieć się, co jest wykonywane po stronie serwera. Nie mam doświadczenia, aby to zrobić szybko, ani czasu na samodzielne zbadanie, jak to zrobić. Sugestie, co zrobić, aby określić, co się dzieje, zostaną docenione.
My tylko hipotezy:
• Zapytanie jest jakoś się zły plan wykonania. Np. Niewłaściwe użycie sprzężenia LOOP zamiast sprzężenia HASH, gdy istnieją dziesiątki tysięcy wierszy "lewej" lub zewnętrznej pętli, a nie tylko kilkaset.
• SSRS może przesyłać parametry jako nvarchar (4000) lub coś zamiast czegoś sensownego, a jako parametry funkcji Oracle SP & nie mają specyfikacji długości, ale uzyskują długość ich wykonania z zapytania, to niektóre procesy ponieważ sniffowanie parametrów zakłóca plan wykonania, tak jak w poprzednim punkcie.
• Zapytanie jest w jakiś sposób przepisywane przez SSRS/dostawcę. Używam parametru wielowartościowego, ale nie takiego jak jest: parametr jest przesyłany jako wyrażenie Join (Parametry! MultiValuedParameter.Value, ","), więc nie powinno być potrzeby przepisywania. Wystarczy proste wiązanie i przesyłanie. Nie rozumiem, jak to może być w SP, a funkcja dzwoni, ale co to może być?
Rozumiem, że jest to bardzo skomplikowane i długie zapytanie, ale robi dokładnie to, czego potrzebuję. Działa w ciągu 1-5 sekund w zależności od ilości żądanych danych. Niektóre z przyczyn złożoności są:
- Prawidłowo obsługi kosztów lista Centrum parametrów oddzielonych przecinkami
- Umożliwienie tygodniowy rozkład być opcjonalne i jeśli włączone, zapewniając wszystkie tygodnie w miesiącu przedstawiono nawet jeśli nie ma dla nich danych.
- Pokazywanie "Brak faktur", gdy jest to właściwe.
- Dopuszczenie zmiennej liczby miesięcy podsumowujących.
- Posiadanie opcjonalnej sumy YTD.
- Łącznie z poprzednimi/historycznymi danymi porównawczymi oznacza, że nie mogę po prostu użyć tych dostawców z miesiąca, muszę pokazać wszystkich dostawców, którzy znajdą się w dowolnej kolumnie historycznej.
W każdym razie, oto zapytanie, wersja SP (choć nie sądzę, że będzie to pomocne).
create or replace
PROCEDURE VendorInvoiceSummary (
FromDate IN date,
ToDate IN date,
CostCenterList IN varchar2,
IncludeWeekly IN varchar2,
ComparisonMonths IN number,
IncludeYTD IN varchar2
)
AS
BEGIN
INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
Mo,
CostCenter,
Vendor,
VendorName,
Section,
TimeUnit,
Amt
FROM (
WITH CostCenters AS (
SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || ' ', 1, 15) CostCenter
FROM DUAL
CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
), Invoices AS (
SELECT /*+ORDERED USE_HASH(D)*/
TRUNC(I.Invoice_Dte, 'YYYY') Yr,
TRUNC(I.Invoice_Dte, 'MM') Mo,
D.Dis_Acct_Unit CostCenter,
I.Vendor,
V.Vendor_VName,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END WkNum,
Sum(D.To_Base_Amt) To_Base_Amt
FROM
ICCompany C
INNER JOIN APInvoice I
ON C.Company = I.Company
INNER JOIN APDistrib D
ON C.Company = D.Company
AND I.Invoice = D.Invoice
AND I.Vendor = D.Vendor
AND I.Suffix = D.Suffix
INNER JOIN CostCenters CC
ON D.Dis_Acct_Unit = CC.CostCenter
INNER JOIN APVenMast V ON I.Vendor = V.Vendor
WHERE
D.Cancel_Seq = 0
AND I.Cancel_Seq = 0
AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
AND I.Invoice_Dte < ToDate
AND V.Vendor_Group = '1 ' -- index help
GROUP BY
TRUNC(I.Invoice_Dte, 'YYYY'),
TRUNC(I.Invoice_Dte, 'MM'),
D.Dis_Acct_Unit,
I.Vendor,
V.Vendor_VName,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END
), Months AS (
SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
FROM DUAL
CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
), Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
), Vals AS (
SELECT LEVEL - 1 TimeUnit
FROM DUAL
CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
), TimeUnits AS (
SELECT S.Section, V.TimeUnit
FROM
Sections S
INNER JOIN Vals V
ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
), Names AS (
SELECT DISTINCT
M.Mo,
Coalesce(I.Vendor, '0') Vendor,
Coalesce(I.Vendor_VName, 'No Paid Invoices') Vendor_VName,
Coalesce(I.CostCenter, ' ') CostCenter
FROM
Months M
LEFT JOIN Invoices I
ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) < I.Mo
AND M.Mo >= I.Mo
WHERE
M.Mo >= FromDate
AND M.Mo < ToDate
)
SELECT
N.Mo,
N.CostCenter,
N.Vendor,
N.Vendor_VName VendorName,
T.Section,
T.TimeUnit,
Sum(I.To_Base_Amt) Amt
FROM
Names N
CROSS JOIN TimeUnits T
LEFT JOIN Invoices I
ON N.CostCenter = I.CostCenter
AND N.Vendor = I.Vendor
AND (
(
T.Section = 1 -- Weeks for current month
AND N.Mo = I.Mo
AND T.TimeUnit = I.WkNum
) OR (
T.Section = 2 -- Summary months
AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
) OR (
T.Section = 3 -- YTD
AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
)
)
WHERE
N.Mo >= FromDate
AND N.Mo < ToDate
AND NOT (-- Only 4 weeks when a month is less than 28 days long
T.Section = 2
AND T.TimeUnit = 5
AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
AND I.CostCenter IS NULL
) AND (
T.Section <> 1
OR IncludeWeekly = 'Y'
)
GROUP BY
N.Mo,
N.CostCenter,
N.Vendor,
N.Vendor_VName,
T.Section,
T.TimeUnit
) X;
COMMIT;
END;
UPDATE
Nawet po nauki o planach realizacji Oracle i podpowiedzi (tłumaczenie moje SQL wiedzy Server), wciąż nie mógł dostać zapytanie do szybkiego uruchomienia w SSRS aż zrobiłem go uruchomić w dwóch krokach, najpierw umieścić wyniki prawdziwego stołu w postaci GLOBAL TEMPORARY TABLE
, a następnie drugie, aby wyodrębnić dane z tego. DYNAMIC_SAMPLING
dał mi dobry plan wykonania, który następnie skopiowałem za pomocą wskazówek dołączenia i dostępu. Oto ostateczny SP (nie może to być funkcja, ponieważ w Oracle nie można wykonać DML w funkcji, gdy ta funkcja jest wywoływana wewnątrz instrukcji SELECT):
Czasami przysięgam, że ignorowałem moje wskazówki do dołączenia, takie jak jako swap_join_inputs
i no_swap_join_inputs
, ale z mojego czytania najwyraźniej Oracle tylko ignoruje podpowiedzi, gdy nie można ich faktycznie użyć lub robisz coś złego. Na szczęście tabele zostały odpowiednio zamienione (tak jak w przypadku USE_NL(CC)
niezawodnie umieszcza tabelę CC jako zamienione, lewe wejście, nawet jeśli jest dołączone jako ostatnie).
CREATE OR REPLACE
PROCEDURE VendorInvoicesSummary (
FromDate IN date,
ToDate IN date,
CostCenterList IN varchar2,
IncludeWeekly IN varchar2,
ComparisonMonths IN number,
IncludeYTD IN varchar2
)
AS
BEGIN
INSERT INTO InvoiceTemp (Yr, Mo, CostCenter, Vendor, WkNum, Amt) -- A global temporary table
SELECT /*+LEADING(C I D CC) USE_HASH(I D) USE_NL(CC)*/
TRUNC(I.Invoice_Dte, 'YYYY') Yr,
TRUNC(I.Invoice_Dte, 'MM') Mo,
D.Dis_Acct_Unit CostCenter,
I.Vendor,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END WkNum,
Sum(D.To_Base_Amt) To_Base_Amt
FROM
ICCompany C
INNER JOIN APInvoice I
ON C.Company = I.Company
INNER JOIN APDistrib D
ON C.Company = D.Company
AND I.Invoice = D.Invoice
AND I.Vendor = D.Vendor
AND I.Suffix = D.Suffix
INNER JOIN (
SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || ' ', 1, 15) CostCenter
FROM DUAL
CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
) CC ON D.Dis_Acct_Unit = CC.CostCenter
WHERE
D.Cancel_Seq = 0
AND I.Cancel_Seq = 0
AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
AND I.Invoice_Dte < ToDate
GROUP BY
TRUNC(I.Invoice_Dte, 'YYYY'),
TRUNC(I.Invoice_Dte, 'MM'),
D.Dis_Acct_Unit,
I.Vendor,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END;
INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
Mo,
CostCenter,
Vendor,
VendorName,
Section,
TimeUnit,
Amt
FROM (
WITH Months AS (
SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
FROM DUAL
CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
), Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
), Vals AS (
SELECT LEVEL - 1 TimeUnit
FROM DUAL
CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
), TimeUnits AS (
SELECT S.Section, V.TimeUnit
FROM
Sections S
INNER JOIN Vals V
ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
), Names AS (
SELECT DISTINCT
M.Mo,
Coalesce(I.Vendor, '0') Vendor,
Coalesce(I.CostCenter, ' ') CostCenter
FROM
Months M
LEFT JOIN InvoiceTemp I
ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) <= I.Mo
AND I.Mo <= M.Mo
WHERE
M.Mo >= FromDate
AND M.Mo < ToDate
)
SELECT
N.Mo,
N.CostCenter,
N.Vendor,
Coalesce(V.Vendor_VName, 'No Paid Invoices') VendorName,
T.Section,
T.TimeUnit,
Sum(I.Amt) Amt
FROM
Names N
INNER JOIN APVenMast V ON N.Vendor = V.Vendor
CROSS JOIN TimeUnits T
LEFT JOIN InvoiceTemp I
ON N.CostCenter = I.CostCenter
AND N.Vendor = I.Vendor
AND (
(
T.Section = 1 -- Weeks for current month
AND N.Mo = I.Mo
AND T.TimeUnit = I.WkNum
) OR (
T.Section = 2 -- Summary months
AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
) OR (
T.Section = 3 -- YTD
AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
)
)
WHERE
N.Mo >= FromDate
AND N.Mo < ToDate
AND V.Vendor_Group = '1 '
AND NOT (-- Only 4 weeks when a month is less than 28 days long
T.Section = 2
AND T.TimeUnit = 5
AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
AND I.CostCenter IS NULL
) AND (
T.Section <> 1
OR IncludeWeekly = 'Y'
)
GROUP BY
N.Mo,
N.CostCenter,
N.Vendor,
V.Vendor_VName,
T.Section,
T.TimeUnit
) X;
COMMIT;
END;
To była długa, bolesna jazdy, ale jeśli jest jedna rzecz ja nauczyłem jest to, że działa w bazie danych bez odpowiednio aktualizowanych statystyk (które mam zamiar spojrzeć na uzyskanie naszego DBA, aby dodać jeszcze chociaż sprzedawca nie dba o nie) może być prawdziwą katastrofą dla kogoś, kto chce załatwić sprawy w rozsądnym czasie.
czy Twoje statystyki są aktualne? –
Nie, jak już powiedziałem, sprzedawca nie używa statystyk. Opierają się one na regułach dla każdego zapytania w tym DB, które ma starożytne pochodzenie. – ErikE