Вопрос

У меня есть таблица, похожая на эту:

CREATE TABLE example (
  id integer primary key,
  name char(200),
  parentid integer,
  value integer);

Я могу использовать поле parentid для упорядочивания данных в виде древовидной структуры.

А теперь вот что я никак не могу понять.Учитывая родительский идентификатор, возможно ли написать инструкцию SQL для суммирования всех полей значений под этим родительским идентификатором и рекурсии вниз по ветви дерева?

Обновить: Я использую PosgreSQL, поэтому модные функции MS-SQL мне недоступны.В любом случае, я бы хотел, чтобы это рассматривалось как общий SQL-вопрос.

Кстати, я очень впечатлен, что получил 6 ответов в течение 15 минут после того, как задал вопрос!Перейти к переполнению стека!

Это было полезно?

Решение

Есть несколько способов сделать то, что вам нужно в PostgreSQL.

  • Если вы можете устанавливать модули, посмотрите на содержимое tablefunc.У него есть функция connectby(), которая обрабатывает обход деревьев. http://www.postgresql.org/docs/8.3/interactive/tablefunc.html

  • Также ознакомьтесь с контентом ltree, для использования которого вы могли бы адаптировать свою таблицу: http://www.postgresql.org/docs/8.3/interactive/ltree.html

  • Или вы можете самостоятельно пройти по дереву с помощью функции PL / PGSQL.

Что - то вроде этого:

create or replace function example_subtree (integer)
returns setof example as
'declare results record;
         child record;
 begin
  select into results * from example where parent_id = $1;
  if found then
    return next results;
    for child in select id from example
                  where parent_id = $1
      loop
        for temp in select * from example_subtree(child.id)
        loop
          return next temp;
        end loop;
      end loop;
  end if;
  return null;
end;' language 'plpgsql';

select sum(value) as value_sum
  from example_subtree(1234);

Другие советы

Вот пример скрипта, использующего обычное табличное выражение:

with recursive sumthis(id, val) as (
    select id, value
    from example
    where id = :selectedid
    union all
    select C.id, C.value
    from sumthis P
    inner join example C on P.id = C.parentid
)
select sum(val) from sumthis

Приведенный выше скрипт создает "виртуальную" таблицу под названием sumthis в котором есть столбцы id и val.Он определяется как результат двух выборок, объединенных с union all.

Первый select получает корень (where id = :selectedid).

Второй select повторяет дочерние элементы предыдущих результатов до тех пор, пока не останется ничего, что можно было бы вернуть.

Затем конечный результат может быть обработан как обычная таблица.В этом случае столбец val суммируется.

Начиная с версии 8.4, PostgreSQL имеет поддержка рекурсивных запросов для общих табличных выражений с использованием стандарта SQL WITH синтаксис.

Если вам нужно портативное решение, которое будет работать на любом ANSI SQL-92 RDBMS, вам нужно будет добавить новый столбец в вашу таблицу.

Джо Селко является оригинальным автором Вложенные наборы подход к хранению иерархий в SQL.Вы можете погуглить иерархия "вложенных множеств" чтобы больше узнать о предыстории.

Или вы можете просто переименовать parentid в левый и добавьте правый.

Вот моя попытка обобщить вложенные множества, которая будет крайне неудачной, потому что я не Джо Селко:SQL - это язык, основанный на множествах, а модель смежности (хранение родительского идентификатора) НЕ является представлением иерархии на основе множеств.Следовательно, не существует чистого метода, основанного на множестве, для запроса схемы смежности.

Однако, в последние годы большинство крупных платформ внедрили расширения именно для решения этой проблемы.Поэтому, если кто-то ответит с решением, специфичным для Postgres, используйте это во что бы то ни стало.

Стандартный способ выполнения рекурсивного запроса в SQL являются рекурсивными CTE. PostgreSQL поддерживает их с тех пор, как 8.4.

В более ранних версиях вы можете написать рекурсивную функцию, возвращающую набор:

CREATE FUNCTION fn_hierarchy (parent INT)
RETURNS SETOF example
AS
$$
        SELECT  example
        FROM    example
        WHERE   id = $1
        UNION ALL
        SELECT  fn_hierarchy(id)
        FROM    example
        WHERE   parentid = $1
$$
LANGUAGE 'sql';

SELECT  *
FROM    fn_hierarchy(1)

Смотрите эту статью:

Если вы используете SQL Server 2005, есть действительно классный способ сделать это, используя обычные табличные выражения.

Это избавляет от всей рутинной работы по созданию временной таблицы и в основном позволяет вам делать все это с помощью всего лишь WITH и UNION .

Вот хороший учебник:

http://searchwindevelopment.techtarget.com/tip/0 ,289483,sid8_gci1278207,00.html

используйте общее табличное выражение.

Возможно, потребуется указать, что это только SQL Server 2005 или выше. Дейл Рэган

вот статья о рекурсии с помощью SqlTeam без общих табличных выражений.

Следующий код компилируется, и он тестируется нормально.

create or replace function subtree (bigint)
returns setof example as $$
declare
    results record;
    entry   record;
    recs    record;
begin
    select into results * from example where parent = $1;
    if found then
        for entry in select child from example where parent = $1 and child  parent loop
            for recs in select * from subtree(entry.child) loop
                return next recs;
            end loop;
        end loop;
    end if;
    return next results;
end;
$$ language 'plpgsql';

Условие "ребенок <> parent" необходим в моем случае, потому что узлы указывают на самих себя.

Получайте удовольствие :)

В Oracle есть "НАЧАТЬ С" и "ПОДКЛЮЧИТЬСЯ С ПОМОЩЬЮ".

select 
    lpad(' ',2*(level-1)) || to_char(child) s

from 
    test_connect_by 

start with parent is null
connect by prior child = parent;

http://www.adp-gmbh.ch/ora/sql/connect_by.html

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

общий SQL-вопрос

тогда реализация SQL довольно проста, поскольку SQL'99 допускает линейную рекурсию в спецификации (хотя я считаю, что ни одна СУБД не реализует стандарт полностью) через WITH RECURSIVE заявление.Так что с теоретической точки зрения мы можем сделать это прямо сейчас.

Ни один из примеров не сработал нормально для меня, поэтому я исправил это следующим образом:

declare
    results record;
    entry   record;
    recs    record;
begin
    for results in select * from project where pid = $1 loop
        return next results;
        for recs in select * from project_subtree(results.id) loop
            return next recs;
        end loop;
    end loop;
    return;
end;

это SQL Server?Не могли бы вы написать хранимую процедуру TSQL, которая перебирает и объединяет результаты вместе?

Мне также интересно, существует ли способ сделать это только для SQL.Судя по тому, что я помню из моего класса географических баз данных, там должно быть.

Я думаю, что в SQL 2008 это проще с Иерархический идентификатор

Если вам нужно хранить произвольные графики, а не только иерархии, вы можете отложить Postgres в сторону и попробовать использовать базу данных графиков, такую как Аллегрография:

Все в базе данных graph хранится в виде тройки (исходный узел, ребро, целевой узел), и это дает вам первоклассную поддержку для манипулирования структурой graph и запросов к ней с использованием SQL-подобного языка.

Он плохо интегрируется с чем-то вроде Hibernate или Django ORM, но если вы серьезно относитесь к графическим структурам (а не только к иерархиям, как дает вам модель вложенного набора), проверьте это.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top