通过外键的相关产品的SQL计数

时间:2013-11-29 10:06:14

标签: sql

我们需要构建一个树形视图,其中包含组结构和适合其下每个级别的所有产品的计数,如:

1 - Drink related (120)
> 11 - Plastic Cups (70)
  > 111 - Vending Machine Cups (20)
     > 1111 - Vending Machine Cups 100-150cc (12)
        o Cup 1
        o Cup 2
        etc..
  > 112 - Party Cups (25)
  > 113 - Childrens Cups (25)
> 12 - Paper Cups (50)
2 - Food related (198)
> 21 - Plastic Plates (75)
etc...

所以,我们有两张桌子。一个拥有产品,一个拥有产品的组结构。每个产品行仅包含指向其所属组行的ID的直接外键链接,因此例如产品ID 42231具有对组ID 4的引用,因为它是具有特定大小的透明塑料杯。产品可以适用于任何组级别,如果它不适合特定类别,则不一定总是在第4组级别。 (例如,一个新的饮料杯系列可能被倾倒在Group ID 1“Drink Related”中,直到几个月后它最终获得自己的类别。)

组表(当前)有1800行,基本上形成一个类别树。每个组ID都是字母数字,因为有些组的变体太多而无法使用数字,所以:

ID     Gp1     Gp2     Gp3     Gp4     Desc
1      1       0       0       0       Drink Related
2      1       11      0       0       Plastic Cups
3      1       11      111     0       Vending machine cups
4      1       11      111     1111    Vending machine cups 100-150cc

如果我只想显示每个ID中的确切产品数量,我可以这样做:

select *,
(select count(1) 
    from products
    where groupID=g.id and isDeleted=0) 
    as groupProductCount
 from groups g
 order by g.group1, g.group2, g.group3, g.group4

...但我正在进行更加递归的计数,其中显示所有低于当前水平的产品的数量,所以我一眼就可以看到第1组中有120种与饮料相关的产品,而不是3种直接目前在组ID 1中。

就我个人而言,我认为我必须让DBA将4组级别添加到产品记录中,否则对于组表中的每条记录,我必须确定我们处于哪个级别(未使用级别的零,因此组4中的零表示我们处于级别3,组3中的零表示我们处于级别2等,然后扫描每个产品记录(当前为10,000并且正在增长)以查看是否它所属的组(通过外键组ID读取)的级别与我试图计算的当前组级别记录相匹配。

我看不出只有产品记录中的组ID才能有效实现这一目标。我在这里还是我错过了一些明显的东西?

1 个答案:

答案 0 :(得分:0)

这是一个强力解决方案。 (我假设是SqlServer,但移植到其他人并不困难。)

WITH LeafGroup AS (
    SELECT Id
          ,CASE WHEN Gp4 <> '0' 
                THEN Gp4
                ELSE CASE WHEN Gp3 <> '0'
                          THEN Gp3
                          ELSE CASE WHEN Gp2 <> '0'
                                    THEN Gp2
                                    ELSE Gp1
                               END
                     END
           END AS GroupId
    FROM Groups
)
-- Build a delimited string from the groups
,DelimitedGroups AS (
    SELECT Id
          ,Gp1 + '|' + 
           CASE WHEN Gp2 = '0' THEN '' ELSE Gp2 + '|' END +
           CASE WHEN Gp3 = '0' THEN '' ELSE Gp3 + '|' END + 
           CASE WHEN Gp4 = '0' THEN '' ELSE Gp4 + '|' END AS Delim
    FROM Groups
)
-- Find ids where the delimited string starts with the same groups
,ConnectedGroups AS (
    SELECT DG1.Id AS TopId
         , DG2.Id AS ConnectedId
    FROM DelimitedGroups DG1
         INNER JOIN DelimitedGroups DG2
             ON LEFT(DG2.Delim, LEN(DG1.Delim)) = DG1.Delim
)
-- Now we can fetch all groups for each Id
,GroupsPerId AS (
    SELECT ConnectedGroups.TopId AS Id
          ,LeafGroup.GroupId
    FROM ConnectedGroups
         INNER JOIN LeafGroup
             ON ConnectedGroups.ConnectedId = LeafGroup.Id
)
-- Count all products for each id
SELECT GroupsPerId.Id
      ,COUNT(1)
FROM GroupsPerId
     LEFT JOIN Products
         ON GroupsPerId.GroupId = Products.GroupId
GROUP BY GroupsPerId.Id