Перебирать «связанный список» в одном SQL-запросе? - PullRequest
3 голосов
/ 08 августа 2009

У меня есть таблица, которая выглядит в основном так:

id | redirectid | data

где redirectid - это идентификатор другой строки. В основном, если строка выбрана, и у нее есть redirectid, тогда данные redirectid должны использоваться вместо нее. Может быть несколько переадресаций, пока redirectid не станет NULL. По сути, эти перенаправления образуют связанный список в таблице. Что я хотел бы знать, так это то, что при наличии идентификатора можно настроить запрос sql, который будет перебирать все возможные перенаправления и возвращать идентификатор в конце «списка»?

Это использует Postgresql 8.3, и я хотел бы сделать все возможное в SQL-запросе, если это возможно (а не повторять в моем коде).

Ответы [ 2 ]

2 голосов
/ 08 августа 2009

Поддерживает ли postgresql рекурсивные запросы, в которых используются предложения WITH? Если так, что-то подобное может сработать. (Если вы хотите получить проверенный ответ, предоставьте в своем вопросе несколько операторов CREATE TABLE и INSERT, а также результаты, необходимые для выборки данных в INSERT.)

with Links(id,link,data) as (
  select
    id, redirectid, data
  from T
  where redirectid is null
  union all
  select
    id, redirectid, null
  from T
  where redirectid is not null
  union all
  select
    Links.id,
    T.redirectid,
    case when T.redirectid is null then T.data else null end
  from T
  join Links
  on Links.link = T.id
)
  select id, data
  from Links
  where data is not null;

Дополнительные замечания:

:( Вы можете реализовать рекурсию самостоятельно на основе выражения WITH. Я не знаю синтаксиса postgresql для последовательного программирования, так что это немного псевдо:

Вставьте результат этого запроса в новую таблицу с именем Links:

select
    id, redirectid as link, data, 0 as depth
  from T
  where redirectid is null
  union all
  select
    id, redirectid, null, 0
  from T
  where redirectid is not null

Также объявите целое число :: глубины и инициализируйте его до нуля. Затем повторяйте следующее, пока строки больше не будут добавлены в ссылки. Ссылки будут содержать ваш результат.

  increment ::depth;
  insert into Links
  select
    Links.id,
    T.redirectid,
    case when T.redirectid is null then T.data else null end,
    depth + 1
  from T join Links
  on Links.link = T.id
  where depth = ::depth-1;
end;

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

Обратите внимание, что это не прекратится, если есть какие-либо циклы (перенаправления, которые в конечном итоге являются круговыми).

1 голос
/ 09 августа 2009

Я бы сказал, что вы должны создать пользовательскую функцию в этом ключе:

create function FindLastId (ID as integer) returns integer as $$
    declare newid integer;
    declare primaryid integer;
    declare continue boolean;
    begin
        set continue = true;
        set primaryid = $1;
        while (continue)
            select into newid redirectid from table where id = :primaryid;

            if newid is null then
                set continue = false;
            else
                set primaryid = :newid;
            end if;
        end loop;

        return primaryid;
    end;
    $$ language pgplsql;

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

select id, FindLastId(id) as EndId from table

На столе вот так:

id     redirectid    data
1          3          ab
2        null         cd
3          2          ef
4          1          gh
5        null         ij

Это вернет:

id    EndId
1       2
2       2
3       2
4       2
5       5

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

...