使用 window 函数将存储函数转换为 Select

Convert Stored Function to Select with window-functions

我有一个计算价格的函数:

ALTER FUNCTION [dbo].[LAWI_DEinkauf](@sArtikelID varchar(36)) 
RETURNS NUMERIC(14, 2) 
AS
BEGIN
    DECLARE @menge DECIMAL(16,6)
    DECLARE @tmenge DECIMAL(16,6)
    DECLARE @wert DECIMAL(16,6)
    DECLARE @ekpreis DECIMAL(16,6)
    DECLARE @ekmenge DECIMAL(16,6)

    DECLARE @myposI CURSOR

    SET @ekpreis = 0
    SET @ekmenge = 0
    SET @wert = 0
    SET @menge = 0
    SET @tmenge = 0

    SET @myposI = CURSOR SCROLL FOR
         SELECT einkaufspreis, menge 
         FROM lawi_bewegung 
         WHERE artikelid = @sArtikelID 
         ORDER BY datum, ident;

    OPEN @myposI

    FETCH NEXT FROM @myposI INTO @ekpreis, @ekmenge

    WHILE @@fetch_status = 0
    BEGIN
       SET @tmenge = @tmenge + @ekmenge

       IF @ekpreis <> 0 
           SET @menge = @menge + @ekmenge

       SET @wert = @wert + @ekpreis * @ekmenge

       IF @tmenge = 0 
           SET @wert = 0

       IF @tmenge = 0 
           SET @menge = 0

       FETCH NEXT FROM @myposI INTO @ekpreis, @ekmenge;
    END

    CLOSE @myposI
    DEALLOCATE @myposI

    IF @menge = 0 
        SET @menge = 1

    RETURN @wert / @menge
END

我尝试将此存储函数转换为具有 window 函数的 select。原因是存储函数在具有多行的 select 中使用时性能不佳。

通常情况如下:

(SELECT TOP 1 
     wert / CASE WHEN menge = 0 THEN 1 ELSE menge END 
 FROM
     (SELECT 
          SUM(menge) OVER (ORDER BY datum, ident) as tmenge, 
          SUM(CASE WHEN einkaufspreis <> 0 THEN menge ELSE 0 END) OVER (ORDER BY datum, ident) AS menge,
          SUM(einkaufspreis * menge) OVER (ORDER BY datum, ident) AS wert,
          ROW_NUMBER() OVER (ORDER BY datum, ident) AS rn
      FROM
          lawi_bewegung 
      WHERE
          artikelid = XXXX) a order by rn desc

在那里,XXXX 将是参数 sArtikelID(在我的例子中来自外部 select)。

我的问题是存储函数的一部分,其中总和被重置:

IF @tmenge = 0 
    SET @wert = 0
IF @tmenge = 0 
    SET @menge = 0

如何将此逻辑包含到使用 window 函数的 select 中?

以下查询似乎可以解决您的问题,但我不确定它是否会正常运行。你能用一些数据试试看并检查值和性能吗?

WITH step1 AS (
    SELECT
        *,
        CASE WHEN SUM(menge) OVER (PARTITION BY artikelid ORDER BY datum, ident) = 0 THEN 1 ELSE 0 END AS reset
    FROM lawi_bewegung
),
step2 AS (
    SELECT *,
        SUM(reset) OVER (PARTITION BY artikelid ORDER BY datum, ident) - reset AS g
    FROM step1
),
step3 AS (
    SELECT artikelid,
        SUM(CASE WHEN einkaufspreis <> 0 THEN menge ELSE 0 END)
            OVER (PARTITION BY artikelid, g ORDER BY datum, ident) AS menge,
        SUM(einkaufspreis * menge)
            OVER (PARTITION BY artikelid, g ORDER BY datum, ident) AS wert,
        ROW_NUMBER() OVER (PARTITION BY artikelid ORDER BY datum, ident) AS rn
    FROM step2
)

SELECT i.artikelid,
    d.*
FROM lawi_inventur i
CROSS APPLY (
    SELECT TOP 1
        CAST(wert / CASE WHEN menge = 0 THEN 1 ELSE menge END AS DECIMAL(14, 2)) AS DEinkauf
    FROM step3 s
    WHERE s.artikelid = i.artikelid
    ORDER BY rn DESC
) d
;

其工作方式如下:

  • 第 1 步将标记 tmenge 为 0 的行,这是您的重置点;
  • 第 2 步将对 reset 列进行 运行 求和,有效地创建一个分组列。每次tmenge总和为0,后面的行就会有不同的分组值;
  • 第 3 步利用此分组值,并使用 OVER 子句的 PARTITION BY 选项使用它对要求和的行进行分区。它还计算所有行的 ROW_NUMBER() ,然后外部查询使用它来选择最后一行;

如果您查看中间步骤的输出,可能会更容易理解。只需将最后的 SELECT(最后 4 行)更改为 SELECT * from step1,或 SELECT * FROM step2 等等。

如果您有一个适合此查询的索引,可能会有所帮助,类似于 ON (artikelid, datum, ident) INCLUDE (menge, einkaufspreis)