SQL表中的XML包含嵌套元素

时间:2016-05-06 09:23:34

标签: xml sql-server-2008 tsql nested elements

我的任务是调查一个黑盒子的替代解决方案'这个过程需要花费大量时间,但我们无法在其中改变或改进。

我要做的是从表中提取XML信息,当前它作为文本字段保存(使用CAST进行转换)。有多行,XML包含许多包含属性的嵌套元素。

为一行存储的XML的一个示例如下:

<offerContext weightExpr="90">
  <filter label="Description of XML held here">
    <where displayFilter="Second description of XML held here" filterName="backGroundFilterFrm" id="13706004488">
      <condition boolOperator="AND" compositeKey="" dependkey="FK_Rcp_Brand" enabledIf="" expr="@BrandId = 1" internalId="-1548698833" />
      <condition boolOperator="AND" compositeKey="FK_Rcp_Brand" dependkey="" expr="FK_Rcp_Brand = '1'" internalId="1370600592" />
      <condition boolOperator="AND" compositeKey="" dependkey="" expr="proposition" internalId="1370600625" setOperator="EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="@status = 3" internalId="1370600632" />
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="[offer/@name] = 'Spend20get5Off'" internalId="1370600644" />
        <condition compositeKey="" dependkey="" expr="[offerSpace/@channel] = 0" internalId="1370600655" />
      </condition>
      <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="proposition" internalId="1372382776" setOperator="NOT EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offer/@name] = 'Spend20get5Off'" internalId="1372382779" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="@eventDate &gt;= DaysAgo(21)" internalId="1372382782" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offerSpace/@channel] = 0" internalId="1372382786" />
      </condition>
    </where>
    <humanCond>Query: Description of XML held here</humanCond>
  </filter>
  <extension useBuildPropositionsScript="false" />
</offerContext>

我需要提取的是来自offerContext元素的weightexpr。除此之外,我需要来自每个条件元素的booloperator,compositekey,dependkey,expr和internalId。我需要提取这些,以便子元素链接到它们的父元素,这是我遇到一些困难的地方。我有以下内容将两个元素拉成一行,但这会需要一些操作(我没有问题,但想知道是否有更好的方法),因为父条件元素重复多次。

我到目前为止的代码是:

;WITH contexts AS
    (
    SELECT a.iOfferId, a.iOfferContextId, a.mdata, CONVERT(xml,a.mdata) AS XMLmData
    FROM NmsOfferContext a
    )
SELECT 
    iOfferId 
    ,iOfferContextId
    ,p2.value('(@weightExpr)[1]', 'nvarchar(max)' ) AS dweight
    ,p2.value('(@boolOperator)[1]', 'nvarchar(max)' ) AS boolOperator2
    ,p2.value('(@dependKey)[1]', 'nvarchar(max)' )  AS dependKey2
    ,p2.value('(@expr)[1]', 'nvarchar(max)' )           AS expr2
    ,p2.value('(@setOperator)[1]', 'nvarchar(max)' )    AS setoperator2
    ,p2.value('(@internalId)[1]', 'nvarchar(max)' ) AS internalID2
    ,p3.value('(@boolOperator)[1]', 'nvarchar(max)' ) AS boolOperator3
    ,p3.value('(@dependKey)[1]', 'nvarchar(max)' )  AS dependKey3
    ,p3.value('(@expr)[1]', 'nvarchar(max)' )           AS expr3
    ,p3.value('(@setOperator)[1]', 'nvarchar(max)' )    AS setoperator3
    ,p3.value('(@internalId)[1]', 'nvarchar(max)' ) AS internalID3
FROM contexts 
CROSS APPLY XMLmData.nodes('/offerContext/*/*/condition') t(p2)
CROSS APPLY XMLmData.nodes('/offerContext/*/*/condition/condition') t2(p3)
ORDER BY iOfferContextId,
    p2.value('(@internalId)[1]', 'nvarchar(max)' ),
    p3.value('(@internalId)[1]', 'nvarchar(max)' )

最终我需要根据expr值构造SQL查询并使用booloperator作为WHERE子句,因此为什么我对元素有正确的顺序很重要(我相信我也可以使用internalId属性实现)但保留父母和孩子之间的关系是我需要帮助的地方。

如果我走在正确的轨道上,任何帮助都会受到赞赏和肯定。如果有什么需要更明确的解释,请随时提出。

提前致谢。

2 个答案:

答案 0 :(得分:2)

您可以使用递归CTE来分解XML,以构建节点级别之间的关系。

declare @X xml = '
<offerContext weightExpr="90">
  <filter label="Description of XML held here">
    <where displayFilter="Second description of XML held here" filterName="backGroundFilterFrm" id="13706004488">
      <condition boolOperator="AND" compositeKey="" dependkey="FK_Rcp_Brand" enabledIf="" expr="@BrandId = 1" internalId="-1548698833" />
      <condition boolOperator="AND" compositeKey="FK_Rcp_Brand" dependkey="" expr="FK_Rcp_Brand = ''1''" internalId="1370600592" />
      <condition boolOperator="AND" compositeKey="" dependkey="" expr="proposition" internalId="1370600625" setOperator="EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="@status = 3" internalId="1370600632" />
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="[offer/@name] = ''Spend20get5Off''" internalId="1370600644" />
        <condition compositeKey="" dependkey="" expr="[offerSpace/@channel] = 0" internalId="1370600655" />
      </condition>
      <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="proposition" internalId="1372382776" setOperator="NOT EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offer/@name] = ''Spend20get5Off''" internalId="1372382779" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="@eventDate &gt;= DaysAgo(21)" internalId="1372382782" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offerSpace/@channel] = 0" internalId="1372382786" />
      </condition>
    </where>
    <humanCond>Query: Description of XML held here</humanCond>
  </filter>
  <extension useBuildPropositionsScript="false" />
</offerContext>';

with A as
(
  select T.X.value('@weightExpr', 'int') as weightExpr,
         T.X.query('filter/where/condition') as C,
         cast(null as int) as internalID,
         cast(null as int) as internalParentID,
         cast(null as varchar(10)) as boolOperator,
         cast(null as varchar(20)) as dependKey,
         cast(null as varchar(50)) as expr
  from @X.nodes('/offerContext') as T(X)
  union all
  select null,
         T.X.query('condition'),
         T.X.value('@internalId', 'int'),
         A.internalID,
         T.X.value('@boolOperator', 'varchar(10)'),
         T.X.value('@dependkey', 'varchar(100)'),
         T.X.value('@expr', 'varchar(100)')
  from A
    cross apply A.C.nodes('condition') as T(X)
)
select A.weightExpr,
       A.internalID,
       A.internalParentID,
       A.boolOperator,
       A.dependKey,
       A.expr
from A
order by A.internalID

结果:

weightExpr internalID  internalParentID boolOperator dependKey     expr
---------- ----------- ---------------- ------------ ------------- --------------------------------
90         NULL        NULL             NULL         NULL          NULL
NULL       -1548698833 NULL             AND          FK_Rcp_Brand  @BrandId = 1
NULL       1370600592  NULL             AND                        FK_Rcp_Brand = '1'
NULL       1370600625  NULL             AND                        proposition
NULL       1370600632  1370600625       AND                        @status = 3
NULL       1370600644  1370600625       AND                        [offer/@name] = 'Spend20get5Off'
NULL       1370600655  1370600625       NULL                       [offerSpace/@channel] = 0
NULL       1372382776  NULL             AND                        proposition
NULL       1372382779  1372382776       AND                        [offer/@name] = 'Spend20get5Off'
NULL       1372382782  1372382776       AND                        @eventDate >= DaysAgo(21)
NULL       1372382786  1372382776       AND                        [offerSpace/@channel] = 0

当源是表时重写。

with A as
(
  select Y.offerID,
         T.X.value('@weightExpr', 'int') as weightExpr,
         T.X.query('filter/where/condition') as C,
         cast(null as int) as internalID,
         cast(null as int) as internalParentID,
         cast(null as varchar(10)) as boolOperator,
         cast(null as varchar(20)) as dependKey,
         cast(null as varchar(50)) as expr
  from dbo.YourTable as Y
    cross apply Y.X.nodes('/offerContext') as T(X)
  union all
  select A.offerID,
         null,
         T.X.query('condition'),
         T.X.value('@internalId', 'int'),
         A.internalID,
         T.X.value('@boolOperator', 'varchar(10)'),
         T.X.value('@dependkey', 'varchar(20)'),
         T.X.value('@expr', 'varchar(50)')
  from A
    cross apply A.C.nodes('condition') as T(X)
)
select A.offerID,
       A.weightExpr,
       A.internalID,
       A.internalParentID,
       A.boolOperator,
       A.dependKey,
       A.expr
from A
order by A.offerID,
         A.internalID

答案 1 :(得分:1)

这是另一种方法,可能比递归方法更容易。这是基于OP的评论,至少目前 - 至少不超过2个级别。

在第一步中,第一级的条件被采用,他们的内部第二级条件。它们按照它们出现的顺序编号。

在第二阶段,采取第二级的条件。它们的出现顺序再次编号,由父母划分。

最后两个选择被联合并按条件的位置排序:

DECLARE @xml XML=
'<offerContext weightExpr="90">
  <filter label="Description of XML held here">
    <where displayFilter="Second description of XML held here" filterName="backGroundFilterFrm" id="13706004488">
      <condition boolOperator="AND" compositeKey="" dependkey="FK_Rcp_Brand" enabledIf="" expr="@BrandId = 1" internalId="-1548698833" />
      <condition boolOperator="AND" compositeKey="FK_Rcp_Brand" dependkey="" expr="FK_Rcp_Brand = ''1''" internalId="1370600592" />
      <condition boolOperator="AND" compositeKey="" dependkey="" expr="proposition" internalId="1370600625" setOperator="EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="@status = 3" internalId="1370600632" />
        <condition boolOperator="AND" compositeKey="" dependkey="" expr="[offer/@name] = ''Spend20get5Off''" internalId="1370600644" />
        <condition compositeKey="" dependkey="" expr="[offerSpace/@channel] = 0" internalId="1370600655" />
      </condition>
      <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="proposition" internalId="1372382776" setOperator="NOT EXISTS">
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offer/@name] = ''Spend20get5Off''" internalId="1372382779" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="@eventDate &gt;= DaysAgo(21)" internalId="1372382782" />
        <condition boolOperator="AND" compositeKey="" dependkey="" enabledIf="" expr="[offerSpace/@channel] = 0" internalId="1372382786" />
      </condition>
    </where>
    <humanCond>Query: Description of XML held here</humanCond>
  </filter>
  <extension useBuildPropositionsScript="false" />
</offerContext>';

WITH AllConditionsLevel1 AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS CondLevel1_Position
          ,CAST(0 AS BIGINT) AS CondLevel2_Position
          ,1 AS CondLevel 
          ,@xml.value('(offerContext/@weightExpr)[1]','int') AS OfferContext_WeightExpr
          ,@xml.value('(offerContext/filter/@label)[1]','varchar(max)') AS Filter_Label
          ,@xml.value('(offerContext/where/@displayFilter)[1]','varchar(max)') AS Where_DisplayFilter
          ,@xml.value('(offerContext/where/@filterName)[1]','varchar(max)') AS Where_FilterName
          ,@xml.value('(offerContext/where/@id)[1]','varchar(max)') AS Where_Id
          ,CondLevel1.value('@boolOperator','varchar(max)') AS Cond_BoolOperator
          ,CondLevel1.value('@compositeKey','varchar(max)') AS Cond_CompositeKey
          ,CondLevel1.value('@dependkey','varchar(max)') AS Cond_DependKey
          ,CondLevel1.value('@expr','varchar(max)') AS Cond_Expr
          ,CondLevel1.value('@internalId','varchar(max)') AS Cond_InternalId
          ,CondLevel1.value('@setOperator','varchar(max)') AS Cond_SetOperator
          ,CondLevel1.value('@enabledIf','varchar(max)') AS Cond_EnabledIf
          ,CondLevel1.query('*') AS CondLevel2Nodes
    FROM @xml.nodes('offerContext/filter/where/condition') AS A(CondLevel1)
)
SELECT *
FROM
(
    SELECT * FROM AllConditionsLevel1
    UNION ALL
    SELECT  CondLevel1_Position
            ,ROW_NUMBER() OVER(PARTITION BY CondLevel1_Position ORDER BY (SELECT NULL))
            ,2   
            ,OfferContext_WeightExpr
            ,Filter_Label
            ,Where_DisplayFilter
            ,Where_FilterName
            ,Where_Id
            ,CondLevel2.value('@boolOperator','varchar(max)') 
            ,CondLevel2.value('@compositeKey','varchar(max)') 
            ,CondLevel2.value('@dependkey','varchar(max)') 
            ,CondLevel2.value('@expr','varchar(max)') 
            ,CondLevel2.value('@internalId','varchar(max)') 
            ,CondLevel2.value('@setOperator','varchar(max)') 
            ,CondLevel2.value('@enabledIf','varchar(max)') 
            ,NULL
    FROM AllConditionsLevel1
    CROSS APPLY CondLevel2Nodes.nodes('condition') AS B(CondLevel2)
) AS tbl
ORDER BY CondLevel1_Position,CondLevel2_Position