I have a xml string from other system, I need select xml as a table in sql
<jrt>
<item>
<id>cell1</id>
<id>cell2</id>
<id>cell3</id>
</item>
<item>
<id>cell4</id>
<id>cell5</id>
<id>cell6</id>
</item>
</jrt>
The results should be:
row value
----------- -----
1 cell1
2 cell2
3 cell3
1 cell4
2 cell5
3 cell6
Notice: the row number is every item's id postion
Please help me how to use tsql select this xml?
declare #xml xml set #xml = '
<jrt>
<item>
<id>cell1</id>
<id>cell12</id>
<id>cell83</id>
</item>
<item>
<id>cell4</id>
<id>cell5</id>
<id>cell6</id>
</item>
</jrt>'
SELECT item.number itemrow,
id.number idrow,
o.c.value('.', 'nvarchar(max)') value
FROM master..spt_values item
inner join master..spt_values id on id.type='P' and id.number >= 1
CROSS APPLY #xml.nodes('/jrt/item[sql:column("item.number")]') n(c)
CROSS APPLY n.c.nodes('id[sql:column("id.number")]') o(c)
where item.type='P' and item.number >= 1
ORDER BY itemrow, idrow
With help from https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=383888
Related
Is it possible to get the 'A' and 'B' Columns in the Same(Single) SELECT Statement instead of using the 2 SELECT Statements:
Tried query:
DECLARE #Invoice_Types VARCHAR(MAX)='T-E,T-I,3-E,D-E,D-I'
,#Invoice_TypesXML XML
SET #Invoice_TypesXML=CAST(('<I>'+REPLACE(REPLACE(#Invoice_Types,',','</R><I>'),'-' ,'</I><R>') +'</R>')
AS XML);
SELECT I.value('.', 'VARCHAR(MAX)') A
FROM #Invoice_TypesXML.nodes('I') AS FN(I)
SELECT R.value('.', 'VARCHAR(MAX)') B
FROM #Invoice_TypesXML.nodes('R') AS FN(R)
Expected Result: in Single Select Statement
A B
-----------------
T E
T I
3 E
D E
D I
For your requirement, you need to revise your XML structure like this:
<Item>
<I>T</I><R>E</R>
</Item>
<Item>
<I>T</I><R>I</R>
</Item>
<Item>
<I>3</I><R>E</R>
</Item>
<Item>
<I>D</I><R>E</R>
</Item>
<Item>
<I>D</I><R>I</R>
</Item>
And then you can select your final result in one select statement.
Try this:
DECLARE #Invoice_Types VARCHAR(MAX)='T-E,T-I,3-E,D-E,D-I'
,#Invoice_TypesXML XML
SET #Invoice_TypesXML=CAST(('<Item><I>'+REPLACE(REPLACE(#Invoice_Types,',','</R></Item><Item><I>'),'-' ,'</I><R>') +'</R></Item>')
AS XML);
SELECT
x.Rec.query('./I').value('.', 'nvarchar(2000)') AS 'A',
x.Rec.query('./R').value('.', 'nvarchar(2000)') AS 'B'
FROM #Invoice_TypesXML.nodes('/Item') as x(Rec)
Final result:
A B
_____
T E
T I
3 E
D E
D I
See this Fiddle
One solution is :
SELECT I.value('(I[1])', 'VARCHAR(MAX)') A, I.value('(R[1])', 'VARCHAR(MAX)') B
FROM #Invoice_TypesXML.nodes('.') AS FN(I)
UNION ALL
SELECT I.value('(I[2])', 'VARCHAR(MAX)') A, I.value('(R[2])', 'VARCHAR(MAX)') B
FROM #Invoice_TypesXML.nodes('.') AS FN(I)
UNION ALL
...
More generically :
WITH
TI AS
(
SELECT I.value('(.)', 'VARCHAR(MAX)') I, row_number() over(order by I) AS N
FROM #Invoice_TypesXML.nodes('I') AS FN(I)
),
TR AS
(
SELECT I.value('(.)', 'VARCHAR(MAX)') R, row_number() over(order by I) AS N
FROM #Invoice_TypesXML.nodes('R') AS FN(I)
)
SELECT I, R
FROM TI JOIN TR ON TI.N = TR.N
This should be simple but cannot get it to work after trying multiple suggestions from things I researched. I have some basic XML and just want to return the values from all the elements in seperate rows in a query result.
<Root>
<QueryRequest>
<ReturnedReuslts>
<Rows>
<Row>"1","Value 1"</Row>
<Row>"2","Value 2"</Row>
<Row>"3","Value 3"</Row>
</Rows>
</ReturnedReuslts>
</QueryRequest>
</Root>
I want it to return
Column 1 Column 2
-------- --------
1 Value 1
2 Value 2
3 Value 3
If I can't get it into two different columns, I'll settle for:
Column 1
----------------
"1","Value 1"
"2","Value 2"
"3","Value 3"
In SQL Server:
declare #strxml xml;
set #strxml = '<Root>
<QueryRequest>
<ReturnedReuslts>
<Rows>
<Row>"1","Value 1"</Row>
<Row>"2","Value 2"</Row>
<Row>"3","Value 3"</Row>
</Rows>
</ReturnedReuslts>
</QueryRequest>
</Root>'
select
substring(t1.col1, 1, charindex(',', t1.col1) - 1) Column1,
substring(t1.col1, charindex(',', t1.col1) + 1, len(t1.col1)) Column2
from
(select
col1 = replace(Node.Data.value('.', 'varchar(200)'), '"', '')
from
#strxml.nodes('/Root/QueryRequest/ReturnedReuslts/Rows/Row') Node(Data)) t1
Consider the following situation. I have the following table
CREATE TABLE [dbo].[GoldenEgg]
(
rowIndex int NOT NULL IDENTITY(1,1),
AccountNumber varchar(256) NULL,
SubscriptionID int NOT NULL,
SubscriptionData_XML xml NULL,
SubscriptionData_AFTER_XML NULL
CONSTRAINT [PK_GoldenEgg]
PRIMARY KEY CLUSTERED ([rowIndex] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GoldenEgg sample data:
SubscriptionData_XML data for SubscriptionID 6070:
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>
I want to append all account numbers for each SubscriptionID to the already existing xml <Value> node in the SubscriptionData_XML column and I do not want to add account numbers that already exist in the xml.
So for SubscriptionID 6070 account number 39448474 should only be listed once in the xml like so:
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
<ValueItem>41447395</ValueItem>
</Value>
</Item>
</NVPList>
If there are not other nodes within your XML you might choose the FLWOR-query.
Some hints:
first I create a mock-up table and fill it with data
I use and updateable CTE to collect the data
I use a FOR XML-sub-select without a namespace to build the <Value> node wihtout bothering about already existing IDs in your actual XML
I use a FLWOR-query() to build up the full XML out of the just created Value-node
As this CTE is updateable, I can use it directly for the UPDATE
The final SELECT * FROM #tbl shows to you, that all AFTER_XML are filled
Try this:
DECLARE #tbl TABLE(rowIndex INT IDENTITY,AccountNumber INT,SubscriptionID INT, SubscriptionData_XML XML,SubscriptionData_AFTER_XML XML);
INSERT INTO #tbl VALUES
(1111,6070,N'<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>',NULL)
,(2222,6070,NULL,NULL)
,(3333,6070,NULL,NULL)
,(4444,6070,NULL,NULL)
,(5555,6071,N'<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>',NULL)
,(6666,6071,NULL,NULL)
,(7777,6071,NULL,NULL)
,(8888,6071,NULL,NULL);
--Here starts the updateable CTE
WITH UpdateableCTE AS
(
SELECT t1.rowIndex
,t1.SubscriptionData_AFTER_XML
,(
SELECT t2.AccountNumber AS ValueItem
FROM #tbl AS t2
WHERE t2.SubscriptionID=t1.SubscriptionID
FOR XML PATH(''),ROOT('Value'),TYPE
).query
(N'declare default element namespace "http://www.whatevernamspace.com/v1";
let $nd:=/*:Value
return
<NVPList>
<Item>
<Name>{sql:column("XmlName")}</Name>
<Value>
{
for $vi in $nd/*:ValueItem
return <ValueItem>{$vi/text()}</ValueItem>
}
</Value>
</Item>
</NVPList>
'
) AS NewXML
FROM #tbl AS t1
CROSS APPLY( SELECT t1.SubscriptionData_XML.value('(//*:Name)[1]','nvarchar(max)') AS XmlName) AS x
WHERE SubscriptionData_XML IS NOT NULL
)
--The UPDATE statement
UPDATE UpdateableCTE SET SubscriptionData_AFTER_XML=NewXML
FROM UpdateableCTE;
--The SELECT to check the success
SELECT * FROM #tbl
I was able to accomplish this task with a sql UPDATE statement using the xml modify() method and without using any loops. Here is a breakdown of the solution:
1) I had to get all the AccountNumbers for the SubscriptionID and format them in
into xml <ValueItem> nodes.
SQL QUERY 1:
SELECT
ge.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge2.AccountNumber,'')
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionID = ge.SubscriptionID
FOR XML PATH('')) AS xml) AS AccountNumberXml
FROM dbo.GoldenEgg ge
WHERE ge.SubscriptionData_XML IS NOT NULL
SQL QUERY 1 RESULT:
SQL QUERY 1 XML RESULT (SubscriptionID 6070):
<ValueItem>39448474</ValueItem>
<ValueItem>41447395</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
2) Now that I have the AccountNumbers in a single value, I can now use the xml modify() method and insert the AccountNumberXml value into the last position of the <Value> xml node. I will do this using an UPDATE statement with INNER JOIN. Also note that I initally set SubscriptionData_AFTER_XML equal to SubscriptionData_XML before doing anything.
SQL QUERY 2:
UPDATE ge
SET SubscriptionData_AFTER_XML.modify
('declare default element namespace "http://www.whatevernamspace.com/v1";
insert sql:column("t1.AccountNumberXml") as last into (/NVPList/Item/Value)[1]')
FROM dbo.GoldenEgg ge
INNER JOIN (SELECT
ge2.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge1.AccountNumber,'')
FROM dbo.GoldenEgg ge1
WHERE ge1.SubscriptionID = ge2.SubscriptionID
FOR XML PATH('')) AS xml) as AccountNumberXml
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionData_AFTER_XML IS NOT NULL) t1 ON t1.SubscriptionID = ge.SubscriptionID
WHERE ge.SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 2 RESULT:
SQL QUERY 2 XML RESULT (SubscriptionID 6070 SubscriptionData_AFTER_XML column):
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem xmlns="">39448474</ValueItem>
<ValueItem xmlns="">41447395</ValueItem>
<ValueItem xmlns="">56936495</ValueItem>
<ValueItem xmlns="">70660044</ValueItem>
</Value>
</Item>
</NVPList>
As you may see there are now two problems with the final xml result in the SubscriptionData_AFTER_XML column.
Problem 1
For subscriptionID 6070 AccountNumber 39448474 is being repeated in the <ValueItem> node list, which I do not want. To fix this I have to query the current AccountNumber values in the xml and exclude those AccountNumbers from the previous INNER JOIN
SQL QUERY 3:
This query will give me a result set with all the current AccountNumbers in the SubscriptionData_XML column, which I can then use to exclude these AccountNumbers from the SQL QUERY 1 result set
SELECT SubscriptionID, t.c.value('.', 'varchar(MAX)') as CurrentValueItems
FROM dbo.GoldenEgg
CROSS APPLY SubscriptionData_XML.nodes('declare default element namespace "http://www.whatevernamspace.com/v1";
/NVPList/Item/Value/ValueItem') as t(c)
WHERE SubscriptionData_XML IS NOT NULL
SQL QUERY 3 RESULT:
Now putting it all together to get the correct final result
SQL QUERY 4:
UPDATE ge
SET SubscriptionData_AFTER_XML.modify
('declare default element namespace "http://www.whatevernamspace.com/v1";
insert sql:column("t1.AccountNumberXml") as last into (/NVPList/Item/Value)[1]')
FROM dbo.GoldenEgg ge
INNER JOIN (SELECT
ge2.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge1.AccountNumber,'')
FROM dbo.GoldenEgg ge1
--make sure we are not inserting AccountNumbers that already exists in the subscription data
WHERE ge1.AccountNumber NOT IN (SELECT t.c.value('.', 'varchar(MAX)') as CurrentValueItems
FROM dbo.GoldenEgg
CROSS APPLY SubscriptionData_XML.nodes('declare default element namespace "http://www.whatevernamspace.com/v1";
/NVPList/Item/Value/ValueItem') as t(c)
WHERE SubscriptionData_XML IS NOT NULL
AND SubscriptionID = ge2.SubscriptionID)
AND ge1.SubscriptionID = ge2.SubscriptionID
FOR XML PATH('')) AS xml) as AccountNumberXml
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionData_AFTER_XML IS NOT NULL) t1 ON t1.SubscriptionID = ge.SubscriptionID
WHERE ge.SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 4 XML RESULT (SubscriptionID 6070 SubscriptionData_AFTER_XML column):
As you can see AccountNumber 39448474 is now only listed once in the xml
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem xmlns="">41447395</ValueItem>
<ValueItem xmlns="">56936495</ValueItem>
<ValueItem xmlns="">70660044</ValueItem>
</Value>
</Item>
</NVPList>
Problem 2
When the with AccountNumber node list is inserted, it is being inserted with an empty xmlns="" namespace. This is query I used to remove the empty xmlns="" namespace.
SQL QUERY 5:
UPDATE dbo.GoldenEgg
SET SubscriptionData_AFTER_XML = CONVERT(XML, REPLACE(CONVERT(NVARCHAR(MAX), SubscriptionData_AFTER_XML), N'xmlns=""',''))
WHERE SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 5 XML RESULT (SubscriptionID 6070):
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem>41447395</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
</Value>
</Item>
</NVPList>
I hope this helps anyone who may need to do something similar
I'm currently trying to select multiple nodes for a xml variable that get passed into a stored procedure. But I only get one node from it's xml. How can I list all of the item names and string values?
Test code:
DECLARE #T TABLE (AllXml ntext)
INSERT #T VALUES('<error>
<item name="item 1">
<value string="string 1" />
</item>
<item name="item 2">
<value string="string 2" />
</item>
<item name="item 3">
<value string="string 3" />
</item>
</error>')
SELECT
CAST(AllXml as xml).value('(/error/item/#name)[1]', 'varchar(100)' ),
CAST(AllXml as xml).value('(/error/item/value/#string)[1]', 'varchar(max)' )
FROM #T
Desired result:
Item 1 string 1
Item 2 string 2
Item 3 string 3
You can achieve it using the CROSS Apply. And Sub-Select.
SELECT
m.c.value('(#name)[1]', 'varchar(100)') AS Name,
m.c.value('(value/#string)[1]', 'varchar(max)') AS Value
FROM
(
SELECT CAST(AllXml as xml) AllXml
FROM #T
) AS data
CROSS APPLY AllXml.nodes('/error/item') as m(c)
Or you can use the one more CROSS APPLY as below,
SELECT
m.c.value('(#name)[1]', 'varchar(100)') AS Name,
m.c.value('(value/#string)[1]', 'varchar(max)') AS Value
FROM #T
CROSS APPLY (SELECT CAST(AllXml AS XML)) as D(D)
CROSS APPLY D.D.nodes('/error/item') as m(c)
I have a column of ntext data type and NOT XML. It stores all xml data. I need to get records based on xml node value.
=>input value is CpsiaId = 456 and should return all records which has this value in the xml
I tried select * from tableName
where convert(xml,column_name).value('data((/root/ProductInformation/CPSIA/CpsiaDetails/Item/CpsiaId)[1])','int') = 456
but it didn't work....any ideas or other way of getting the records based xml node value.
Sample Xml:
<root>
<ProductInformation>
<Name> Truck with Battery Charger</Name>
<Description>Fr.</Description>
<CPSIA>
<CpsiaDetails>
<Item>
<CpsiaId>456</CpsiaId>
<CpsiaMessage>waring</CpsiaMessage>
</Item>
<Item>
<CpsiaId>236</CpsiaId>
<CpsiaMessage>to health</CpsiaMessage>
</Item>
</CpsiaDetails>
</CPSIA>
</ProductInformation>
</root>
convert(xml,column_name).exist('/root/ProductInformation/CPSIA/CpsiaDetails/Item[CpsiaId=456]') = 1
This works, and you can replace the constant (456) with an sql:variable() if you need a dynamic value, like so (assuming a numeric variable #i):
'/root/ProductInformation/CPSIA/CpsiaDetails/Item[CpsiaId=sql:variable("#i")]'
Edit:
Sample code as requested:
DECLARE #t nvarchar(MAX);
SET #t = '<root>
<ProductInformation>
<Name> Truck with Battery Charger</Name>
<Description>Fr.</Description>
<CPSIA>
<CpsiaDetails>
<Item>
<CpsiaId>456</CpsiaId>
<CpsiaMessage>waring</CpsiaMessage>
</Item>
<Item>
<CpsiaId>236</CpsiaId>
<CpsiaMessage>to health</CpsiaMessage>
</Item>
</CpsiaDetails>
</CPSIA>
</ProductInformation>
</root>'
DECLARE #i int;
SET #i = 456;
SELECT convert(xml,#t).exist('/root/ProductInformation/CPSIA/CpsiaDetails/Item[CpsiaId=sql:variable("#i")]')
It may be easier to use a CTE to convert it to XML first and then do a CROSS Apply. Here's a sample
Declare #test table (id int identity, xmlData nvarchar(max))
Insert Into #test
(xmldata)
Values
('<root>
<ProductInformation>
<Name> Truck with Battery Charger</Name>
<Description>Fr.</Description>
<CPSIA>
<CpsiaDetails>
<Item>
<CpsiaId>456</CpsiaId>
<CpsiaMessage>waring</CpsiaMessage>
</Item>
<Item>
<CpsiaId>236</CpsiaId>
<CpsiaMessage>to health</CpsiaMessage>
</Item>
</CpsiaDetails>
</CPSIA>
</ProductInformation>
</root>'),
('<root>
<ProductInformation>
<Name> Truck with Battery Charger</Name>
<Description>Fr.</Description>
<CPSIA>
<CpsiaDetails>
<Item>
<CpsiaId>999</CpsiaId>
<CpsiaMessage>waring</CpsiaMessage>
</Item>
<Item>
<CpsiaId>123</CpsiaId>
<CpsiaMessage>to health</CpsiaMessage>
</Item>
</CpsiaDetails>
</CPSIA>
</ProductInformation>
</root>')
;with cte as (select id, convert(xml,t.xmlData) xdata from #test t )
SELECT t.*
from cte inner join #test t
on cte.id = t.id
cross apply cte.xdata.nodes('//root/ProductInformation/CPSIA/CpsiaDetails/Item') x(nd)
WHERE
x.nd.value('CpsiaId[1]','int') =456
Using Lucero's answer for the Select against a table (rather than an xml variable)
DECLARE #i int;
SET #i = 456;
SELECT *
FROM
#test
WHERE
convert(xml,xmlData).exist('/root/ProductInformation/CPSIA/CpsiaDetails/Item[CpsiaId=sql:variable("#i")]') = 1