Pergunta

I'm currently developing an app in Delphi which uses SQL to tap into the back end of a 3rd party invoicing system so we can extend the reporting capabilities of it. I consider myself to be reasonably proficient in the delphi side of programming, however SQL is new to me so with the immense help of this forum, and other resources, i have managed to teach myself more than I thought I would be able to.

Most of the data is pulled out of several tables (i don't have an issue with that side, so I won't clog up the post with those details), however I have an issue getting the cost price. It's stored in a table that tracks the historical cost price so for each product (16'000+) there are potentially hundreds of records, however I only need the cost for each product that is closest (<=) to the date of the invoice. Here is the function:

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 

And here is one of the queries (there are several different ones, but all based around the same structure):

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'

Now this works fine when I only have a few invoices in the selected range, but given that it calls the function a minimum of three times per record, the performance really degrades when I go to produce the report over a time period of more than a day (we often need reports covering a two week period).

Unfortunately, given that it is a third party product (inFlow Inventory for anyone who is curious) I can't change the table structures.

Is there any way whether it be using more efficient joins, a derived table (I understand the concept, but have never done it) or even rewriting the whole query that will improve the performance greatly?

Foi útil?

Solução

It appears I have managed to solve my own problem, it just took a little bit more research, lateral thinking, a lot of failed attempts and curse words (oh so many curse words!)

I toyed with the idea of adding extra steps in the delphi side of this program that would select from the cost prices table based upon the date range i needed and then re-write my original query to incorporate the new table joined in. However it's not as fun to solve a problem if you don't learn any new skills along the way ;-).

The answer: TVF- Table Valued Function. After a lot of research on alternative ways i stumbled across these TVF's. Further investigation seemed to reveal that because of the way the optimizer handles scalar functions as opposed to TVF's, they were tremendously quicker in certain applications so i decided to re-write my function as such:

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)

And instead of calling it the traditional way

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

I re-jigged all references of it in my query to:

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

Testing it i found a significant increase in performance (4sec for 65k+ records, as opposed to the previous function which would usually time out after a few minutes even though the expected resultset was ~10k records.)

I'm sure plenty of you know an even better way, but for the moment this works well....and i found it all by myself: kudos to me!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top