views:

239

answers:

1

Hello,

for a simple data structure such as so:

ID    parentID    Text        Price
1                 Root
2     1           Flowers
3     1           Electro
4     2           Rose        10
5     2           Violet      5
6     4           Red Rose    12
7     3           Television  100
8     3           Radio       70
9     8           Webradio    90

For reference, the hierarchy tree looks like this:

ID    Text        Price
1     Root
|2    Flowers
|-4   Rose        10
| |-6 Red Rose    12
|-5   Violet      5
|3    Electro
|-7   Television  100
|-8   Radio       70
  |-9 Webradio    90

I'd like to count the number of children per level. So I would get a new column "NoOfChildren" like so:

ID    parentID    Text        Price  NoOfChildren
1                 Root               8
2     1           Flowers            3
3     1           Electro            3
4     2           Rose        10     1
5     2           Violet      5      0
6     4           Red Rose    12     0
7     3           Television  100    0
8     3           Radio       70     1
9     8           Webradio    90     0

I read a few things about hierarchical data, but I somehow get stuck on the multiple inner joins on the parentIDs. Maybe someone could help me out here.

moon

+7  A: 

Using a CTE would get you what you want.

  • Recursively go through all children, remembering the root.
  • COUNT the items for each root.
  • JOIN these again with your original table to produce the results.

Test Data

DECLARE @Data TABLE (
  ID INTEGER PRIMARY KEY
  , ParentID INTEGER
  , Text VARCHAR(32)
  , Price INTEGER
)

INSERT INTO @Data
  SELECT 1, Null, 'Root', NULL
  UNION ALL SELECT 2, 1, 'Flowers', NULL
  UNION ALL SELECT 3, 1, 'Electro', NULL
  UNION ALL SELECT 4, 2, 'Rose', 10
  UNION ALL SELECT 5, 2, 'Violet', 5
  UNION ALL SELECT 6, 4, 'Red Rose', 12
  UNION ALL SELECT 7, 3, 'Television', 100
  UNION ALL SELECT 8, 3, 'Radio', 70
  UNION ALL SELECT 9, 8, 'Webradio', 90

SQL Statement

;WITH ChildrenCTE AS (
  SELECT  RootID = ID, ID
  FROM    @Data
  UNION ALL
  SELECT  cte.RootID, d.ID
  FROM    ChildrenCTE cte
          INNER JOIN @Data d ON d.ParentID = cte.ID
)
SELECT  d.ID, d.ParentID, d.Text, d.Price, cnt.Children
FROM    @Data d
        INNER JOIN (
          SELECT  ID = RootID, Children = COUNT(*) - 1
          FROM    ChildrenCTE
          GROUP BY RootID
        ) cnt ON cnt.ID = d.ID
Lieven
Thanks for your explicit answer, perfect solution. Want to enlighten me on the usage of ";"? I sometimes get a syntax error when using WITH statements, that I don't have that nasty ";".Do I always have to "escape" WITH statements with an appended ;?
moontear
@moontear: `WITH` clause that defines a `CTE` may mix with the `WITH` clause that defines a table hint. The former should be explicitly separated with a semicolon if it's not the first statement in the batch.
Quassnoi
+1, almost as hard as a CROSS APPLY using a split function, not as common though ;-)
KM
@moontear, just get in the habit of always coding the semicolon adjacent to the WITH command like: `;WITH` and you'll never have a problem (just like in @Lieven's code)
KM