How to write an xquery containing sequence elements? - sql-server

I've got a very large xml data set that is structured like the following:
<root>
<person>
<personid>HH3269732</personid>
<firstname>John</firstname>
<lastname>Smith</lastname>
<entertime>01/02/2008 10:15</entertime>
<leavetime>01/02/2008 11:45</leavetime>
<entertime>03/01/2008 08:00</entertime>
<leavetime>03/01/2008 10:00</leavetime>
...
// number of enter times and leave times vary from person to person
// there may not be a final leave time (ie, they haven't left yet)
</person>
...
</root>
The structure of the data is not under my control. This data is currently residing in a single xml column in a single row in MS SQL Server 2005. I am trying to construct a query which results in the following output:
HH3269732 John Smith 01/02/2008 10:15 01/02/2008 11:45
HH3269732 John Smith 03/01/2008 08:00 01/02/2008 10:00
HH3269735 Mark Pines 02/01/2008 09:00 NULL
HH3263562 James Frank NULL NULL
HH3264237 Harold White 04/18/2008 03:00 04/18/2008 05:00
...
My query currently looks like the following:
DECLARE #xml xml
SELECT #xml = XmlCol FROM Data
SELECT
[PersonId] = Persons.PersonCollection.value('(personid)[1]', 'NVARCHAR(50)')
,[First Name] = Persons.PersonCollection.value('(firstname)[1]', 'NVARCHAR(50)')
,[Last Name] = Persons.PersonCollection.value('(lastname)[1]', 'NVARCHAR(50)')
??????
FROM #xml.nodes('root\person') Persons(PersonCollection)
That query may not be 100% right as I'm pulling it from memory, but the problem I'm having is that I don't know how to include the entertime leavetime sequence elements in such a way as to get the desired rowset that I indicated above.
Thanks.
UPDATE:
I wanted to add that a given person record may have no entertime/leavetime sequence elements at all, but still needs to be returned in the rowset. I have updated the example of the desired output to reflect this.

with cte_entertime as (
SELECT
[PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
,[First Name] = t.c.value('(../firstname)[1]', 'NVARCHAR(50)')
,[Last Name] = t.c.value('(../lastname)[1]', 'NVARCHAR(50)')
,[Entertime] = t.c.value('.', 'NVARCHAR(50)')
,[entry_number] = ROW_NUMBER() OVER (ORDER BY t.c)
FROM #x.nodes('root/person/entertime') t(c))
, cte_leavetime as (
SELECT
[Leavetime] = t.c.value('.', 'NVARCHAR(50)')
,[entry_number] = ROW_NUMBER() OVER (ORDER BY t.c)
FROM #x.nodes('root/person/leavetime') t(c))
SELECT PersonID
, [First Name]
, [Last Name]
, [Entertime]
, [Leavetime]
FROM cte_entertime e
LEFT OUTER JOIN cte_leavetime l on e.entry_number = l.entry_number

I have accepted Remus's answer as it got me 95% to the solution. For informational purposes, here is the final query structure:
with cte_maindata as (
SELECT
[PersonId] = t.c.value('(personid)[1]', 'NVARCHAR(50)')
,[First Name] = t.c.value('(firstname)[1]', 'NVARCHAR(50)')
,[Last Name] = t.c.value('(lastname)[1]', 'NVARCHAR(50)')
FROM #x.nodes('root/person') t(c))
, cte_entertime as (
SELECT
[PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
,[Entertime] = t.c.value('.', 'NVARCHAR(50)')
FROM #x.nodes('root/person/entertime') t(c))
, cte_leavetime as (
SELECT
[PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
,[Leavetime] = t.c.value('.', 'NVARCHAR(50)')
FROM #x.nodes('root/person/leavetime') t(c))
SELECT
m.PersonID
,[First Name]
,[Last Name]
,[Entertime]
,[Leavetime]
FROM cte_maindata m
LEFT OUTER JOIN cte_entertime e on m.PersonId = e.PersonId
LEFT OUTER JOIN cte_leavetime l on m.PersonId = l.PersonId

Haven't realized you may have multiple persons in the document. My query would be incorrect in that case anyway. I thought maybe if you first shred out each person into its own XML fragment and ten extract the enter/leave times might perform better. I don't have 215k person XML to try, but here is an idea:
declare #x xml;
select #x = N'<root>
<person>
<personid>HH3269732</personid>
<firstname>John</firstname>
<lastname>Smith</lastname>
<entertime>01/02/2008 10:15</entertime>
<leavetime>01/02/2008 11:45</leavetime>
<entertime>03/01/2008 08:00</entertime>
<leavetime>03/01/2008 10:00</leavetime>
<entertime>04/01/2008 08:00</entertime>
</person>
<person>
<personid>HH3269733</personid>
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<entertime>01/03/2008 10:15</entertime>
<leavetime>01/03/2008 11:45</leavetime>
<entertime>03/04/2008 08:00</entertime>
<leavetime>03/04/2008 10:00</leavetime>
<entertime>04/04/2008 08:00</entertime>
</person>
</root>';
with cte_person as (
select
t.c.value('(personid)[1]', 'NVARCHAR(50)') as personid
, t.c.value('(firstname)[1]', 'NVARCHAR(50)') as firstname
, t.c.value('(lastname)[1]', 'NVARCHAR(50)') as lastname
, t.c.query('entertime') as entertime
, t.c.query('leavetime') as leavetime
FROM #x.nodes('root/person') t(c))
, cte_cross_enter as (
select
p.personid
, p.firstname
, p.lastname
, x.c.value('.', 'datetime') as entertime
, row_number() over (partition by personid order by x.c) as row_enter
from cte_person p
cross apply p.entertime.nodes('/entertime') x(c))
, cte_cross_leave as (
select
p.personid
, x.c.value('.', 'datetime') as leavetime
, row_number() over (partition by personid order by x.c) as row_leave
from cte_person p
cross apply p.leavetime.nodes('/leavetime') x(c))
select e.personid
, e.firstname
, e.lastname
, e.entertime
, l.leavetime
from cte_cross_enter e
left outer join cte_cross_leave l
on e.personid = l.personid and
e.row_enter = l.row_leave

Related

When joining tables adapt the "on" statement depending on the query results

I have 2 tables:
Table_1 with columns col_A, col_B , col_C , col_D , col_E
Table_2 with columns col_A, col_B , col_C , col_D , col_F
I would like to join them on columns col_A, col_B , col_C , col_D.
For the rows in Table_1 that do not get joined this way (as they don't have a match in Table_2), I would like to join them only on columns col_A, col_B , col_C.
If there are still rows in Table_1 that did not get joined, i would like to join them only on columns col_A, col_B.
And once that is done and there are still rows in Table_1 that did not get joined, i would like to join them only on column col_A.
I wrote the following script where i use a new table to get this result.
Is there is a more efficient way to do this? Preferably by creating a view, not a table?
create table new_table (col_A nvarchar(50) , col_B nvarchar(50) , col_C nvarchar(50)
, col_D nvarchar(50) , col_E nvarchar(50) , col_F nvarchar(50) )
go
insert into new_table
select Table_1.* , Table_2.col_F
from Table_1
inner join Table_2
on Table_1.col_A=Table_2.col_A
and Table_1.col_B=Table_2.col_B
and Table_1.col_C=Table_2.col_C
and Table_1.col_D=Table_2.col_D
go
insert into new_table
select Table_1.* , Table_2.col_F
from Table_1
inner join Table_2
on Table_1.col_A=Table_2.col_A
and Table_1.col_B=Table_2.col_B
and Table_1.col_C=Table_2.col_C
where concat (Table_1.col_A, Table_1.col_B , Table_1.col_C , Table_1.col_D , Table_1.col_E
not in (select concat (col_A, col_B , col_C , col_D , col_E) from new_table)
go
insert into new_table
select Table_1.* , Table_2.col_F
from Table_1
inner join Table_2
on Table_1.col_A=Table_2.col_A
and Table_1.col_B=Table_2.col_B
where concat (Table_1.col_A, Table_1.col_B , Table_1.col_C , Table_1.col_D , Table_1.col_E
not in (select concat (col_A, col_B , col_C , col_D , col_E) from new_table)
go
insert into new_table
select Table_1.* , Table_2.col_F
from Table_1
inner join Table_2
on Table_1.col_A=Table_2.col_A
where concat (Table_1.col_A, Table_1.col_B , Table_1.col_C , Table_1.col_D , Table_1.col_E
not in (select concat (col_A, col_B , col_C , col_D , col_E) from new_table)
go
You could join them on just colA and then assign some different numbers:
WITH cte AS(
SELECT
CASE WHEN t1.D = t2.D THEN 100 ELSE 0 END +
CASE WHEN t1.C = t2.C THEN 10 ELSE 0 END +
CASE WHEN t1.B = t2.B THEN 1 ELSE 0 END as whatMatched,
*
FROM
t1 JOIN t2 on t1.A = t2.A
)
Now if a row got 111 we know that all (ABCD) matched, got 0 then only A matched etc..
So we can ask for only some rows:
SELECT * FROM cte WHERE whatmatched IN (111,11,1,0)
And lastly if there were multiples (matching on just A might mean there are duplicates), we can assign a row number to them in descending order and only take the first row:
SELECT x.* FROM
(SELECT *, ROW_NUMBER() OVER(ORDER BY whatmatched DESC) rown FROM cte WHERE whatmatched IN (111,11,1,0)) x
WHERE x.rown = 1
If it suits you better to use letters
we can assess the matches, choose only A, AB, ABC, or ABCD, then pick the most specific one by looking at the LENgth of the match string:
WITH cte AS(
SELECT
'A' +
CASE WHEN t1.B = t2.B THEN 'B' ELSE '' END +
CASE WHEN t1.C = t2.C THEN 'C' ELSE '' END +
CASE WHEN t1.D = t2.D THEN 'D' ELSE '' END as whatMatched,
*
FROM
t1 JOIN t2 on t1.A = t2.A
)
SELECT x.* FROM
(SELECT *, ROW_NUMBER() OVER(ORDER BY LEN(whatmatched) DESC) rown FROM cte WHERE whatmatched IN ('A','AB','ABC','ABCD')) x
WHERE x.rown = 1
If you want ties (i.e. a row from t1 that matches two rows from t2 because their A/B/C is the same and D differs, use DENSE_RANK instead of ROW_NUMBER so they end up tied for first place

Can't get my data sorted as needed in query results using SQL Server 2016

I have query that displays the results as follows:
However, I need the query results to be displayed as follows:
Here is my query code:
SELECT *
FROM #tempRecordsWithoutSUBJ
WHERE [Policy Number] = 'EAA1396891' --'EAA1380770'
GROUP BY [RN], [RTN], [AM Best Number], [Policy Number], [Line of Business (LOB)], [Transaction Effective Date], [Data Fields], [PolicySysID], [Record Type]
ORDER BY RN, [Record Type];
What I'm needing is to have the PROP and PRP1 records for each POLR record. There are 3 PROP and 3 corresponding PRP1 records and I need them sorted under each POLR record as displayed in the 2nd image. I added ROW_NUMBER to each section of the code to help but obviously I'm struggling. Any help/direction would be greatly appreciated.
From what you've provided, it looks like your requirement is to return all of the RTN=1 rows, each one with a full set of 2&3 rows under it, in order of RN,RTN.
If that's right, seems like one way to get this is with a UNION ALL that duplicates the 2&3 for each 1, then supplies an artificial column for ordering.
SELECT * , RTN AS Ord
FROM..WHERE..AND RTN=1
UNION ALL
SELECT t1.* , t2.RTN AS Ord
FROM myTable t1
CROSS JOIN myTable t2
WHERE..AND t1.RTN<>1 AND t2.RTN=1
ORDER BY Ord,RN,RTN
EDIT:
Ok, I see what I missed. We need to actually use RN AS Ord, and we also need to add one more artificial column to force the POLR records to the top of their grouping. So more like this:
SELECT *, RN AS Ord, 1 AS Ord2
FROM myTable
WHERE..AND RTN=1
UNION ALL
SELECT t1.* , t2.RN AS Ord, 2 AS Ord2
FROM myTable t1
CROSS JOIN myTable t2
WHERE..AND t1.RTN<>1 AND t2.RTN=1
ORDER BY Ord,Ord2,RN,RTN
I tested this and it works:
CREATE TABLE #tmp (
L1 char(1)
, I1 int
, I2 int
);
INSERT INTO #tmp
VALUES
('a',1,1)
, ('a',1,2)
, ('c',2,1)
, ('d',3,1)
, ('c',2,2)
, ('d',3,2)
;
SELECT *, I2 AS Ord, 1 AS Ord2
FROM #tmp
WHERE I1=1
UNION ALL
SELECT t1.*, t2.I2 AS Ord, 2 AS Ord2
FROM #tmp t1
CROSS JOIN #tmp t2
WHERE t1.I1<>1 AND t2.I1=1
ORDER BY Ord, Ord2, I2, I1
;
DROP TABLE #tmp;

Custom column holds multiple values

This is my code for a query to produce a specific report. This works, however when I added the last select statement
(Select Name
from RPT_CUSTOM_LIST_VALUES
where CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_10) AS [Application]
The RI1.Cust_10 column holds multiple values delimited by commas. How can I get it so that the look up table pulls each value and provides the correct name for that value? I cannot create or modify the tables within this database.
select
RI1.incident_id as [Project Incident #],
(Select Name from RPT_CUSTOM_LIST_VALUES
where CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_02) as [Business Name],
RI1.NAME as [Project Name],
RI1.INCIDENT_STATUS_NAME as [Phase],
(Select Name from RPT_CUSTOM_LIST_VALUES
where CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_09) as [Key Milestone Name],
convert(nvarchar(10), RI1.CUST_26,103) as [Key Milestone Date], -- leave as date
convert(nvarchar(10), RI1.CUST_29,103) as [Target Completion Date], -- leave as date
RI1.SEVERITY_NAME as [Status Color],
RI1.CUST_01 as [Status Summary],
RI1.OWNER_NAME as [IT Owner],
(Select Name from RPT_CUSTOM_LIST_VALUES
where CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_10) AS [Application]
from
RPT_INCIDENTS RI1
where
RI1.PROJECT_ID = 445
and RI1.IS_DELETED = 0
and (RI1.INCIDENT_STATUS_NAME <> '5.1-Cancelled' and RI1.INCIDENT_STATUS_NAME <> '5.2-Completed')
My output should be, however, the last column should have names not values. The values are from the Lookup table and I need a way to pull that data so that the values are now names.
Report Output
I can't test without your data, but hopefully it will work for you:
select RI1.incident_id as [Project Incident #]
, [Business Name] = s1.Name
,RI1.NAME as [Project Name]
,RI1.INCIDENT_STATUS_NAME as [Phase]
, [Key Milestone Name] = s2.Name
,convert(nvarchar(10), RI1.CUST_26,103) as [Key Milestone Date] -- leave as date
,convert(nvarchar(10), RI1.CUST_29,103) as [Target Completion Date] -- leave as date
,RI1.SEVERITY_NAME as [Status Color]
,RI1.CUST_01 as [Status Summary]
,RI1.OWNER_NAME as [IT Owner]
, [Application] = LEFT(s3.App,LEN(APP) - SIGN(LEN(s3.APP)))
From RPT_INCIDENTS AS RI1
OUTER APPLY (Select TOP 1 i.Name from RPT_CUSTOM_LIST_VALUES as i where i.CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_02) as s1
OUTER APPLY (Select TOP 1 i.Name from RPT_CUSTOM_LIST_VALUES as i where i.CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_09) as s2
OUTER APPLY (SELECT App = (
Select i.Name + ','
from RPT_CUSTOM_LIST_VALUES as i
where i.CUSTOM_PROPERTY_VALUE_ID = RI1.CUST_10
FOR XML PATH(''))
) as s3
where RI1.PROJECT_ID = 445
and RI1.IS_DELETED = 0
and (RI1.INCIDENT_STATUS_NAME <> '5.1-Cancelled' and RI1.INCIDENT_STATUS_NAME <> '5.2-Completed')

SQL Server: How to show list of manager name separated by comma

i have to show employee name and his manager name hierarchy separated by comma.
if ram is top manager and ram is sam manager and sam is rahim manager then i would like to have output like
Desired output
EMP Name Manager's Name
--------- ---------------
Rahim Sam,Ram
Sam Ram
Ram No manager
i got script which show the employee and his manager name. here is script
;WITH EmployeeCTE AS
(
Select ID,Name,MgrID, 0 as level FROM #Employee
WHERE ID=3
UNION ALL
Select r.ID,r.Name,r.MgrID, level+1 as level
FROM #Employee r
JOIN EmployeeCTE p on r.ID = p.MgrID
)
Select e1.Name
,ISNULL(e2.Name,'Top BOSS') as [Manager Name]
,row_number() over (order by e1.level desc) as [Level]
from EmployeeCTE e1
left join EmployeeCTE e2 on e1.MgrID=e2.ID
Output
Name Manager Name Level
Simon Top BOSS 1
Katie Simon 2
John Katie 3
i also know how to show comma separated list. here is one sample script.
SELECT
PNAME,
STUFF
(
(
SELECT ',' + Mname
FROM Myproducts M
WHERE M.PNAME = P.PNAME
ORDER BY Mname
FOR XML PATH('')
), 1, 1, ''
) AS Models
FROM
Myproducts p
GROUP BY PNAME
now some tell me how could i merge two script to get the desired output. thanks
CREATE TABLE #EMP (
EmpID INT
, ManagerID INT
, Name NVARCHAR(50) NULL
);
INSERT INTO #EMP (EmpID, ManagerID, Name)
VALUES
( 1, NULL, 'John')
, (2, 1, 'Katie')
, (3, 2, 'Simon');
SELECT *
FROM
#EMP;
WITH a AS (
SELECT
EmpID
, Name
, ManagerID
, CONVERT(NVARCHAR(MAX),'') AS ManagerChain
FROM
#Emp
WHERE
ManagerID IS NULL
UNION ALL
SELECT
e.EmpID
, e.Name
, e.ManagerID
, CASE
WHEN a.ManagerChain ='' THEN a.Name
ELSE CONCAT(a.Name, CONCAT(',',a.ManagerChain))
END
FROM
#Emp e
JOIN a ON e.ManagerID = a.EmpID
)
SELECT
a.Name
, IIF(a.ManagerChain='','No Manager',a.ManagerChain) AS ManagerChain
FROM
a;
DROP TABLE #EMP;
Assuming a table structure of
DECLARE #Employee TABLE(
ID INT,
Name VARCHAR(10),
MgrID INT)
INSERT INTO #Employee
VALUES (1,'Ram',NULL),
(2,'Sam',1),
(3,'Rahim',2);
You can use
WITH EmployeeCTE
AS (SELECT ID,
Name,
MgrID,
0 AS level,
CAST('No manager' AS VARCHAR(8000)) AS [Managers Name]
FROM #Employee
WHERE MgrID IS NULL
UNION ALL
SELECT r.ID,
r.Name,
r.MgrID,
level + 1 AS level,
CAST(P.Name + CASE
WHEN level > 0
THEN ',' + [Managers Name]
ELSE ''
END AS VARCHAR(8000))
FROM #Employee r
JOIN EmployeeCTE p
ON r.MgrID = p.ID)
SELECT *
FROM EmployeeCTE

Get full hierarchy by children

I have 2 tables:
contains full tree data
contains only specific childs
I need to get full hierarchy by child values. I can do it by one specific child node by following way:
;with tree as
(
select id, parent_id, name, level from f_all where id = #specefic_id
union all
select f.id, f.parent_id, f.name, f.level from f_all f
inner join tree t on f.id = t.parent_id and f.id <> f.parent_id
)
select *
from tree
OPTION (Maxrecursion 0)
I have an idea but I think it is not good. My idea is create function with above code. And call it by select my second table. I even didn't try it. Can you give me a right direction.
This is 2012+ ( Using concat() ... easily converted ).
Declare #f_all table (id int,parent_id int,name varchar(50))
Insert into #f_all values
(1,null,'1'),(2,1,'2'),(3,1,'3'),(4,2,'4'),(5,2,'5'),(6,3,'6'),(7,null,'7'),(8,7,'8')
Declare #Top int = null --<< Sets top of Hier Try 9
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
Declare #Filter varchar(25) = '4,6' --<< Empty for All or try 4,6
;with cteP as (
Select Seq = cast(1000+Row_Number() over (Order by name) as varchar(500))
,ID
,parent_id
,Lvl=1
,name
From #f_all
Where IsNull(#Top,-1) = case when #Top is null then isnull(parent_id,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.name)) as varchar(500))
,r.ID
,r.parent_id
,p.Lvl+1
,r.name
From #f_all r
Join cteP p on r.parent_id = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select Distinct
A.R1
,B.R2
,A.ID
,A.parent_id
,A.Lvl
,name = Replicate(#Nest,A.Lvl-1) + A.name
From cteR1 A
Join cteR2 B on A.ID=B.ID
Join (Select R1 From cteR1 where IIF(#Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',#Filter+','))>0) F on F.R1 between A.R1 and B.R2
Order By A.R1
Returns (#Top=null and #Filter='4,6')
Return Full Hier (#Top=null and #Filter='')
Returns Just a portion (#Top=2 and #Filter='')
The problem for me was that i didn't know how cte recursion works. Now i know how it works line by line: Recursive Queries Using Common Table Expressions.
Code below returns all hierarchy by children nodes:
;with tree as(
select id, parent_id, name, level from f_all fa
inner join #2nd_table_cildren_id c on c.id = fa.id
union all
select f.id, f.parent_id, f.name, f.level from f_all f
inner join tree t on f.id = t.parent_id and f.id <> f.parent_id
)
select distinct *
from tree
OPTION (Maxrecursion 0)

Resources