如何使用 SQL 服务器递归获取节点的 XPath?

How to recursively get XPath of a node using SQL Server?

我厌倦了查看可能是我创建过的最丑陋的 SQL 语句,需要您的帮助。我正在 XML 文档中搜索各种元素,并希望查看它们的 XPath。下面的查询通过蛮力工作,但我想不出一种方法来创建一个函数或 CTE 来正确支持 N 级别。

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

向下递归而不是向上递归:

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