递归计算产品树中的产品权重

时间:2019-02-15 09:45:37

标签: sql sql-server tsql

我正在开发一种用于消费物价指数计算的软件,所有计算的第一步是计算产品类别中的产品权重。这应该在SQL Server端完成。我有MS SQL Server 2008。 产品目录存储在树中:

|id|ProductCode|ParentId|Weight|
--------------------------------
|1 |01         |NULL    |0     |
|2 |01.1       | 1      |0     |
|3 |01.1.1     | 2      |0     |
|4 |01.1.1.101 | 3      |0.14  |
|5 |01.1.1.102 | 3      |0.1   |
|6 |01.1.1.103 | 3      |0.25  |
|7 |01.1.1.104 | 3      |0.02  |
|8 |01.1.2     | 2      |0     |
|9 |01.1.2.201 | 8      |0.05  |
|10|01.1.2.202 | 8      |0.3   |
--------------------------------

这是我表的简化结构,因此我需要计算类别的权重。

例如:

  • 类别'01 .1.1'的重量=产品重量的总和01.1.1.101 + 01.1.1.102 + 01.1.1.103 + 01.1.1.104 = 0.51
  • 对于类别'01 .1.2'= 01.1.2.201 + 01.1.2.201 = 0.35
  • 对于类别01.1 = 01.1.1 +01.1.2的总和= 0.51 +0.35 = 0.86
  • 对于类别01 =总和为01.1 + 01.2(在我的示例中未显示)

我的问题实际上是,我无法计算父类别的权重。

也许有人可以帮助我解决我的问题? 谢谢!

3 个答案:

答案 0 :(得分:1)

这可以通过沿ParentId链跟踪的递归方法来完成。在这种情况下,我将从叶节点开始(没有行使用此ID作为父ID),并在向上移动链时计算总和。

但是-由于存在路径 ProductCode,使用这样的方法可能会更容易:

DECLARE @mockup TABLE(id INT, ProductCode VARCHAR(100),ParentId INT,[Weight] DECIMAL(10,4));
INSERT INTO @mockup VALUES
 (1,'01'         ,NULL,0     )
,(2,'01.1'       ,1   ,0     )
,(3,'01.1.1'     ,2   ,0     )
,(4,'01.1.1.101' ,3   ,0.14  )
,(5,'01.1.1.102' ,3   ,0.1   )
,(6,'01.1.1.103' ,3   ,0.25  )
,(7,'01.1.1.104' ,3   ,0.02  )
,(8,'01.1.2'     ,2   ,0     )
,(9,'01.1.2.201' ,8   ,0.05  )
,(10,'01.1.2.202',8   ,0.3   );

DECLARE @depth INT=3;

SELECT groupCode
      ,*
FROM @mockup 
CROSS APPLY(SELECT CAST('<x>' + REPLACE(ProductCode,'.','</x><x>') + '</x>' AS XML)
                  .query(N'for $frgmt in /x[position()<=sql:variable("@depth")]/text() 
                           return <y>{concat(".",$frgmt)}</y>')
                  .value('substring(.,2,1000)','nvarchar(max)')) A(groupCode);

这使您可以定义一个 depth 并在那里进行下一步。

您可以创建一个cte,将其分组以获得不同的代码并加总

DECLARE @depth INT=3;

WITH cte As
(
    SELECT *
    FROM @mockup 
    CROSS APPLY(SELECT CAST('<x>' + REPLACE(ProductCode,'.','</x><x>') + '</x>' AS XML)
                      .query(N'for $frgmt in /x[position()<=sql:variable("@depth")]/text() 
                               return <y>{concat(".",$frgmt)}</y>')
                      .value('substring(.,2,1000)','nvarchar(max)')) A(groupCode)
)
SELECT groupCode
      ,(SELECT SUM(cte2.[Weight]) FROM cte cte2 WHERE cte2.groupCode LIKE cte.groupCode + '%')
FROM cte
GROUP BY groupCode

结果

groupCode   SumOfWeigth
01          0.8600
01.1        0.8600
01.1.1      0.5100
01.1.2      0.3500

答案 1 :(得分:1)

我认为Joakim的回答没有给出正确的结果。

也许这行得通:

-- Use a safe database
use tempdb

if object_id('Products') is not null drop table Products

create table Products (
    id int,
    ProductCode varchar(100),
    ParentId int,
    Weight float
)

-- Original sample data
insert into Products (id,ProductCode, ParentId, Weight)
SELECT 1 ,'01',        NULL    ,'0'      UNION ALL
SELECT 2 ,'01.1'       , '1'      ,'0'      UNION ALL
SELECT '3 ','01.1.1',' 2      ','0     ' UNION ALL
SELECT '4 ','01.1.1.101',' 3      ','0.14  ' UNION ALL
SELECT '5 ','01.1.1.102',' 3      ','0.1   ' UNION ALL
SELECT '6 ','01.1.1.103',' 3      ','0.25  ' UNION ALL
SELECT '7 ','01.1.1.104',' 3      ','0.02  ' UNION ALL
SELECT '8 ','01.1.2',' 2      ','0     ' UNION ALL
SELECT '9 ','01.1.2.201',' 8      ','0.05  ' UNION ALL
SELECT '10','01.1.2.202',' 8      ','0.3   ' 

-- Extra test data
insert into Products (id,ProductCode, ParentId, Weight)
SELECT 11 ,'01.2',        1    ,'1.1'      

-- Calculate result
select b.ProductCode, SUM(a.Weight) as WeightSum
from Products a
join Products b on b.ProductCode = left(a.ProductCode, len(b.ProductCode))  and len(b.ProductCode) < len(a.ProductCode)
group by b.ProductCode

结果:

ProductCode WeightSum
01  1,96
01.1    0,86
01.1.1  0,51
01.1.2  0,35

答案 2 :(得分:1)

假设ProductCode包含层次结构,则无需递归即可执行此操作。这是使用OUTER APPLY的方法:

select p.*, (case when p.weight = 0 then pchild.weight else p.weight end) as weight
from products p outer apply
     (select sum(pchild.weight) as weight
      from products pchild
      where p.weight = 0 and
            pchild.productCode like p.productCode  + '.%'
     ) pchild;

还有一个db<>fiddle

您实际上可以将SELECT简化为:

select p.*, coalesce(pchild.weight, p.weight) as weight