Position of an XML element in SQL Server 2012 - sql-server

Create Table #tmp(ID int IDentity(1,1), XMLData XML)
Insert Into #tmp(XMLData)
Values('<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>')
SELECT
ID,
A.x.query('data(.)') as name,
Row_Number() over(order by A.x) as number
FROM #tmp
CROSS APPLY XMLData.nodes('SampleXML/Fruit/Fruits') AS A(x)
This results into the following:
ID name number
1 Apple 1
1 Pineapple 2
1 Grapes 3
1 Melon 4
but I do want it to look like this:
ID name number
1 Apple 1
1 Pineapple 1
1 Grapes 2
1 Melon 2
I want to know in which "Fruit" element the "Fruits" were found.

You can use following SQL, please be aware that using "OVER XML nodes" is an undocumented and unsupported feature:
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>'
SELECT
Friuts.col.query('data(.)') as name,
DENSE_RANK() over(order by Friut.col) as number
FROM #MyXML.nodes('SampleXML/Fruit') AS Friut(col)
CROSS APPLY Friut.col.nodes('./Fruits') AS Friuts(col)

Here is another method by using a Node Order Comparison operator. Here is the link: Node Order Comparison Operators
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE(ID INT IDENTITY PRIMARY KEY, XMLData XML);
INSERT INTO #tbl (XMLData)
VALUES (N'<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>');
-- DDL and sample data population, end
-- Method #1
-- by Piotr, adjusted for a table and optimized
SELECT Friuts.col.value('(./text())[1]', 'VARCHAR(30)') as [name],
DENSE_RANK() OVER(ORDER BY Friut.col) as number
FROM #tbl AS tbl
CROSS APPLY tbl.XMLData.nodes('/SampleXML/Fruit') AS Friut(col)
CROSS APPLY Friut.col.nodes('./Fruits') AS Friuts(col);
-- Method #2
-- by using a Node Order Comparison operator
SELECT ID
, col.value('let $n := . return count(../../*[. << $n])', 'INT') AS pos
, col.value('(./text())[1]', 'VARCHAR(30)') as [name]
FROM #tbl AS tbl
CROSS APPLY tbl.XMLData.nodes('/SampleXML/Fruit/Fruits') AS tab(col);
Output
+----+-----+-----------+
| ID | pos | name |
+----+-----+-----------+
| 1 | 1 | Apple |
| 1 | 1 | Pineapple |
| 1 | 2 | Grapes |
| 1 | 2 | Melon |
+----+-----+-----------+

Related

Loop on XML data

In my SQL Server table one column has datatype as XML. It contains data as:
<P1>
<P2>
<P3 name='[1] name1', value='val1'> </P3>
<P3 name='[2] name2', value='val2'> </P3>
<P3 name='[3] name3', value='val3'> </P3>
</P2>
</p1>
I am fetching name1, name2, name3 using query:
select top(1)
col.value('(/P1[1]/P2[1]/P3/#name)[1]', 'VARCHAR(max)') as q1,
col.value('(/P1[1]/P2[1]/P3/#name)[2]', 'VARCHAR(max)') as q2,
col.value('(/P1[1]/P2[1]/P3/#name)[3]', 'VARCHAR(max)') as q3,
FROM table
How can I loop on this data to get all the names. Something like:
declare #x=1
while #x<4:
begin
select top(1)
col.value('(/P1[1]/P2[1]/P3/#name)[#x]', 'VARCHAR(max)') as q#x
from table
set #x=#x+1
end
Also how to loop on the same XML to get all the values?
I took the liberty of making your XML well-formed. SQL Server XQuery .nodes() method and CROSS APPLY are delivering what you are after.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata)
VALUES
(N'<P1>
<P2>
<P3 name="[1] name1" value="val1">
</P3>
<P3 name="[2] name2" value="val2">
</P3>
<P3 name="[3] name3" value="val3">
</P3>
</P2>
</P1>');
-- DDL and sample data population, end
SELECT tbl.ID
, c.value('#name','VARCHAR(30)') AS [name]
, c.value('#value','VARCHAR(30)') AS [value]
FROM #tbl AS tbl
CROSS APPLY tbl.xmldata.nodes('/P1/P2/P3') AS t(c);
Output
+----+-----------+-------+
| ID | name | value |
+----+-----------+-------+
| 1 | [1] name1 | val1 |
| 1 | [2] name2 | val2 |
| 1 | [3] name3 | val3 |
+----+-----------+-------+

Shredding XML column data into rows in SQL

I have an xml value
<ITEMS>
<ITEM><ID>1</ID><NAME>John</NAME></ITEM>
<ITEM><ID>5</ID><NAME>James</NAME></ITEM>
</ITEMS>
I am able to shred the above xml into tables of ID and Name column using the below query
Declare #X xml
select x.r.value('(ID)[1]','int') as [ID],
x.r.value('(DATA)[1]','VARCHAR(100)') AS [DATA]
FROM #X.nodes ('/ITEMS/ITEM') AS x(r)
But how will i able to do this when the above xml is present in a row.
S.No COMPANY DATA
1 ABC </ITEMS><ITEM><ID>1</ID><NAME>John</Name>....
I need to populate like below
S.No COMPANY ID NAME
1 ABC 1 John
2 ABC 5 James
Note : The Data column in the table is of varchar data type and not xml data type.
First, you can use a CTE to convert it to XML data type. Second way is via a derived table.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (SequentialNo INT PRIMARY KEY, Company VARCHAR(20), [DATA] VARCHAR(MAX));
INSERT INTO #tbl (SequentialNo, Company, [DATA])
VALUES
(1, 'ABC', '<ITEMS>
<ITEM>
<ID>1</ID>
<NAME>John</NAME>
</ITEM>
<ITEM>
<ID>5</ID>
<NAME>James</NAME>
</ITEM>
</ITEMS>');
-- DDL and sample data population, end
-- Method #1
-- CTE
;WITH rs AS
(
SELECT *, TRY_CAST([DATA] AS XML) AS [xmldata]
FROM #tbl
)
SELECT SequentialNo
, Company
, col.value('(ID/text())[1]','INT') AS ID
, col.value('(NAME/text())[1]','VARCHAR(40)') AS [Name]
FROM rs AS tbl
CROSS APPLY tbl.[xmldata].nodes('/ITEMS/ITEM') AS tab(col);
-- Method #2
-- Derived table
SELECT SequentialNo
, Company
, col.value('(ID/text())[1]','INT') AS ID
, col.value('(NAME/text())[1]','VARCHAR(40)') AS [Name]
FROM (SELECT *, TRY_CAST([DATA] AS XML) AS [xmldata]
FROM #tbl) AS tbl
CROSS APPLY tbl.[xmldata].nodes('/ITEMS/ITEM') AS tab(col);
Output
+--------------+---------+----+-------+
| SequentialNo | Company | ID | Name |
+--------------+---------+----+-------+
| 1 | ABC | 1 | John |
| 1 | ABC | 5 | James |
+--------------+---------+----+-------+

Pivot in sql server: Vertical to Horizontal data [duplicate]

This question already has an answer here:
Pivot without aggregate function in MSSQL 2008 R2
(1 answer)
Closed 6 years ago.
Hi I have the following table that I would like to use the pivot function on:
Id|Number| Code
1 | 34 |abc12345
1 | 23 |xqwe6758
2 | 37 |ghut564hg
3 | 456 |ghut8695
3 | 39 |ghtuj678
3 | 22 |fsdifje12
And I want it to be displayed horizontally as the following:
Id| Code1 | Code2 | Code3
1 | abc12345 | xqwe6758 | null
2 |ghut564hg | null | null
3 |ghut8695 | ghtuj678 | fsdifje12
SELECT Id
,[Code1]
,[Code2]
,[Code3]
FROM(SELECT Id,Code
FROM [TableName]
)d
pivot(
max(Id)
for Code in([Code1],[Code2],[Code3])
)as piv;
This throws an invalid column name error on the Id column. Could someone help identify the error ?
You can use pivot as below:
;with cte as
(
select
id, code,
RowN = Row_Number() over (partition by id order by code)
from
yourtable1
)
select *
from cte
pivot ( max(code) for RowN in([1], [2], [3])) p
For varying columns you can use stuff to create columns list and then use dynamic SQL to run with varying columns... But it is available in various examples in SO itself...
Added my output:
Try this
DECLARE #tbl TABLE(Id INT, Code VARCHAR(100));
INSERT INTO #tbl VALUES
(1,'abc12345')
,(1,'xqwe6758')
,(2,'ghut564hg')
,(3,'ghut8695')
,(3,'ghtuj678')
,(3,'fsdifje12');
SELECT p.*
FROM
(
SELECT Id
,'Code' + CAST(ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Code) AS VARCHAR(10)) AS ColumnName
,Code
FROM #tbl
) AS t
PIVOT
(
MAX(Code) FOR ColumnName IN(Code1,Code2,Code3 /*add as many as you need*/)
) AS p
The result
Id Code1 Code2 Code3
1 abc12345 xqwe6758 NULL
2 ghut564hg NULL NULL
3 fsdifje12 ghtuj678 ghut8695

Stop sum when value reached

Assume Table1:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
| P4 | 700|
| P5 | 5500|
| P6 | 1900|
If I want to sum of CashAmount to be 'at least' 9000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
If I want to sum of CashAmount to be 'at least' 4000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
I had a look at limiting the rows to where the sum a column equals a certain value in MySQL. But the accepted answer is not working with MSSQL and is not exactly what I'm looking for. Most of the answers there I've tested and they return only rows that the total amount is less than, not at least specific value.
SQL Server 2005 and Later
SELECT *
FROM TableName t
CROSS APPLY (SELECT SUM(Amount)
FROM TableName
WHERE [Date] <= t.[DATE]) c(AmtSum)
WHERE AmtSum <= 13
SQL Server 2012 and Later
SELECT *
FROM (
SELECT *
,SUM(Amount) OVER (ORDER BY [Date], Amount) AmtSum
FROM TableName
)t
WHERE AmtSum <= 13
According to your new input I changed my approach slightly. Hope this is what you need...
EDIT: Here's the version with SUM(x) OVER(...):
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=9000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx
,SUM(CashAmount) OVER(ORDER BY PaymentID) AS Summa
FROM #payment
)
SELECT * FROM SortedPayment
WHERE inx<=(SELECT TOP 1 x.inx
FROM SortedPayment AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
And that's the old version for SQL-Server < 2012
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=4000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx,*
FROM #payment
)
,Accumulated AS
(
SELECT tbl.*
FROM
(
SELECT SortedPayment.*
,Accumulated.Summa
FROM SortedPayment
CROSS APPLY
(
SELECT SUM(ps2.CashAmount) AS Summa
FROM SortedPayment AS ps2
WHERE ps2.inx<=SortedPayment.inx
) AS Accumulated
) AS tbl
)
SELECT * FROM Accumulated
WHERE inx<=(SELECT TOP 1 x.inx
FROM Accumulated AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
declare #s int;
update table set rc=row_count() over (order by date)
declare #i int;
set #i=1;
while #s<=12 or #i<100000
set #s=#s+(select amount from table where rc=#i+1);
set #i=#i+1;
end
// #s has at least 12

How to replace a functional (many) OUTER APPLY (SELECT * FROM)

Applies to Microsoft SQL Server 2008 R2.
The problem is
If we have a few dozen Outer Apply (30) then they begin to work pretty slowly. In the middle of the Outer Apply I have something more complicated than a simple select, a view.
Details
I'm writing a sort of attributes assigned to tables (in the database). Generally, a few tables, holds a reference to a table of attributes (key, value).
Pseudo structure looks like this:
DECLARE #Lot TABLE (
LotId INT PRIMARY KEY IDENTITY,
SomeText VARCHAR(8))
INSERT INTO #Lot
OUTPUT INSERTED.*
VALUES ('Hello'), ('World')
DECLARE #Attribute TABLE(
AttributeId INT PRIMARY KEY IDENTITY,
LotId INT,
Val VARCHAR(8),
Kind VARCHAR(8))
INSERT INTO #Attribute
OUTPUT INSERTED.* VALUES
(1, 'Foo1', 'Kind1'), (1, 'Foo2', 'Kind2'),
(2, 'Bar1', 'Kind1'), (2, 'Bar2', 'Kind2'), (2, 'Bar3', 'Kind3')
LotId SomeText
----------- --------
1 Hello
2 World
AttributeId LotId Val Kind
----------- ----------- -------- --------
1 1 Foo1 Kind1
2 1 Foo2 Kind2
3 2 Bar1 Kind1
4 2 Bar2 Kind2
5 2 Bar3 Kind3
I can now run a query such as:
SELECT
[l].[LotId]
, [SomeText]
, [Oa1].[AttributeId]
, [Oa1].[LotId]
, 'Kind1Val' = [Oa1].[Val]
, [Oa1].[Kind]
, [Oa2].[AttributeId]
, [Oa2].[LotId]
, 'Kind2Val' = [Oa2].[Val]
, [Oa2].[Kind]
, [Oa3].[AttributeId]
, [Oa3].[LotId]
, 'Kind3Val' = [Oa3].[Val]
, [Oa3].[Kind]
FROM #Lot AS l
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind1') AS Oa1
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind2') AS Oa2
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind3') AS Oa3
LotId SomeText AttributeId LotId Kind1Val Kind AttributeId LotId Kind2Val Kind AttributeId LotId Kind3Val Kind
----------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- --------
1 Hello 1 1 Foo1 Kind1 2 1 Foo2 Kind2 NULL NULL NULL NULL
2 World 3 2 Bar1 Kind1 4 2 Bar2 Kind2 5 2 Bar3 Kind3
The simple way to get the pivot table of attribute values ​​and results for Lot rows that do not have attribute such a Kind3.
I know Microsoft PIVOT and it is not simple and do not fits here.
Finally, what will be faster and will give the same results?
In order to get the result you can unpivot and then pivot the data.
There are two ways that you can perform this. First, you can use the UNPIVOT and the PIVOT function:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
unpivot
(
value
for col in (attributeid, a_Lotid, val, kind)
) unpiv
) d
pivot
(
max(value)
for col in (attributeid_1, a_LotId_1, Val_1, Kind_1,
attributeid_2, a_LotId_2, Val_2, Kind_2,
attributeid_3, a_LotId_3, Val_3, Kind_3)
) piv
See SQL Fiddle with Demo.
Or starting in SQL Server 2008+, you can use CROSS APPLY with a VALUES clause to unpivot the data:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values ('attributeid', attributeid),('LotId', a_LotId), ('Value', Val), ('Kind', Kind)
) c (col, value)
) d
pivot
(
max(value)
for col in (attributeid_1, LotId_1, Value_1, Kind_1,
attributeid_2, LotId_2, Value_2, Kind_2,
attributeid_3, LotId_3, Value_3, Kind_3)
) piv
See SQL Fiddle with Demo.
The unpivot process takes the multiple columns for each LotID and SomeText and converts it into rows giving the result:
| LOTID | SOMETEXT | COL | VALUE |
--------------------------------------------
| 1 | Hello | attributeid_1 | 1 |
| 1 | Hello | LotId_1 | 1 |
| 1 | Hello | Value_1 | Foo1 |
| 1 | Hello | Kind_1 | Kind1 |
| 1 | Hello | attributeid_2 | 2 |
I added a row_number() to the inner subquery to be used to create the new column names to pivot. Once the names are created the pivot can be applied to the new columns giving the final result
This could also be done using dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+rn)
from
(
select
cast(ROW_NUMBER() over(partition by l.lotid order by a.attributeid) as varchar(10)) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) t
cross apply (values ('attributeid', 1),
('LotId', 2),
('Value', 3),
('Kind', 4)) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT LotId,
SomeText,' + #cols + '
from
(
select LotId,
SomeText,
col+''_''+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values (''attributeid'', attributeid),(''LotId'', a_LotId), (''Value'', Val), (''Kind'', Kind)
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
All three versions will give the same result:
| LOTID | SOMETEXT | ATTRIBUTEID_1 | LOTID_1 | VALUE_1 | KIND_1 | ATTRIBUTEID_2 | LOTID_2 | VALUE_2 | KIND_2 | ATTRIBUTEID_3 | LOTID_3 | VALUE_3 | KIND_3 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | Hello | 1 | 1 | Foo1 | Kind1 | 2 | 1 | Foo2 | Kind2 | (null) | (null) | (null) | (null) |
| 2 | World | 3 | 2 | Bar1 | Kind1 | 4 | 2 | Bar2 | Kind2 | 5 | 2 | Bar3 | Kind3 |

Resources