Лучший способ подсчитать количество потомков из массива предков? - PullRequest
0 голосов
/ 18 ноября 2018

Таблица tree - это пример таблицы с массивом предков в PostgreSQL 8.3 +:

----+-----------
 id | ancestors 
----+-----------
  1 | {}
  2 | {1}
  3 | {1,2}
  4 | {1}
  5 | {1,2}
  6 | {1,2}
  7 | {1,4}
  8 | {1}
  9 | {1,2,3}
 10 | {1,2,5}

для того, чтобы получить каждый номер счетчика потомка, я могу сделать это:

SELECT 1 AS id, COUNT(id) AS descendant_count FROM tree WHERE 1 = ANY(ancestors)
  UNION
SELECT 2 AS id, COUNT(id) AS descendant_count FROM tree WHERE 2 = ANY(ancestors)
  UNION
SELECT 3 AS id, COUNT(id) AS descendant_count FROM tree WHERE 3 = ANY(ancestors)
  UNION
SELECT 4 AS id, COUNT(id) AS descendant_count FROM tree WHERE 4 = ANY(ancestors)
  UNION
SELECT 5 AS id, COUNT(id) AS descendant_count FROM tree WHERE 5 = ANY(ancestors)
  UNION
SELECT 6 AS id, COUNT(id) AS descendant_count FROM tree WHERE 6 = ANY(ancestors)
  UNION
SELECT 7 AS id, COUNT(id) AS descendant_count FROM tree WHERE 7 = ANY(ancestors)
  UNION
SELECT 8 AS id, COUNT(id) AS descendant_count FROM tree WHERE 8 = ANY(ancestors)
  UNION
SELECT 9 AS id, COUNT(id) AS descendant_count FROM tree WHERE 9 = ANY(ancestors)
  UNION
SELECT 10 AS id, COUNT(id) AS descendant_count FROM tree WHERE 10 = ANY(ancestors)

и получите результат как:

----+------------------
 id | descendant_count
----+------------------
  1 | 9
  2 | 5
  3 | 1
  4 | 1
  5 | 1
  6 | 0
  7 | 0
  8 | 0
  9 | 0
 10 | 0

Полагаю, должно существовать более короткое или умное выражение запроса, чтобы получить тот же результат, возможно ли это? Может быть, как WITH RECURSIVE или создать функцию с циклом для генерации запроса?

Ответы [ 2 ]

0 голосов
/ 18 ноября 2018

Ваш набор союзов буквально просто самостоятельное объединение ...

SELECT
    tree.id,
    COUNT(descendant.id) AS descendant_count
FROM
    tree
LEFT JOIN
    tree   AS descendant
        ON tree.id = ANY(descendant.ancestors)
GROUP BY
    tree.id
0 голосов
/ 18 ноября 2018

На первый взгляд выглядит как случай для рекурсивного запроса, но этот проще:
просто распакуйте, сгруппируйте и посчитайте:

SELECT id AS ancestor, COALESCE (a1.id, 0) AS descendants_count
FROM   tree
LEFT   JOIN (
   SELECT a.id, count(*) AS descendant_count
   FROM   tree t, unnest(t.ancestors) AS a(id)
   GROUP  BY 1
   ) a1 USING (id)
ORDER  BY 1;

И, чтобы включить предков без каких-либо потомков, добавьте LEFT JOIN.

Существует неявное LATERAL объединение с функцией, возвращающей множество unnest(). См:

За исключением:
Если вы когда-нибудь окажетесь в затруднительном положении, где вам действительно придется использовать несколько UNION предложений, рассмотрите UNION ALL. Смотрите:

...