Для простой структуры данных, такой как:

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

Для справки дерево иерархии выглядит так:

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

Я хочу посчитать количество детей на уровне. Итак, я бы получил новый столбец «NoOfChildren» вот так:

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

Я читал кое-что об иерархических данных, но почему-то застрял на нескольких внутренних соединениях по родительским идентификаторам. Может, здесь кто-нибудь сможет мне помочь.

23
Dennis G 26 Фев 2010 в 12:51
Ваше иерархическое дерево не соответствует вашим входным данным.
 – 
Lieven Keersmaekers
26 Фев 2010 в 13:35
И ваш вывод, похоже, не соответствует вашей иерархии . Глядя на вашу иерархию, я предполагаю, что ID 4 и 7 имеют 0 детей.
 – 
Lieven Keersmaekers
26 Фев 2010 в 13:38
Вы совершенно правы, перепутались с деревом иерархии + вывод, исправлю
 – 
Dennis G
26 Фев 2010 в 15:55

2 ответа

Лучший ответ

Использование CTE даст вам то, что вы хотите.

  • Рекурсивно перебрать всех дочерних элементов, запомнив корень.
  • COUNT элементы для каждого корня.
  • JOIN это снова с исходной таблицей для получения результатов.

Тестовые данные

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

;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
27
Lieven Keersmaekers 26 Фев 2010 в 13:27
Спасибо за четкий ответ, отличное решение. Хотите просветить меня по поводу использования ";"? Иногда я получаю синтаксическую ошибку при использовании операторов WITH, что у меня нет этого мерзкого ";". Всегда ли мне нужно «экранировать» инструкции WITH с добавлением;?
 – 
Dennis G
26 Фев 2010 в 16:00
3
@moontear: предложение WITH, определяющее CTE, может смешиваться с предложением WITH, которое определяет подсказку таблицы. Первый оператор следует явно разделять точкой с запятой, если он не является первым оператором в пакете.
 – 
Quassnoi
26 Фев 2010 в 16:10
+1, почти так же сложно, как CROSS APPLY с использованием функции разделения, хотя и не так часто ;-)
 – 
KM.
26 Фев 2010 в 16:20
1
@moontear, просто возьмите за привычку всегда кодировать точку с запятой рядом с командой WITH, например: ;WITH, и у вас никогда не будет проблем (как в коде @ Lieven)
 – 
KM.
26 Фев 2010 в 16:22
1
- Вы можете добавить следующую подсказку запроса, чтобы пройти уровень рекурсии по умолчанию SELECT ... FROM ... OPTION (MAXRECURSION 200)
 – 
Lieven Keersmaekers
2 Июн 2021 в 15:01

Рассмотрите возможность использования модифицированного способа обхода дерева предварительного заказа для хранения иерархических данных. См. http://www.sitepoint.com/hierarchical-data-database/.

Тогда определение количества дочерних элементов для любого узла становится простым:

SELECT (right-left-1) / 2 AS num_children FROM ...
6
mxsscott 18 Ноя 2012 в 14:57
Вы экономите мне много времени. Лучший ответ для меня :)
 – 
andrzej1_1
17 Июн 2014 в 10:20
Эта система великолепна! К сожалению, мне это не подходит, так как мне приходится работать с установленной системой, но я обязательно буду помнить об этом для новых разработок.
 – 
Manuel Hoffmann
5 Дек 2019 в 17:38