使用窗口函数将存储函数转换为选择

时间:2017-11-06 15:43:35

标签: sql sql-server tsql sql-server-2014 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

我尝试将此存储函数转换为带窗口函数的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(在我的情况下来自外部选择)。

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

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

如何将此逻辑包含在使用窗口函数的select中?

1 个答案:

答案 0 :(得分:2)

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

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 step1SELECT * FROM step2,依此类推。

如果您为此查询提供了良好的索引,可能会有所帮助,类似于ON (artikelid, datum, ident) INCLUDE (menge, einkaufspreis)