XML element value with width limitation - sql

I am new to XML and trying to solve the following in SQL Server 2008 using customer table.
NAME column has fixed width, so the value (customer name) needs to be separated into more than one representation.
Please see:
NAME index="1"....
NAME index="2"....
Any idea how to tackle this?
Thank you,
Anne
<PARTNER>
<NAME index="1">XEXSY SMALL REALTY LLC</NAME>
<NAME index="2">AA/NAX TEEEENERGY</NAME>
<PARTNRTYPE>703884</PARTNRTYPE>
<ADDRESS>
<ADDRLINE index="1">544 PACIFIC BLVD</ADDRLINE>
<CITY>LONG BEACH</CITY>
<COUNTRY>US</COUNTRY>
<POSTALCODE>07740</POSTALCODE>
</ADDRESS>
</PARTNER>

This design is awfull. If you have to the slightest chance to change this, you should...
If you have to stick with this, you can try it like this:
DECLARE #mockup TABLE(Name VARCHAR(100),PartnerType INT,Addr VARCHAR(100),City VARCHAR(100));
INSERT INTO #mockup VALUES
('This is a very long name which needs to be splitted in smaller parts'
,12345
,'And this address is very long too, the person has a really long address...'
,'Washington')
,('ShortName'
,12345
,'ShortAddress'
,'New York');
--You can set the length of the slices. The TOP(20) is hardcoded and sets an upper limit to the count of parts.
DECLARE #PartLenght INT=20;
--The query will get a number's table (tally table) on-the-fly and then use FOR XML PATH() to create the XML with the nestings you need.
WITH Tally AS
(
SELECT TOP(20) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr
FROM master..spt_values
)
SELECT (
SELECT Nr AS [NAME/#index]
,SUBSTRING(m.Name,Nr+((Nr-1) * (#PartLenght-1)),#PartLenght) AS [NAME]
FROM Tally
WHERE LEN(SUBSTRING(m.Name,Nr+((Nr-1) * (#PartLenght-1)),#PartLenght))>0
FOR XML PATH(''),TYPE
)
,m.PartnerType AS [PARTNERTYPE]
,(
SELECT
(
SELECT Nr AS [ADDRLINE/#index]
,SUBSTRING(m.Addr,Nr+((Nr-1) * (#PartLenght-1)),#PartLenght) AS [ADDRLINE]
FROM Tally
WHERE LEN(SUBSTRING(m.Addr,Nr+((Nr-1) * (#PartLenght-1)),#PartLenght))>0
FOR XML PATH(''),TYPE
)
,City AS [CITY]
FOR XML PATH('ADDRESS'),TYPE
)
FROM #mockup AS m
FOR XML PATH('PARTNER')
The result
<PARTNER>
<NAME index="1">This is a very long </NAME>
<NAME index="2">name which needs to </NAME>
<NAME index="3">be splitted in small</NAME>
<NAME index="4">er parts</NAME>
<PARTNERTYPE>12345</PARTNERTYPE>
<ADDRESS>
<ADDRLINE index="1">And this address is </ADDRLINE>
<ADDRLINE index="2">very long too, the p</ADDRLINE>
<ADDRLINE index="3">erson has a really l</ADDRLINE>
<ADDRLINE index="4">ong address...</ADDRLINE>
<CITY>Washington</CITY>
</ADDRESS>
</PARTNER>
<PARTNER>
<NAME index="1">ShortName</NAME>
<PARTNERTYPE>12345</PARTNERTYPE>
<ADDRESS>
<ADDRLINE index="1">ShortAddress</ADDRLINE>
<CITY>New York</CITY>
</ADDRESS>
</PARTNER>

Related

Stuck in T-SQL : XML to temp table

I have a little challenge where I am currently stuck in.
This is my query:
DECLARE #xml XML
SET #xml =
'<beginmsg>
<refmsg>A1234567</refmsg>
<shipments>
<shipment>
<load>
<address>
<name>Loadname</name>
<street>Street</street>
<zipcode>ABCDE</zipcode>
<city>Munchen</city>
<countrycode>DE</countrycode>
</address>
<dateTime>
<date>2021-02-02</date>
<timeFrom>08:00:00</timeFrom>
</dateTime>
</load>
<unload>
<address>
<name>unloadname</name>
<street>Street unload</street>
<zipcode>1111</zipcode>
<city>Dresden</city>
<countrycode>DE</countrycode>
</address>
</unload>
<goods>
<good>
<description>Cookies</description>
<quantity>1</quantity>
</good>
<good>
<description>Cookies</description>
<quantity>3</quantity>
</good>
<good>
<description>Some food</description>
<quantity>2</quantity>
</good>
</goods>
<barcodes>
<barcode>7829348742910092309325</barcode>
<barcode>7829348742112344114414</barcode>
<barcode>0984746166149566188446</barcode>
</barcodes>
<references>
<reference>GBP-4362</reference>
</references>
</shipment>
<shipment>
<load>
<address>
<name>shipment 2 load</name>
<street>Street load2</street>
<zipcode>1234 RR</zipcode>
<city>Koln</city>
<countrycode>DE</countrycode>
</address>
<dateTime>
<date>2021-03-03</date>
<timeFrom>10:00:00</timeFrom>
</dateTime>
</load>
<unload>
<address>
<name>Shipment 2 unload</name>
<street>Street 2 unl</street>
<zipcode>1000 AA</zipcode>
<city>Amsterdam</city>
<countrycode>NL</countrycode>
</address>
</unload>
<goods>
<good>
<description>Televisions</description>
<quantity>2</quantity>
</good>
</goods>
<barcodes>
<barcode>0984746166149566188446</barcode>
</barcodes>
<references>
<reference>HBR-7211CX</reference>
</references>
</shipment>
</shipments>
</beginmsg>'
IF OBJECT_ID('tempdb..#Goods') IS NOT NULL DROP TABLE #Goods;
CREATE TABLE #Goods
(
row INT,
shipmentreference VARCHAR(100),
description VARCHAR(MAX),
quantity INT,
barcode VARCHAR(MAX)
);
INSERT INTO #Goods
SELECT
"row" = ROW_NUMBER() OVER (PARTITION BY shipment.shipment ORDER BY shipment.shipment),
"shipmentreference" = CAST(shipment.shipment.value('(references/reference)[1]', 'VARCHAR(100)') AS VARCHAR(MAX)) + '-' + CAST(ROW_NUMBER() OVER (PARTITION BY shipment.shipment ORDER BY shipment.shipment) AS VARCHAR(MAX)),
"description" = goods.goods.value('(description)[1]', 'VARCHAR(10)'),
"quantity" = goods.goods.value('(quantity)[1]', 'VARCHAR(10)'),
"barcode" = barcode.barcode.value('(barcode)[1]', 'VARCHAR(100)')
FROM
#Xml.nodes('beginmsg/shipments/shipment') shipment(shipment)
CROSS APPLY
shipment.shipment.nodes('goods/good') goods(goods)
CROSS APPLY
shipment.shipment.nodes('barcodes') barcode(barcode)
SELECT * FROM #Goods
The XML contains 2 shipments.
In the 1st shipment there are 3 goods segment with each 1 'good' segment. In the 2nd shipment there is one good segment.
I would like to merge each good, with their information and with the 1st barcode for the 1st good, 2nd barcode for the 2nd good etc. etc.
So I was thinking to create a TEMP table #goods and a temp table to store the barcodes in. In both temp tables I would like to add the "reference" so I can join both tables on that unique field.
But at this moment I am stuck. In my final query (as shown above) the output is as follow:
row
reference
description
quantity
barcode
1
GBP-4362-1
Cookies
1
7829348742910092309325
2
GBP-4362-1
Cookies
3
7829348742910092309325
3
GBP-4362-1
Some food
2
7829348742910092309325
1
HBR-7211CX-1
Television
2
0984746166149566188446
So the barcode is each first (what is actually what I am querying :))
How can I iterate through these barcodes nodes and how can I manage to merge it into the row?

How to get value from a node in XML via SQL Server

I've found several pieces of information online about this but I can't get it working for the life of me.
This is the XML I have:
I need to extract the ID & Name value for each node. There are a lot.
I tried to do this but it returns NULL:
select [xml].value('(/Alter/Object/ObjectDefinition/MeasureGroup/Partitions/Partition/ID)[1]', 'varchar(max)')
from test_xml
I understand the above would return only 1 record. My question is, how do I return all records?
Here's the XML text (stripped down version):
<Alter xmlns="http://schemas.microsoft.com/analysisservices/2003/engine" AllowCreate="true" ObjectExpansion="ExpandFull">
<ObjectDefinition>
<MeasureGroup xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>ts_homevideo_sum_20140430_76091ba1-3a51-45bf-a767-f9f3de7eeabe</ID>
<Name>table_1</Name>
<StorageMode valuens="ddl200_200">InMemory</StorageMode>
<ProcessingMode>Regular</ProcessingMode>
<Partitions>
<Partition>
<ID>123</ID>
<Name>2012</Name>
</Partition>
<Partition>
<ID>456</ID>
<Name>2013</Name>
</Partition>
</Partitions>
</MeasureGroup>
</ObjectDefinition>
</Alter>
You need something like this:
DECLARE #MyTable TABLE (ID INT NOT NULL, XmlData XML)
INSERT INTO #MyTable (ID, XmlData)
VALUES (1, '<Alter xmlns="http://schemas.microsoft.com/analysisservices/2003/engine" AllowCreate="true" ObjectExpansion="ExpandFull">
<ObjectDefinition>
<MeasureGroup xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>ts_homevideo_sum_20140430_76091ba1-3a51-45bf-a767-f9f3de7eeabe</ID>
<Name>table_1</Name>
<StorageMode valuens="ddl200_200">InMemory</StorageMode>
<ProcessingMode>Regular</ProcessingMode>
<Partitions>
<Partition>
<ID>123</ID>
<Name>2012</Name>
</Partition>
<Partition>
<ID>456</ID>
<Name>2013</Name>
</Partition>
</Partitions>
</MeasureGroup>
</ObjectDefinition>
</Alter>')
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/analysisservices/2003/engine')
SELECT
tbl.ID,
MeasureGroupID = xc.value('(ID)[1]', 'varchar(200)'),
MeasureGroupName = xc.value('(Name)[1]', 'varchar(200)'),
PartitionID = xp.value('(ID)[1]', 'varchar(200)'),
PartitionName = xp.value('(Name)[1]', 'varchar(200)')
FROM
#MyTable tbl
CROSS APPLY
tbl.XmlData.nodes('/Alter/ObjectDefinition/MeasureGroup') AS XT(XC)
CROSS APPLY
XC.nodes('Partitions/Partition') AS XT2(XP)
WHERE
ID = 1
First of all, you must respect and include the default XML namespace defined in the root of your XML document.
Next, you need to do a nested call to .nodes() to get all <MeasureGroup> and all contained <Partition> nodes, so that you can reach into those XML fragments and extract the ID and Name from them.
This should then result in something like this as output:

How to get value from ntext (in xml format) column in sql

I have a column in my SQL database that is called Triggers_xml_data and its type is ntext. The column is in a xml format and I am trying to get a value from a certain part of the xml. I seen an example of this being done without a column like this:
declare #fileContent xml
set #fileContent ='<my:Header>
<my:Requestor>Mehrlein, Roswitha</my:Requestor>
<my:RequestorUserName>SJM\MehrlR01</my:RequestorUserName>
<my:RequestorEmail>RMehrlein#SJM.com</my:RequestorEmail>
<my:HRContact>Roswita Mehrlein, Beatrice Porta</my:HRContact>
<my:Entity>SJM Germany</my:Entity>
<my:Department>HR/Administration</my:Department>
<my:PositionTitle>Sales Representative</my:PositionTitle>
<my:JobDescription>x0lGQRQAAAABAAAAAAAAAAAeAQAyAAAAVgBAAAAA=</my:JobDescription>
<my:PositionDepartment>Sales</my:PositionDepartment>'
 
;WITH XMLNAMESPACES ('http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-08-29T12-58-51' as my)
select #fileContent.value('(//my:PositionDepartment)[1]', 'varchar(255)')
But I want to select my column like this:
Declare #filevalue xml
select de.triggers_xml_data
from dbo.DEPLOYMENT_ENVIRONMENT as de
But this is not working and I tried to use this #filecontent.value('(//value)[1]','varchar(255)') and making it equal the column value, I have tried casting it but I can't find a way to do this. Is this possible?
When I do this:
SELECT
CAST(
REPLACE(CAST(de.TRIGGERS_XML_DATA AS VARCHAR(MAX)), 'encoding="utf-16"', '')
AS XML).value('(triggers/triggerDefinition/config/item/value)[1]', 'NVARCHAR(max)') as Item, de.ENVIRONMENT_ID
from dbo.DEPLOYMENT_ENVIRONMENT as de
where de.ENVIRONMENT_ID = 19234819
I am getting a null value returned.
Here is an example of what my xml could look like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xml:space="preserve">
<triggers>
<defined>true</defined>
<triggerDefinition>
<id>1</id>
<name>After successful deployment</name>
<userDescription/>
<isEnabled>true</isEnabled>
<pluginKey>com.atlassian.bamboo.triggers.atlassian-bamboo-triggers:afterSuccessfulDeployment</pluginKey>
<triggeringRepositories/>
<config>
<item>
<key>deployment.trigger.afterSuccessfulDeployment.triggeringEnvironmentId</key>
<value>19234819</value>
</item>
</config>
</triggerDefinition>
</triggers>
<bambooDelimiterParsingDisabled>true</bambooDelimiterParsingDisabled>
</configuration>
The XML, as you posted it, is not valid. Your code example does not work... It is not allowed to use a namespace prefix without a namespace declaration. Furthermore your example misses the closing Header-tag...
I corrected this...
DECLARE #yourTbl TABLE(ID INT, YourXML NTEXT);
INSERT INTO #yourTbl VALUES
(1,N'<my:Header xmlns:my="DummyUrl">
<my:Requestor>Mehrlein, Roswitha</my:Requestor>
<my:RequestorUserName>SJM\MehrlR01</my:RequestorUserName>
<my:RequestorEmail>RMehrlein#SJM.com</my:RequestorEmail>
<my:HRContact>Roswita Mehrlein, Beatrice Porta</my:HRContact>
<my:Entity>SJM Germany</my:Entity>
<my:Department>HR/Administration</my:Department>
<my:PositionTitle>Sales Representative</my:PositionTitle>
<my:JobDescription>x0lGQRQAAAABAAAAAAAAAAAeAQAyAAAAVgBAAAAA=</my:JobDescription>
<my:PositionDepartment>Sales</my:PositionDepartment>
</my:Header>');
--Lazy approach
SELECT ID
,CAST(CAST(YourXml AS NVARCHAR(MAX)) AS XML).value(N'(//*:PositionDepartment)[1]','nvarchar(max)')
FROM #yourTbl;
--explicit approach
WITH XMLNAMESPACES('DummyUrl' AS my)
SELECT ID
,CAST(CAST(YourXml AS NVARCHAR(MAX)) AS XML).value(N'(/my:Header/my:PositionDepartment)[1]','nvarchar(max)')
FROM #yourTbl
Some Background
If possible you should not store XML in other format than XML and further more one should avoid NTEXT, as it is depricated since SS2005!.
You have to cast NTEXT to NVARCHAR(MAX) first, than cast this to XML. The second will break, if the XML is not valid. That means: If the XML is really the way you posted it, this cannot work!
UPDATE: String-based approach, if XML does not work
If you cannot cast this to XML you might try this
--String based
WITH Casted AS
(
SELECT ID
,CAST(YourXML AS NVARCHAR(MAX)) AS TheXmlAsString
FROM #yourTbl
)
,WithPosition AS
(
SELECT Casted.*
,CHARINDEX(N'<my:PositionDepartment>',TheXmlAsString) + LEN(N'<my:PositionDepartment>') AS FirstLetter
FROM Casted
)
SELECT ID
,SUBSTRING(TheXmlAsString,FirstLetter,CHARINDEX('<',TheXmlAsString,FirstLetter)-FirstLetter)
FROM WithPosition
UPDATE 2
According to your edit the following returns a NULL value. This is good, because it shows, that the cast was successfull.
SELECT
CAST(
REPLACE(CAST(de.TRIGGERS_XML_DATA AS VARCHAR(MAX)), 'encoding="utf-16"', '')
AS XML).value('(triggers/triggerDefinition/config/item/value)[1]',
'NVARCHAR(max)') as Item, de.ENVIRONMENT_ID
from dbo.DEPLOYMENT_ENVIRONMENT as de
where de.ENVIRONMENT_ID = 19234819
Try this (skip namespace with wildcard):
SELECT
CAST(
REPLACE(CAST(de.TRIGGERS_XML_DATA AS VARCHAR(MAX)), 'encoding="utf-16"', '')
AS XML).value('(*:triggers/*:triggerDefinition/*:config/*:item/*:value)[1]', 'NVARCHAR(max)') as Item, de.ENVIRONMENT_ID
from dbo.DEPLOYMENT_ENVIRONMENT as de
where de.ENVIRONMENT_ID = 19234819
And this should be even better:
SELECT
CAST(CAST(de.TRIGGERS_XML_DATA AS NVARCHAR(MAX)) AS XML).value('(*:triggers/*:triggerDefinition/*:config/*:item/*:value)[1]', 'NVARCHAR(max)') as Item, de.ENVIRONMENT_ID
from dbo.DEPLOYMENT_ENVIRONMENT as de
where de.ENVIRONMENT_ID = 19234819
UPDATE 3
I'd rather cut away the full declaration. Your posted example would go like this
DECLARE #DEPLOYMENT_ENVIRONMENT TABLE(ENVIRONMENT_ID INT, TRIGGERS_XML_DATA NTEXT);
INSERT INTO #DEPLOYMENT_ENVIRONMENT VALUES
(19234819,N'<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xml:space="preserve">
<triggers>
<defined>true</defined>
<triggerDefinition>
<id>1</id>
<name>After successful deployment</name>
<userDescription/>
<isEnabled>true</isEnabled>
<pluginKey>com.atlassian.bamboo.triggers.atlassian-bamboo-triggers:afterSuccessfulDeployment</pluginKey>
<triggeringRepositories/>
<config>
<item>
<key>deployment.trigger.afterSuccessfulDeployment.triggeringEnvironmentId</key>
<value>19234819</value>
</item>
</config>
</triggerDefinition>
</triggers>
<bambooDelimiterParsingDisabled>true</bambooDelimiterParsingDisabled>
</configuration>');
WITH Casted AS
(
SELECT CAST(de.TRIGGERS_XML_DATA AS NVARCHAR(MAX)) AS XmlAsSting
FROM #DEPLOYMENT_ENVIRONMENT as de
where de.ENVIRONMENT_ID = 19234819
)
SELECT CAST(SUBSTRING(XmlAsSting,CHARINDEX('?>',XmlAsSting)+2,8000) AS XML).value('(/*:configuration/*:triggers/*:triggerDefinition/*:config/*:item/*:value)[1]', 'NVARCHAR(max)') as Item
FROM Casted;

Safely retrieve values from element that can contain different values

We have some xml elements in a database that [for older data] can sometimes contain guids and sometimes contain integers.
is there a nice way of pulling out all the integrs only?
This will fail if the value element contains a guid!
select
ra.*,
t.c.value('.', 'int') as organisationId
from
Audit.EmployeeAudit ra
cross apply ra.EmployeeXml.nodes('//*:employee/*:property[*:name="ORG"]/*:value') t(c)
Sample Xml
<employee>
<property>
<name>ORG</name>
<value>39</value> <!-- Sometimes this will be a guid -->
<description>Leeds</description>
</property>
</employee>
You could add a predicate to only match entries of less than or equal to 10 characters.
;with EmployeeAudit as
(
SELECT CAST('<employee><property>
<name>ORG</name>
<value>39</value> <!-- Sometimes this will be a guid -->
<description>Leeds</description>
</property></employee>
' AS XML) AS EmployeeXml
UNION ALL
SELECT CAST('<employee><property>
<name>ORG</name>
<value>2FD29F11-59FC-47FD-BC30-DD330A53284E</value>
<description>Leeds</description>
</property></employee>
' AS XML)
)
select
ra.*,
t.c.value('.', 'int') as organisationId
from
EmployeeAudit ra
cross apply
ra.EmployeeXml.nodes
('//*:employee/*:property[*:name="ORG"]/*:value[string-length() <= 10]') t(c)
Or actually this might be a bit more robust
('//*:employee/*:property[*:name="ORG"]/*:value[ceiling(.) = .]') t(c)

SQL XML parsing different nodes

i would like to ask you for help with parsing XML in SQL, where my XML looks like this, where Load is parrent which can be repeated X-times. I need Column SerNr and for each row need to bound Order name
where final table will looks like this
Example of table:
<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976"/>
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>
sql is
with data as (SELECT CAST(MY_XML AS xml) as MY_XML FROM OPENROWSET(BULK 'addres to xml', SINGLE_BLOB) AS T(MY_XML)),
datainfo as (
SELECT
MY_XML.Blasting.value(' #name', 'BIGINT') as Size_mb,
MY_XML.Blasting.value('#SerNr', 'varchar(32)') as SerNr
FROM data CROSS APPLY MY_XML.nodes('ImageHistory/Load/Data/Disk') AS MY_XML (Blasting))
select * from datainfo
thank you for help
I saved the XML as a file on the file system: e:\Temp\DiskSerialNumbers.xml
The rest is below.
SQL
;WITH XmlFile (Contents) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'e:\Temp\DiskSerialNumbers.xml', SINGLE_BLOB) AS XmlData
)
SELECT c.value('(../../Orders/Order/#name)[1]', 'INT') AS [Name]
, c.value('#SerNr', 'VARCHAR(20)') AS [SerNr]
FROM XmlFile CROSS APPLY Contents.nodes('/ImageHistory/Load/Data/Disk') AS t(c);
Output
+----------+--------+
| Name | SerNr |
+----------+--------+
| 20548976 | XXXXXX |
| 20548976 | ZZZZZ |
+----------+--------+
You've got your answer already, but I want to point to the fact, that backward navigation (using ../../) is horribly slow with bigger structures.
I'd suggest this:
A mockup to simulate your issue:
DECLARE #yourTable TABLE(ID INT IDENTITY,YourXml XML);
INSERT INTO #yourTable VALUES
(N'<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976" />
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>');
--The query
SELECT t.ID
,ld.value('#targets','int') AS Load_Targets
,ld.value('#totalTime','int') AS Load_TotalTime
,ld.value('(Orders/Order/#name)[1]','int') AS Order_Name
,dsk.value('#SerNr','nvarchar(100)') AS Disk_SerNr
,dsk.value('#Size_mb','nvarchar(100)') AS Disk_Size_mb
,dsk.value('#LoadSuccessfull','bit') AS Disk_LoadSuccessfull
FROM #yourTable AS t
CROSS APPLY t.YourXml.nodes('/ImageHistory/Load') A(ld)
CROSS APPLY A.ld.nodes('Data/Disk') B(dsk);
The idea in short:
The first APPLY will dive down to <Load> and will return all <Load> elements (if there are more...
The second APPLY will use the fragment returned by the first APPLY and dive deeper down to <Disk>.
We fetch the order's name (and other values) calling .value() against the first fragment (which is the <Load> element) and we fetch the values of <Disk> calling .value() against the fragment of the second APPLY.