Stuck in T-SQL : XML to temp table - sql

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?

Related

Convert XML data into a table SQL, Different tag in one script

I have one XML data which I want to bring them into a one table
the XML data is like this:
<return>
<start>
<name>Sara</name>
<familyname>Moradi</familyname>
<age>22</age>
</start>
<start>
<name>Sam</name>
<familyname>Mic</familyname>
<age>32</age>
</start>
<errorCode>0</errorCode>
<resultStatus/>
<extra>22255</extra>
</return>
and I wanna create a table like this:
name
familyname
age
errorCode
extra
Sara
Moradi
22
0
22255
Sam
Mic
32
0
22255
I check the previous ones but they didn't help me.
Try below:
declare #data xml = convert(xml, '<return>
<start>
<name>Sara</name>
<familyname>Moradi</familyname>
<age>22</age>
</start>
<start>
<name>Sam</name>
<familyname>Mic</familyname>
<age>32</age>
</start>
<errorCode>0</errorCode>
<resultStatus/>
<extra>22255</extra>
</return>')
SELECT X.Y.value('(name)[1]', 'VARCHAR(20)') as name,
X.Y.value('(familyname)[1]', 'VARCHAR(20)') as familyname,
X.Y.value('(age)[1]', 'int') as age,
A.B.value('(errorCode)[1]','int') as errorCode,
A.B.value('(extra)[1]','int') as extra
FROM #data.nodes('return') as A(B)
cross apply A.B.nodes('start') as X(Y)
For quick solution, I have stored xml data into variable, you need to replace it with your original column from table.

XML element value with width limitation

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>

How to convert nested XML into corresponding tables?

I have a complex nested XML (generated from a C# entity graph), for example:
<Customers>
<Customer>
<Id>1</Id>
<Number>12345</Number>
<Addresses>
<Address>
<Id>100</Id>
<Street>my street </street>
<city>London</city>
</Address>
<Address>
<Id>101</Id>
<street>my street 2</street>
<city>Berlin</city>
</Address>
</Addresses>
<BankDetails>
<BankDetail>
<Id>222</Id>
<Iban>DE8439834934939434333</Iban>
</BankDetail>
<BankDetail>
<Id>228</Id>
<Iban>UK1237921391239123213</Iban>
</BankDetail>
</BankDetails>
<Orders>
<Order>
<OrderLine>
</OrderLine>
</Order>
</Orders>
</Customer>
</Customers>
Before saving the above XML data into the actual tables, I need to process it first. For this reason, I created corresponding table types. Each of these table types have an extra column (guid as ROWGUID) so that if I'm processing new data (not yet assigned primary key) I generate a unique key. I use this column to keep the relational integrity between different table types.
What is the SQL syntax to convert the above nested XML to their corresponding tables, keeping in mind that child records must reference the generated parent guid?
Try it like this:
DECLARE #xml XML=
N'<Customers>
<Customer>
<Id>1</Id>
<AccountNumber>12345</AccountNumber>
<Addresses>
<Address>
<Id>100</Id>
<street>my street></street>
<city>London</city>
</Address>
<Address>
<Id>101</Id>
<street>my street></street>
<city>Berlin</city>
</Address>
</Addresses>
<BankDetails>
<BankDetail>
<Id>222</Id>
<Iban>DE8439834934939434333</Iban>
</BankDetail>
<BankDetail>
<Id>228</Id>
<Iban>UK1237921391239123213</Iban>
</BankDetail>
</BankDetails>
<Orders>
<Order>
<OrderLine />
</Order>
</Orders>
</Customer>
</Customers>';
--This query will create a table #tmpInsert with all the data
SELECT cust.value('Id[1]','int') AS CustomerID
,cust.value('AccountNumber[1]','int') AS CustomerAccountNumber
,addr.value('Id[1]','int') AS AddressId
,addr.value('street[1]','nvarchar(max)') AS AddressStreet
,addr.value('city[1]','nvarchar(max)') AS AddressCity
,bank.value('Id[1]','int') AS BankId
,bank.value('Iban[1]','nvarchar(max)') AS BankIban
,ord.value('OrderLine[1]','nvarchar(max)') AS OrderLine
INTO #tmpInsert
FROM #xml.nodes('/Customers/Customer') AS A(cust)
OUTER APPLY cust.nodes('Addresses/Address') AS B(addr)
OUTER APPLY cust.nodes('BankDetails/BankDetail') AS C(bank)
OUTER APPLY cust.nodes('Orders/Order') AS D(ord);
--Here you can check the content
SELECT * FROM #tmpInsert;
--Clean-Up
GO
DROP TABLE #tmpInsert
Once you've got all your data in the table, you can use simple DISTINCT, GROUP BY, if needed ROW_NUMBER() OVER(PARTITION BY ...) to select each set separately for the proper insert.

TSQL remove characters after a string

I have a table column containing set of addresses as XML data:
<?xml version="1.0" encoding="utf-16"?>
<AddressBook>
<Address>
<ID>-1</ID>
<AddressID>PRIMARY</AddressID>
<FirstName>JOHN L.</FirstName>
<LastName>JOHSON/JOHN L.</LastName>
<StreetLine1>123 NOWHERE ST</StreetLine1>
<City>OVERHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>12345</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5551231234</PhoneNumber>
<PhoneNumber2>5551231234</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
<Address>
<ID>-1</ID>
<AddressID>SECONDARY</AddressID>
<FirstName>SAM</FirstName>
<LastName>LARSON/SAM</LastName>
<StreetLine1>456 OVERHERE</StreetLine1>
<City>NOTTHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>54321</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5553334444</PhoneNumber>
<PhoneNumber2>5553334444</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
</AddressBook>
My issues is that the last name element contains an unnecessary duplicate of the first named preceding with '/' :
<FirstName>JOHN L.</FirstName>
<LastName>JOHSON/JOHN L.</LastName>
How can I remove the '/' and every character following '/' in the last name elements only to create an outcome as such?
<FirstName>JOHN L.</FirstName>
<LastName>JOHSON</LastName>
Try using charindex :
Select Left('JOHSON/JOHN L.', CHARINDEX('/', 'JOHSON/JOHN L.') - 1)
Fully working sample with table variable containing 2 records:
declare #t table (xmldata xml);
insert #t values ('
<AddressBook>
<Address>
<ID>-1</ID>
<AddressID>PRIMARY</AddressID>
<FirstName>JOHN L.</FirstName>
<LastName>JOHSON/JOHN L.</LastName>
<StreetLine1>123 NOWHERE ST</StreetLine1>
<City>OVERHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>12345</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5551231234</PhoneNumber>
<PhoneNumber2>5551231234</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
<Address>
<ID>-1</ID>
<AddressID>SECONDARY</AddressID>
<FirstName>SAM</FirstName>
<LastName>LARSON/SAM</LastName>
<StreetLine1>456 OVERHERE</StreetLine1>
<City>NOTTHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>54321</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5553334444</PhoneNumber>
<PhoneNumber2>5553334444</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
</AddressBook>');
insert #t values ('
<AddressBook>
<Address>
<ID>-1</ID>
<AddressID>PRIMARY</AddressID>
<FirstName>JANE L.</FirstName>
<LastName>JOHSON/JANE L.</LastName>
<StreetLine1>123 NOWHERE ST</StreetLine1>
<City>OVERHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>12345</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5551231234</PhoneNumber>
<PhoneNumber2>5551231234</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
<Address>
<ID>-1</ID>
<AddressID>SECONDARY</AddressID>
<FirstName>NIGEL</FirstName>
<LastName>NIGEL JR./NIGEL</LastName>
<StreetLine1>456 OVERHERE</StreetLine1>
<City>NOTTHERE</City>
<StateName>CA</StateName>
<StateCode>5</StateCode>
<PostalCode>54321</PostalCode>
<CountryName>United States</CountryName>
<CountryCode>en-US</CountryCode>
<PhoneNumber>5553334444</PhoneNumber>
<PhoneNumber2>5553334444</PhoneNumber2>
<TaxScheduleID>CA</TaxScheduleID>
<ShowAddress>1</ShowAddress>
</Address>
</AddressBook>');
update t
set xmldata =
(select n.a.value('ID[1]','nvarchar(max)') ID,
n.a.value('AddressID[1]','nvarchar(max)') AddressID,
n.a.value('FirstName[1]','nvarchar(max)') FirstName,
LEFT(n.a.value('LastName[1]','nvarchar(max)'), LEN(n.a.value('LastName[1]','nvarchar(max)'))
-1-LEN(n.a.value('FirstName[1]','nvarchar(max)'))) LastName,
n.a.value('StreetLine1[1]','nvarchar(max)') StreetLine1,
n.a.value('City[1]','nvarchar(max)') City,
n.a.value('StateName[1]','nvarchar(max)') StateName,
n.a.value('StateCode[1]','nvarchar(max)') StateCode,
n.a.value('PostalCode[1]','nvarchar(max)') PostalCode,
n.a.value('CountryName[1]','nvarchar(max)') CountryName,
n.a.value('CountryCode[1]','nvarchar(max)') CountryCode,
n.a.value('PhoneNumber[1]','nvarchar(max)') PhoneNumber,
n.a.value('PhoneNumber2[1]','nvarchar(max)') PhoneNumber2,
n.a.value('TaxScheduleID[1]','nvarchar(max)') TaxScheduleID,
n.a.value('ShowAddress[1]','nvarchar(max)') ShowAddress
from AddressBook.nodes('Address') n(a)
for xml path('Address'), root('AddressBook'), type
)
from #t t
cross apply xmldata.nodes('AddressBook') ab(AddressBook);
select *
from #t;

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.