Querying XML file in SQL Server - sql-server

This is my xml file
<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parents>
<Parent id="1234">
<name>
<firstname>ABC</firstname>
<lastname>XYSX</lastname>
</name>
</Parent>
<Parent id="1235">
<name>
<firstname>TFU</firstname>
<lastname>GHY</lastname>
</name>
</Parent>
</Parents>
<Children>
<Child id="457" Parentid="1234">
<name>
<cfirstname>JOHN</cfirstname>
<clastname>SMITH</clastname>
</name>
</Child>
<Child id="459" Parentid="1235">
<name>
<cfirstname>DAVID</cfirstname>
<clastname>SMITH</clastname>
</name>
</Child>
</Children>
</Detials>
I stored it in a table (using Bulk Insert).
When I query like this
SELECT
x.c.value('(./Parents/Parent/#id)[1]', 'nvarchar(4)' ) AS id
FROM
T1 s
CROSS APPLY
s.XMLData.nodes('Detials') AS x(c)
I get the result as
Id
----
1234
When I changed it a little bit
SELECT
x.c.value('#id', 'nvarchar(4)' ) AS id
FROM
T1 s
CROSS APPLY
s.XMLData.nodes('/Detials/Parents/Parent') AS x(c)
I get:
Id
----
1234
1235
I just want to query them to result like this
Parent_id | firstname | lastname | Child_id | Child_First_name | child_last_name
----------+-----------+----------+----------+------------------+-----------
1234 | ABC | XYSX | 457 | JOHN | SMITH
1235 | TFU | GHY | 459 | DAVID | SMITH
How could I do this in query ?
In my result I want total no. of rows which depends on the Parentid. If my xml file contains 6 <parent id> then my query should show all 6 rows for each parent id's along with comparing the Childid's to also be included if the Parentid matches in <child> tag.
Thanks, Jayendran

You were on the right track, but this would require a left join on the two result sets Parent/Children
Example
Declare #T1 table (ID int,XMLData xml)
Insert Into #T1 values
(1,'<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Parents><Parent id="1234"><name><firstname>ABC</firstname><lastname>XYSX</lastname></name></Parent><Parent id="1235"><name><firstname>TFU</firstname><lastname>GHY</lastname></name></Parent></Parents><Children><Child id="457" Parentid="1234"><name><cfirstname>JOHN</cfirstname><clastname>SMITH</clastname></name></Child><Child id="459" Parentid="1235"><name><cfirstname>DAVID</cfirstname><clastname>SMITH</clastname></name></Child></Children></Detials>')
Select A.ID
,B.*
From #T1 A
Cross Apply (
Select B1.Parent_id
,B1.First_Name
,B1.Last_Name
,B2.Child_id
,B2.Child_First_name
,B2.child_last_name
From (
Select
Parent_id = x.c.value('#id', 'nvarchar(4)' )
,First_Name = x.c.value('(name/firstname)[1]','varchar(50)')
,Last_Name = x.c.value('(name/lastname)[1]','varchar(50)')
From #T1 s
Cross Apply s.XMLData.nodes('/Detials/Parents/Parent') AS x(c)
) B1
Left Join (
Select
pt_id = x.c.value('#Parentid', 'nvarchar(4)' )
,Child_id = x.c.value('#id', 'nvarchar(4)' )
,Child_First_name = x.c.value('(name/cfirstname)[1]','varchar(50)')
,child_last_name = x.c.value('(name/clastname)[1]','varchar(50)')
From #T1 s
Cross Apply s.XMLData.nodes('/Detials/Children/Child ') AS x(c)
) B2
on B1.Parent_id = B2.pt_id
) B
Returns

I'd solve this in one single go:
SELECT Parent.ID
,p.value('(name/firstname/text())[1]','nvarchar(max)')
,p.value('(name/lastname/text())[1]','nvarchar(max)')
,c.value('#id','int')
,c.value('(name/cfirstname/text())[1]','nvarchar(max)')
,c.value('(name/clastname/text())[1]','nvarchar(max)')
FROM #xml.nodes('/Detials/Parents/Parent') AS A(p)
OUTER APPLY (SELECT p.value('#id','int')) AS Parent(ID)
OUTER APPLY #xml.nodes('/Detials/Children/Child[#Parentid=sql:column("Parent.ID")]') AS B(c);
The trick is, to read the Parentid into a named column via APPLY. This value can be used as predicate to fetch the children via sql:column().

Related

How to use a Left Join with a bunch of other joins

I am trying to join two tables based on EquipWorkOrderID. Tables(EquipWorkOrder and EquipWorkOrderHrs)
With the query I have below it duplicates the row based on ID if there is two UserNm's for the Same ID. I want the two UserNm's and the Hrs if the ID match's in the same role if possible.
example of what my results give me now
EquipWorkOrderID/Equip/Description/Resolution/UserNm/Hrs
---------------------------------------------------------
1 / ForkLift / Bad /Fixed/John Doe / 2
1 /Forklift / Bad /Fixed/Jane Doe /2
What I would Like to see
EquipWorkOrderID/Equip/Description/Resolution/UserNm1/Hrs1/UserNm2/Hrs2
---------------------------------------------------------
1 / ForkLift / Bad /Fixed/John Doe / 2 / Jane Doe / 2
Select * From
(
Select
a.EquipWorkOrderID,
c.UserNm,
b.Hrs
From
EquipWorkOrder a
Left Join EquipWorkOrderHrs b
On a.EquipWorkOrderID = b.EquipWorkOrderID
Left Join AppUser c
On c.UserID = b.UserID
) t
Pivot (
Count(Hrs)
For UserNm IN (
[Tech1],
[Tech2],
[Tech3],
[Tech4],
[Tech5])
) AS pivot_table
I have placed the result of your query in a table (selection) and retrieved the data from it in a common table expression (cte). Replace the content of the CTE with your query and add the two new columns I created (UsrNum and HrsNum).
My solution uses a double pivot (one for the UserNm column and one for the Hrs column) followed by a grouping. This may not be ideal, but it gets the job done.
Here is a fiddle to show how I built up the solution.
Sample data
This just recreates the results of your current query.
create table selection
(
EquipWorkOrderID int,
Equip nvarchar(10),
Description nvarchar(10),
Resolution nvarchar(10),
UserNm nvarchar(10),
Hrs int
);
insert into selection (EquipWorkOrderID,Equip,Description,Resolution,UserNm,Hrs) values
(1, 'ForkLift', 'Bad', 'Fixed', 'John Doe', 2),
(1, 'Forklift', 'Bad', 'Fixed', 'Jane Doe', 2);
Solution
Replace the first part of the CTE with your query and add the two new columns.
with cte as
(
select EquipWorkOrderID,Equip,Description,Resolution,UserNm,Hrs,
'Usr' + convert(nvarchar(10),row_number() over(partition by Equip order by UserNm)) as 'UsrNum',
'Hrs' + convert(nvarchar(10),row_number() over(partition by Equip order by UserNm)) as 'HrsNum'
from selection
)
select ph.EquipWorkOrderId, ph.Equip, ph.Description, ph.Resolution,
max(ph.Usr1) as 'UserNm1',
max(ph.Hrs1) as 'Hrs1',
max(ph.Usr2) as 'UserNm2',
max(ph.Hrs2) as 'Hrs2'
from cte c
pivot (max(c.UserNm) for c.UsrNum in ([Usr1], [Usr2])) pu
pivot (max(pu.Hrs) for pu.HrsNum in ([Hrs1], [Hrs2])) ph
group by ph.EquipWorkOrderId, ph.Equip, ph.Description, ph.Resolution;
Result
Outcome looks like this. Jane Doe is UserNm1 because that is how the new UserNum column was constructed (order by UserNm). Adjust the order by if you need John Doe to remain first.
EquipWorkOrderId Equip Description Resolution UserNm1 Hrs1 UserNm2 Hrs2
----------------- --------- ------------ ----------- --------- ----- -------- -----
1 ForkLift Bad Fixed Jane Doe 2 John Doe 2
Edit: solution merged with original query (untested)
with cte as
(
SELECT TOP 1000
--Start original selection field list
ewo.EquipWorkOrderID,
ewo.DateTm,
equ.Equip,
equ.AccountCode,
equ.Descr,
ewo.Description,
ewo.Resolution,
sta.Status,
au.UserNm,
ewoh.Hrs,
cat.Category,
ml.MaintLoc,
equt.EquipType,
cre.Crew,
ewo.MeterReading,
typ.Type,
--Added two new fields
'Usr' + convert(nvarchar(10),row_number() over(partition by Equip order by UserNm)) as 'UsrNum',
'Hrs' + convert(nvarchar(10),row_number() over(partition by Equip order by UserNm)) as 'HrsNum'
FROM EquipWorkOrder ewo
JOIN EquipWorkOrderHrs ewoh
ON ewo.EquipWorkOrderID = ewoh.EquipWorkOrderID
JOIN AppUser au
ON au.UserID = ewoh.UserID
JOIN Category cat
ON cat.CategoryID = ewo.CategoryID
JOIN Crew cre
ON cre.CrewID = ewo.CrewID
JOIN Equipment equ
ON equ.EquipmentID = ewo.EquipmentID
JOIN Status sta
ON sta.StatusID = ewo.StatusID
JOIN PlantLoc pll
ON pll.PlantLocID = ewo.PlantLocID
JOIN MaintLocation ml
ON ml.MaintLocationID = ewo.MaintLocationID
JOIN EquipType equt
ON equt.EquipTypeID = ewo.EquipTypeID
JOIN Type typ
ON typ.TypeID = equ.TypeID
ORDER BY ewo.DateTm DESC
)
select ph.EquipWorkOrderId, ph.Equip, ph.Description, ph.Resolution,
max(ph.Usr1) as 'UserNm1',
max(ph.Hrs1) as 'Hrs1',
max(ph.Usr2) as 'UserNm2',
max(ph.Hrs2) as 'Hrs2'
from cte c
pivot (max(c.UserNm) for c.UsrNum in ([Usr1], [Usr2])) pu
pivot (max(pu.Hrs) for pu.HrsNum in ([Hrs1], [Hrs2])) ph
group by ph.EquipWorkOrderId, ph.Equip, ph.Description, ph.Resolution;
Edit2: how to use pivot...
Select pivot_table.*
From
(
Select a.EquipWorkOrderID,
b.Hrs,
c.UserNm,
'Tech' + convert(nvarchar(10), row_number() over(order by c.UserNm)) -- construct _generic_ names
From EquipWorkOrder a
Left Join EquipWorkOrderHrs b
On a.EquipWorkOrderID = b.EquipWorkOrderID
Left Join AppUser c
On c.UserID = b.UserID
) t
/*
Pivot (Count(Hrs) For UserNm IN ([Tech1], [Tech2], [Tech3], [Tech4], [Tech5])) AS pivot_table -- UserNm does not contain values like "Tech1" or "Tech2"
*/
Pivot (Count(Hrs) For GenUserNm IN ([Tech1], [Tech2], [Tech3], [Tech4], [Tech5])) AS pivot_table -- pivot over the _generic_ names

get values from xml by sql query when several attributes

There is xml with several attributes "Num"
DECLARE #XML XML = '
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num = "1"/>
<Number Num = "2"/>
</file>
</FileId>';
With this sql query only one attribute can be get
SELECT F.[File].value(N'../#global_id','varchar(100)') as id_payment,
F.[File].value('#id', 'varchar(4)') AS id,
F.[File].value('(Number/#Num)[1]', 'int') as [Num]
FROM (VALUES (#XML)) V (X)
CROSS APPLY V.X.nodes('/FileId/file') F([File])
How to get all attributes -- Num = 1 and Num = 2.
Can be a variable amount of attributes.
id_payment id Num
1234 12aa NULL
1234 12bb 1
1234 12bb 2
Much simpler version. (1) No need to use the VALUES clause. (2) The OUTER APPLY simulates LEFT OUTER JOIN. (3) Most efficient way to retrieve the global_id attribute. The credit goes to Shnugo.
SQL
DECLARE #XML XML = N'
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num="1"/>
<Number Num="2"/>
</file>
</FileId>';
SELECT #xml.value('(/FileId/#global_id)[1]','INT') AS id_payment
, c.value('#id', 'VARCHAR(4)') AS id
, n.value('#Num', 'INT') AS [Num]
FROM #xml.nodes('/FileId/file') AS t(c)
OUTER APPLY t.c.nodes('Number') AS t2(n);
Output
+------------+------+------+
| id_payment | id | Num |
+------------+------+------+
| 1234 | 12aa | NULL |
| 1234 | 12bb | 1 |
| 1234 | 12bb | 2 |
+------------+------+------+
DECLARE #XML XML = '
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num = "1"/>
<Number Num = "2"/>
<Number Num = "3"/>
<Number Num = "4"/>
<Number Num = "5"/>
<Number Num = "6"/>
</file>
</FileId>';
SELECT F.[File].value(N'../#global_id','varchar(100)') as id_payment,
F.[File].value('#id', 'varchar(4)') AS id,
F.[File].value('(Number/#Num)[1]', 'int') as [Num],
n.num.value('(#Num)[1]', 'int') as [Numxyz]
FROM (VALUES (#XML)) V (X)
CROSS APPLY V.X.nodes('/FileId/file') F([File])
outer apply F.[File].nodes('Number') as n(num)

UNPIVOT in SQL Server

I have a table as below:
I need to convert this table's rows into columns
Expected output:
depotCode | containerYardCode | chargeCode | 20"GP | 40"GP |YAC 20"GP|YAC 40"GP
-------------------------------------------------------------------------------
HQ |CT1 KCT | DGC | 25.000 |25.000 | NULL |NULL
HQ |HARBOUR YARD | DGC |21.000 |32.000 |18.000 |28.000
Sample data:
declare #t table (depotCode varchar(2), containerYardCode varchar(25), chargeCode varchar(3), cargoTypeCode varchar(10), effectiveDate datetime, rate numeric(5,3), yardActualCost numeric(5,3), includeGST bit)
insert into #t values ('HQ', 'CT1 KCT', 'DGC', '20"GP', '6/1/2015', 25, null, 0)
insert into #t values ('HQ', 'CT1 KCT', 'DGC', '40"GP', '6/1/2015', 25, null, 0)
insert into #t values ('HQ', 'HARBOUR YARD', 'DGC', '20"GP', '1/1/2017', 21, 18, 0)
insert into #t values ('HQ', 'HARBOUR YARD', 'DGC', '40"GP', '1/1/2017', 32, 28, 0)
I think you need to join 2 pivot queries together, like this:
SELECT sq1.depotCode, sq1.containerYardCode, sq1.chargeCode, [20"GP], [40"GP], [YAC 20"GP], [YAC 40"GP]
FROM
(
SELECT depotCode, containerYardCode, chargeCode, [20"GP], [40"GP]
FROM (SELECT depotCode, containerYardCode, chargeCode, cargoTypeCode, rate FROM #t)t
PIVOT (sum(rate) for cargoTypeCode in ([20"GP], [40"GP]))p
)sq1
INNER JOIN
(
SELECT depotCode, containerYardCode, chargeCode, [20"GP] as [YAC 20"GP], [40"GP] as [YAC 40"GP]
FROM (SELECT depotCode, containerYardCode, chargeCode, cargoTypeCode, yardActualCost FROM #t)t
PIVOT (sum(yardActualCost) for cargoTypeCode in ([20"GP], [40"GP]))p
)sq2 ON sq1.depotCode = sq2.depotCode AND sq1.containerYardCode = sq2.containerYardCode AND sq1.chargeCode = sq2.chargeCode
And here's the output:
depotCode containerYardCode chargeCode 20"GP 40"GP YAC 20"GP YAC 40"GP
HQ CT1 KCT DGC 25.000 25.000 NULL NULL
HQ HARBOUR YARD DGC 21.000 32.000 18.000 28.000
The key is in the FROM clauses of the subqueries - only include the columns necessary for each individual pivot, and then join the subqueries on the common columns (e.g. depotCode, containerYardCode, chargeCode).

Add count to the root elements using FOR XML PATH

I have a Sql statement that returns xml of products in which root element is products. How can i add count attribute to the root element.
My sql is:
SELECT
id AS 'product_id',
name AS 'product_name'
FROM product
WHERE status = 1 AND ......
ORDER BY productid
FOR XML PATH('product'), ROOT('products')
Result is
<products>
<product>
.
.
</product>
</products>
I want to change result to
<products count="100">
<product>
.
.
</product>
</products>
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM product c1 FOR XML PATH('product'), TYPE
)
FROM product ct FOR XML PATH('products')
The easiest way to filter is to add condition in both query and sub-query:
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM product c1
WHERE c1.status = 1 AND ......
FOR XML PATH('product'), TYPE
)
FROM product ct
WHERE ct.status = 1 AND ......
FOR XML PATH('products')
Or use temp-table:
SELECT *
INTO #Temp
FROM product c1
WHERE c1.status = 1 AND ......
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM #Temp c1
FOR XML PATH('product'), TYPE
)
FROM #Temp ct
FOR XML PATH('products')

EF6 - Generating unneeded nested queries

I have the following tables:
MAIN_TBL:
Col1 | Col2 | Col3
------------------
A | B | C
D | E | F
And:
REF_TBL:
Ref1 | Ref2 | Ref3
------------------
A | G1 | Foo
D | G1 | Bar
Q | G2 | Xyz
I wish to write the following SQL query:
SELECT M.Col1
FROM MAIN_TBL M
LEFT JOIN REF_TBL R
ON R.Ref1 = M.Col1
AND R.Ref2 = 'G1'
WHERE M.Col3 = 'C'
I wrote the following LINQ query:
from main in dbContext.MAIN_TBL
join refr in dbContext.REF_TBL
on "G1" equals refr.Ref2
into refrLookup
from refr in refrLookup.DefaultIfEmpty()
where main.Col1 == refr.Col1
select main.Col1
And the generated SQL was:
SELECT
[MAIN_TBL].[Col1]
FROM (SELECT
[MAIN_TBL].[Col1] AS [Col1],
[MAIN_TBL].[Col2] AS [Col2],
[MAIN_TBL].[Col3] AS [Col3]
FROM [MAIN_TBL]) AS [Extent1]
INNER JOIN (SELECT
[REF_TBL].[Ref1] AS [Ref1],
[REF_TBL].[Ref2] AS [Ref2],
[REF_TBL].[Ref3] AS [Ref3]
FROM [REF_TBL]) AS [Extent2] ON [Extent1].[Col1] = [Extent2].[Ref1]
WHERE ('G1' = [Extent2].[DESCRIPTION]) AND ([Extent2].[Ref1] IS NOT NULL) AND CAST( [Extent1].[Col3] AS VARCHAR) = 'C') ...
Looks like it is nesting a query within another query, while I just want it to pull from the table. What am I doing wrong?
I may be wrong, but it looks like you don't do the same in linq query and sql query, especially on your left joining clause.
I would go for this, if you want something similar to your sql query.
from main in dbContext.MAIN_TBL.Where(x => x.Col3 == "C")
join refr in dbContext.REF_TBL
on new{n = "G1", c = main.Col1} equals new{n = refr.Ref2, c = refr.Col1}
into refrLookup
from r2 in refrLookup.DefaultIfEmpty()
select main.Col1
By the way, it doesn't make much sense to left join on a table which is not present in the select clause : you will just get multiple identical Col1 if there's more than one related item in the left joined table...

Resources