Можно ли сделать рекурсивный запрос SQL? - PullRequest
62 голосов
/ 10 сентября 2008

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

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

Я могу использовать поле парентид, чтобы упорядочить данные в древовидную структуру.

Теперь вот что я не могу отработать. Имея парентиду, можно ли написать оператор SQL, чтобы сложить все поля значений в этой парентиде и переписать ветвь дерева?

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

Кстати, я очень впечатлен тем, что получил 6 ответов в течение 15 минут после постановки вопроса! Переполнение стека Go!

Ответы [ 14 ]

42 голосов
/ 18 апреля 2011

Вот пример сценария с использованием общего табличного выражения:

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 суммируется.

34 голосов
/ 14 февраля 2009

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

15 голосов
/ 10 сентября 2008

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

Джо Селко - автор вложенных наборов подхода к хранению иерархий в SQL. Вы можете Google иерархия "вложенных наборов" , чтобы понять больше о фоне.

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

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

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

11 голосов
/ 10 сентября 2008

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

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

  • Также проверьте ltree contrib, который вы можете адаптировать для своей таблицы: 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);
10 голосов
/ 11 января 2011

Стандартный способ сделать рекурсивный запрос в 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)

См. Эту статью:

5 голосов
/ 10 сентября 2008

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

Он берет на себя всю тяжелую работу по созданию временной таблицы и, в основном, позволяет вам делать все это только с WITH и UNION.

Вот хороший урок:

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

5 голосов
/ 10 сентября 2008

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

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

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

2 голосов
/ 04 февраля 2009

Следующий код компилируется и проверяется ОК.

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';

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

Веселитесь:)

1 голос
/ 11 января 2011

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

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;
1 голос
/ 18 марта 2009

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

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

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

...