替代的负面和正面价值观

时间:2015-08-30 08:48:48

标签: sql sql-server

我有一个名为PROD_COST的表,有5个字段:

(ID, Duration, Cost, COST_NEXT, COST_CHANGE)

我需要额外的字段Groups进行聚合。

  1. 持续时间=价格有效的天数(1天= 1row)。
  2. 成本=当天的产品价格。
  3. -Cost_next = lead(cost,1,0)。
  4. Cost_change = Cost_next - 费用。
  5. + ID + Duration + Cost + Next_Cost + Cost_change + Groups+
    |  1 | 1        | 10   | 8.5       | -1.5        | 1     |
    |  2 | 1        | 8.5  | 12.2      | 3.7         | 2     |
    |  3 | 1        | 12.2 | 5.3       | -6.9        | 3     |
    |  4 | 1        | 5.3  | 4.2       | 1.2         | 4     |
    |  5 | 1        | 4.2  | 6.2       | 2           | 4     |
    |  6 | 1        | 6.2  | 9.2       | 3           | 4     |
    |  7 | 1        | 9.2  | 7.5       | -2.7        | 5     |
    |  8 | 1        | 7.5  | 6.2       | -1.3        | 5     |
    |  9 | 1        | 6.2  | 6.3       | 0.1         | 6     |
    | 10 | 1        | 6.3  | 7.2       | 0.9         | 6     |
    | 11 | 1        | 7.2  | 7.5       | 0.3         | 6     |
    | 12 | 1        | 7.5  | 0         | 7.5         | 6     |
    +----+----------+------+-----------+-------------+-------+
    

    我需要按GroupsCost_change字段进行分组。它可以是正数,负数或零。

    有些人建议我使用这段代码:

    Select 
        id
        , COST_CHANGE
        , sum(Groups) over (order by id asc) +1
    from 
        (
        select 
            pc.*,
            (case when sign(cost_change) - sign(lag(cost_change) over (order by id)) between -1 and 1
            then 0
            else 1 -- `NULL` intentionally goes here
            end) Groups
        from Prod_Cost
        ) pc
    

    但是有一个问题:如果两个正/负或负/正值之间有0个值,那么它将它们组合在一起,例如:

     Cost_change Groups
    | -5.279    | 33   |
    |  5.279    | 34   |
    |  0.000    | 34   |
    | -5.279    | 34   |
    |  0.000    | 34   |
    |  5.279    | 34   |
    | -8.769    | 35   |
    

    我需要:

     Cost_change Groups 
    | -5.279    | 33   |
    |  5.279    | 34   |
    |  0.000    | 34   |
    | -5.279    | 35   |
    |  0.000    | 35   |
    |  5.279    | 36   |
    | -8.769    | 37   |
    

    第二个例子:

     Cost_change Groups
    |  7.574    | 68   |
    |  0.000    | 68   |
    | -5.279    | 68   |
    | -3.490    | 68   |
    

    但我需要:

     Cost_change Groups
    |  7.574    | 68   |
    |  0.000    | 68   |
    | -5.279    | 69   |
    | -3.490    | 69   |
    

    我将非常感谢任何帮助。

2 个答案:

答案 0 :(得分:1)

以下是SQL Fiddle解决方案。

我会专注于您的问题,并在表格中仅保留相关列:IDCost_change

让我们创建一些涵盖所有可能情况的样本数据。我在不同的地方添加了几个零:

DECLARE @T TABLE (ID int IDENTITY(1,1), Cost_change decimal(10,3));

INSERT INTO @T (Cost_change) VALUES
( 0.000),
( 0.000),
( -1.5),
(  3.7),
( -6.9),
(  1.2),
( 0.000),
( 0.000),
(  2.0),
(  3.0),
( -2.7),
( 0.000),
( -1.3),
(  0.1),
(  0.9),
(  0.3),
(  7.5),
(-5.279),
( 5.279),
( 0.000),
(-5.279),
( 0.000),
( 5.279),
(-8.769),
( 7.574),
( 0.000),
(-5.279),
(-3.490),
(-5.279),
( 5.279),
( 0.000),
( 0.000),
( 0.000),
(-5.279),
( 0.000),
( 0.000),
( 5.279),
(-8.769),
( 7.574),
( 0.000),
( 0.000),
( 0.000),
( 0.000),
(-5.279),
(-3.490);

我会明确地说出每一步,这样会更容易理解。

处理数据将有两个主要部分。首先,我们将处理所有非零值并为它们生成忽略零值的组号。然后,对于每个零值,我们将在生成的中找到相应的组号。

CTE_Signs使用sign函数计算当前行和上一行Cost_change的{​​{1}}。请注意,我们在这里过滤掉零值。

LAG比较当前行和上一行的符号,如果它们不同,则将CTE_Changes设置为1。第一行有Change作为上一行的符号,NULL会处理它。

ISNULL计算CTE_Groups的运行总和,生成的组号随每个Change而增加。

这将为我们提供所有非零值的正确组号。

第二个主要部分是使用Change获取所有零值并为它们找到正确的组号。最后OUTER APPLY两个部分。

UNION ALL

<强>结果

WITH
CTE_Signs
AS
(
    SELECT *
        ,SIGN(Cost_change) AS SignCurr
        ,SIGN(LAG(Cost_change) OVER (ORDER BY ID)) AS SignPrev
    FROM @T
    WHERE Cost_change <> 0
)
,CTE_Changes
AS
(
    SELECT *
        , CASE WHEN SignCurr <> ISNULL(SignPrev, SignCurr) THEN 1 ELSE 0 END AS Change
    FROM CTE_Signs
)
,CTE_Groups
AS
(
    SELECT *
        , SUM(Change) OVER (ORDER BY ID) AS Groups
    FROM CTE_Changes
)
SELECT TT.ID, TT.Cost_change, ISNULL(CA.Groups, 0) AS Groups
FROM 
    @T AS TT
    OUTER APPLY
    (
        SELECT TOP(1) CTE_Groups.Groups
        FROM CTE_Groups
        WHERE CTE_Groups.ID < TT.ID
        ORDER BY CTE_Groups.ID DESC
    ) AS CA
WHERE
    TT.Cost_change = 0

UNION ALL

SELECT ID, Cost_change, Groups
FROM CTE_Groups

ORDER BY ID;

答案 1 :(得分:0)

如果该表不是太大,您可以尝试递归cte:

create table t(id int, v int)

insert into t values
(1, -4),
(2, -3),
(3, 0),
(4, 0),
(5, 1),
(6, 2),
(7, -7),
(8, 9),
(9, 0),
(10, -5)

;with cte as
(select *, 1 as gr, case when v <> 0 then v end pr 
 from t where id = 1
 union all
 select t.*, c.gr + case when t.v = 0 or(t.v*c.v > 0) then 0 else 1 end,
 case when t.v <> 0 then t.v else c.pr end
 from cte c 
 join t on c.id + 1 = t.id
)
select * from cte
order by id

http://sqlfiddle.com/#!3/92b11/1

或者,如果分组数量可能不是正数或连续数:

;with cte as(select *, ( select case when t.v <> 0 then t.v
                                     else(select top 1 ti.v from t ti 
                                          where ti.id < t.id and ti.v <> 0
                                          order by ti.id desc)
                                end) nv
             from t)
select *, 
       row_number() over(order by case when nv < 0 then 1 else 2 end),
       id - row_number() over(order by case when nv < 0 then 1 else 2 end, id)
from cte
order by id

http://sqlfiddle.com/#!3/92b11/2