2015-04-10 2 views
1

Я устал смотреть на, возможно, самый уродливый оператор SQL, который я когда-либо создавал и нуждался в вашей помощи. Я просматриваю XML-документ для различных элементов и хочу видеть их XPath. Нижеприведенный запрос работает с помощью грубой силы, но я не могу создать способ создания функции или CTE, который будет правильно поддерживать N уровней.Как рекурсивно получить XPath узла с использованием SQL Server?

declare @article xml = '<article> 
    <front> 
    <article-meta> 
     <title-group> 
     <article-title>Update on ...</article-title> 
     </title-group> 
    </article-meta> 
    </front> 
    <back> 
    <ref-list> 
     <ref id="R1"> 
     <citation citation-type="journal"> 
      <article-title>Retrospective study of ...</article-title> 
     </citation> 
     </ref> 
    </ref-list> 
    </back> 
</article>' 

SELECT 
     Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' + 
     Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' + 
     Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' + 
     Cast(T.r.query('local-name(parent::*/parent::*/parent::*)') AS varchar(max)) + '/' + 
     Cast(T.r.query('local-name(parent::*/parent::*)') AS varchar(max)) + '/' + 
     Cast(T.r.query('local-name(parent::*)') AS varchar(max)) AS ThePath, 
     Cast(T.r.query('local-name(.)') AS varchar(max)) AS TheElement, 
     T.r.query('.') AS TheXml 
FROM @article.nodes('//article-title') T(r) 

Результат:

ThePath         TheElement 
//article/front/article-meta/title-group article-title 
/article/back/ref-list/ref/citation   article-title 

То, что я действительно хочу:

SELECT 
    x.RowId, 
    dbo.GetXPath(T.r.query('.')) AS ThePath, -- <---- Magic function goes here 
    T.r.query('.') AS TheXml 
FROM dbo.InputFormatXml x 
    JOIN dbo.InputFormat f 
    ON F.InputFormatId = x.InputFormatId 
CROSS APPLY TheData.nodes('//article-title') T(r) 
WHERE F.Description = 'NLM'; 

ответ

0
DECLARE @idoc int; 

EXEC sp_xml_preparedocument @idoc OUTPUT, @article; 

SELECT ISNULL(id,'') id, parentid, localname 
INTO #nodetree 
FROM OPENXML(@idoc,'/',3) 
WHERE nodetype = 1; 

EXEC sp_xml_removedocument @idoc; 

ALTER TABLE #nodetree ADD PRIMARY KEY (id); 

WITH cte AS (
    SELECT 
    parentid 
    ,CAST('/' AS varchar(max)) + localname AS xpath 
    FROM #nodetree WHERE localname = 'article-title' 
    UNION ALL 
    SELECT 
    parent.parentid 
    ,CAST('/' AS varchar(max)) + localname + xpath 
    FROM cte AS node 
    INNER JOIN #nodetree parent on parent.id = node.parentid 
) 
SELECT xpath 
FROM cte 
WHERE parentid IS NULL 
0

рекурсии вниз, а не вверх:

WITH cte AS (
    SELECT 
    node = x.query('.') 
    ,name = x.value('local-name(.)','varchar(max)') 
    ,xpath = CAST('' AS varchar(max)) 
    FROM (SELECT @article AS node) parent 
    CROSS APPLY node.nodes('/*') T(x) 
    UNION ALL 
    SELECT 
    node = x.query('.') 
    ,name = x.value('local-name(.)','varchar(max)') 
    ,xpath = parent.xpath + '/' + parent.name 
    FROM cte parent 
    CROSS APPLY node.nodes('/*/*') T(x) 
) 
SELECT 
    xpath 
,name 
FROM cte 
WHERE name = 'article-title' 
+0

Спасибо. Это хорошо работает для упрощенного сценария, который я изначально перечислял. Я упомянул о создании функции или CTE, поэтому я мог бы использовать ее в «реальном» сценарии, где я собираю тысячи элементов

на основе оператора XPath и запуская запрос, который будет производить все родительские узлы, занимает несколько часов, что не является разумным. Как я могу параметризовать запрос «SELECT @article AS node» без необходимости предоставления полного набора данных из тысяч статей? – kratka

Смежные вопросы