Join table rows as column values sql - sql

In this situation I have two tables that I have joined, and it creates multiple duplicates due to each physician being able to have multiple licenses. I would like to pivot and make the multiple license state and numbers additional columns to table 1 as shown below.
This will need to be dynamically done since it can be an substantial amount of licenses assigned to one physician.
Table 1 looks like this.
assignid physician_name profession
-------------------------------
1 bob md
2 travis do
3 ryan md
4 pete pa
5 susan np
6 ashley cnp
Table 2
assignid license_state license_num
-------------------------------
1 oh 561
2 mi 987
3 ca 785
4 ny 965
4 mi 125
5 oh 369
5 ca 541
Joined
assignid physician_name profession license_state license_num
----------------------------------------------------------------
1 bob md oh 561
2 travis do mi 987
3 ryan md ca 785
4 pete pa ny 965
4 pete pa mi 125
5 susan np oh 369
5 susan np ca 541
I want to dynamically change the joined table to look like this.
assignid physician_name profession license_state1 license_num1 license_state2 license_num2
--------------------------------------------------------------------------------------------------
1 bob md oh 561
2 travis do mi 987
3 ryan md ca 785
4 pete pa ny 965 mi 125
5 susan np oh 369 ca 541
I attempted this route, but this gives me states as column header.
WITH pivotdata AS (
SELECT assignid,physician_name, profession, license_state,license_num
FROM dbo.Physicians p JOIN dbo.Licenses l ON p.AssignID = l.AssignID
)
SELECT *
FROM
pivotdata
PIVOT (MAX(license_num) FOR license_state IN ([oh], [mi], [ca],[ny])) TT;
Results
physician_name profession oh mi ca ny
-------------------------------------------
bob md 561
travis do 987
ryan md 785
pete pa 125 965
susan np 369 541

You can solve this using a dynamic cross tab. I learned this technique from Jeff Moden and his article here. http://www.sqlservercentral.com/articles/Crosstab/65048/
if OBJECT_ID('tempdb..#Physicians') is not null
drop table #Physicians
create table #Physicians
(
AssignID int
, PhysicianName varchar(20)
, Profession varchar(10)
)
insert #Physicians values
(1, 'bob', 'md')
, (2, 'travis', 'do')
, (3, 'ryan', 'md')
, (4, 'pete', 'pa')
, (5, 'susan', 'np')
, (6, 'ashley', 'cnp')
if OBJECT_ID('tempdb..#Licenses') is not null
drop table #Licenses
create table #Licenses
(
AssignID int
, LicenseState char(2)
, LicenseNum int
)
insert #Licenses values
(1, 'oh', 561)
, (2, 'mi', 987)
, (3, 'ca', 785)
, (4, 'ny', 965)
, (4, 'mi', 125)
, (5, 'oh', 369)
, (5, 'ca', 541)
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
SELECT p.AssignID
, p.PhysicianName
, p.Profession
, l.LicenseState
, l.LicenseNum
, ROW_NUMBER() over(partition by p.AssignID order by l.LicenseState) as RowNum
FROM #Physicians p
JOIN #Licenses l ON p.AssignID = l.AssignID
)
select AssignID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by AssignID order by AssignID';
--the following cte is a tally table (another trick I learned from Jeff Moden)
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then LicenseState end) as LicenseState' + CAST(N as varchar(6)) + CHAR(10)
+ ', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then LicenseNum end) as LicenseNum' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(p.AssignID)
FROM #Physicians p
JOIN #Licenses l ON p.AssignID = l.AssignID
group by p.AssignID
order by COUNT(*) desc
)
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
--you can comment the following. it is here for now so you can view the dynamic sql before it executes
select #SqlToExecute
--Once you are satisfied that the dynamic sql generated is correct simply uncomment the below line
--exec sp_executesql #SqlToExecute

Related

Convert rows of one table into column of another table. Explanation provided in body

Source table:
Student_ID Subject _ID Subject Marks
1 ------------ 10 ------------ Chem - 82
1 ------------ 11 ------------ Phy ---- 89
1 ------------ 12 ------------ Maths - 88
1 ------------ 13 ------------ Eng ---- 80
Target table is like this:
Student_ID Phy Chem Maths Eng
1 ------------- 82-- 89 --- 88 ---- 80
Note: These "-" are for sole purpose of depicting table. Sorry for the bad formatting.
You can do conditional aggregation with case expression :
select student_id,
sum(case when Subject _ID = 10 then marks else 0 end) as Chem,
sum(case when Subject _ID = 11 then marks else 0 end) as Phy,
sum(case when Subject _ID = 12 then marks else 0 end) as Maths,
sum(case when Subject _ID = 13 then marks else 0 end) as Eng
from table t
group by student_id;
You are looking to create a dynamic pivot able.
CREATE TABLE #T1 (StudentID int,SubjectID INT, Subject varchar(10), Marks int)
INSERT INTO #T1 VALUES (1, 10, 'Chem',82)
INSERT INTO #T1 VALUES (1,11, 'Phy',89)
INSERT INTO #T1 VALUES (1,12, 'Maths',88)
INSERT INTO #T1 VALUES (1,13, 'Eng',80)
INSERT INTO #T1 VALUES (2, 10, 'Chem',80)
INSERT INTO #T1 VALUES (2,11, 'Phy',85)
INSERT INTO #T1 VALUES (2,12, 'Maths',91)
INSERT INTO #T1 VALUES (2,13, 'Mgmt',82)
DECLARE #PivotColumnHeaders varchar(MAX)
SELECT #PivotColumnHeaders =
COALESCE(
#PivotColumnHeaders + ',[' + UC.Subject + ']',
'[' + Subject + ']'
)
FROM (SELECT Subject FROM #T1 GROUP BY Subject) UC
DECLARE #PQuery varchar(MAX) = '
SELECT * FROM (SELECT StudentID, Subject, Marks FROM #T1 T0) T1
PIVOT (SUM([Marks]) FOR Subject IN (' + #PivotColumnHeaders + ') ) AS P'
EXECUTE (#PQuery)
DROP TABLE #T1
Result is:
StudentID Chem Eng Maths Mgmt Phy
1 82 80 88 NULL 89
2 80 NULL 91 82 85

Left Join between 3 tables

I have 3 tables as follows:
Table 1 contains the following
Id Name Section Salary
---------------------------------
1 Mark It 1000
2 Dad Hr 2000
Table 2 contains this sample data:
Id Item Salary
--------------------------
1 Holday 50
1 Food 30
1 Rent 100
2 Food 200
2 Rent 200
Table 3 contains this data:
Id Descriptions Cost
---------------------------
1 Bonce 150
1 Rate 300
2 Car 100
2 Bonce 15
2 Rate 30
I need to have the data like the attached screenshot:
That is an interesting computing problem independent of your use. It does not a simple join the tables. That is the part of SQL Server enforce, in your report tool you need to do the adjusts.
Table preparation
DECLARE #table1 table (Id int, Name varchar(10), Section varchar(10), Salary decimal(18,2))
DECLARE #table2 table (Id int, Item varchar(10), Salary decimal(18,2))
DECLARE #table3 table (Id int, Descriptions varchar(10), Cost decimal(18,2))
INSERT #table1 values
(1, 'Mark', 'It', 1000)
,(2, 'Dad ', 'Hr', 2000)
INSERT #table2 Values
(1,'Holday', 50)
,(1,'Food ', 30)
,(1,'Rent ',100)
,(2,'Food ',200)
,(2,'Rent ',200)
INSERT #table3 values
(1, 'Bonce', 150)
,(1, 'Rate ', 300)
,(2, 'Car ', 100)
,(2, 'Bonce', 15)
,(2, 'Rate ', 30)
The Select
;WITH
lv0(n) AS (SELECT 0 FROM (VALUES (0), (0))G(n)), --2
lv1(n) AS (SELECT 0 FROM lv0 a CROSS JOIN lv0 b), -- 4
lv2(n) AS (SELECT 0 FROM lv1 a CROSS JOIN lv1 b), -- 16
lv3(n) AS (SELECT 0 FROM lv2 a CROSS JOIN lv2 b), -- 256
lv4(n) AS (SELECT 0 FROM lv3 a CROSS JOIN lv3 b), -- 65,536
--lv5(N) as (select 0 from lv4 a cross join lv4 b), -- 4,294,967,296
tally(n) AS (SELECT Row_number() OVER( ORDER BY (SELECT NULL)) FROM lv4),
t1 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table1),
t2 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table2),
t3 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table3)
SELECT a.NAME
,a.section
,a.section
,b.item
,b.salary
,c.descriptions
,c.cost
,a.id
,b.id
,c.id
FROM tally t
CROSS JOIN t1 a
LEFT JOIN t2 b
ON t.n = b.n
AND a.id = b.id
LEFT JOIN t3 c
ON t.n = C.n
AND a.id = c.id
WHERE a.id IS NOT NULL
AND ( b.id IS NOT NULL
OR c.id IS NOT NULL )
ORDER BY a.n
,b.n
,c.n
The result
NAME section section item salary descriptions cost id id id
---------- ---------- ---------- ---------- --------------------------------------- ------------ --------------------------------------- ----------- ----------- -----------
Mark It It Holday 50.00 Bonce 150.00 1 1 1
Mark It It Food 30.00 Rate 300.00 1 1 1
Mark It It Rent 100.00 NULL NULL 1 1 NULL
Dad Hr Hr NULL NULL Car 100.00 2 NULL 2
Dad Hr Hr Food 200.00 Bonce 15.00 2 2 2
Dad Hr Hr Rent 200.00 Rate 30.00 2 2 2

SQL Query Parent Child

Can you help me?
Table: LOS
ID Name ParentID
1 Item Null
2 Pharmacy 1
3 Consumable 1
4 Ethical 2
5 Non Ethical 2
6 MCCP 4
7 Nurse 3
Table: Item
id ItemName lOS_Id Los_Name
123 Panadol 6 MCCP
321 Nacl 7 Nurse
Expected Result.
ID ItemName ParentID ParentName LOS_Id Los_Name
123 Panadol 2 Pharmacy 6 MCCP
321 Nacl 3 Consumable 7 Nurse
Explanation : ParentID from los_id join LOS Table using ID THEN Get parentID to find Parent
Just see this example.
Declare #LOS table (ID int, Name nvarchar(50), ParentID int)
Declare #Item table (ID int, ItemName nvarchar(50), lOS_ID int, lOS_Name nvarchar(50) )
Insert into #LOS values (1, 'Item',null), (2 ,'Pharmacy', 1), (3 ,'Consumable', 1), (4 ,'Ethical', 2), (5 ,'Non Ethical', 2), (6 ,'MCCP', 4)
, (7 ,'Nurse', 3)
Insert into #Item values (123, 'Panadol',6 ,'MCCP'), (321, 'Nacl',7 ,'Nurse')
select * from #LOS
select * from #Item
;with cte as
(
select i.id, ItemName, lOS_ID, lOS_Name, lOS_ID parentid, lOS_ID parentparentid, lOS_Name parentname from #Item i
join #los l on i.lOS_ID = l.id
-- where i.id = 123
union all
select
c.id, ItemName, lOS_ID, lOS_Name, l.ID parentid, l.ParentID parentparentid, l.Name parentname
from
cte c join #los l on c.parentparentid = l.id --and c.ParentID <> null
)
select * from cte where parentparentid = 1
order by id
I hope I understood you correctly and this query will fix your problem :)
select I.ID, I.ItemName, A.ParentID, A.ParentName, I.LOS_ID, I.LOS_Name from Item AS I join (
select l1.id, l1.name, l1.ParentID, l2.name as ParentName from LOS as l1 join LOS as l2 on l1.id = l2.ParentID
) as A join on I.LOS_ID = A.ID

Calculating averages in SQL Server without ignoring null values

I have a query like below:
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
--SELECT * FROM #t
SELECT
EmpName, [Dog], [Cat], [Goat], [Ram]
FROM
(SELECT
EmpName, Qty, Item
FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN ([Dog], [Cat], [Goat], [Ram])) AS p
And the result is as seen in the screenshot below:
I want to calculate the average Qty across Item without ignoring null values in the calculation. For example, in row 1, EmpName Abay should be 5 divided by 4 (number of columns), as seen in this screenshot:
How do I get the average column?
I'm not really familiar with the PIVOT query, so here is an alternative using conditional aggregation:
SELECT
Empname,
Dog = SUM(CASE WHEN Item = 'Dog' THEN Qty ELSE 0 END),
Cat = SUM(CASE WHEN Item = 'Cat' THEN Qty ELSE 0 END),
Goat = SUM(CASE WHEN Item = 'Goat' THEN Qty ELSE 0 END),
Ram = SUM(CASE WHEN Item = 'Ram' THEN Qty ELSE 0 END),
Average = SUM(ISNULL(Qty, 0))/ 4.0
FROM #t
GROUP BY EmpName;
Note that this will only work if you only have 4 Items. Otherwise, you need to resort to dynamic crosstab.
ONLINE DEMO
For dynamic crosstab, I used a temporary table instead of a table variable:
DECLARE #sql NVARCHAR(MAX) = '';
SELECT #sql =
'SELECT
Empname' + CHAR(10);
SELECT #sql = #sql +
' , SUM(CASE WHEN Item = ''' + Item + ''' THEN Qty ELSE 0 END) AS ' + QUOTENAME(Item) + CHAR(10)
FROM (
SELECT DISTINCT Item FROM #t
) t;
SELECT #sql = #sql +
' , SUM(ISNULL(Qty, 0)) / (SELECT COUNT(DISTINCT Item) * 1.0 FROM #t) AS [Average]' + CHAR(10) +
'FROM #t
GROUP BY EmpName;';
ONLINE DEMO
Try a combination of AVG and ISNULL, i.e. AVG(ISNULL(Dog, 0)).
One simple method is:
select empname, goat, cat, dog, ram,
(coalesce(goat, 0) + coalesce(cat, 0) + coalesce(dog, 0) + coalesce( ram, 0)
) / 4.0 as average
from t;
Another simple method uses outer apply:
select t.*, v.average
from t outer apply
(select avg(coalesce(x, 0))
from (values (t.goat), (t.cat), (t.dog), (t.ram)
) v(x)
) v(average);
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,p.total/4.0 as av
FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
EmpName Dog Cat Goat Ram av
---------- ----------- ----------- ----------- ----------- ---------------------------------------
Abay NULL NULL 5 NULL 1.250000
Carle 2 11 NULL NULL 3.250000
Jane 6 NULL 8 3 4.250000
V2: Dynamic script:
CREATE TABLE #t
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
INSERT #t ( EmpName, Qty, Item )VALUES('Abay',100,'abc')
DECLARE #cols VARCHAR(max),#sql VARCHAR(MAX),#cnt INT
SELECT #cols=ISNULL(#cols+',[','[')+Item+']',#cnt=ISNULL(#cnt+1,1) FROM #t GROUP BY Item
PRINT #cols
PRINT #cnt
SET #sql='SELECT EmpName, '+#cols+',p.total*1.0/'+LTRIM(#cnt)+' as av'+CHAR(13)
+' FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b'+CHAR(13)
+' PIVOT(SUM(Qty) FOR Item IN('+#cols+')) AS p'
EXEC(#sql)
EmpName abc Cat Dog Goat Ram av
---------- ----------- ----------- ----------- ----------- ----------- ---------------------------------------
Carle NULL 11 2 NULL NULL 2.600000
Jane NULL NULL 6 8 3 3.400000
Abay 100 NULL NULL 5 NULL 21.000000
Avoid NULL from your pivot sentence and compute AVG.
;with ct as
(
SELECT EmpName
, ISnull([Dog],0) Dog
, ISnull([Cat],0) Cat
, ISnull([Goat],0) Goat
, ISnull([Ram],0) Ram
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
)
select empname, avg(dog) dog, avg(cat) cat, avg(goat) goat, avg(ram) ram
from ct
group by empname;
+---------+-----+-----+------+-----+
| empname | dog | cat | goat | ram |
+---------+-----+-----+------+-----+
| Abay | 0 | 0 | 5 | 0 |
+---------+-----+-----+------+-----+
| Carle | 2 | 11 | 0 | 0 |
+---------+-----+-----+------+-----+
| Jane | 6 | 0 | 8 | 3 |
+---------+-----+-----+------+-----+
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,(isnull(p.cat,0)+isnull(p.dog,0)+isnull(p.Goat,0)+isnull(p.Ram,0))/4.0 as average
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p

SQL Server 2008 Management Studio : convert row data into columns [duplicate]

This question already has answers here:
Transpose rows and columns with no aggregate
(2 answers)
Closed 8 years ago.
I have tried viewing other posts on the subject but all the examples I've seen are based on knowing a specific value.
Example of what I have:
Address Name Number
------- ------- -------
1234 Main Bob 555-555-5555
1234 Main Karen 444-444-4444
1990 Maple Susie 333-333-3333
1010 12th Joe 222-222-2222
1010 12th Beth 111-111-1111
1010 12th Steve 444-433-3221
Example of what I want:
Address Contact1 Contact2 Contact3
------- ------- -------- --------
1234 Main Bob:555-555-5555 Karen:444-444-4444 NULL
1990 Maple Susie:333-333-3333 NULL NULL
1010 12th Joe: 222-222-2222 Beth 111-111-1111 Steve 444-433-3221
There are thousands of rows so I can't CASE and.. I'm a more than a little lost here.
Any suggestions?
I think you can use the following query. This assumes you have maximum three contacts
;WITH orderedAddress(Address,Name,Number,Sort)
AS
(
SELECT Address,Name,Number,ROW_NUMBER() OVER(PARTITION BY Address ORDER BY Name) AS num
FROM Addresses
)
SELECT main.Address,Contact1.Name + ':' + Contact1.Number AS Contact1 ,
Contact2.Name + ':' + ISNULL(Contact2.Number,'') AS Contact2 ,
Contact3.Name + ':' + ISNULL(Contact3.Number,'') AS Contact3
FROM
(SELECT DISTINCT Address FROM Addresses) AS main
LEFT JOIN OrderedAddress Contact1 ON main.Address = Contact1.Address AND Contact1.Sort=1
LEFT JOIN OrderedAddress Contact2 ON main.Address = Contact2.Address AND Contact2.Sort=2
LEFT JOIN OrderedAddress Contact3 ON main.Address = Contact3.Address AND Contact3.Sort=3
A dynamic pivot will work but so will a dynamic cross tab. Generally speaking a cross tab will beat the pivot for performance. Here is an example of one I posted just a few days ago.
if OBJECT_ID('Something') is not null
drop table Something
create table Something
(
ID int,
Subject1 varchar(50)
)
insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
from Something
)
select ID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from Something
group by ID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute