Иерархический SQL-запрос

Я работаю над простой системой CMS, для которой у меня есть база данных со следующими таблицами:

Items
Contents
Langs

Таблица Items имеет следующую структуру:

itemId
name (for semantic reasons)
type
parent (foreign key to itemId)

Элемент может быть типа document или section. Раздел — это часть содержимого документа, которая связана с ним через родительский столбец. Но также у документа может быть родитель, что делает его subpage.

Теперь я застрял на выполнении запроса для извлечения всех элементов из базы данных иерархически. Что-то вроде этого:

documentId => name
              metaDescription => language => meta
              sections => sectionId => language => title
                                                   content
                                                   uri
              subPages => documentId => name
                                        metaDescription
                                        sections => etc...

Просто чтобы уточнить, веб-сайт может иметь несколько языков, которые находятся в таблице Langs, и каждый язык связан с частью контента в таблице Contents, которая также связана с элементом в таблице Items. metaDescription — это связанный столбец content, связанный с элементом типа document.

Есть ли способ сделать это одним запросом? Это была моя первая попытка, но она не работает для подстраниц:

    SELECT
        documents.itemId        AS id,
        documents.name          AS documentName,
        documents.lastModified  AS lastModified,
        meta.content            AS metaDescription,
        meta.uri                AS documentUri,
        sections.itemId         AS sectionId,
        sections.name           AS sectionName,
        sections.lastModified   AS sectionLastModified,
        contents.name           AS sectionTitle,
        contents.content        AS sectionContent,
        contents.uri            AS contentUri,
        contents.lastModified   AS contentLastModified,
        langs.name              AS contentLang
    FROM 
        SITENAME_kw_items AS documents
            INNER JOIN
        SITENAME_kw_contents AS meta
        ON documents.itemId = meta.itemId
            INNER JOIN
        SITENAME_kw_items AS sections
        ON sections.parent = documents.itemId
            INNER JOIN
        SITENAME_kw_contents AS contents
        ON sections.itemId = contents.itemId
            INNER JOIN
        SITENAME_kw_langs AS langs
        ON langs.langId = contents.langId

Извините за длинный вопрос. Надеюсь, вы, ребята, можете помочь!


person Raapwerk    schedule 20.06.2013    source источник
comment
Это для какой базы данных?   -  person OldProgrammer    schedule 20.06.2013
comment
Вы имеете в виду двигатель? ИнноДБ.   -  person Raapwerk    schedule 20.06.2013
comment
OldProgrammer просил СУБД (Postgres, Oracle, MySQL,...). Поскольку вы думаете, что база данных такая же, как и движок, я предполагаю, что вы используете MySQL, что, к сожалению, является одной из (очень) немногих СУБД, не поддерживающих рекурсивные запросы.   -  person a_horse_with_no_name    schedule 20.06.2013
comment
См. этот ответ на аналогичный вопрос о способах обхода иерархического дерева в MySQL: stackoverflow.com/a/11035966/359040   -  person    schedule 20.06.2013


Ответы (2)


Ниже показано, как я делаю это в "нашей" DMS (рекурсивной CTE), которая является расширенным предложением Адама Гента.
Обратите внимание, что я просто вижу, что можно использовать COALESCE вместо вложения ISNULL.

Порядок, который вы бы сделали в соответствии с панировочными сухарями (здесь Bez_Path или UID_Path).

Гораздо лучше было бы использовать архитектуру замыкающей таблицы.
См. здесь:
http://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html
и здесь:
http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/

Преимущество таблицы замыкания также в том, что она работает в MySQL, где CTE и рекурсия не поддерживаются.

Также обратите внимание, что таблицы замыкания намного лучше (и проще и быстрее выполнять запросы), чем рекурсия.
Также подумайте о символических ссылках в такой структуре.
Шаблон something_UID, something_parent_UID (как показано ниже) почти всегда является антишаблоном.

CREATE VIEW [dbo].[V_DMS_Navigation_Structure]
AS 
SELECT 
     NAV_UID 
    ,NAV_Typ 
    ,NAV_Parent_UID 
    ,NAV_Stufe 
    ,NAV_ApertureKey 
    ,NAV_Nr 
    --,NAV_Bemerkung 
    ,NAV_Status 
    ,NAV_Referenz 

    ,ISNULL(PJ_Bezeichnung, ISNULL(FO_Bezeichnung, DOC_Bezeichnung + '.' + DOC_Dateiendung)  ) AS NAV_Bezeichnung 
    ,NAV_PJ_UID 
    ,NAV_FO_UID 
    ,NAV_DOC_UID 
    ,ISNULL(NAV_PJ_UID, ISNULL(NAV_FO_UID,NAV_DOC_UID)) AS NAV_OBJ_UID 
FROM T_DMS_Navigation 

LEFT JOIN T_DMS_Projekt 
    ON T_DMS_Projekt.PJ_UID = T_DMS_Navigation.NAV_PJ_UID 

LEFT JOIN T_DMS_Folder 
    ON T_DMS_Folder.FO_UID = T_DMS_Navigation.NAV_FO_UID 

LEFT JOIN T_DMS_Dokument 
    ON T_DMS_Dokument.DOC_UID = T_DMS_Navigation.NAV_DOC_UID 








CREATE VIEW [dbo].[V_DMS_Navigation_Structure_Path]
AS 
WITH Tree 
(
     NAV_UID
    ,NAV_Bezeichnung
    ,NAV_Parent_UID
    ,Depth
    ,Sort
    ,Bez_Path
    ,UID_Path
    ,PJ_UID
    ,FO_UID
    ,DOC_UID
    ,OBJ_UID
) 
AS
(
    SELECT 
         NAV_UID 
        ,NAV_Bezeichnung 
        ,NAV_Parent_UID 
        ,0 AS Depth 
        ,CAST('0' AS varchar(10)) AS Sort 
        ,CAST(NAV_Bezeichnung AS varchar(4000)) AS Bez_Path 
        ,CAST(NAV_OBJ_UID AS varchar(4000)) AS UID_Path 
        ,NAV_PJ_UID AS PJ_UID 
        ,NAV_FO_UID AS FO_UID 
        ,NAV_DOC_UID AS DOC_UID 
        ,NAV_OBJ_UID AS OBJ_UID 
    FROM V_DMS_Navigation_Structure 

    WHERE NAV_Parent_UID IS NULL 

    UNION ALL 

    SELECT 
         CT.NAV_UID 
        ,CT.NAV_Bezeichnung 
        ,CT.NAV_Parent_UID 
        ,Parent.Depth + 1 AS Depth 
        ,CONVERT(varchar(10), Parent.Sort + '.' + CAST(Parent.Depth + 1 AS varchar(10))) AS Sort 
        ,CONVERT(varchar(4000), Parent.Bez_Path + '\' + CAST(CT.NAV_Bezeichnung AS varchar(1000))) AS Bez_Path 
        ,CONVERT(varchar(4000), Parent.UID_Path + '\' + CAST(CT.NAV_OBJ_UID AS varchar(1000))) AS UID_Path 
        ,NAV_PJ_UID AS PJ_UID 
        ,NAV_FO_UID AS FO_UID 
        ,NAV_DOC_UID AS DOC_UID 
        ,NAV_OBJ_UID AS OBJ_UID 
    FROM V_DMS_Navigation_Structure CT 

    INNER JOIN Tree AS Parent 
        ON Parent.NAV_UID = CT.NAV_Parent_UID
)

SELECT TOP 999999999999999 * FROM Tree
ORDER BY Depth
person Stefan Steiger    schedule 20.06.2013
comment
Замыкающую таблицу намного сложнее поддерживать и обновлять, чем список смежности. - person a_horse_with_no_name; 20.06.2013
comment
@a_horse_with_no_name: Я согласен, что это сложнее, но ненамного. Все, что вам нужно сделать, это вставить узлы self, self на глубину 0 с помощью триггера, остальное как есть, с немного другим запросом sql. Вы можете найти и скопировать почти весь необходимый код из Интернета. Некоторые незначительные перезаписи необходимы для использования в MS SQL. Вы поймете, что соседний список на самом деле сложнее, как только будет добавлено требование, что вы должны иметь возможность ссылаться на папки в структуре каталогов. - person Stefan Steiger; 21.06.2013
comment
Спасибо! Заключительная таблица кажется подходящим вариантом :) - person Raapwerk; 26.06.2013

Короткий ответ: вы не можете сделать это с RDBMS. Длинный ответ: вы можете сделать это либо программно (выбор N + 1), либо вы можете использовать общие табличные выражения (CTE).

Другой вариант — схитрить и использовать столбец глубины в качестве подсказки для порядка.

person Adam Gent    schedule 20.06.2013
comment
Спасибо за быстрый ответ! Поскольку я немного новичок в SQL, ни один из вышеперечисленных вариантов не звонит мне в колокола. Но, конечно, всегда есть Google. Интересно, какой вариант вы посоветуете в этом случае? - person Raapwerk; 20.06.2013
comment
Конечно, это можно сделать с помощью рекурсивного CTE. И это не то, чтобы делать это. Но Raapwerk использует MySQL, которая не поддерживает эту стандартную функцию. - person a_horse_with_no_name; 20.06.2013
comment
Я сказал, что наборы результатов по-прежнему будут основаны на столбцах, а не на дереве (например, JSON или XML-документ). Когда он впервые задал вопрос, он не сказал, что такое DB. - person Adam Gent; 23.06.2013