それはとても簡単です。Oracle 11g に接続する SQL Developer でわずか数秒で実行されるクエリは、SSRS 2008 R2 では 15 ~ 25 分かかります。他のバージョンの SSRS は試していません。これまでのところ、VS 2008 からすべてのレポートの実行を行っています。
私は OLE DB プロバイダー "OraOLEDB.Oracle.1" を使用しています。これは、過去に Oracle プロバイダーを使用するよりも良い結果が得られたようです。
これが私がこれまでに決定できたものです:
• 遅延は DataSet の実行段階で発生し、結果セットやレンダリング時間とは関係ありません。(挿入先のテーブルから直接同じ行セットを選択することで証明します。)
• SSRS 自体はハングアップしていません。遅延が発生している場所であるOracleを本当に待っています(Oracle側からDBセッションを終了することで証明され、セッションが強制終了されたことについてSSRSでプロンプトエラーが発生しました)。
• :Parameter の形式のパラメーターを使用して直接クエリを実行してみました。より単純な私のクエリの非常に初期のバージョンは、直接クエリを実行しても問題なく動作しましたが、特定の複雑さを超えると、クエリは SSRS から永遠にかかり始めるように見えました。
• 次に、クエリ結果をテーブルまたはグローバル一時テーブルに挿入する SP の実行に切り替えました。これはしばらくの間、直接クエリよりも先に進むのに役立ちましたが、クエリの複雑さや長さの増加により、最終的にこの方法も壊れたようです。注: DataSource オプションで「単一のトランザクションを使用する」オプションをオンにすると、DataSet が rdl ファイルに表示される順序で実行されるため、テーブルに入力する SP を実行すると機能します。フィールドを返さない DataSet は、すべてのパラメーターが満たされている限り実行されます。
• テーブルを返す関数を試してみましたが、SQL Developer でリテラル パラメータを使用した直接呼び出しを行うと 1 ~ 5 秒で返されますが、改善は見られませんでした。
• 問題のデータベースには統計がありません。これはベンダーによって作成された製品の一部であり、統計を作成/更新するための時間や経営陣の同意がありませんでした. 動的に統計を計算するために DYNAMIC_SAMPLING ヒントを試してみたところ、より良い実行計画が得られました。したがって、結合順序を強制し、戦略的なハッシュ結合を使用するようにクエリ ヒントを挿入して、実行時間をわずか数秒に短縮しました。戻って、これらの実行ヒントを使用して SSRS で直接クエリを実行することはしませんでした。
• トレース (または Oracle に相当するもの) を設定した Oracle DBA の助けを借りました。残念ながら、彼の時間は限られているため、サーバー側で何が実行されているかを突き止めることができませんでした。私にはこれを素早く行う経験がありませんし、自分でこれを行う方法を勉強する時間もありません。何が起こっているのかを判断するために何をすべきかについての提案をいただければ幸いです。
私の唯一の仮説は次のとおりです。
• クエリが何らかの理由で不適切な実行計画を取得している。たとえば、数百ではなく数万の「左」または外側のループ行がある場合に、HASH 結合の代わりに LOOP 結合を不適切に使用する。
• SSRS は、妥当なものではなく nvarchar(4000) などのパラメーターを送信する可能性があります。また、Oracle SP および関数パラメーターには長さの指定がなく、クエリ呼び出しから実行長を取得するため、パラメーター スニッフィングなどのプロセスが実行されます。前のポイントのように実行計画を台無しにします。
• クエリが何らかの形で SSRS/プロバイダーによって書き換えられている。多値パラメーターを使用していますが、そのままではありません。パラメーターは式 Join(Parameters!MultiValuedParameter.Value, ",") として送信されているため、書き換える必要はありません。シンプルなバインドと送信だけです。SPと関数呼び出しでこれがどのように当てはまるかわかりませんが、他に何がありますか?
非常に複雑で長いクエリであることは理解していますが、必要なことは正確に実行されます。要求されるデータの量に応じて、1 ~ 5 秒で実行されます。複雑さの理由のいくつかは次のとおりです。
- カンマ区切りのコスト センター リスト パラメータの適切な処理
- 週ごとの内訳をオプションにすることを許可し、含まれている場合は、データがない場合でも月のすべての週が表示されるようにします。
- 必要に応じて「請求書なし」を表示します。
- 可変数の要約月を許可します。
- オプションの YTD 合計を持つ。
- 以前/履歴の比較データを含めるということは、今月のベンダーを単純に使用することはできず、履歴列に含まれるすべてのベンダーを表示する必要があることを意味します。
とにかく、これがクエリ、SPバージョンです(あまり役に立たないと思いますが)。
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;
アップデート
Oracle の実行計画とヒントについて (SQL Server の知識を翻訳するために) すべて学習した後でも、最初に実際のテーブルの結果GLOBAL TEMPORARY TABLE
を次に、そこからデータを抽出します。DYNAMIC_SAMPLING
良い実行計画が得られたので、結合ヒントとアクセスヒントを使用してコピーしました。最終的な SP は次のとおりです (Oracle では、その関数が SELECT ステートメント内で呼び出されたときに関数で DML を実行できないため、関数にすることはできません)。
swap_join_inputs
andなどの結合ヒントを無視していたことを断言することもありno_swap_join_inputs
ますが、私の読書から、オラクルは実際に使用できない場合、または何か間違ったことをしている場合にのみヒントを無視するようです。幸いなことに、テーブルは適切にスワップされました ( USE_NL(CC)
CC テーブルが最後に結合されたとしても、スワップされた左の入力として確実に配置されるように)。
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;
これは長くてつらい道のりでしたが、私が学んだことが1つあるとすれば、適切に更新された統計なしでデータベースで作業しているということです(ベンダーは気にしませんが、DBAに追加してもらうことを検討します)それらについて)は、合理的な時間内に物事を終わらせたいと思っている人にとっては、本当の惨事になる可能性があります。