我使用的是SQL Server 2008。 我尝试制作一个报告,需要计算一个关于我表格每一行的新列。 我写这个函数:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date, ,>
-- Description: <Description, ,>
-- =============================================
ALTER FUNCTION [dbo].[fn_CalcProductStock]
(
@ProductID INT = NULL ,
@ToDate DATETIME = NULL ,
@FinYear INT = NULL ,
@InventoryID INT = NULL
)
RETURNS FLOAT
AS
BEGIN
DECLARE @stock FLOAT
SET @stock = ISNULL(( SELECT SUM(IDI.InvDocItemNumbers)
FROM InvDocItem IDI
LEFT OUTER JOIN InvDoc ID ON IDI.CenterID = ID.CenterID
AND IDI.InvDocID = ID.InvDocID
WHERE IDI.ProductID = @ProductID
AND ID.FinYearID = @FinYear
AND ( ID.InventoryID = @InventoryID
OR @InventoryID IS NULL
)
AND ID.InvDocDate < @ToDate
AND ( ID.InvTransTypeID = 1
OR ID.InvTransTypeID = 4
OR ID.InvTransTypeID = 6
OR ID.InvTransTypeID = 13
OR ID.InvTransTypeID = 9
)
), 0)
- ISNULL(( SELECT SUM(II.InvoiceItemNumbers)
FROM InvoiceItem II
LEFT OUTER JOIN Invoice I ON ( II.CenterID = I.CenterID
AND II.InvoiceID = I.InvoiceID
)
WHERE II.ProductID = @ProductID
AND I.FinYearId = @FinYear
AND I.InvoiceDate < @ToDate
AND ( I.InventoryID = @InventoryID
OR @InventoryID IS NULL
) --Tehran:1 Tehrantakh:101 Mashhad:21 Tabriz:6
AND ( I.InvoiceType = 1
OR I.InvoiceType = 3
)
), 0)
+ ISNULL(( SELECT SUM(II.InvoiceItemNumbers)
FROM InvoiceItem II
LEFT OUTER JOIN Invoice I ON II.CenterID = I.CenterID
AND II.InvoiceID = I.InvoiceID
WHERE II.ProductID = @ProductID
AND I.FinYearId = @FinYear
AND I.InvoiceDate < @ToDate
AND ( I.InventoryID = @InventoryID
OR @InventoryID IS NULL
)
AND ( I.InvoiceType = 2
OR I.InvoiceType = 4
)
), 0)
- ISNULL(( SELECT SUM(IDI.InvDocItemNumbers)
FROM InvDocItem IDI
LEFT OUTER JOIN InvDoc ID ON IDI.CenterID = ID.CenterID
AND IDI.InvDocID = ID.InvDocID
WHERE IDI.ProductID = @ProductID
AND ID.InvTransTypeID = 3
AND ID.FinYearID = @FinYear
AND ( ID.InventoryID = @InventoryID
OR @InventoryID IS NULL
)
AND ID.InvDocDate < @ToDate
), 0)
-- Return the result of the function
RETURN @stock
END
我也尝试在这种情况下使用这个功能:
SELECT Prdct.InventoryID ,
Prdct.ProductID ,
( dbo.fn_CalcProductStock(Prdct.ProductID, '2014-04-29', 92, 101) )
FROM ProductInv AS Prdct
这有真正的计算结果,但需要很长时间 像40分钟...... !!!适用于3000种产品
我怎样才能让它的表现更好?
答案 0 :(得分:1)
不幸的是,具有数据访问权限的标量UDF是臭名昭着的性能杀手。它们以RBAR方式执行,而不是与主查询一起优化。
没有简单的方法来解决它。我通常尝试做的是将主查询的结果存储到临时表中,然后考虑如何从函数重写查询,以便我可以对所有行执行一次。或者,因为你实际上有四个选择,分别运行它们,将结果存储到临时表中,然后将它们连接在一起。
关键是不要为每一行执行一次,这样就有更好的机会进行正确优化。
答案 1 :(得分:1)
我认为这会起作用
CREATE FUNCTION [dbo].[usp_CalcProductStock] (
-- Add the parameters for the function here
@ProductID INT = NULL
,@ToDate DATETIME = NULL
,@FinYear INT = NULL
,@InventoryID INT = NULL
)
RETURNS FLOAT
AS
BEGIN
DECLARE @stock FLOAT
DECLARE @DocItems TABLE (
InvTransTypeID INT
,SumOfInvDocItemNumbers BIGINT
)
INSERT INTO @DocItems
SELECT ID.InvTransTypeID
,SUM(IDI.InvDocItemNumbers) AS SumOfInvDocItemNumbers
FROM (
SELECT InvDocItemNumbers
,CenterID
,InvDocID
FROM InvDocItem
WHERE ProductID = @ProductID
) IDI
LEFT JOIN (
SELECT CenterID
,InvDocID
,InvTransTypeID
FROM InvDoc
WHERE ID.FinYearID = @FinYear
AND (
ID.InventoryID = @InventoryID
OR @InventoryID IS NULL
)
AND ID.InvDocDate < @ToDate
) ID ON IDI.CenterID = ID.CenterID
AND IDI.InvDocID = ID.InvDocID HERE
GROUP BY ID.InvTransTypeID
DECLARE @InvoiceItems TABLE (
InvoiceType NVARCHAR(250)
,SumofInvoiceItemNumbers BIGINT
)
INSERT INTO @InvoiceItems
SELECT I.InvoiceType
,SUM(II.InvoiceItemNumbers) AS SumofInvoiceItemNumbers
FROM (
SELECT CenterID
,InvoiceID
,InvoiceItemNumbers
FROM InvoiceItem
WHERE ProductID = @ProductID
) II
LEFT JOIN (
SELECT CenterID
,InvoiceID
,InvoiceType
FROM Invoice
WHERE I.FinYearId = @FinYear
AND I.InvoiceDate < @ToDate
AND (
I.InventoryID = @InventoryID
OR @InventoryID IS NULL
)
) I ON II.CenterID = I.CenterID
AND II.InvoiceID = I.InvoiceID
GROUP BY I.InvoiceType
DECLARE @DocItems_I FLOAT;
DECLARE @DocItems_II FLOAT;
DECLARE @InvoiceItems_I FLOAT;
DECLARE @InvoiceItems_II FLOAT;
SELECT @DocItems_I = ISNULL(SUM(SumOfInvDocItemNumbers), 0)
FROM @DocItems
WHERE InvTransTypeID IN (
1
,4
,6
,9
,13
)
SELECT @InvoiceItems_I = ISNULL(SUM(SumofInvoiceItemNumbers), 0)
FROM @InvoiceItems
WHERE InvoiceType IN (
1
,3
)
SELECT @InvoiceItems_II = ISNULL(SUM(SumofInvoiceItemNumbers), 0)
FROM @InvoiceItems
WHERE InvoiceType IN (
2
,4
)
SELECT @DocItems_II = ISNULL(SUM(SumOfInvDocItemNumbers), 0)
FROM @DocItems
WHERE InvTransTypeID IN (3)
SET @stock = @DocItems_I - @InvoiceItems_I + @InvoiceItems_II - @DocItems_II
RETURN @stock
END
GO
答案 2 :(得分:0)
您可能想要尝试使用table-valued function:
CREATE FUNCTION Calculated_Product_Stock (@toDate DATETIME,
@finYear INT,
@inventoryId INT = NULL)
RETURNS table AS
RETURN (SELECT COALESCE(Doc.productId, Inv.productId) AS productId,
COALESCE(Doc.stock, 0) + COALESCE(Inv.stock, 0) AS stock
FROM (SELECT Item.productId,
SUM(CASE WHEN Doc.invTransTypeId = 3
THEN 0 - Item.invDocItemNumbers
ELSE Item.invDocItemNumbers END) AS stock
FROM InvDoc Doc
JOIN InvDocItem Item
ON Item.centerId = Doc.centerId
AND Item.invDocId = Doc.invDocId
WHERE (@inventoryId IS NULL OR Doc.inventoryId = @inventoryId)
AND Doc.finYearId = @finYear
AND Doc.invDocDate < @toDate
AND Doc.invTransTypeId IN (1, 3, 4, 6, 9, 13)
GROUP BY Item.productId) Doc
FULL JOIN (SELECT Item.productId,
SUM(CASE WHEN Inv.invoiceType IN (1, 3)
THEN 0 - Item.invoiceItemNumbers
ELSE Item.invoiceItemNumbers END) AS stock
FROM Invoice Inv
JOIN InvoiceItem Item
ON Item.invoiceId = Inv.invoiceId
AND Item.centerId = Inv.centerId
WHERE (@inventoryId IS NULL OR Inv.inventoryId = @inventoryId)
AND Inv.finYearId = @finYear
AND Inv.invoiceDate < @toDate
AND Inv.invoiceType IN (1, 2, 3, 4)
GROUP BY Item.productId) Inv
ON Inv.productId = Doc.productId);
然后可以将该函数包含在如下的查询中:
SELECT Product.inventoryId, Product.productId, COALESCE(Invoice.stock, 0)
FROM ProductInv Product
LEFT JOIN Calculated_Product_Stock('2014-04-29', 92, 101) Invoice
ON Invoice.productId = Product.productId
(没有任何东西经过测试,因为我没有任何东西可以反对。而且我从未使用过这个功能)
与往常一样,测试它是否适合您的情况。