Рекурсивная проблема CTE - PullRequest
0 голосов
/ 06 июня 2018

У меня возникли проблемы с подходом CTE к выполнению задачи.У меня уже есть метод цикла, и он достаточно быстрый, но я хотел сделать его надлежащим образом, чтобы лучше изучить и понять использование CTE.

SQL:

DECLARE @Income MONEY=125000.00,
    @Active INT=0,
    @Year CHAR(4)='2018'

DECLARE @T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor DECIMAL(6,3))
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;

WITH GradientCTE ([Year], Active, UpperLimit, Factor,[Income],WeightedValue,[Row])  
AS  
(  
    SELECT [Year], Active, UpperLimit, Factor
        ,@Income AS [Income]
        ,CAST(0.0 AS DECIMAL(16,3))AS WeightedValue
        ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
    From  @T
)
SELECT *
FROM GradientCTE
ORDER BY UpperLimit

TLDR версия;токовый выход:

Year    Active  UpperLimit     Factor   Income      WeightedValue   Row
2018    0       5000.000       1.000    125000.000  0.000           1
2018    0       100000.000     0.850    125000.000  0.000           2
2018    0       500000.000     0.800    125000.000  0.000           3
2018    0       999999999.000  0.750    125000.000  0.000           4

Что бы я хотел:

Year    Active  UpperLimit    Factor    Income      WeightedValue   Row
2018    0       5000.000       1.000    125000.000  5000.000        1
2018    0       100000.000     0.850    120000.000  85000.000       2
2018    0       500000.000     0.800     20000.000  16000.000       3
2018    0       999999999.000  0.750         0.000  0.000           4

Объяснено :

В настоящее время логика цикла проходит по заданной строкепо строке и уменьшает @Income на UpperLimit для каждой строки, пока не останется денег.Он использует это, чтобы умножить эту сумму на коэффициент, чтобы получить взвешенную сумму.Итак, в приведенном примере начальный доход составляет 125 000,00.Первые 5000 имеют полный вес (1,00), поэтому мы уменьшаем доход на 5000 и перемещаем следующий ряд, сохраняя итоговое взвешенное значение.Это делается до тех пор, пока доход не станет равным 0. Таким образом, 125 000 должны выйти на (5000 * 1,0) + (100000 * 0,85) + (20000 * 0,80) + (0,00 * 0,75) или на сумму 106 000, если их суммировать.

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Благодаря ответу Росса Буша это привело меня к правильному пути решения проблемы.С точки зрения обслуживания, я думаю, что шаблон цикла проще понять, поэтому я, скорее всего, не буду реализовывать версию CTE, и производительность не является проблемой, поскольку набор данных крошечный.

DECLARE @Income DECIMAL(18,3)=125000.00,
    @Active INT=0,
    @Year CHAR(4)='2018'

DECLARE @T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE 
AS  
(  
    SELECT DISTINCT
        [YEAR],Active,UpperLimit=0.00, Factor = 0.00, [Row] = 0
    FROM @T
    UNION ALL
    SELECT [Year],Active,UpperLimit, Factor
        ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
    From  @T
)
,Reduce AS (
    SELECT 
        [YEAR],Active,CAST(@Income AS DECIMAL(18,3)) AS [RemainingIncome], 
        Row, 
        Factor
        ,UpperLimit
        ,CAST(0.00 AS DECIMAL(18,3)) AS WeightedValue
    FROM GradientCTE
    WHERE UpperLimit=0
    UNION ALL
    SELECT 
        g.[YEAR],g.Active,CASE WHEN CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3))  END AS [RemainingIncome], 
        G.Row,
        g.Factor
        ,g.UpperLimit
        ,CAST(CASE WHEN [RemainingIncome]>G.UpperLimit  THEN G.UpperLimit * G.Factor ELSE R.[RemainingIncome] * G.Factor END AS DECIMAL(18,3)) AS WeightedValue
    FROM GradientCTE G
    INNER JOIN Reduce R ON R.Row = G.Row -1
       AND g.Year=r.Year
       AND g.Active=r.Active
)
SELECT
    *
    -- [Year],Active,SUM(WeightedValue)
FROM Reduce
WHERE [RemainingIncome] >= 0
--GROUP BY [Year],Active
0 голосов
/ 06 июня 2018
         DECLARE @Income MONEY=125000.00,
         @Active INT=0,
         @Year CHAR(4)='2018',
         @vIncome Money = 0

        DECLARE @T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor 
        DECIMAL(6,3))
        INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT 
         '2018',0,5000.0,1.00;
          INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT 
   '2018',0,100000.0,0.85;
   INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT 
  '2018',0,500000.0,0.80;
   INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT 
  '2018',0,999999999.0,0.75;




   Select * ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY 
   UpperLimit ASC) AS [Row],CAST(0.0 AS DECIMAL(16,3))AS Income,CAST(0.0 AS 
   DECIMAL(16,3))AS WeightedValue
   into #tmp from @T t1



    --Select (t2.UpperLimit * t2.Factor),t2.*,t1.Row as prev,t1.UpperLimit 
 --from #tmp t1
  --inner join #tmp t2 on (t1.Row = t2.Row +1)  

   update t2
   set 
   @vIncome = @Income,
    @Income = case when (@Income > t2.UpperLimit) then  
                @Income - t2.UpperLimit 
          else 
                0 
          end,
  t2.Income = @Income,
  t2.WeightedValue = case when (@vIncome > t2.UpperLimit) then 
                            (t2.UpperLimit * t2.Factor) 
                    else

                        @vIncome *t2.Factor
                    end

   from #tmp t1
   inner join #tmp t2 on (t1.Row = t2.Row +1)

  Select * from #tmp  


  drop table #tmp
0 голосов
/ 06 июня 2018

Вы можете уменьшить результаты в другом CTE, рекурсивно.Я добавил UNION с 0 в первый набор, чтобы получить первую строку, показывающую начальный доход.

DECLARE @Income DECIMAL(18,3)=125000.00,
    @Active INT=0,
    @Year CHAR(4)='2018'

DECLARE @T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO @T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE 
AS  
(  
    SELECT ReduceAmount = 0, UpperLimit=0.00, Factor = 0.00, Row = 0
    UNION ALL
    SELECT ReduceAmount = UpperLimit * Factor, UpperLimit, Factor
        ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
    From  @T
)
,Reduce AS (
    SELECT 
        Income = CAST(@Income AS DECIMAL(18,3)), 
        Row, 
        ReduceAmount
    FROM GradientCTE
    WHERE ReduceAmount=0
    UNION ALL
    SELECT 
        Income = CASE WHEN CAST(Income - G.ReduceAmount AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST(Income - G.ReduceAmount AS DECIMAL(18,3))  END, 
        G.Row,
        G.ReduceAmount
    FROM GradientCTE G
    INNER JOIN Reduce R ON R.Row = G.Row -1
)

SELECT  * FROM Reduce
WHERE
    Income >= 0
...