How to parse XML file in a folder with dynamic t-sql? - sql

I'd like to change this XML to SQL statement below to work with a path rather than a file name, at the moment it's looking for 'C:\Test\XML\PT38.xml'.
I need it to parse any .XML file in the folder rather than look for a specific one. It will be just one file at a time but they'll have different names (number increase: PT39, PT40, etc.).
I tried adding a variable for the path then changing the BULK to look for the variable, but it failed as expected.
I've read something about creating a temporary table then parse the date, but I'm not sure that would work for me.
I'd appreciate the help.
This is what I tried:
DECLARE #xmlFileName varchar(100) = 'C:\Test\XML\'
FROM OPENROWSET(BULK ''' + #xmlFileName + ''', SINGLE_BLOB) AS T(MY_XML)) AS T(MY_XML)
This is the XML content:
<?xml version="1.0" encoding="UTF-8" ?>
<MOD1>
<DC BEGIN="1">
<DC4 SEGMENT="1">
<TABNAM>DC4</TABNAM>
<DOCNUM>0000003899888135</DOCNUM>
</DC4>
<ZPR SEGMENT="1">
<AUFNR>000915229446</AUFNR>
<LNO>RM01PL01</LNO>
<CHARG>0006186588</CHARG>
<STR2>211609</STR2>
<QTY>4166.000</QTY>
<PLN_ORDER>6963701111</PLN_ORDER>
</ZPR>
</DC>
</MOD1>
This is the SQL table:
CREATE TABLE XMLTESTTABLE
(
PON int,
ASP int,
LTN varchar(11),
GAS int,
QY varchar(15),
LNO varchar(2),
StartTime date,
);
This is the statement:
INSERT INTO XMLTESTTABLE(PON, ASP, LTN, GAS, QY, LNO, StartTime)
SELECT ZPRM.value('(AUFNR/text())[1]', 'int')
, ZPRM.value('(CHARG/text())[1]', 'int')
, ZPRM.value('(PLN_ORDER/text())[1]', 'VARCHAR(10)')
, ZPRM.value('(CHARG/text())[1]', 'int')
, ZPRM.value('(QTY/text())[1]', 'DECIMAL(10,0)') AS [qty]
, RIGHT(ZPRM.value('(LNO/text())[1]', 'VARCHAR(10)'), 2) AS [LNO]
, TRY_CAST(STUFF(STUFF(ZPRM.value('(STR2/text())[1]', 'CHAR(6)'),3,0,':'),6,0,':') AS TIME)
FROM (SELECT TRY_CAST(MY_XML AS xml)
FROM OPENROWSET(BULK 'C:\Test\XML\PT38.xml', SINGLE_BLOB) AS T(MY_XML)) AS T(MY_XML)
CROSS APPLY MY_XML.nodes('/MOD1/DC/ZPR') AS MY_XML(ZPRM);

It is much easier to implement in SQL Server 2017 and later. It has much better API to deal with the file system.
Please try the following solution. It will work in SQL Server 2012.
I modified the StartTime column data type as TIME(0).
You would need to modify #folder variable value to match what you have in your environment.
SQL
USE tempdb;
GO
DROP TABLE IF EXISTS dbo.XMLTESTTABLE;
CREATE TABLE dbo.XMLTESTTABLE
(
PON varchar(10),
ASP int,
LTN varchar(11),
GAS int,
QY varchar(15),
LNO varchar(2),
StartTime TIME(0)
);
DECLARE #xml XML
, #sql NVARCHAR(MAX)
, #XMLfileName VARCHAR(256) -- 'e:\Temp\TradeFeed\PT38.xml';
, #folder VARCHAR(256) = 'e:\Temp\TradeFeed';
DECLARE #tbl TABLE (
id INT IDENTITY(1,1) PRIMARY KEY,
[fileName] VARCHAR(512),
depth INT,
isfile BIT
);
INSERT INTO #tbl ([fileName], depth, isfile)
EXEC master.sys.xp_dirtree #folder,1,1;
-- just to see
SELECT * FROM #tbl;
-- filter out not need files
SELECT TOP(1) #XMLfileName = CONCAT(#folder, '\', [fileName])
FROM #tbl
WHERE isfile = 1
AND [fileName] LIKE '%.xml';
SET #sql = N'SELECT #xmlOut = XmlDoc FROM OPENROWSET (BULK ' + QUOTENAME(#XMLfileName,NCHAR(39)) + ', SINGLE_BLOB) AS Tab(XmlDoc)';
EXEC master.sys.sp_executesql #sql, N'#xmlOut XML OUTPUT', #xmlOut = #xml OUTPUT;
INSERT INTO XMLTESTTABLE(PON, ASP, LTN, GAS, QY, LNO, StartTime)
SELECT #xml.value('(/MOD1/DC/DC4/TABNAM/text())[1]', 'VARCHAR(10)')
, c.value('(CHARG/text())[1]', 'int')
, c.value('(PLN_ORDER/text())[1]', 'VARCHAR(10)')
, c.value('(CHARG/text())[1]', 'int')
, c.value('(QTY/text())[1]', 'DECIMAL(10,0)') AS [qty]
, RIGHT(c.value('(LNO/text())[1]', 'VARCHAR(10)'), 2) AS [LNO]
, TRY_CAST(STUFF(STUFF(c.value('(STR2/text())[1]', 'CHAR(6)'),3,0,':'),6,0,':') AS TIME(0))
FROM #xml.nodes('/MOD1/DC/ZPR') AS t(c);
-- test
SELECT * FROM dbo.XMLTESTTABLE;
Output
+-----------+---------+------------+---------+------+-----+-----------+
| PON | ASP | LTN | GAS | QY | LNO | StartTime |
+-----------+---------+------------+---------+------+-----+-----------+
| DC4 | 6186588 | 6963701111 | 6186588 | 4166 | 01 | 21:16:09 |
+-----------+---------+------------+---------+------+-----+-----------+

Related

Import xml data in SQL Server 2017

I have XML data like this:
<?xml version="1.0" ?>
<SettlementFile>
<Transaction>
<Identifier>
<StationID>049215901 </StationID>
<TransactionTimestamp>2021-04-01T10:39:32</TransactionTimestamp>
</Identifier>
<TerminalInfo>
<TerminalID>21590151</TerminalID>
<TerminalType>2</TerminalType>
</TerminalInfo>
<TransactionInfo>
<TransactionType>0</TransactionType>
<TransactionAmount>3.74</TransactionAmount>
<CurrencyCode>978</CurrencyCode>
<CustomerInput>
<Mileage>0</Mileage>
</CustomerInput>
<TicketNumber>213510037</TicketNumber>
<AuthorisationType>1</AuthorisationType>
</TransactionInfo>
</Transaction>
</SettlementFile>
My SQL Server table structure:
CREATE TABLE dbo.Import_Oase
(
StationID varchar(50) NULL,
TransactionTimestamp datetime NULL,
TicketNumber int NULL,
Mileage varchar(50) NULL
) ON PRIMARY
I'm trying to use this SELECT query:
SELECT
MY_XML.Details.query('StationID') .value('.', 'VARCHAR(50)'),
MY_XML.Details.query('TransactionTimestamp').value('.', 'Datetime'),
MY_XML.Details.query('TicketNumber') .value('.', 'Integer'),
MY_XML.Details.query('Mileage') .value('.', 'VARCHAR(50)')
FROM
(SELECT
CAST(MY_XML AS XML)
FROM
OPENROWSET(BULK '\\EO-TEST\SQL-Daten\Temp\MY_XML.xml', SINGLE_BLOB) AS T(MY_XML)
) AS T(MY_XML)
CROSS APPLY
MY_XML.nodes('SettlementFile/Transaction/Identifier, SettlementFile/Transaction/TransactionInfo, SettlementFile/Transaction/TransactionInfo/CustomerInput') AS MY_XML (Details);
I need to get the result in one line because it is all for 1 transactions. But it is in three different line in SQL.
Please try the following solution.
When you are satisfied with the outcome, just uncomment the INSERT INTO line.
SQL
WITH rs (xmlData) AS
(
SELECT TRY_CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK N'e:\Temp\ daryosmitan.xml', SINGLE_BLOB) AS x
)
-- INSERT INTO dbo.Import_Oase (StationID, TransactionTimestamp, TicketNumber, Mileage)
SELECT c.value('(Identifier/StationID/text())[1]', 'VARCHAR(50)') AS StationID
, c.value('(Identifier/TransactionTimestamp/text())[1]', 'DATETIME') AS TransactionTimestamp
, c.value('(TransactionInfo/TicketNumber/text())[1]', 'VARCHAR(50)') AS TicketNumber
, c.value('(TransactionInfo/CustomerInput/Mileage/text())[1]', 'INT') AS Mileage
FROM rs
CROSS APPLY xmlData.nodes('/SettlementFile/Transaction') AS t(c);

How to format XML after fetching from table in sql server

I have a requirement where I have to format XML structure coming from following output:
DECLARE #cousumptionFileName VARCHAR(50)
DECLARE #sqlCmd VARCHAR(1000)
DECLARE #sqlConStr VARCHAR(1000)
SET #cousumptionFileName = 'C:\export\IDE\Consumption.xml' -- SELECT * FROM ' + DB_NAME()+'.dbo.ReadingData
SET #sqlConStr = 'select top 10 * from [FixedNetworkist].[dbo].[ConsumptionReading0] order by deviceid FOR XML AUTO'
SET #sqlCmd = 'bcp "' + #sqlConStr + '" queryout ' + #cousumptionFileName + ' -w -T'
EXEC xp_cmdshell #sqlCmd
The output is:
<Reading0 RecordId="14452573" PartitionSequence="1" DeviceId="1015" DeviceType="13" CollectorId="74045037" CollectorType="120" Reading="0" ReadingDateTime="2019-01-21T01:15:00" PresentationInterval="15" RecordEpochTime="-599450337"/>
<Reading0 RecordId="14452859" PartitionSequence="1" DeviceId="1015" DeviceType="13" CollectorId="51000666" CollectorType="120" ChannelNumber="1" Reading="0" IntervalStatus="0" ReadingDateTime="2019-01-21T01:15:00" PresentationInterval="15" RecordEpochTime="-599450335"/>
I want this to be structure like to avoid the repetitive value:
<Reading0 PartitionSequence="1" DeviceId="1015" DeviceType="13" ChannelNumber="1" >
<Readings>
<Reading RecordId="14452573" CollectorId="74045037" Reading="0" ReadingDateTime="2019-01-21T00:29:58" RecordEpochTime="-599453037" />
<Reading RecordId="14452859" CollectorId="51000666" Reading="0" ReadingDateTime="2019-01-21T00:29:58" RecordEpochTime="-599453037" />
</Readings>
Could anyone help me to achieve this?
For your transformation consider the use of 'FOR XML EXPLICIT', instead of 'FOR XML AUTO'. Good article to read more about:
https://learn.microsoft.com/en-us/sql/relational-databases/xml/use-explicit-mode-with-for-xml?view=sql-server-2017
My sample data:
DECLARE #Reading TABLE ( PartitionSequence int, DeviceId int, DeviceType int, ChannelNumber int,CollectorId int,RecordId int, Reading int, ReadingDateTime datetime, RecordEpochTime int)
INSERT INTO #Reading ( PartitionSequence, DeviceId , DeviceType, ChannelNumber,CollectorId , RecordId, Reading, ReadingDateTime, RecordEpochTime )
VALUES
(1,1015,13,1,1,14452573,0,'2019-01-21T00:29:58',-599453037)
,(1,1015,13,1,1,51000666,0,'2019-01-21T00:29:58',-599453037)
Sql query:
SELECT
1 AS Tag
,NULL AS parent
,r.PartitionSequence AS [Readings!1!PartitionSequence]
,r.DeviceId AS [Readings!1!DeviceId]
,r.DeviceType AS [Readings!1!DeviceType]
,r.ChannelNumber AS [Readings!1!ChannelNumber]
,NULL AS [Reading!2!RecordId]
,NULL AS [Reading!2!Reading]
,NULL AS [Reading!2!ReadingDateTime]
,NULL AS [Reading!2!RecordEpochTime]
FROM #Reading r
UNION
SELECT
2 AS Tag
,1 AS parent
,r.PartitionSequence
,r.DeviceId
,r.DeviceType
,r.ChannelNumber
,r.RecordId
,r.Reading
,r.ReadingDateTime
,r.RecordEpochTime
FROM #Reading r
FOR XML EXPLICIT
Result of the query:
<Readings PartitionSequence="1" DeviceId="1015" DeviceType="13" ChannelNumber="1">
<Reading RecordId="14452573" Reading="0" ReadingDateTime="2019-01-21T00:29:58" RecordEpochTime="-599453037" />
<Reading RecordId="51000666" Reading="0" ReadingDateTime="2019-01-21T00:29:58" RecordEpochTime="-599453037" />
</Readings>

Read XML data in SQL Server 2012

I want to read xml data in the xml file.
I have a table column consist with the xml data.
if i click on the xml file it will open in the Sql server Management studio.
xml file format shown below.
I want to read only NTDomainName, DatabaseName and ServerName and write that data in the another Table. Table format shown below
NTDomainName | DatabaseName | ServerName
----------
ABC | TestCube1 | SERXYZ
Try this:
declare #xml xml
set #xml = '<event><data name="NTUserName"><value>MyName</value></data><data name="NTDomainName"><value>DomainName</value></data><data name="ServerName"><value>ServerName</value></data></event>'
select [NTDomainName], [DatabaseName], [ServerName] from
(
select [name],[value] from (
select c.value('./#name', 'varchar(100)') [name], c.value('(./value)[1]', 'varchar(100)') [value]
from #xml.nodes('/event/data') as t(c)
) a where [name] in ('NTDomainName', 'DatabaseName', 'ServerName')
) x
pivot (
max(value) for [name] in ([NTDomainName], [DatabaseName], [ServerName])
) as [pivot_Name]
The most inner query will retrieve information from XML, one row for every name attribute value, that you want to retrieve. So, output of this query needs to be pivoted.
i think you look for this:
SELECT * FROM (
SELECT
CAST(f.x.query('data(#name)') as varchar(150)) as data_name,
CAST(f.x.query('data(value)') as varchar(150)) as data_value
FROM #xml.nodes('/event') as t(n)
CROSS APPLY t.n.nodes('data') as f(x)) X
PIVOT (MAX(data_value) FOR data_name IN (NTDomainName, DatabaseName, ServerName)) as pvt
If you do not want to use PIVOT:
DECLARE #DataSource TABLE
(
[ID] TINYINT IDENTITY(1,1)
,[XML] XML
);
INSERT INTO #DataSource ([XML])
VALUES ('<event><data name="SessionID">S1</data><data name="NTUserName">User1</data><data name="DatabaseName">DB1</data><data name="ServerName">SN1</data></event>')
,('<event><data name="SessionID">S1</data><data name="NTUserName">User2</data><data name="DatabaseName">DB2</data><data name="ServerName">SN2</data></event>');
SELECT [ID]
,MAX(CASE wHEN C.value('(./#name)[1]', 'varchar(50)') = 'NTUserName' THEN C.value('(.)[1]', 'varchar(50)') END) AS [NTUserName]
,MAX(CASE wHEN C.value('(./#name)[1]', 'varchar(50)') = 'DatabaseName' THEN C.value('(.)[1]', 'varchar(50)') END) AS [DatabaseName]
,MAX(CASE wHEN C.value('(./#name)[1]', 'varchar(50)') = 'ServerName' THEN C.value('(.)[1]', 'varchar(50)') END) AS [ServerName]
FROM #DataSource
CROSS APPLY [XML].nodes('event/data[#name = "NTUserName" or #name = "DatabaseName" or #name = "ServerName"]') T(c)
GROUP BY [ID];

split the string and store it in three different variables in sql server

I am using below function to split the string into multiple strings everything is working fine but i need to store the results into multiple variables
instead of that the results are storing into a table
this is my query
DECLARE #strngLen int
DECLARE #split TABLE(siteId VARCHAR(100))
DECLARE #city varchar(100), #state varchar(100), #zip varchar(100)
SET #siteIds = 'OMAHA NE 68117'
SET #strngLen = CHARINDEX(' ', #siteIds)
WHILE CHARINDEX(' ', #siteIds) > 0
BEGIN
SET #strngLen = CHARINDEX(' ', #siteIds);
INSERT INTO #split
SELECT SUBSTRING(#siteIds,1,#strngLen - 1);
SET #siteIds = SUBSTRING(#siteIds, #strngLen+1, LEN(#siteIds));
END
INSERT INTO #split
SELECT #siteIds
SELECT * FROM #split
---Here i need to store the #siteIds into multiple local variables
---like in (#city varchar(100), #state varchar(100), #zip varchar(100))
if they are always in the same order and same separator then there is not need for a loop, this will do all the job:
DECLARE #siteIds varchar(100)
DECLARE #city varchar(100), #state varchar(100), #zip varchar(100)
SET #siteIds = 'OMAHA NE 68117'
select #city=(parsename(replace(#siteIds,' ','.'),3)),
#state=(parsename(replace(#siteIds,' ','.'),2)),
#zip=(parsename(replace(#siteIds,' ','.'),1))
If there is going to be only 3 strings (cit,state and zip) then the below select will fetch you the result.
declare #str1 varchar(10)
declare #str2 varchar(10)
select #city= substring(#siteIds,1,charindex(' ',#siteIds)-1)
,#str1 = substring(#siteIds,charindex(' ',#siteIds)+1,len(#siteIds))
,#state = substring(#str1,1,charindex(' ',#str1)-1)
,#str2 = substring(#str1,charindex(' ',#str1)+1,len(#str1))
,#zip = substring(#str2,1,len(#str2))
First, the standard answer - don't do this on the server. It's far easier to parse strings on the client.
Second, this isn't as trivial as it appears - this is parsing, not splitting a string. In this case, each token has a different meaning. All string splitting techniques, and the STRING_SPLIT command return a table of unordered tokens.
Third, the splitting technique is one of the slowest, and can't be modified parse instead of split. The fastest techniques are SQLCLR and XML parsing. Both can be modified to return a table with multiple columns.
For example, the following XML parsing function:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
works by surrounding each token with i tags, producing <i>OMAHA</i><i>NE</i><i>68117</i>. Then it selects the contents of each I element with tags with CROSS APPLY x.nodes('i')
You can modify it to nest the elements and return the 1st, 2nd and 3rd element, as follows:
CREATE FUNCTION dbo.SplitTriplets_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT
Item1 = o.i.value('#value', 'nvarchar(4000)'),
Item2 = p.i.value('#value', 'nvarchar(4000)'),
Item3 = q.i.value('#value', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i value="'
+ REPLACE(#List, #Delimiter, '"><i value="')
+ '"></i></i></i>').query('.')
) AS a
OUTER APPLY x.nodes('/i') AS o(i)
OUTER APPLY x.nodes('/i/i') AS p(i)
OUTER APPLY x.nodes('/i/i/i') AS q(i)
)
In this case, the generated XML is <i value="OMAHA"><i value="NE"><i value="68117" /></i></i> and the OUTER APPLY x.nodes('/i') AS p(i) statements select the root and nested tags as separate tables.
This example :
declare #table table (id int,value nvarchar(200))
insert into #table
values
(1,'OMAHA NE 68117'),
(2,'sdfA Nw 68227')
select id,value,item1 as City,item2 as State,item3 as Zip
from #table x cross apply dbo.SplitTriplets_XML(value,' ')
Returns :
id value City State Zip
----------- ----------------- ------ ------ ------
1 OMAHA NE 68117 OMAHA NE 68117
2 sdfA Nw 68227 sdfA Nw 68227

Why is select query not working?

I am trying to select each PartNum, WarehouseCode, and OnhandQty from the "StkPart" attributes and select the plant from the "Plant" attribute. There is only one "Plant" in this xml file and the "Plant" would be associated with each row from the "StkPart" attribute.
For Example:
PartNum WarehouseCode OnhandQty Plant
1. 10-12345 Corona 150 MfgSys
2. 10-12351 Cork 1 MfgSys
3. 10-51617a Here 198 MfgSys
4. 10-97654 There 67 MfgSys
This is what I have been trying (The XML code is at the bottom):
This code works and enters the data into my table:
USE Database
GO
CREATE TABLE XMLwithOpenXML
(
Id INT IDENTITY PRIMARY KEY,
XMLData XML,
LoadedDateTime DATETIME
)
INSERT INTO XMLwithOpenXML(XMLData, LoadedDateTime)
SELECT CONVERT(XML, BulkColumn) AS BulkColumn, GETDATE()
FROM OPENROWSET(BULK 'C:\Test\StockStatusReport30597.XML', SINGLE_BLOB) AS x;
SELECT * FROM XMLwithOpenXML
Then I try to select the data, but this is not working:
USE Database
GO
DECLARE #XML AS XML, #hDoc AS INT, #SQL NVARCHAR (MAX)
SELECT #XML = XMLData FROM XMLwithOpenXML
EXEC sp_xml_preparedocument #hDoc OUTPUT, #XML
SELECT PartNum
FROM OPENXML(#hDoc, 'ReportDataSet/PartNum')
WITH
(
PartNum [varchar](50) '#PartNum'
)
EXEC sp_xml_removedocument #hDoc
GO
How do I get the above code to work?
Here is the XML file to download:
http://wikisend.com/download/101282/StockStatusReport30597.XML
This might help
Declare #xml xml = N'<ReportDataSet
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
...
</ReportDataSet>'
Select
N.value('PartNum[1]', 'varchar(20)') PartNum,
N.value('WarehouseCode[1]', 'varchar(20)') WarhouseCode,
N.value('OnhandQty[1]', 'int') OnhandQty,
#xml.value('(/ReportDataSet/Plant/Plant)[1]', 'varchar(20)') Plant
from
#xml.nodes('/ReportDataSet/StkPart') as T(n)
I've cut some stuff out of the example below to fit into the limitations of SQLFiddle, but it worked with the query from the question with appropriate namespaces added:
Example SQLFiddle
For your big data example, you'll need to set the default namespace for the query:
With xmlnamespaces (default 'http://www.epicor.com/Mfg/100')
Select
N.value('PartNum[1]', 'varchar(20)') PartNum,
N.value('WarehouseCode[1]', 'varchar(20)') WarhouseCode,
N.value('OnhandQty[1]', 'decimal(10,2)') OnhandQty,
#xml.value('(/ReportDataSet/Plant/Plant)[1]', 'varchar(20)') Plant
from
#xml.nodes('/ReportDataSet/StkPart') as T(n)