0

I am using SQL Server 2012 and am looking a way to update my xml column by looking up values from another table.

I have a the following schema:

USE tempdb;
GO

DROP TABLE IF EXISTS [dbo].[tblstepid];

CREATE TABLE [dbo].[tblstepid](
    [stepid] [uniqueidentifier]  NOT NULL,
    [name] nvarchar(32) NOT NULL
);

INSERT INTO dbo.tblstepid ([stepid], name ) VALUES ('E36A3450-1C8F-44DA-B4D0-58E5BFE2A987','step1')

INSERT INTO dbo.tblstepid ([stepid], name ) VALUES ('11D70A50-08AC-4767-A0D3-87717384FF45','step2')

DROP TABLE IF EXISTS [dbo].[tblStepList];

CREATE TABLE [dbo].[tblStepList](
    [ToDoId] [int] IDENTITY(1,1) NOT NULL,
    [Data] [xml] NOT NULL
);

INSERT INTO dbo.tblStepList ([Data]) VALUES
(N'<Steplist>
  <Step>
    <StepId>e36a3450-1c8f-44da-b4d0-58e5bfe2a987</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>     
  </Step>
  <Step>
    <StepId>4078c1b1-71ea-4578-ba61-d2f6a5126ba1</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
    <TextReadingId>12</TextReadingId>

  </Step>
</Steplist>');





    INSERT INTO dbo.tblStepList ([Data]) VALUES
(N'<Steplist>
  <Step>
    <StepId>d9e42387-56e3-40a1-9698-e89c930d98d1</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>   
    <TextReadingId>0</TextReadingId>  
  </Step>
  <Step>
    <StepId>e5eaf947-24e1-4d3b-a92a-d6a90841293b</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
  </Step>
</Steplist>')

Now I want to a update all the steps with matching step ids in the other table as follows:

UPDATE sl
SET Data = (
    SELECT v.Step.query('
<Step>{./*,    
            <stepname>{sql:column("sr.name")}</stepname>
        }
</Step>
    ')
    FROM sl.Data.nodes('/Steplist/Step') v(Step)
     left JOIN tblStepID sr ON sr.StepId = v.Step.value('(StepId/text())[1]','uniqueidentifier')
    FOR XML PATH(''), ROOT('Steplist'), TYPE
)
FROM tblStepList sl;

And my result xml output has an empty node which I do not want. How can I write my join to not add empty nodes for me and also not to set the data if nothing matches which means my second record in my table xml should never be touched?

The problem I am trying to solve is that I am trying to update xml for a table with millions of records by joining from another table and I don't want the update statement to touch the data if the join does not match.

<Steplist>
  <Step>
    <StepId>e36a3450-1c8f-44da-b4d0-58e5bfe2a987</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>
    <stepname>step1</stepname>
  </Step>
  <Step>
    <StepId>4078c1b1-71ea-4578-ba61-d2f6a5126ba1</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
    <TextReadingId>12</TextReadingId>
    <stepname />
  </Step>
</Steplist>

My expected output is as follows:

<Steplist>
  <Step>
    <StepId>e36a3450-1c8f-44da-b4d0-58e5bfe2a987</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>
    <stepname>step1</stepname>
  </Step>
  <Step>
    <StepId>4078c1b1-71ea-4578-ba61-d2f6a5126ba1</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
    <TextReadingId>12</TextReadingId>
  </Step>
</Steplist>
Helen Araya
  • 1,886
  • 3
  • 28
  • 54
  • Please update your question with DDL and sample data population for the tblStepID table. You always need to provide a **minimal reproducible example** – Yitzhak Khabinsky Nov 05 '21 at 15:12

1 Answers1

1

Please try the following solution.

I added a couple of XQuery if/else statements to prevent the empty <stepname /> tag.

SQL

USE tempdb;
GO

-- DDL and sample data population, start
DROP TABLE IF EXISTS [dbo].[tblStepID];
DROP TABLE IF EXISTS [dbo].[tblStepList];

CREATE TABLE dbo.tblstepid(
    stepid uniqueidentifier  NOT NULL,
    [name] nvarchar(32) NOT NULL
);
INSERT INTO dbo.tblStepID (StepId, [Name]) VALUES
('e36a3450-1c8f-44da-b4d0-58e5bfe2a987', 'step1'),
('4078c1b1-71ea-4578-ba61-d2f6a5126ba1', 'step2');

CREATE TABLE [dbo].[tblStepList](
    [ToDoId] [int] IDENTITY(1,1) NOT NULL,
    [Data] [xml] NOT NULL
);

INSERT INTO dbo.tblStepList ([Data]) VALUES
(N'<Steplist>
  <Step>
    <StepId>e36a3450-1c8f-44da-b4d0-58e5bfe2a987</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>     
  </Step>
  <Step>
    <StepId>4078c1b1-71ea-4578-ba61-d2f6a5126ba1</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
    <TextReadingId>12</TextReadingId>
  </Step>
</Steplist>'),
(N'<Steplist>
  <Step>
    <StepId>d9e42387-56e3-40a1-9698-e89c930d98d1</StepId>
    <Rank>1</Rank>
    <IsComplete>false</IsComplete>
    <TextReadingName>bug-8588_Updated3</TextReadingName>   
    <TextReadingId>0</TextReadingId>  
  </Step>
  <Step>
    <StepId>e5eaf947-24e1-4d3b-a92a-d6a90841293b</StepId>
    <Rank>2</Rank>
    <TextReadingName>reading1</TextReadingName>
  </Step>
</Steplist>');
-- DDL and sample data population, end

UPDATE sl
SET Data = (
    SELECT v.Step.query('
    <Step>{./*,   
            if (not(./stepname)) then 
                if (not(empty(sql:column("sr.name")))) then <stepname>{sql:column("sr.name")}</stepname>
                else ()
            else ()
        }
    </Step>
')
FROM sl.Data.nodes('/Steplist/Step') AS v(Step)
LEFT JOIN tblStepID sr ON sr.StepId = v.Step.value('(StepId/text())[1]','uniqueidentifier')
FOR XML PATH(''), ROOT('Steplist'), TYPE
)
FROM tblStepList AS sl;

-- test
SELECT * FROM dbo.tblStepList;
Yitzhak Khabinsky
  • 18,471
  • 2
  • 15
  • 21
  • that will not solve my problem because this will still reset the data with no change if the joined fields do not match. I want the set not to be called for row 2 where none of the instructions match? – Helen Araya Nov 05 '21 at 16:03
  • In such case, you can try to change `LEFT JOIN tblStepID sr` to `INNER JOIN` – Yitzhak Khabinsky Nov 05 '21 at 16:36
  • that would assign nulls to my xml. – Helen Araya Nov 05 '21 at 17:30
  • I am running out of any other ideas. The original proposed solution should work. – Yitzhak Khabinsky Nov 05 '21 at 17:50
  • You need the `LEFT JOIN` because you need to replace the *whole* XML. I'd suggest using `CROSS APPLY` with a `WHERE` filter, so you only update if there is actually any changes to make. Also probably faster to switch around the order of the conditions, and merge them to a single `if(... and ...)` – Charlieface Nov 06 '21 at 17:57