0

私は現在、SQL を使用してサードパーティの請求システムのバックエンドを利用する Delphi でアプリを開発しているため、そのレポート機能を拡張できます。私はプログラミングの Delphi 側にかなり精通していると思いますが、SQL は私にとって初めてなので、このフォーラムやその他のリソースの多大な助けを借りて、自分ができると思っていた以上に多くのことを独学することができました。

ほとんどのデータはいくつかのテーブルから引き出されます (私はその側に問題がないので、それらの詳細で投稿を詰まらせることはありません) が、原価を取得する際に問題があります。過去のコスト価格を追跡するテーブルに保存されるため、各製品 (16,000 以上) には数百のレコードが存在する可能性がありますが、請求書の日付に最も近い (<=) 各製品のコストのみが必要です。 . 関数は次のとおりです。

CREATE FUNCTION dbo.CostAtDate ( @costdate AS datetime , @product AS int )
RETURNS decimal(18,2)
AS
BEGIN
DECLARE @result decimal(18,2)
SET @result = (
  Select Top 1
    BASE_InventoryCostLogDetail.AverageCostAfter
  From
    BASE_InventoryCostLogDetail
  Where
    CreatedDttm < @costdate And CreatedDttm > DATEADD(month,-1,@costDate) And
    ProdId = @product
  Order By
    CreatedDttm Desc)

RETURN @result
END 

そして、これがクエリの 1 つです (いくつかの異なるクエリがありますが、すべて同じ構造に基づいています)。

Select
  BASE_Customer.Name,
  SO_SalesOrder.OrderNumber,
  SO_SalesOrderInvoice_Line.Description,
  SO_SalesOrderInvoice_Line.UnitPrice,
  Case SO_SalesOrderInvoice_Line.ItemTaxCodeId
    When '100' Then (SO_SalesOrderInvoice_Line.UnitPrice / 11) * 10
    Else SO_SalesOrderInvoice_Line.UnitPrice End As exgst,
  SO_SalesOrderInvoice_Line.QuantityUom,
  SO_SalesOrderInvoice_Line.QuantityDisplay,
  Case SO_SalesOrderInvoice_Line.QuantityUom
    When 'cases.' Then dbo.CostAtDate(SO_SalesOrder.OrderDate,
    SO_SalesOrderInvoice_Line.ProdId)  * BASE_Product.SoUomRatioStd
    Else dbo.CostAtDate(SO_SalesOrder.OrderDate,
    SO_SalesOrderInvoice_Line.ProdId) End As cost,
  Case SO_SalesOrderInvoice_Line.QuantityUom
    When 'cases.' Then ((dbo.CostAtDate(SO_SalesOrder.OrderDate,
    SO_SalesOrderInvoice_Line.ProdId) * BASE_Product.SoUomRatioStd) / 11) * 10
    Else (dbo.CostAtDate(SO_SalesOrder.OrderDate,
    SO_SalesOrderInvoice_Line.ProdId) / 11) * 10 End As exgstcost,
  BASE_Product.SoUomRatioStd,
  BASE_Product.Name As Name1,
  SO_SalesOrder.OrderDate
From
  BASE_Customer Inner Join
  SO_SalesOrder On SO_SalesOrder.CustomerId = BASE_Customer.CustomerId
  Inner Join
  SO_SalesOrderInvoice_Line On SO_SalesOrderInvoice_Line.SalesOrderId =
    SO_SalesOrder.SalesOrderId Inner Join
  BASE_Product On SO_SalesOrderInvoice_Line.ProdId = BASE_Product.ProdId
Where
  SO_SalesOrder.OrderDate Between '20131028' And '20131029'

これで、選択した範囲に数件の請求書しかない場合は問題なく動作しますが、関数がレコードごとに最低 3 回呼び出されることを考えると、レポートを 1 日以上にわたって作成しようとすると、パフォーマンスが実際に低下します。日 (2 週間にわたるレポートが必要になることがよくあります)。

残念ながら、これはサード パーティの製品であるため (興味のある人は inFlow Inventory を参照してください)、テーブル構造を変更することはできません。

より効率的な結合、派生テーブル (概念は理解していますが、実行したことはありません) を使用するか、クエリ全体を書き直してパフォーマンスを大幅に向上させる方法はありますか?

4

1 に答える 1

0

私は自分の問題をなんとか解決できたようです。もう少し調査し、横方向の考え方をし、多くの失敗した試みと呪いの言葉 (ああ、呪いの言葉がたくさんあります!) が必要でした。

このプログラムの Delphi 側に、必要な日付範囲に基づいてコスト価格テーブルから選択するステップを追加し、元のクエリを書き直して、結合された新しいテーブルを組み込むというアイデアをいじりました。しかし、そうではありません。途中で新しいスキルを学ばなければ、問題を解決するのは楽しいものです ;-)。

答え: TVF - テーブル値関数。別の方法について多くの調査を行った後、これらの TVF に出くわしました。さらなる調査により、TVF とは対照的にオプティマイザーがスカラー関数を処理する方法のために、特定のアプリケーションでは非常に高速であることが明らかになったように思われたため、関数を次のように書き直すことにしました。

CREATE FUNCTION dbo.CostAtDate ( @costdate AS datetime , @product AS int )
RETURNS table
AS
Return (
  Select Top 1
    BASE_InventoryCostLogDetail.AverageCostAfter
  From
    BASE_InventoryCostLogDetail
  Where
    CreatedDttm < @costdate And CreatedDttm > DATEADD(month,-1,@costDate) And
    ProdId = @product
  Order By
    CreatedDttm Desc)

そしてそれを伝統的な方法と呼ぶ代わりに

dbo.CostAtDate(SO_SalesOrder.OrderDate, SO_SalesOrderInvoice_Line.ProdId)

クエリでのすべての参照を次のように再調整しました。

(Select * from dbo.CostAtDate(SO_SalesOrder.OrderDate, SO_SalesOrderInvoice_Line.ProdId))

それをテストすると、パフォーマンスが大幅に向上することがわかりました(予想される結果セットが〜10kレコードであったにもかかわらず、通常数分後にタイムアウトする以前の関数とは対照的に、65k以上のレコードで4秒)。

もっと良い方法を知っている人はたくさんいると思いますが、今のところこれでうまくいきます....そして、私はすべて自分で見つけました。

于 2013-11-05T05:40:28.697 に答える