2015-08-30 3 views
2

У меня есть таблица под названием PROD_COST с 5 полями:Альтернативные отрицательные и положительные значения

(ID, Duration, Cost, COST_NEXT, COST_CHANGE)

Мне нужно дополнительное поле, называемое Groups для агрегации.

  1. Продолжительность = количество дней, когда цена действительна (1 день = 1row).
  2. Цена = цена продукта в этот день.
  3. -Cost_next = lead (стоимость, 1,0).
  4. Cost_change = Cost_next - Стоимость.

.

+ 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  | 
+----+----------+------+-----------+-------------+-------+ 

Мне нужно группе Groups поля Cost_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 | 

Я был бы очень признателен за любую помощь.

+0

Вы можете иметь несколько последовательных нулей (более одного)? Может ли первая строка иметь нуль? Какой конечный результат должен быть в этих случаях? –

+0

Вам необходимо указать нули в своих данных образца, чтобы уточнить, что вы действительно хотите сделать. Что происходит, когда несколько нулей подряд? Что происходит, когда знаки одинаковы с обеих сторон? –

+0

@GordonLinoff, привет Гордон, Вот фрагмент из моих данных http://s000.tinyupload.com/index.php?file_id=59699169718899539984 (ваш запрос). Но мне нужно http://s000.tinyupload.com/?file_id=99257550972254124566 Вы правильно меня поняли о 0 значениях, они должны быть включены в значения +/-, когда мои данные похожи на -8.2; 0; 0; -8.2, тогда ваш запрос группирует его как: 1; 1; 1; 1, и это правильно, потому что последовательность: минус; 0; 0; минус ", но когда мои данные: -8.2; 0; 0; +7.2, то это должно быть 1; 1; 1; 2 becausesequence:" минус; 0; 0; плюс "Im не очень хорошо в Egnlish, но надеюсь вы понимаете меня – RussianBear7

ответ

1

Адрес SQL Fiddle с решением проблемы.

Я сосредоточусь на вашей проблеме и оставляю только соответствующие столбцы в таблице: ID и Cost_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 для текущей строки и для предыдущей строки с использованием LAG функции. Обратите внимание, что мы отфильтровываем здесь нулевые значения.

CTE_Changes сравнивает знаки текущих и предыдущих строк и наборов Change до 1, если они отличаются. Самая первая строка имеет 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; 

Результат

ID Cost_change Groups 
1  0.000   0 
2  0.000   0 
3 -1.500   0 
4  3.700   1 
5 -6.900   2 
6  1.200   3 
7  0.000   3 
8  0.000   3 
9  2.000   3 
10 3.000   3 
11 -2.700   4 
12 0.000   4 
13 -1.300   4 
14 0.100   5 
15 0.900   5 
16 0.300   5 
17 7.500   5 
18 -5.279   6 
19 5.279   7 
20 0.000   7 
21 -5.279   8 
22 0.000   8 
23 5.279   9 
24 -8.769   10 
25 7.574   11 
26 0.000   11 
27 -5.279   12 
28 -3.490   12 
29 -5.279   12 
30 5.279   13 
31 0.000   13 
32 0.000   13 
33 0.000   13 
34 -5.279   14 
35 0.000   14 
36 0.000   14 
37 5.279   15 
38 -8.769   16 
39 7.574   17 
40 0.000   17 
41 0.000   17 
42 0.000   17 
43 0.000   17 
44 -5.279   18 
45 -3.490   18 
+0

Это не работает, чувак. Если 0 значений между двумя положительными или двумя отрицательными значениями, чем это разделяет их ... Например: 2.1; 0; 0; 1.8 Группы: 1; 1; 1; 2, Но это должны быть Группы: 1; 1; 1; 1; – RussianBear7

+0

Хм ... ОК. Я об этом не думал. Позвольте мне посмотреть, что делать .... –

+0

http://s000.tinyupload.com/? file_id = 86112541269641548360 Вы знаете русский язык? – RussianBear7

0

Если таблица не слишком большой, вы можете попробовать рекурсивное ОТВ:

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

Смежные вопросы