The xml root is repeating multiple time in sql? - sql-server

I wanted the output of my xml to look like this:
<CustomConfigurationSteps>
<CustomConfigurations>
<CustomConfiguration SiteURL="abc">
<CustomElements>
<CustomElement Type="Searchtype" unit="3" price="5">
<ClassValue>AZSupreme</ClassValue>
</CustomElement>
</CustomElements>
<CustomElements>
<CustomElement Type="Searchtype" unit="3" price="5">
<ClassVaue>AZSupreme</ClassVaue>
</CustomElement>
</CustomElements>
</CustomConfiguration>
</CustomConfigurations>
</CustomConfigurationSteps>
I tried the below logic, but its not giving me the exact output.
DECLARE #xml xml
SET #xml = (SELECT
config.SiteURL AS '#SiteURL',
(SELECT
CustomConfiguration.Type AS '#Type',
CustomConfiguration.[PackageName] AS '#unit',
CustomConfiguration.[UseExecute] AS '#price',
ClassValue
FOR XML PATH('CustomElement'), TYPE) AS CustomElements
FROM
[dbo].[CustomConfigurationSteps] AS CustomConfiguration WITH (nolock)
INNER JOIN
WebSiteConfiguration AS config WITH (nolock) ON config.ConfigurationID = CustomConfiguration.[ConfigurationID]
WHERE
CustomConfiguration.[ConfigurationID] = 9
FOR XML path ('CustomConfiguration'), TYPE)
--set #xml1=( SELECT #xml FOR XML path ('CustomConfiguration') ,type)
SELECT #xml
FOR XML path ('CustomConfigurations'), ROOT ('CustomConfigurationSteps')

Try it like this (although this looks a little odd, especially the repeating <CustomElements> node.
A mockup table to simulate your issue:
DECLARE #tbl TABLE([Type] VARCHAR(100),unit int, price DECIMAL(10,4),ClassValue VARCHAR(100));
INSERT INTO #tbl VALUES('Searchtype 1',1,11,'AZSupreme 1')
,('Searchtype 2',2,22,'AZSupreme 2');
--The query
SELECT 'abc' AS [CustomConfiguration/#SiteURL]
,(
SELECT t.[Type] AS [CustomElement/#Type]
,t.unit AS [CustomElement/#unit]
,t.price AS [CustomElement/#pricee]
,t.ClassValue [CustomElement/ClassValue]
FROM #tbl t
FOR XML PATH('CustomElements'),TYPE
) AS CustomConfiguration
FOR XML PATH('CustomConfigurations'),ROOT('CustomConfigurationSteps');

Related

How cut a thousands of line T-SQL to small stored procedures or functions?

I have some problem to reduce the size of my stored procedure, which has more than 2000 lines of code.
Here is a short example (shortly 3 outer XML, 3 inner XML).
CREATE PROCEDURE spu_Get_Rep_Data
#IdContrat INT,
#Annee INT,
#XML XML OUTPUT
AS
BEGIN
DECLARE #XML_Contrat XML;
EXEC spu_Get_Contrat #IdContrat = #IdContrat, #XML = #XML_Contrat OUTPUT;
DECLARE #XML_ProdSous_Ev XML;
EXEC spu_Get_ProdSous_Ev #IdContrat = #IdContrat, #XML = #XML_ProdSous_Ev OUTPUT;
DECLARE #XML_Ana_Bact XML;
EXEC spu_Get_Ana_Bact #IdContrat = #IdContrat, #XML = #XML_Ana_Bact OUTPUT;
SET #XML = (
SELECT #Annee AS '#Annee',
#XML_Contrat,
#XML_ProdSous_Ev,
#XML_Ana_Bact,
(SELECT
(SELECT IpLibelle AS '#Name',
(SELECT
(SELECT 'Entry' AS '#Name',
(SELECT 'Stick' AS '#Type',
'2E5576' AS '#Color'
FOR XML PATH ('SeriesConfig'), TYPE),
(SELECT p.Year AS '#Name',
ISNULL(CAST(SUM(p.Valeur) AS DECIMAL(18, 8)), '0') AS '#Val'
FROM dbo.Production AS p
WHERE p.Year = #Annee
AND p.IdContrat = #IdContrat
AND p.IpTypeCode = 407
AND p.IpId = e.IpId
GROUP BY p.Annee
ORDER BY p.Annee
FOR XML PATH ('Categorie'), TYPE)
FOR XML PATH ('Serie'), TYPE)
FOR XML PATH ('VolYear'), TYPE),
(SELECT CAST(SUM(Valeur) AS DECIMAL(18, 8)) AS '#Elec'
FROM dbo.Production AS p
WHERE p.IdContrat = #IdContrat
AND p.IpId = e.IpId
AND p.IpTypeCode = 101
FOR XML PATH ('ElecStep'), TYPE),
(SELECT Name AS '#Val1',
Value AS '#Val2'
FROM dbo.Production AS p
WHERE p.IdContrat = #IdContrat
AND p.IpId = e.IpId
AND p.IpTypeCode = 653
FOR XML PATH ('ReparationStep'), TYPE)
FROM dbo.Entite AS e
WHERE IdContrat = #IdContrat
ORDER BY e.IpLibelle
FOR XML PATH ('StepData'), TYPE)
FOR XML PATH ('StepsData'), TYPE)
FOR XML PATH ('Elements')
)
END
It retrieves the data of a "contrat", which may has several types of equipments and each type contains at least one instance, to format XML.
It would be so much better to extract the inner SELECT ... FOR XML PATH to stored procedure (which is better in performance than the user-defined function).
Expect result 1 using user-defined function:
SET #XML = (
SELECT #Annee AS '#Annee',
#XML_Contrat,
#XML_ProdSous_Ev,
#XML_Ana_Bact,
(SELECT
(SELECT IpLibelle AS '#Name',
(EXEC spu_Get_Vol_Year(e.IpId, #IdContrat, #Annee)),
(EXEC spu_Get_Elec(e.IpId, #IdContrat)),
(EXEC spu_Get_Reparation(e.IpId, #IdContrat))
FROM dbo.Entite AS e
WHERE IdContrat = #IdContrat
ORDER BY e.IpLibelle
FOR XML PATH ('StepData'), TYPE)
FOR XML PATH ('StepsData'), TYPE)
FOR XML PATH ('Elements')
)
The 3 outer stored procedure works. Since the stored procedure IS NOT allowed using in SELECT, the 3 inner stored procedures which need the parameter of IpId would not work. Considering that there must be someone who wanted to simplify any large T-SQL to pieces, I took the chance for help.
Thank you in advance for any suggestions.

Concatenate XML without type casting to string

I have the following XML generated from various tables in my SQL SERVER database
<XMLData>
...
<Type>1</Type>
...
</XMLData>
AND
<XMLData>
...
<Type>2</Type>
...
</XMLData>
AND
<XMLData>
...
<Type>3</Type>
...
</XMLData>
The final output I need is single combined as follows:
<AllMyData>
<XMLData>
...
<Type>1</Type>
...
</XMLData>
<XMLData>
...
<Type>2</Type>
...
</XMLData>
<XMLData>
...
<Type>3</Type>
...
</XMLData>
<AllMyData>
NOTE - all the independent elements that I am combining have the same tag name.
Thanks in advance for looking this up.
I have the following XML generated from various tables in my SQL
SERVER database
Depends on how you have it but if it is in a XML variable you can do like this.
declare #XML1 xml
declare #XML2 xml
declare #XML3 xml
set #XML1 = '<XMLData><Type>1</Type></XMLData>'
set #XML2 = '<XMLData><Type>2</Type></XMLData>'
set #XML3 = '<XMLData><Type>3</Type></XMLData>'
select #XML1, #XML2, #XML3
for xml path('AllMyData')
I can't comment but can answer so even though I think a comment is more appropriate, I'll expand on what rainabba answered above to add a bit more control. My .Net code needs to know the column name returned so I can't rely on auto-generated names but needed the very tip rainabba provided above otherwise.
This way, the xml can effectively be concatenated into a single row and the resulting column named. You could use this same approach to assign the results to an XML variable and return that from a PROC also.
SELECT (
SELECT XmlData as [*]
FROM
(
SELECT
xmlResult AS [*]
FROM
#XmlRes
WHERE
xmlResult IS NOT NULL
FOR XML PATH(''), TYPE
) as DATA(XmlData)
FOR XML PATH('')
) as [someColumnName]
If you use for xml type, you can combine the XML columns without casting them. For example:
select *
from (
select (
select 1 as Type
for xml path(''), type
)
union all
select (
select 2 as Type
for xml path(''), type
)
union all
select (
select 3 as Type
for xml path(''), type
)
) as Data(XmlData)
for xml path(''), root('AllMyData'), type
This prints:
<AllMyData>
<XmlData>
<Type>1</Type>
</XmlData>
<XmlData>
<Type>2</Type>
</XmlData>
<XmlData>
<Type>3</Type>
</XmlData>
</AllMyData>
As an addendum to Mikael Eriksson's answer - If you have a process where you need to continually add nodes and then want to group that under a single node, this is one way to do it:
declare #XML1 XML
declare #XML2 XML
declare #XML3 XML
declare #XMLSummary XML
set #XML1 = '<XMLData><Type>1</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML1 FOR XML PATH(''))
set #XML2 = '<XMLData><Type>2</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML2 FOR XML PATH(''))
set #XML3 = '<XMLData><Type>3</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML3 FOR XML PATH(''))
SELECT #XMLSummary FOR XML PATH('AllMyData')
I needed to do the same but without knowing how many rows/variables were concerned and without extra schema added so here was my solution. Following this pattern, I can generate as many snippets as I want, combine them, pass them between PROCS or even return them from procs and at any point, wrap them up in containers all without modifying the data or being forced to add XML structure into my data. I use this approach with HTTP end points to provide XML Web services and with another trick that converts XML into JSON, to provide JSON WebServices.
-- SETUP A type (or use this design for a Table Variable) to temporarily store snippets into. The pattern can be repeated to pass/store snippets to build
-- larger elements and those can be further combined following the pattern.
CREATE TYPE [dbo].[XMLRes] AS TABLE(
[xmlResult] [xml] NULL
)
GO
-- Call the following as much as you like to build up all the elements you want included in the larger element
INSERT INTO #XMLRes ( xmlResult )
SELECT
(
SELECT
'foo' '#bar'
FOR XML
PATH('SomeTopLevelElement')
)
-- This is the key to "concatenating" many snippets into a larger element. At the end of this, add " ,ROOT('DocumentRoot') " to wrapp them up in another element even
-- The outer select is a time from user2503764 that controls the output column name
SELECT (
SELECT XmlData as [*]
FROM
(
SELECT
xmlResult AS [*]
FROM
#XmlRes
WHERE
xmlResult IS NOT NULL
FOR XML PATH(''), TYPE
) as DATA(XmlData)
FOR XML PATH('')
) as [someColumnName]
ALTER PROCEDURE usp_fillHDDT #Code int
AS
DECLARE #HD XML,#DT XML;
SET NOCOUNT ON;
select invhdcode, invInvoiceNO,invDate,invCusCode,InvAmount into #HD
from dbo.trnInvoiceHD where invhdcode=#Code
select invdtSlNo No,invdtitemcode ItemCode,invdtitemcode ItemName,
invDtRate Rate,invDtQty Qty,invDtAmount Amount ,'Kg' Unit into #DT from
dbo.trnInvoiceDt where invDtTrncode=#Code
set #HD = (select * from #HD HD FOR XML AUTO,ELEMENTS XSINIL);
set #DT = (select* from #DT DT FOR XML AUTO,ELEMENTS XSINIL);
SELECT CAST ('<OUTPUT>'+ CAST (ISNULL(#HD,'') AS VARCHAR(MAX))+ CAST ( ISNULL(#DT,'') AS VARCHAR(MAX))+ '</OUTPUT>' AS XML)
public String ReplaceSpecialChar(String inStr)
{
inStr = inStr.Replace("&", "&");
inStr = inStr.Replace("<", "<");
inStr = inStr.Replace(">", ">");
inStr = inStr.Replace("'", "'");
inStr = inStr.Replace("\"", """);
return inStr;
}

XML Parsing in TSQL gives invalid column error?

I am trying to parse a simple XML file. As soon as I un comment the insert statement it gives me invalid column errors.
drop table #TEMP
drop table #TEMP_T
declare #XMl_DATA AS XML
set #XMl_DATA =
'<DocumentElement>
<Att_Table>
<L_ATTR_CD>GAS_FLOW_START_DATE</L_ATTR_CD>
<L_ATTR_DESC>GAS FLOW START DATE</L_ATTR_DESC>
<L_ATTR_VALUE>01/01/2012</L_ATTR_VALUE>
<R_ATTR_CD>EX_CTRCT_CO_ID</R_ATTR_CD>
<R_ATTR_DESC>EXCLUDE GID(S)</R_ATTR_DESC>
<R_ATTR_VALUE />
</Att_Table>
<Att_Table>
<L_ATTR_CD>GAS_FLOW_END_DATE</L_ATTR_CD>
<L_ATTR_DESC>GAS FLOW END DATE</L_ATTR_DESC>
<L_ATTR_VALUE>01/31/2012</L_ATTR_VALUE>
<R_ATTR_CD>EX_CTRCT_NBR</R_ATTR_CD>
<R_ATTR_DESC>EXCLUDE CONTRACT NUMBER(S)</R_ATTR_DESC>
<R_ATTR_VALUE />
</Att_Table>
<Att_Table>
<L_ATTR_CD>CTRCT_CO_ID</L_ATTR_CD>
<L_ATTR_DESC>GID(S)</L_ATTR_DESC>
<L_ATTR_VALUE />
<R_ATTR_CD>EX_RATE_CMPNT_CD</R_ATTR_CD>
<R_ATTR_DESC>EXCLUDE RATE COMPONENT CODE(S)</R_ATTR_DESC>
<R_ATTR_VALUE />
</Att_Table>
<Att_Table>
<L_ATTR_CD>CTRCT_NBR</L_ATTR_CD>
<L_ATTR_DESC>DART STYLE CONTRACT NUMBER(S)</L_ATTR_DESC>
<L_ATTR_VALUE />
<R_ATTR_CD>EX_PT_ID_NBR</R_ATTR_CD>
<R_ATTR_DESC>EXCLIDE POINT ID NUMBER(S)</R_ATTR_DESC>
<R_ATTR_VALUE />
</Att_Table>
</DocumentElement>'
Temp table:
CREATE TABLE #TEMP_T
(
ID INT IDENTITY(1,1),
ATT_CD VARCHAR(50),
ATT_CD_VALUE VARCHAR(1000)
)
SELECT
cast(Colx.query('data(L_ATTR_CD)') as varchar(max))as L_ATTR_CD,
cast(Colx.query('data(L_ATTR_VALUE)') as varchar(max))as L_ATTR_CD_VALUE,
cast(Colx.query('data(R_ATTR_CD)') as varchar(max)) as R_ATTR_CD,
cast(Colx.query('data(R_ATTR_VALUE)') as varchar(max))as R_ATTR_CD_VALUE
INTO #TEMP
FROM #XMl_DATA.nodes('DocumentElement/Att_Table') AS T(Colx)
--INSERT INTO #TEMP_T(ATT_CD,ATT_CD_VALUE)
--SELECT LTRIM(RTRIM(L_ATT_CD)),LTRIM(RTRIM(L_ATT_CD_VALUE))
--FROM #TEMP
--WHERE L_ATT_CD_VALUE <> 'NO_DATA'
--INSERT INTO #TEMP_T(ATT_CD,ATT_CD_VALUE)
--SELECT LTRIM(RTRIM(R_ATT_CD)),LTRIM(RTRIM(R_ATT_CD_VALUE))
--FROM #TEMP
--WHERE R_ATT_CD_VALUE <>'NO_DATA'
Output:
select * from #TEMP_T
select * from #TEMP
Sometimes you use things like L_ATTR and R_ATTR, and other times you use things like L_ATT and R_ATT (with no Rs). Pick one and stick to it.
The error message mentioning "invalid column" was trying to tell you this: the columns you try to select from #TEMP are "invalid" because you aren't using the same names as when you created #TEMP.
Why don't you just do something like this?
-- define your XML structure and create the #TEMP_T table
;WITH ParsedData AS
(
SELECT
Colx.value('(L_ATTR_CD)[1]', 'varchar(50)') as L_ATTR_CD,
Colx.value('(L_ATTR_VALUE)[1]', 'varchar(50)') as L_ATTR_CD_VALUE,
Colx.value('(R_ATTR_CD)[1]', 'varchar(50)') as R_ATTR_CD,
Colx.value('(R_ATTR_VALUE)[1]', 'varchar(50)') as R_ATTR_CD_VALUE
FROM
#XMl_DATA.nodes('DocumentElement/Att_Table') AS T(Colx)
)
INSERT INTO #temp_T(ATT_CD, ATT_CD_VALUE)
SELECT L_ATTR_CD, L_ATTR_CD_VALUE
FROM parseddata
UNION
SELECT R_ATTR_CD, R_ATTR_CD_VALUE
FROM parseddata
This just parses the XML (much simpler than your approach, too!) and then inserts both the (L_ATTR_CD, L_ATTR_CD_VALUE) as well as the (R_ATTR_CD, R_ATTR_CD_VALUE) pairs into the temp table in a single go.

Query XML creating field names whithout knowing node names

If I have a SQL SERVER 2012 table containing an XML field type. The records it could contain are as follows.
I have simplified my problem to the following.
Record 1:
ID_FIELD='nn1'
XML_FIELD=
<KNOWN_NAME_1>
<UNKNOWN_NAME1>Some value</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value</UNKNOWN_NAME2>
... Maybe more ...
</KNOWN_NAME_1>
Record 2:
ID_FIELD='nn2'
XML_FIELD=
<KNOWN_NAME_2>
<UNKNOWN_NAME1>Some value</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value</UNKNOWN_NAME2>
... Maybe more unknown fields ...
</KNOWN_NAME_2>
I want to output non xml:
UNKNOWN_NAME1 | UNKNOWN_NAME2 | ETC
-----------------------------------
Some Value Some value
For a known root value (i.e. KNOWN_NAME_1)
I.e. If I new the node values (which I don't) I could
SELECT
XMLData.Node.value('UNKNOWN_NAME1[1]', 'varchar(100)') ,
XMLData.Node.value('UNKNOWN_NAME2[1], 'varchar(100)')
FROM FooTable
CROSS APPLY MyXmlField.nodes('//KNOWN_NAME_1') XMLData(Node)
-- WHERE SOME ID value = 'NN1' (all XML records have a separate id)
All is good however I want to do this for all the nodes (unknown quantity) without knowing the node names. The root will only contain nodes it wont get any deeper.
Is this possible in SQL?
I have looked at this but I doubt I can get enough rights to implement it.
http://architectshack.com/ClrXmlShredder.ashx
If you don't know the column names in the output you have to use dynamic SQL:
-- Source table
declare #FooTable table
(
ID_FIELD char(3),
XML_FIELD xml
)
-- Sample data
insert into #FooTable values
('nn1', '<KNOWN_NAME_1>
<UNKNOWN_NAME1>Some value1</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value2</UNKNOWN_NAME2>
</KNOWN_NAME_1>')
-- ID to look for
declare #ID char(3) = 'nn1'
-- Element name to look for
declare #KnownName varchar(100) = 'KNOWN_NAME_1'
-- Variable to hold the XML to process
declare #XML xml
-- Get the XML
select #XML = XML_FIELD
from #FooTable
where ID_FIELD = #ID
-- Variable for dynamic SQL
declare #SQL nvarchar(max)
-- Build the query
select #SQL = 'select '+stuff(
(
select ',T.N.value('''+T.N.value('local-name(.)', 'sysname')+'[1]'', ''varchar(max)'') as '+T.N.value('local-name(.)', 'sysname')
from #XML.nodes('/*[local-name(.)=sql:variable("#KnownName")]/*') as T(N)
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 1, '')+
' from #XML.nodes(''/*[local-name(.)=sql:variable("#KnownName")]'') as T(N)'
-- Execute the query
exec sp_executesql #SQL,
N'#XML xml, #KnownName varchar(100)',
#XML = #XML,
#KnownName = #KnownName
Result:
UNKNOWN_NAME1 UNKNOWN_NAME2
--------------- ---------------
Some value1 Some value2
The dynamically generated query looks like this:
select T.N.value('UNKNOWN_NAME1[1]', 'varchar(max)') as UNKNOWN_NAME1,
T.N.value('UNKNOWN_NAME2[1]', 'varchar(max)') as UNKNOWN_NAME2
from #XML.nodes('/*[local-name(.)=sql:variable("#KnownName")]') as T(N)
SE-Data

join multiple 'for XML' outputs in an efficient manner

In the code below, I am join the xml output of two independent select clauses into one XML.
create procedure Test
as
declare #result nvarchar(max)
set #result = '<data>'
set #result = #result + (select FieldA from Table1 for xml raw('a'), root('table1'))
set #result = #result + (select FieldB from Table2 for xml raw('b'), root('table2'))
set #result = #result + '/<data>'
return #result
Output would look like:
<data>
<table1>
<a FieldA="01"/>
<a FieldA="02"/>
</table1>
<table2>
<b FieldB="aa"/>
<b FieldB="bb"/>
</table2>
</data>
I would like to know if there is a much better way to achieve the above output with greater Performance.
I believe 'for xml' output (xml data type) is streamed while the nvarchar is not. So, is it beneficial to output as XML data type? or, does Xml datatype overheads (parsing, wellformedness checks, etc) overweight the streaming benefit over nvarchar datatype?
Edit The procedure will be called by an ASP.Net application and results are read through the ADO.Net classes
It is possible to combine xml from subqueries into a bigger xml result. The key is to use the sqlserver xml type.
with foodata as (
select 1 as n
union all
select n+1 from foodata where n < 20
)
select
(select n as 'text()' from foodata where n between 10 and 13 for xml path('row'), root('query1'), type),
(select n as 'text()' from foodata where n between 15 and 18 for xml path('row'), root('query2'), type)
for xml path('root'), type
The query above will generate the following output:
<root>
<query1>
<row>10</row>
<row>11</row>
<row>12</row>
<row>13</row>
</query1>
<query2>
<row>15</row>
<row>16</row>
<row>17</row>
<row>18</row>
</query2>
</root>
I can't say which is faster though. You will just have to do some benchmarks.

Resources