I have a table that does not have a primary key, but has an ID field that could be used as a primary key. I would like to transform this table into something useable to track inserts, updates, and deletes. Just adding a Identity column is not a viable solution because the table gets recreated every evening.
The table looks something like:
ID Specialty
-- ---------
01 Fam Med
02 Fam Med
02 Int Med
03 Surgery
03 Thor Sur
03 Card Sur
04 Undersea
I would like to transform it into something like the following and use ID as the primary key:
ID Specialty1 Specialty2 Specialty3
-- ---------- ---------- ----------
01 Fam Med
02 Fam Med Int Med
03 Surgery Thor Sur Card Sur
04 Undersea
I was leaning toward using the SQL pivot operator, but I am not familiar enough with pivots and I am not sure it is appropriate in this case.
Thank you in advance for your input.
The SQL you would need to do the PIVOT is:
SELECT ID,
Specialty1,
Specialty2,
Specialty3
FROM ( SELECT ID,
Specialty,
SpecialtyNum = 'Specialty' +
CAST(ROW_NUMBER() OVER(PARTITION BY ID
ORDER BY Specialty) AS VARCHAR(10))
FROM T
) AS t
PIVOT
( MAX(Specialty)
FOR SpecialtyNum IN ([Specialty1], [Specialty2], [Specialty3])
) AS pvt;
Example on SQL Fiddle
The key is adding a further column that you can pivot on, so the result of the subquery:
SELECT ID,
Specialty,
SpecialtyNum = 'Specialty' +
CAST(ROW_NUMBER() OVER(PARTITION BY ID
ORDER BY Specialty) AS VARCHAR(10))
FROM T;
Gives you:
ID Specialty SpecialtyNum
-- --------- -------------
01 Fam Med Specialty1
02 Fam Med Specialty1
02 Int Med Specialty2
03 Surgery Specialty1
03 Thor Sur Specialty2
03 Card Sur Specialty3
04 Undersea Specialty1
Then you can pivot on the SpecialtyNum column. Since each value of specialtyNum is unique to an ID, there is no data loss through aggregation.
But with an unknown number of specialities you would need to generate this dynamically:
DECLARE #Cols NVARCHAR(MAX) =
STUFF(( SELECT DISTINCT ',[Specialty' + CAST(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Specialty) AS VARCHAR(10)) + '] '
FROM T
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #SQL NVARCHAR(MAX) =
'SELECT ID, ' + #Cols +
'FROM ( SELECT ID,
Specialty,
SpecialtyNum = ''Specialty'' +
CAST(ROW_NUMBER() OVER(PARTITION BY ID
ORDER BY Specialty) AS VARCHAR(10))
FROM T
) AS t
PIVOT
( MAX(Specialty)
FOR SpecialtyNum IN (' + #Cols + ')
) AS pvt;';
EXECUTE sp_executesql #SQL;
Example on SQL Fiddle
Related
The sql table is as follows,
Name Salary NoticePeriod CTC
Jack 1520 15 123
Bruce 1423 35 165
and it contains about 1000 rows.
I need to do a transpose of this table ,such that the expected output is
Fields Jack Bruce Mike ..... Sam
Salary 1520 1423 235 .. 561
NoticePeriod 15 35 23 253
CTC 123 165 45 ... 125
I tried using Pivot and Unpivot function in Sql Server 2008 . But Since the Name record is large , Pivot query doesnt helps.
My sql attempt is follows,
SELECT *
FROM (
SELECT NAME,
Salary,
NoticePeriod,
CTC
FROM CTCTABLE WITH (NOLOCK)
) AS queryTable
UNPIVOT(Value FOR NAME IN (NAME, Salary, NoticePeriod, CTC)) AS unpv
PIvot(max(Value) FOR NAME IN (Salary, NoticePeriod, CTC)) pv
In your case, you should unpivot the columns Salary, NoticePeriod, CTC into rows, then PIVOT:
WITH Unpivoted
AS
(
SELECT Name, Fields, SalaryValue
FROM salaries AS s
UNPIVOT
(
SalaryValue
FOR Fields IN(Salary, NoticePeriod, CTC)
) AS u
)
SELECT Fields, jack,bruce
FROM Unpivoted AS u
PIVOT
(
MAX(SalaryValue)
FOR Name IN(Jack, Bruce)
) AS p;
The UNPIVOT will transform the columns Salary, NoticePeriod, CTC into values:
Then the pivot will pivot the salary values for each field value and transform the names into columns.
And of course you have to do it dynamically instead of writing list of names like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #colnames AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(name)
FROM salaries
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'WITH Unpivoted
AS
(
SELECT Name, Fields, SalaryValue
FROM salaries AS s
UNPIVOT
(
SalaryValue
FOR Fields IN(Salary, NoticePeriod, CTC)
) AS u
)
SELECT Fields, ' + #cols + '
FROM Unpivoted AS u
PIVOT
(
MAX(SalaryValue)
FOR Name IN(' + #cols + ')' +
') p';
execute(#query);
This will give you:
Live Demo (Thanks to #lad2025 for it)
I am using MS-SQL 2008 and have a dynamic SQL query in a Stored Proc. that generates results using PIVOT. The last columns of the pivot table can change both in the number of columns and the names based on user provided parameters.
The user is able to select/provide the number of months prior to the current month that they want to see results for.
Example Results: (User selects 2 months)
Product DataDescript 2015-06 2015-05
------- -------------------- ------- -------
Apples AVG_Days_To_Ripen 15 18
Peaches AVG_Days_To_Shipping 03 08
Example Results: (User selects 3 months)
Product DataDescript 2015-06 2015-05 2015-04
------- -------------------- ------- ------- -------
Apples AVG_Days_To_Ripen 15 18 20
Peaches AVG_Days_To_Shipping 03 08 07
I know how to return HTML (XML) results from a static dataset, but I am having a hard time thinking through how I would do it with a data set that is dynamic like this.
Typically When I'm doing An HTML table based off of a static set of columns
I usually do something similar to the following:
SET #vMyHTMLTABLE = (
'< table>
< TR>
< TH> WIDTH ="10%">Column1_Header < /TH>
< TH> WIDTH ="10%">Column2_Header < /TH>
< TH> WIDTH ="10%">Column3_HEader < /TH>
< /TR>'
+ CAST((SELECT
CASE WHEN (ROW_NUMBER() OVER(order by emt.Email_RUID ASC ) %2) = 0 THEN 'evn' ELSE 'odd' END as [td/#class],'Col1ID' as [td/#id] ,(emt.Column1) as 'td','',
CASE WHEN (ROW_NUMBER() OVER(order by emt.Email_RUID ASC ) %2) = 0 THEN 'evn' ELSE 'odd' END as [td/#class],'Col2ID' as [td/#id] ,(emt.Column2) as 'td','',
CASE WHEN (ROW_NUMBER() OVER(order by emt.Email_RUID ASC ) %2) = 0 THEN 'evn' ELSE 'odd' END as [td/#class],'Col3ID' as [td/#id] ,(emt.Column3) as 'td'
FROM #t_MyEmailTable emt
WHERE
ORDER BY emt.Email_RUID ASC
FOR XML PATH('tr'), ELEMENTS ) AS nvarchar(max))
+ '< /table>'
)
To Generate my Dynamic Pivot table I'm doing something like this:
DECLARE #DynamicPivotQuery NVARCHAR(MAX)
DECLARE #ColumnName NVARCHAR(MAX)
--Get my list of dynamic pivot Columns
SELECT #ColumnName= ISNULL(#ColumnName + ',','') + QUOTENAME(LEFT(CAST(MyMonths.[YoMo] AS DATE),7))
FROM (SELECT DISTINCT fr.[YoMo] FROM MyDB.dbo.FinalResults fr) AS MyMonths
SET #DynamicPivotQuery = (
N'SELECT
ROW_NUMBER() OVER(order by DataAsOf ASC ) AS [RowNum]
,CASE
WHEN (ROW_NUMBER() OVER(order by DataAsOf ASC ) %2) = 0 THEN ''evn''
ELSE ''odd''
END AS [class]
,LEFT(DataPoint,3) AS [Act]
,RIGHT(DataPoint,3) AS [Pct]
, *
FROM (
select distinct
fr.[Product] AS [Product]
, fr.[DataDescript] AS [DataDescript]
, LEFT(CAST(fr.[YoMo] AS DATE),7) AS [YoMo]
, fr.[DataValue] AS [DataValue]
FROM MyDB.dbo.FinalResults fr
join MyDB.dbo.FinalGroups g
ON fr.GID = g.GID
) AS D
PIVOT(
MAX(D.DataValue) FOR D.YoMo IN(' + #ColumnName + ')
) AS P
order by Product, DataDescript
') -- END: SET #DynamicPivotQuery
EXEC tempdb.dbo.sp_executesql #DynamicPivotQuery --TempDB because source DB is in SQL 2000 Compat mode.
Any thoughts or insight would be greatly appreciated!
I have a select statement which checks for a name and a score from two separated tables.
After select operation, I want second scores to be in another column for the same name.
Consider Score 69 will be next to 64 in another column.
How can I do that?
Name Score
John 64
Lisa 45
Jack 23
John 69
Requested:
Name Score Score2
John 64 69
For a dynamic number of result columns, you'll almost certainly need to resort to dynamic SQL. Here's a take on this adapted from Bluefeet's answer here
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(CAST(ScoreNum AS VARCHAR))
FROM
(
SELECT Name, Score, ROW_NUMBER() OVER (Partition By Name ORDER BY Score ASC) AS ScoreNum
FROM NameScore
) scores
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = 'SELECT Name, ' + #Cols
+ '
FROM
(
SELECT Name, Score, ROW_NUMBER() OVER (Partition By Name ORDER BY Score ASC) AS ScoreNum
FROM NameScore
) scores
PIVOT
(
MIN(Score)
FOR ScoreNum IN (' + #cols + ')
) AS p';
execute(#query);
With a sample SqlFiddle here
Noted that we had to run the query twice - once to see how many columns to create, and then again for the actual Pivot. For large amounts of data, you could DRY this up into a #Temp table #TableVar.
I have a temp table with 3 Columns Like below,
JobID JobType JobValue
12 HR Jesica
23 MANAGER Ravi
5 MANAGER Jacob
60 EMPLOYEE Kiruan
45 MANAGER Abidam
27 HR Kamsura
21 MANAGER Chio Bin
87 EMPLOYEE Gamanya
22 HR Pradeep
56 HR Hari
67 EMPLOYEE Om
14 MANAGER Kiran
My result table should be like
JobID HR MANAGER EMPLOYEE
12
23
5
60
45
27
21
87
22
56
67
14
Jobvalue column values should come into result set.
I have tried like below.
Created a temp table with distict Jobtype row values.
then using while loop inseted JobValue column values into that table.
But it looks very dirty procedure.
Can any one give me a good suggesion to complete this.
Thanks,
You should be able to use the PIVOT function to get the result:
select jobid, hr, manager, employee
from yourtable
pivot
(
max(jobvalue)
for jobtype in (hr, manager, employee)
) piv;
See SQL Fiddle with Demo.
If you want to list the jobvalue under each jobType without showing the jobid, then you could use:
select hr, manager, employee
from
(
select jobtype, jobvalue,
row_number() over(partition by jobtype order by jobid) rn
from yourtable
) d
pivot
(
max(jobvalue)
for jobtype in (hr, manager, employee)
) piv;
See SQL Fiddle with Demo
Try this
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(jobtype)
from yourtable
group by jobtype
ORDER BY jobtype
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
--SELECT #cols
set #query = 'SELECT JobID,' + #cols + ' from
(
select JobID, jobtype, jobvalue from yourtable
) x
pivot
(
MAX(jobvalue)
for jobtype in (' + #cols + ')
) p '
execute(#query)
I have 2 tables in SQL Server:
Table 1 : Department
DeptId Dept Name
------------------
1 Software Development
2 Testing
3 Customization
Table 2 : Designation
DesigId Desig Name DeptId
---------------------------
1 TL 1
2 PL 1
3 TestEngg 2
4 SE 3
I want the following output which takes department as column heading and group designation under the corresponding department column,
Software Development Testing Customization
TL TestEngg SE
PL
I tried with the below query but Im able to get only the Id's
DECLARE #deptcols AS VARCHAR(MAX);
DECLARE #querystr AS VARCHAR(MAX);
select #deptcols = STUFF((SELECT distinct ',' + QUOTENAME(Dept_Id)
FROM Designation
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #querystr = 'SELECT ' + #deptcols + ' from
(
select Desig_Name, Dept_Id,Desig_Id
from Designation
) p
pivot
(
count(Desig_Id) FOR Dept_Id in (' + #deptcols + ')
) pv '
execute(#querystr)
Your code was so very close. My suggestion when working with PIVOT especially a dynamic version is to write the static version first, then convert it to dynamic sql.
The static version, where you hard-code values is like this:
SELECT [Software Development], [Testing], [Customization]
from
(
select d.[Dept Name],
s.[Desig Name],
row_number() over(partition by s.deptid order by s.desigid) rn
from Designation s
left join department d
on s.[DeptId] = d.[DeptId]
) p
pivot
(
max([Desig Name])
FOR [Dept Name] in ([Software Development], [Testing], [Customization])
) pv
See SQL Fiddle with Demo. The static version allows you to be sure that the syntax is correct and all values, columns etc are in the right places.
Then once you have the syntax it is easy to convert to the dynamic SQL version:
DECLARE #deptcols AS VARCHAR(MAX)
DECLARE #querystr AS VARCHAR(MAX)
select #deptcols = STUFF((SELECT ',' + QUOTENAME([Dept Name])
FROM Department
GROUP BY [Dept Name], DeptId
ORDER BY DeptId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #querystr =
'SELECT ' + #deptcols + ' from
(
select d.[Dept Name],
s.[Desig Name],
row_number() over(partition by s.deptid order by s.desigid) rn
from Designation s
left join department d
on s.[DeptId] = d.[DeptId]
) p
pivot
(
max([Desig Name])
FOR [Dept Name] in (' + #deptcols + ')
) pv '
execute(#querystr)
See SQL Fiddle with Demo
Both give the result:
| SOFTWARE DEVELOPMENT | TESTING | CUSTOMIZATION |
---------------------------------------------------
| TL | TestEngg | SE |
| PL | (null) | (null) |
You will notice that I added the line row_number() over(partition by s.deptid order by s.desigid) rn to the SELECT statement. This allows you to return more than one Desig Name for each Dept Name, without this you will only return one value.
I think PIVOT keyword is what you should use here. PIVOT can be used to transform datasets such that columns become rows.
No need to construct dynamic queries
Take a look at this article: http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=PIVOTData
More info: http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx