3

I have been tasked with investigating an alternative solution to a 'black box' process that is taking a substantial amount of time, but one we cannot alter or improve upon within it.

What I am trying to do is extract information from XML within a table where it is currently held as a text field (using CAST to convert it). There are multiple rows and the XML contains a number of nested elements that contain attributes.

One example of the XML stored for one row is as follows:

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

What I need to extract is the weightexpr from the offerContext element. Alongside this I need the booloperator, compositekey, dependkey, expr and internalId from each of the condition elements. I need to extract these so that the child elements are linked to their parent elements and this is where I am having some difficulties. I have the following that pulls both elements into one row but this would take some manipulation afterwards (which I have no issue with but wanted to know is there a better way of doing this) as teh parent condition element is repeated multiple times.

The code I have so far is:

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

Ultimately I need to construct SQL queries based on the expr values and using the booloperator for the WHERE clauses, hence why it is important that I have the right order for the elements (which I believe I can also achieve with the internalId attribute) but the retaining the relationship between parent and child is where I need some assistance.

Any help would be appreciated and confirmation if I am on the right track would be great. If anything needs a clearer explanation then please feel free to ask.

Thanks in advance.

2
  • Your example shows one child-level of conditions... Might there be more? Commented May 6, 2016 at 12:11
  • To my knowledge, there are only the two levels, i am certainly limiting to only these levels at the moment. Commented May 6, 2016 at 15:22

2 Answers 2

2

You can shred the XML using a recursive CTE to build the relation between levels of nodes.

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

Result:

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

A rewrite for when the source is a table.

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
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks Mikael for the response and the time you have spent on this. This is cheeky but if the XML is a column in a table, for instance offerXML and I also need to associate another int field offerID, how do I select both the offerID and shred the XML as per your solution. To use your solution do I need to apply a cursor or use your solution as a function to created the data I need, i suspect the function is the best approach.
@MattPaine Tiny rewrite in the anchor part of the recursive CTE. Updated the answer.
Thanks Mikael, I will give that a try and let you know how I get on.
I like it +1 from my side
Thanks again to both of you, I have tried Mikael's solution and need to make a small adjustment in that I need to know which weighting belongs to which expression set and am sure I know how to do that. Once I have I will post my final solution for you to see and will mark your responses.
|
1

This is another approach, which might be easier than a recursive one. This is based on an OP's comment, that there are - at least for now - not more than 2 Levels.

In the first go the conditions of the first level are taken and their inner second-level conditions are taken as is. They are numbered in the order of their occurence.

In the second go the conditions of the second level are taken. The are again numbered in the order of their occurence, partitioned by the parents.

The final two selects get unioned and sorted by the condition's positions:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.