Конференция "Базы" » Запрос на SQL для выборки иерархического списка [MSSQL]
 
  • SuperChel (19.06.08 22:19) [0]
    Приветствую Вас Мастера.
    Есть простая таблица и для простоты пример: у таблицы два поля id и id_roditel в id храниться уникальное значение, а в id_roditel храниться ссылка на поле id. Как вы уже поняли это связный иерархический список. Хочу выбрать всю ветку потомков, указав какой то конкретный узел. Наведите на мысль как составить такой sql запрос.
  • MsGuns © (19.06.08 23:06) [1]
    Стандартный алгоритм рекурсии - в инете масса примеров
  • SuperChel (20.06.08 00:32) [2]

    > MsGuns ©   (19.06.08 23:06) [1]
    >
    > Стандартный алгоритм рекурсии - в инете масса примеров


    В настоящее время я решаю проблему при помощи хранимой функции- т.е. по сути программно, но заинтересовало как это сделать посредством только sql запроса.
  • ЮЮ © (20.06.08 04:30) [3]
    Если тебя смущает наличие UDF, никто не мешает весь её текст  поместить в SQL компонента.
    Получить нужное одним SELECT, ИМХО, невозможно.
  • Fin (20.06.08 08:31) [4]
    WITH TempTbl AS (SELECT     npp, roditel, 1 AS lv
                             FROM Potrebitel AS A
                             WHERE      (npp = 6)
                            UNION ALL
                            SELECT     node.npp, node.roditel, ft.lv + 1 AS lv
                            FROM Potrebitel AS node INNER JOIN
                            TempTbl AS ft ON node.roditel = ft.npp AND node.roditel <> node.npp)
    SELECT     npp, roditel, lv
    FROM         TempTbl AS pp



    в npp=6 помещаешь номер узла и в результате запроса получаешь всех потомков. Очень удобная штука.

    Почитай тут http://www.sqlbooks.ru/readarticle.aspx?part=02&file=sql200509
  • Johnmen © (20.06.08 08:56) [5]
    Не существует решения в рамках стандартных конструкций SQL. И быть не может, ибо "иерархические списки" (т.е. дерево) никакого отношения к реляционной модели не имеют.
    Но в некоторых диалектах SQL существуют специально введенные искусственные конструкции, специально для этого предназначенные.
  • Fin (20.06.08 09:10) [6]
    Да, мой пример справедлив для MS SQL 2005.
    хотя в приведённой ссылке упоминается Стандарт ISO SQL:1999 - предусматривающий рекрусивные запросы - может с их помощю можно попробывать.
  • stas © (20.06.08 09:35) [7]
    SuperChel   (19.06.08 22:19)  
    Можно без рекурсии,  в функции временная таблица + цикл, могу для примера опубликовать свою функцию. Кстати работает быстрее чем WITH на 2005. Но все равно лучше  написать триггера, которые будут наполнять/изменять таблицу содержащую нужный набор записей.
  • Fin (20.06.08 09:51) [8]
    Кстати вариант с триггером ИМХО очень перспективный вариант.
  • Ega23 © (20.06.08 10:30) [9]

    > Кстати вариант с триггером ИМХО очень перспективный вариант.


    От ситуации зависит. Может быть и перспективным. А может - и нет.
  • clickmaker © (20.06.08 10:39) [10]
    чё-то я не понял: причем тут триггера?
  • MsGuns © (20.06.08 10:42) [11]
    >SuperChel   (20.06.08 00:32) [2]
    >как это сделать посредством только sql запроса.

    Никак. Делается две хранимки - одна "складывает" полученных "деток" (входимость в парент непосредственная) в результсет, вторая - выборка "деток", которая вызывается рекурсивно.

    >stas ©   (20.06.08 09:35) [7]
    >Можно без рекурсии,  в функции временная таблица + цикл, могу для примера >опубликовать свою функцию. Кстати работает быстрее чем WITH на 2005. Но все >равно лучше  написать триггера, которые будут наполнять/изменять таблицу >содержащую нужный набор записей.

    1. Через временную таблицу не быстрее
    2. Зачем там with ?
    3. Каким боком там вообще триггеры ?
  • stas © (20.06.08 10:52) [12]
    MsGuns ©   (20.06.08 10:42) [11]

    1. быстрее чем with
    2. with синтаксис 2005
    3.Тот набор записей, который содержит иеархический список, хранится непосредственно в таблице, которая в свою очередь наполняется триггерами, таким образом при выборке используется таблица, без всяких функций и рекурсий.
  • Ega23 © (20.06.08 10:57) [13]

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


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

    В частном маленьком случае этот вариант ещё может пригодиться, в общем и целом - фтопку.
  • MsGuns © (20.06.08 11:05) [14]
    >stas ©   (20.06.08 10:52) [12]
    >2. with синтаксис 2005

    Чем with 2005-го отличается от with 2000-го ?

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

    Надо полагать, что таблица постоянная (иначе как в нее встромить триггеры) ? Тогда вопрос на засыпку - как эта таблица будет "обслуживать" несколько одновременных запросов на разузлование ?

    Вы придумали велосипед с треугольными колесами и вторым рулем вместо седла. И пытаетесь его "запатентовать" ;)
  • stud © (20.06.08 11:05) [15]

    > Таблица изменяется 3 раза в секунду, а запрос на выборку
    > данных раз в час нужен.
    > Тоже триггеры будешь лепить?
    > А многопользовательский доступ к этой временной таблице?
    >  А её содержимое при перезагрузке сервера?

    ну тут наверное типа временная таблица с тими триггерами (есть такое в 2005?) и при добавлении записи в нее триггер вытаскивает типа "детей", добавляет их в нее и получается в общем и целом бред, хотя работать будет
  • stas © (20.06.08 11:06) [16]
    Ega23 ©   (20.06.08 10:57) [13]

    что имеете ввиду под маленьким случаем?
    Я предложил несколько вариантов выбираемый вариант зависит от задачи.
    Зачастую все происходит наоборот, таблица корректируется не так уж и часто, а выборка используется часто.
    Пример разузловка оборудования на большом предприятии, поступление нового оборудование происходит раз в день. А ремонты существующего и затраты на ремонты постоянно...
    А многопользовательский доступ разрулит MSSQL.
    > А её содержимое при перезагрузке сервера?
    Вы запутались...
    1-й вариант наполняем в нутри функции временную таблицу и возвращаем результат
    2-й в постоянную ведем запись триггерами.
  • stas © (20.06.08 11:08) [17]
    Внимательно читайте:
    1. Можно без рекурсии,  в функции временная таблица + цикл, могу для примера опубликовать свою функцию. Кстати работает быстрее чем WITH на 2005.
    2. Но все равно лучше  написать триггера, которые будут наполнять/изменять таблицу содержащую нужный набор записей.

    1 и 2 не связаны между собой!!!
  • stud © (20.06.08 11:08) [18]

    > 2-й в постоянную ведем запись триггерами

    ?????? и что получится в результате
    через некоторое время она станет копией основного справочника, создавать еще одну "постоянную" таблицу?
  • MsGuns © (20.06.08 11:11) [19]
    В ИБ, где есть suspend (т.е. отсылка клиенту записи сразу после ее выборки без предварительного кэширования во врем.таблицу, которую затем в готовом виде сервер и передает клиенту,- как в мсскл), весь алгоритм можно с успехом реализовать одной-единственной хранимкой, которая вызывает саму себя рекурсивно.

    Работает ясно, просто и быстро.

    Пример можно подсмотреть в "Мир интербэйз" Вострикова-Ковязина (Часть I, глава ИМХО о хранимках)
  • stas © (20.06.08 11:12) [20]
    она не станет копией справочника. Она станет копией результата отработки рекурсивной ХП
  • Fin (20.06.08 11:14) [21]

    > Ega23 ©   (20.06.08 10:30) [9]
    > > Кстати вариант с триггером ИМХО очень перспективный вариант.
    > От ситуации зависит. Может быть и перспективным. А может
    > - и нет.

    Согласен, но я не уточнил что относительно моих задач, где таблица пополняется крайне редко (единичные случаи в месяц), а вот выборка при расчете в основном и "загружает" сервак.
  • MsGuns © (20.06.08 11:15) [22]
    >stud ©   (20.06.08 11:05) [15]
    >ну тут наверное типа временная таблица с тими триггерами (есть такое в 2005?) и при >добавлении записи в нее триггер вытаскивает типа "детей", добавляет их в нее и

    Вы, извиняюсь, вообще представляете себе, для чего используются триггери и как они работают ?

    >получается в общем и целом бред,

    вот это уже ближе к истине

    >хотя работать будет

    Угу, и подвесит нафиг сервер к чертовой матери.
  • stas © (20.06.08 11:15) [23]
    MsGuns ©   (20.06.08 11:11) [19]
    в MSSQL в хранимках есть 1 минус, результат не так уж просто объеденить с другой таблицей.
  • MsGuns © (20.06.08 11:17) [24]
    >stas ©   (20.06.08 11:15) [23]
    >в MSSQL в хранимках есть 1 минус, результат не так уж просто объеденить с другой >таблицей.

    Сами придумали или кто вумный поделился "опытом" ?
  • stas © (20.06.08 11:17) [25]
    MsGuns ©   (20.06.08 11:15) [22]
    У меня наоборот вешался, пока не перешли на таблицы.
  • stas © (20.06.08 11:21) [26]
    MsGuns ©   (20.06.08 11:17) [24]
    у вас есть простое решение?
  • Правильный-Вася (20.06.08 11:25) [27]

    > наполняется триггерами, таким образом при выборке используется
    > таблица, без всяких функций и рекурсий.

    это и есть завуалированная рекурсия, если ты не догадался
    только рекурсия наполнения, а не чтения
  • stas © (20.06.08 11:26) [28]
    Правильный-Вася   (20.06.08 11:25) [27]

    Читаем жирным
    >таким образом при выборке используется
    > таблица, без всяких функций и рекурсий.
  • stud © (20.06.08 11:32) [29]

    > Вы, извиняюсь, вообще представляете себе, для чего используются
    > триггери и как они работают ?

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


    > Она станет копией результата отработки рекурсивной ХП

    это в случае если данные из нее после получения удаляются.
  • Fin (20.06.08 11:48) [30]

    > stud ©   (20.06.08 11:32) [29]
    > > есть таблица,
    >  в нее запросом добавляется запись с ид узла. далее срабатывает
    > триггер на вставку, который ищет "детей" для добавленной
    > записи, вставляет их в эту же таблицу, опять срабатывает
    > (получается аналог рекурсии)


    На сколько я понимаю триггер наложен на основную таблицу и перестраивает служебную в которой в упрошенном варианте тригерров нету.
  • stas © (20.06.08 11:50) [31]
    stud ©   (20.06.08 11:32) [29]
    Напишу только про Insert
    при добавлении записи в основную таблицу срабатывает триггер, который рекурсивно либо каким-то другим способом находит всех родителей только на вставленную запись, и заполняет другую таблицу. Таким образом у нас рекурсия работает, только на корректировку таблицы и только по тем записям которые связаны с редактируемой.
    Это повшает производительность и не тянет много ресурсов при выборке, но снижает при корректировке таблицы.
  • Ega23 © (20.06.08 12:01) [32]
    Я всё равно продолжаю не понимать, нафига нужен триггер и особенно - временная таблица?
    Если есть такая таблица, которая изменяется крайне редко, но оттуда частые селекты идут - ну и заточи её под селекты изначально. Сделай какой-нибудь AbsoluteLevel и какой-нибудь ChildCount и AllChilsCount (тут можно подумать да в сети почитать).

    А временная таблица в данной ситуации - это вообще жесть. В MSSQL их 2 типа - одни с # - на конкретный spid со временем жизни <= времени жизни spid. Второй тип - с двумя ## - в рамках сервера со временем жизни <= времени жизни самого сервера.
    При разрыве соединения в первом случае и остановке (перезапуске) сервера во втором - ТАБЛИЦЫ БУДУТ ГРОХНУТЫ.

    А в первом случае такая временная таблица будет создаваться для КАЖДОГО КЛИЕНТА.
  • stas © (20.06.08 12:03) [33]
    Ega23 ©   (20.06.08 12:01) [32]
    Ты ничего непонял...
  • stas © (20.06.08 12:06) [34]
    триггер+временная таблица такого нет!
    есть триггер+постоянная таблица это 1 вариант
    есть Multi statement function, которая по своей структуре подразумевает временную таблицу и возвращает ее результат это 2 -й вариант.
  • Ega23 © (20.06.08 12:06) [35]

    > Ega23 ©   (20.06.08 12:01) [32]
    > Ты ничего непонял...
    >


    Я понял всё прекрасно. И вообще, за последние 2.5 года 70% времени программирования серверной части уходило на работу с иерархическими структурами под MSSQL.
    Так что не надо мне тут...  :)
  • stas © (20.06.08 12:08) [36]
    аналагично :)
  • stud © (20.06.08 12:40) [37]

    > при добавлении записи в основную таблицу срабатывает триггер,
    >  который рекурсивно либо каким-то другим способом находит
    > всех родителей только на вставленную запись, и заполняет
    > другую таблицу.

    так это получается для каждого узла своя таблица????
    и если нужно получить на клиента только потомков начиная с текущего узла?
    чето логика пользования такого варианта не совсем понятна.....
  • MsGuns © (20.06.08 12:57) [38]
    Чет его знает, как ты умудряешься повесить сервер..
    У меня разузлование выполняется на "деревянной" таблице с 5 млн.записей, причем вместо ид-ра используются нативные стринги, а кроме того еще из 5 разных таблиц берутся уточняющие данные (замены, техотход, позаказная расцеховка, наименования и прочий "интерьер"). "Дерево" из 30 000 узлов выгребается за 4 мин. Из 10 000 - за 30 сек. MS SQL 2000

    Воистину рыба портится с головы ;)
  • stas © (20.06.08 13:07) [39]
    MsGuns ©   (20.06.08 12:57) [38]
    Сколько пользователей юзает одновременно эти деревья?

    stud ©   (20.06.08 12:40) [37]
    нет.
  • Anatoly Podgoretsky © (20.06.08 13:23) [40]
    > stas  (20.06.2008 13:07:39)  [39]

    А тебе зачем, разве это чтото дает в понимании. Неинтересная это информация.
  • MsGuns © (20.06.08 14:39) [41]
    >stas ©   (20.06.08 13:07) [39]
    >Сколько пользователей юзает одновременно эти деревья?

    Пользователи не "юзают деревья" по той простой причине, что этих деревьев на сервере нетути. Они строятся на клиенте по датасету, полученному с сервера в рез-те разузловки. После выполнения ХП сервер передает НД клиенту и благополучно о нем "забывает". Поэтому можно вести речь только о взможной конкуренции РАСЧЕТОВ.
    Однако влияние этой конкуренции малозаметно, т.к. сервер выполняют еще массу другой работы, да и баз данных на сервере не одна и даже не 10.
  • stas © (20.06.08 15:07) [42]
    MsGuns ©   (20.06.08 14:39) [41]
    Ну, сдесь можно долго спорить.
    У меня было построение дерева с помощью функции и есть статичная таблица, которая наполняется триггерами, серверу значительно полегчало.
  • MsGuns © (20.06.08 15:50) [43]
    >stas ©   (20.06.08 15:07) [42]
    >Ну, сдесь можно долго спорить.

    О чем спорить ? Что изобретенный Вами велосипед с двумя рулями и треугольными колесами лучше обычного ?
    Сервер "тормозил" не от отсутствия "статичной" таблицы, а из-за кривизны алгоритма, который здесь не обсуждается.
    Есои Вы полагаете, что наличие триггеров "ускоряет" работу сервера с таблицей, то Вы сильно заблуждаетесь, особенно если эти самые триггеры еще что-то куда-то вставляют.
  • stas © (20.06.08 16:00) [44]
    MsGuns ©   (20.06.08 15:50) [43]
    изобретенный Велосипед? вы заблуждаетесь, велосипед изобретаете вы, к тому же вы не ответили на мой вопрос по поводу простого решения объединения результатов хп с другой таблицей.
    и что вы называете обычным велосипедом?
  • Ega23 © (20.06.08 16:04) [45]

    > к тому же вы не ответили на мой вопрос по поводу простого
    > решения объединения результатов хп с другой таблицей.


    А что такого? В MSSQL только один способ есть :

    ctreate table #xxx (....)

    Insert into #xxx  exec c_proc

    Select ..... from
     Table T1 inner join #xxx X on (T... = X....)

  • stas © (20.06.08 16:06) [46]
    Ega23 ©   (20.06.08 16:04) [45]
    Ну это же не простой способ. функцию проще объеденить.
  • MsGuns © (20.06.08 16:12) [47]
    >stas ©   (20.06.08 16:00) [44]
    >вы заблуждаетесь, велосипед изобретаете вы,

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

    >к тому же вы не ответили на мой вопрос по поводу простого решения объединения >результатов хп с другой таблицей.

    Из фунции можно вызвать ХП и ее результат связать с любым другим запросом, хранимкой или функцией. Напишите нужную Вам функцию на сервере и с клиента будете писать простой запрос Select from функция

    >и что вы называете обычным велосипедом?

    Стандартный механизм рекурсии, упомянутый в [1], который Вас почему-то вообще не заинтересовал.
    Вы хотите, чтобы я дал Вам готовый универсальный код ? Я его не дам по двум причинам. Во-первых такого кода не существует из-за особенностей скл-серверов, о чем Вам тут неоднократно сообщалось. А во-вторых, учитесь не брать готовое, а искать в указанных Вам направлениях.

    Не сем тему обсуждения считаю исчерпанной
  • Ega23 © (20.06.08 16:22) [48]

    > Ну это же не простой способ. функцию проще объеденить.


    В функции динамический SQL низя использовать.
  • stas © (20.06.08 16:25) [49]
    MsGuns ©   (20.06.08 16:12) [47]
    1. Вы сами противоречите себе, я рассказывал что это делается функцией и что у функции есть  приимущество перед хранимкой...
    2. Вы не вызовите хранимку из функции в MS SQL
    3. Мне ваш код ненужен т.к. у меня есть свой и ненужен мне ваш готовый код у меня их целая куча, и быстрее и правильнее нет  я по крайней мере не находил.
  • stas © (20.06.08 16:32) [50]
    Ega23 ©   (20.06.08 16:22) [48]
    Ну не нужен он в этой функции.
    Я вот непонимаю что вы пытаетесь оспорить
    я предлагаю 2 варианта

    1. Это функция без всякой рекурсии (вобще я непредставляю как можно сделать рекурсивную функцию) Которая по перечисленым выше параметрам превосходит хранимку
    2. Это наполнение постоянной таблицы триггерами.

    2-й вариант на выборке довольно шустрый. И с введением именно 2-го варианта у себя в деревьях серверу на много полегчало.
    А какой вариант выбирать автору это уже его дело.
  • Ega23 © (20.06.08 16:37) [51]

    > (вобще я непредставляю как можно сделать рекурсивную функцию


    А что такого?
  • b z (20.06.08 16:39) [52]

    > 2. Это наполнение постоянной таблицы триггерами.
    Покажите, пож-ста, пример структуры для "дерева", основанный на этом варианте.
  • stas © (20.06.08 16:39) [53]
    Ega23 ©   (20.06.08 16:37) [51]
    Результат функции - набор данных, как мне ее вызвать внутри самой себя?
  • stas © (20.06.08 16:45) [54]
    b z   (20.06.08 16:39) [52]
    Структуру таблиц? или привести и триггеры?
  • Ega23 © (20.06.08 16:51) [55]

    > Результат функции - набор данных, как мне ее вызвать внутри
    > самой себя?
    >


    А что такого?
  • b z (20.06.08 16:52) [56]

    > stas ©   (20.06.08 16:45) [54]

    Таблиц, с тригерами надеюсь будет само-собой ... :)
  • stas © (20.06.08 16:55) [57]
    Ega23 ©   (20.06.08 16:51) [55]
    Ну, в принципе можно, только это будет тоже самое что я предложил только вместо цикла который в multi statement функции будет рекурсия.
  • stas © (20.06.08 17:02) [58]
    b z   (20.06.08 16:52) [56]
    Таблица
    IDZ
    GLZAP - PARETNTID
    NOMZAP - ID

    Цель таблицы отобразить всех детей каждого родителя.

    Триггер на INSERT основной таблицы:
    В эту таблицу пакетная вставка данных непредумотрена

    SET @NZAP = (SELECT NOMZAP FROM INSERTED )
    INSERT INTO FSOSTZAKA (GLZAP,NOMZAP)
    SELECT GLZAP, NOMZAP FROM FSOSTZAKUP (@NZAP)
    FSOSTZAKUP (@NZAP)  - функция в цикле достает всех родителей этой записи
  • stas © (20.06.08 17:05) [59]
    Вот функция которая это все делает, думаю разберетесь.
    Основная таблица называется SOSTZAK
    ALTER FUNCTION [dbo].[FSOSTZAKUP](@NOMZP as int )
    RETURNS
    @TAB TABLE (IDNOMZ INT IDENTITY (1,1) NOT NULL,
                            GLZAP    INT,          
                            NOMZAP INT,
                            UROVEN INT
     
               primary key (IDNOMZ)  )   --Создание временной таблицы

    AS
    BEGIN
    DECLARE @MAXID INT,
                     @PMAXID INT,
                     @I INT

    INSERT INTO @TAB (GLZAP, NOMZAP, UROVEN)
               SELECT SOSTZAK.NOMZAP, SOSTZAK.NOZP, 0
               FROM SOSTZAK with(nolock)
              WHERE NOMZAP=@NOMZP     --Занесение во временную таблицу записи из SOSTZAK

    SET @I=1                       --уровень
    SET @PMAXID=0--(SELECT MAX(IDNOMZ) FROM @TAB)--1         --предыдущий максимальный ID
    SET @MAXID=(SELECT MAX(IDNOMZ) FROM @TAB)            --максимальный ID

    WHILE @PMAXID<>@MAXID AND @I<10  --максимальный уровень (защита от зацикливания)


    BEGIN

    INSERT INTO @TAB (GLZAP, NOMZAP, UROVEN)
               SELECT SOSTZAK.GLZAP, SZAK.NOZP, @I
               FROM @TAB SOSTZAK INNER JOIN SOSTZAK SZAK with(nolock) ON SOSTZAK.NOMZAP=SZAK.NOMZAP
               WHERE IDNOMZ>@PMAXID    --Присоединение таблицы SOSTZAK

    SET @I=@I+1
    SET @PMAXID=@MAXID
    SET @MAXID=@@identity

    END

    RETURN
    END
 
Конференция "Базы" » Запрос на SQL для выборки иерархического списка [MSSQL]
Есть новые Нет новых   [134434   +28][b:0][p:0.002]