SQL pivot with variable lines - sql

I have to get a variable amount of lines into columns. I have something like:
EMP EMP_ID DIV_ID ALLOCATION
Smith, Tom 3605 11300 20.00
Smith, Tom 13605 11310 80.00
Benetar, Pat 7460 11012 25.00
Benetar, Pat 7460 11015 75.00
Walkin, Chris 13892 11012 90.00
Walkin, Chris 13892 11015 10.00
Kent, Clark 12262 10015 50.00
Kent, Clark 12262 11210 25.00
Kent, Clark 12262 11220 25.00
What I am looking for is something like:
EMP EMP_ID DIV_ID_01 DIV_01_ALOC DIV_ID_02 DIV_02_ALOC DIV_ID_03 DIV_03_ALOC
Smith, Tom 3605 11300 20.00 11310 80.00
Benetar, Pat 13605 11012 25.00 11015 75.00
Walkin, Chris 13892 11012 90.00 11015 10.00
Kent, Clark 12262 11015 50.00 11210 25.00 11220 25.00
I would like to avoid using a large amount of CASE statements. I am trying now to work with pivots, but am having a tough time with headers.

UPDATED:
After a couple of attempts, I came up with the solution below which utilizes two PIVOT functions and a GROUP BY in order to match your expected result.
Here is the code below. NOTE: This is meant for SQL Server 2005+
with testdata(Emp, EMP_ID, DIV_ID, ALLOCATION)
as
(
select 'Smith, Tom',3605, 11300,20.00
union all
select 'Smith, Tom',3605, 11310, 80.00
union all
select 'Benetar, Pat',7460, 11012,25.00
union all
select 'Benetar, Pat',7460, 11015,75.00
union all
select 'Walkin, Chris',13892, 11012, 90.00
union all
select 'Walkin, Chris', 13892, 11015, 10.00
union all
select 'Kent, Clark', 12262, 10015, 50.00
union all
select 'Kent, Clark', 12262, 11210, 25.00
union all
select 'Kent, Clark', 12262, 11220, 25.00
)
SELECT Emp
,EMP_ID
,MAX([Div1]) AS DIV_ID_01
,MAX([Alloc1]) AS DIV_01_ALOC
,MAX([Div2]) AS DIV_ID_02
,MAX([Alloc2]) AS DIV_02_ALOC
,MAX([Div3]) AS DIV_ID_03
,MAX([Alloc3]) AS DIV_03_ALOC
FROM (
SELECT *
,cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [emp_rnk]
,'Alloc' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Alloc_rnk]
,'Div' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Div_rnk]
FROM testdata td
) query
/* After both PIVOT functions are compplete, it still returns a single row for each EMP_ID.
So further aggregation is needed to 'flatten' the result. */
PIVOT (Max(Div_id) FOR [piv_Div_rnk] IN ([Div1],[Div2],[Div3])) AS pivot1
PIVOT (Max(Allocation) FOR [piv_Alloc_rnk] in([Alloc1],[Alloc2],[Alloc3])) AS pivot2
/* Since there is only one value in each of the columns created by the PIVOTS for each EMP_ID taking
the MAX() value and grouping by EMP and EMP_ID flattens the result down to the desired output. */
GROUP BY emp, emp_id
ORDER BY DIV_ID_01 DESC

Just for reference, if you try to use dynamic PIVOT here, you would end up needing a query that looks something like this.
SELECT [EMP],
[EMP_ID],
MIN(DIV_ID_1) [DIV_ID_1],
SUM(DIV_1_ALOC) [DIV_1_ALOC],
MIN(DIV_ID_2) [DIV_ID_2],
SUM(DIV_2_ALOC) [DIV_2_ALOC],
MIN(DIV_ID_3) [DIV_ID_3],
SUM(DIV_3_ALOC) [DIV_3_ALOC]
FROM (SELECT [EMP],
[EMP_ID],
[DIV_ID],
[ALLOCATION],
CONCAT('DIV_ID_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) ID_RN,
CONCAT('DIV_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]),'_ALOC') ALLOC_RN
FROM EmpTable
) t
PIVOT (
MIN([DIV_ID])
FOR ID_RN IN ([DIV_ID_1],[DIV_ID_2],[DIV_ID_3]) ) p1
PIVOT (
SUM([ALLOCATION])
FOR ALLOC_RN IN ([DIV_1_ALOC],[DIV_2_ALOC],[DIV_3_ALOC]) ) p2
GROUP BY [EMP],
[EMP_ID]
You would need to dynamically create the SELECT and also the PIVOT columns because of the double pivot.
On the other hand, if you use CASE statements, you'll only need to dynamically create the SELECT, since that query would look like
SELECT [EMP],
[EMP_ID],
MIN(CASE WHEN RN = 1 THEN [DIV_ID] END) [DIV_ID_1],
SUM(CASE WHEN RN = 1 THEN [ALLOCATION] END) [DIV_1_ALOC],
MIN(CASE WHEN RN = 2 THEN [DIV_ID] END) [DIV_ID_2],
SUM(CASE WHEN RN = 2 THEN [ALLOCATION] END) [DIV_2_ALOC],
MIN(CASE WHEN RN = 3 THEN [DIV_ID] END) [DIV_ID_3],
SUM(CASE WHEN RN = 3 THEN [ALLOCATION] END) [DIV_3_ALOC]
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP],
[EMP_ID]
Your dynamic statment would look something like
DECLARE #CaseSelect VARCHAR(MAX)
SELECT #CaseSelect = COALESCE(#CaseSelect + ',','')
+ 'MIN(CASE WHEN RN = ' + RN + ' THEN [DIV_ID] END) [DIV_ID_' + RN + '],'
+ 'SUM(CASE WHEN RN = ' + RN + ' THEN [ALLOCATION] END) [DIV_' + RN + '_ALOC]'
FROM (
SELECT DISTINCT CONVERT(VARCHAR(2),DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) RN
FROM EmpTable
) t
ORDER BY RN
DECLARE #SQL VARCHAR(MAX)
SET #SQL = '
SELECT [EMP],
[EMP_ID], '
+ #CaseSelect + '
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP], [EMP_ID]
'
EXEC(#SQL)
Replacing EmpTable with your actual table name ofcourse.

Related

How can I select an ID each month with the highest Mark

I am fairly new to SQL. My table is
id mark datetimes
------|-----|------------
1001 | 10 | 2011-12-20
1002 | 11 | 2012-01-10
1005 | 12 | 2012-01-10
1003 | 10 | 2012-01-10
1004 | 11 | 2018-10-10
1006 | 12 | 2018-10-19
1007 | 13 | 2018-03-12
1008 | 15 | 2018-03-13
I need to select an ID with the highest mark at the end of each month (Year also matters) and ID can be repeated
My desired output would be
id mark
-----|----
1001 | 10
1005 | 12
1006 | 12
1008 | 15
So far I've Only able to get the highest value in each month
Select Max(Mark)'HighestMark'
From StudentMark
Group BY Year(datetimes), Month(datetimes)
When I tried to
Select Max(Mark)'HighestMark', ID
From StudentMark
Group BY Year(datetimes), Month(datetimes), ID
I get
Id HighestMark
----------- ------------
1001 10
1002 11
1003 12
1004 10
1005 11
1006 12
1007 13
1008 15
You can try like following.
Using ROW_NUMBER()
SELECT * FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY YEAR(DATETIMES)
,MONTH(DATETIMES) ORDER BY MARK DESC) AS RN
FROM [MY_TABLE]
)T WHERE RN=1
Using WITH TIES
SELECT TOP 1 WITH TIES ID, mark AS HighestMarks
FROM [MY_TABLE]
ORDER BY ROW_NUMBER() OVER (PARTITION BY YEAR(datetimes)
,MONTH(datetimes) ORDER BY mark DESC)
Example:
WITH MY AS
(
SELECT
* FROM (VALUES
(1001 , 10 , '2011-12-20'),
(1002 , 11 , '2012-01-10'),
(1005 , 12 , '2012-01-10'),
(1003 , 10 , '2012-01-10'),
(1004 , 11 , '2018-10-10'),
(1006 , 12 , '2018-10-19'),
(1007 , 13 , '2018-03-12'),
(1008 , 15 , '2018-03-13')
) T( id , mark , datetimes)
)
SELECT ID,Mark as HighestMark FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY YEAR(DATETIMES),MONTH(DATETIMES) ORDER BY MARK DESC) AS RN
FROM MY
)T WHERE RN=1
Output:
ID HighestMark
1001 10
1005 12
1008 15
1006 12
I don't see a way of doing this in a single query. But we can easily enough use one subquery to find the final mark in the month for each student, and another to find the student with the highest final mark.
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, CONVERT(varchar(7), datetimes, 126)
ORDER BY datetimes DESC) rn
FROM StudentMark
)
SELECT ID, Mark AS HighestMark
FROM
(
SELECT *,
RANK() OVER (PARTITION BY CONVERT(varchar(7), datetimes, 126)
ORDER BY Mark DESC) rk
FROM cte
WHERE rn = 1
) t
WHERE rk = 1
ORDER BY ID;
Demo
In below query you have included ID column for Group By, because of this, it is considering all data for all ID.
Select Max(Mark)'HighestMark', ID From StudentMark Group BY Year(datetimes), Month(datetimes), ID
Remove ID column from this script and try again.
Use RANK in case there are more than 1 student having the same highest mark.
select id, mark
from
(select *,
rank() over( partition by convert(char(7), datetimes, 111) order by mark desc) seqnum
from studentMark ) t
where seqnum = 1
this should work:
select s.ID, t.Mark, t.[Month year] from Studentmark s
inner join (
Select
Max(Mark)'HighestMark'
,cast(Year(datetimes) as varchar(10)) +
cast(Month(datetimes) as varchar(10)) [month year]
From StudentMark
Group BY cast(Year(datetimes) as varchar(10))
+ cast(Month(datetimes) as varchar(10))) t on t.HighestMark = s.mark and
t.[month year] = cast(Year(s.datetimes) as varchar(10)) + cast(Month(s.datetimes) as varchar(10))
If for some reason you abhor subqueries, you can actually do this as:
select distinct
first_value(id) over (partition by year(datetimes), month(datetime) order by mark desc) as id
max(mark) over (partition by year(datetimes), month(datetime))
from StudentMark;
Or:
select top (1) with ties id, mark
from StudentMark
order by row_number() over (partition by year(datetimes), month(datetime) order by mark desc);
In this case, you can get all students in the event of ties by using rank() or dense_rank() instead of row_number().

How to split an SQL Table into half and send the other half of the rows to new columns with SQL Query?

Country Percentage
India 12%
USA 20%
Australia 15%
Qatar 10%
Output :
Country1 Percentage1 Country2 Percentage2
India 12% Australia 15%
USA 20% Qatar 10%
For example there is a table Country which has percentages, I need to divide the table in Half and show the remaining half (i.e. the remaining rows) in the new columns. I've also provided the table structure in text.
First, this type of operation should be done at the application layer and not in the database. That said, it can be an interesting exercise to see how to do this in the database.
I would use conditional aggregation or pivot. Note that SQL tables are inherently unordered. Your base table has no apparent ordering, so the values could come out in any order.
select max(case when seqnum % 2 = 0 then country end) as country_1,
max(case when seqnum % 2 = 0 then percentage end) as percentage_1,
max(case when seqnum % 2 = 1 then country end) as country_2,
max(case when seqnum % 2 = 1 then percentage end) as percentage_2
from (select c.*,
(row_number() over (order by (select null)) - 1) as seqnum
from country c
) c
group by seqnum / 2;
Try this
declare #t table
(
Country VARCHAR(20),
percentage INT
)
declare #cnt int
INSERT INTO #T
VALUES('India',12),('USA',20),('Australia',15),('Quatar',12)
select #cnt = count(1)+1 from #t
;with cte
as
(
select
SeqNo = row_number() over(order by Country),
Country,
percentage
from #t
)
select
*
from cte c1
left join cte c2
on c1.seqno = (c2.SeqNo-#cnt/2)
and c2.SeqNo >= (#cnt/2)
where c1.SeqNo <= (#cnt/2)
My variant
SELECT 'A' Country,1 Percentage INTO #Country
UNION ALL SELECT 'B' Country,2 Percentage
UNION ALL SELECT 'C' Country,3 Percentage
UNION ALL SELECT 'D' Country,4 Percentage
UNION ALL SELECT 'E' Country,5 Percentage
;WITH numCTE AS(
SELECT
*,
ROW_NUMBER()OVER(ORDER BY Country) RowNum,
COUNT(*)OVER() CountOfCountry
FROM #Country
),
set1CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum<=CEILING(CountOfCountry/2.)
),
set2CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum>CEILING(CountOfCountry/2.)
)
SELECT
s1.Country,s1.Percentage,
s2.Country,s2.Percentage
FROM set1CTE s1
LEFT JOIN set2CTE s2 ON s1.RowNum=s2.RowNum
DROP TABLE #Country
I just wanted to try something. I have used the function OFFSET. It does the requirement i think for your sample data, but dont know if its bulletproof all the way:
SQL Code
declare #myt table (country nvarchar(50),percentage int)
insert into #myt
values
('India' ,12),
('USA' ,20),
('Australia' ,15),
('Qatar' ,10),
('Denmark',10)
DECLARE #TotalRows int
SET #TotalRows = (select CEILING(count(*) / 2.) from #myt);
WITH dataset1 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET 0 rows FETCH FIRST #TotalRows ROWS ONLY
) z
)
,dataset2 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET #TotalRows rows FETCH NEXT #TotalRows ROWS ONLY
) z
)
SELECT * FROM dataset1 a LEFT JOIN dataset2 b ON a.rn = b.rn
Result
Assuming you want descending alphabetic country names, but the left column is determined by where India is located in the result:
with CoutryCTE as (
select c.*
, row_number() over (order by country)-1 as rn
from country c
)
, Col as (
select rn % 2 as num from CoutryCTE
where Country = 'India'
)
select max(case when rn % 2 = Col.num then country end) as country_1
, max(case when rn % 2 = Col.num then percentage end) as percentage_1
, max(case when rn % 2 <> Col.num then country end) as country_2
, max(case when rn % 2 <> Col.num then percentage end) as percentage_2
from CoutryCTE
cross join Col
group by rn / 2
;
SQLFiddle Demo
| country_1 | percentage_1 | country_2 | percentage_2 |
|-----------|--------------|-----------|--------------|
| India | 12% | Australia | 15% |
| USA | 20% | Qatar | 10% |
nb: this is extremely similar to an earlier answer by Gordon Linoff

Select Max value For Each Entity

In Microsoft SQL Server, I have a table with columns EmployeeID, Category and Amount. How can I show just the Category with the highest Amount for each Employee?
Data Example:
EmployeeID Category Amount
11111 Vacation 4
11111 Personal 2
11111 Holiday 3
22222 Vacation 1
22222 Personal 3
22222 Holiday 2
33333 Personal 5
33333 Holiday 1
33333 Vacation 3
33333 Unspecified 3
Results:
EmployeeID Category Amount
11111 Vacation 4
22222 Personal 3
33333 Personal 5
Another option is the WITH TIES clause
Example
Select Top 1 with Ties *
From YourTable
Order By Row_Number() over (Partition By EmployeeID Order By Amount Desc)
Returns
Or using Row_Number() and a CTE
;with cte as (
Select *
,RN = Row_Number() over (Partition By EmployeeID Order By Amount Desc)
From YourTable
)
Select EmployeeID
,Category
,Amount
From cte where RN=1
Here are a few different options. Any of them can perform better than the other depending available indexes, so all three are worth testing in your environment.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
EmployeeID INT NOT NULL,
Category VARCHAR(20) NOT NULL,
Amount MONEY NOT NULL
);
INSERT #TestData (EmployeeID, Category, Amount) VALUES
(11111, 'Vacation ', 4),
(11111, 'Personal ', 2),
(11111, 'Holiday ', 3),
(22222, 'Vacation ', 1),
(22222, 'Personal ', 3),
(22222, 'Holiday ', 2),
(33333, 'Personal ', 5),
(33333, 'Holiday ', 1),
(33333, 'Vacation ', 3),
(33333, 'Unspecified', 3);
-- add a covering index to improve performance of the various options
CREATE NONCLUSTERED INDEX ix_TestData ON #TestData (EmployeeID, Amount DESC) INCLUDE (Category);
------------------------------------------
IF OBJECT_ID('tempdb..#Employee', 'U') IS NOT NULL
DROP TABLE #Employee;
CREATE TABLE #Employee (
EmployeeID INT NOT NULL
);
INSERT #Employee (EmployeeID) VALUES
(11111), (22222), (33333);
-- SELECT * FROM #TestData td;
--============================================================
--============================================================
SELECT TOP 1 WITH TIES
td.EmployeeID, td.Category, td.Amount
FROM
#TestData td
ORDER BY
ROW_NUMBER() OVER (PARTITION BY td.EmployeeID ORDER BY td.Amount DESC);
--============================================================
SELECT
tdt.EmployeeID, tdt.Category, tdt.Amount
FROM
#Employee e
CROSS APPLY (
SELECT TOP 1
td.EmployeeID, td.Category, td.Amount
FROM
#TestData td
WHERE
e.EmployeeID = td.EmployeeID
ORDER BY
td.Amount DESC
) tdt;
--============================================================
WITH
cte_AddRN AS (
SELECT
td.EmployeeID, td.Category, td.Amount,
RN = ROW_NUMBER() OVER (PARTITION BY td.EmployeeID ORDER BY td.Amount DESC)
FROM
#TestData td
)
SELECT
ar.EmployeeID, ar.Category, ar.Amount
FROM
cte_AddRN ar
WHERE
ar.RN = 1;
All 3 produce the same results...
EmployeeID Category Amount
----------- -------------------- ---------------------
11111 Vacation 4.00
22222 Personal 3.00
33333 Personal 5.00
EmployeeID Category Amount
----------- -------------------- ---------------------
11111 Vacation 4.00
22222 Personal 3.00
33333 Personal 5.00
EmployeeID Category Amount
----------- -------------------- ---------------------
11111 Vacation 4.00
22222 Personal 3.00
33333 Personal 5.00
Use EXISTS/NOT EXISTS:
select *
from Employees
where not exists
(select * from Employees as E
where E.EmployeeID=Employees.EmployeeID -- join condition
and E.Amount>Employees.Amount) -- filter condition
SELECT EmployeeID, Category, Amount
FROM (select *, ROW_NUMBER() OVER (PARTITION BY EmployeeID
ORDER BY EmployeeID as rn ,Amount desc)
FROM getmaxdata) x WHERE x.rn =1

Display multiple rows and column values into a single row, multiple column values

I have to show multiple incomes, type of income and employer name values for a single individual in a single row. So, if 'A' has three different incomes from three different sources,
id | Name | Employer | IncomeType | Amount
123 | XYZ | ABC.Inc | EarningsformJob | $200.00
123 | XYZ | Self | Self Employment | $300.00
123 | XYZ. | ChildSupport| Support | $500.00
I need to show them as
id | Name | Employer1 | Incometype1| Amount1 | Employer2 | incometype2 | Amount2| Employer3 | Incometype3| Amount3.....
123 |XYZ | ABC.Inc |EarningsformJob | $200.00|Self | Self Employment | $300.00|ChildSupport| Support | $500.00.....
I need both 'fixed number of columns' (where we know how many times employer, incometype and amount colums are going to repeat)logic and 'dynamic display of columns' ( unknown number of times these columns are going to repeat)
Thanks.
Since you are using SQL Server there are several ways that you can transpose the rows of data into columns.
Aggregate Function / CASE: You can use an aggregate function with a CASE expression along with row_number(). This version would require that you have a known number of values to become columns:
select id,
name,
max(case when rn = 1 then employer end) employer1,
max(case when rn = 1 then IncomeType end) IncomeType1,
max(case when rn = 1 then Amount end) Amount1,
max(case when rn = 2 then employer end) employer2,
max(case when rn = 2 then IncomeType end) IncomeType2,
max(case when rn = 2 then Amount end) Amount2,
max(case when rn = 3 then employer end) employer3,
max(case when rn = 3 then IncomeType end) IncomeType3,
max(case when rn = 3 then Amount end) Amount3
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) src
group by id, name;
See SQL Fiddle with Demo.
PIVOT/UNPIVOT: You could use the UNPIVOT and PIVOT functions to get the result. The UNPIVOT converts your multiple columns of Employer, IncomeType and Amount into multiples rows before applying the pivot. You did not specific what version of SQL Server, assuming you have a known number of values then you could use the following in SQL Server 2005+ which uses CROSS APPLY with UNION ALL to unpivot:
select id, name,
employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select 'employer', employer union all
select 'incometype', incometype union all
select 'amount', cast(amount as varchar(50))
) c (col, value)
) src
pivot
(
max(value)
for col in (employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3)
) piv;
See SQL Fiddle with Demo.
Dynamic Version: Lastly, if you have an unknown number of values then you will need to use dynamic SQL to generate the result.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by id order by employer) rn
from yourtable
) d
cross apply
(
select 'employer', 1 union all
select 'incometype', 2 union all
select 'amount', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select ''employer'', employer union all
select ''incometype'', incometype union all
select ''amount'', cast(amount as varchar(50))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions give a result:
| ID | NAME | EMPLOYER1 | INCOMETYPE1 | AMOUNT1 | EMPLOYER2 | INCOMETYPE2 | AMOUNT2 | EMPLOYER3 | INCOMETYPE3 | AMOUNT3 |
-------------------------------------------------------------------------------------------------------------------------------------
| 123 | XYZ | ABC.Inc | EarningsformJob | 200 | ChildSupport | Support | 500 | Self | Self Employment | 300 |

Remove duplicate rows displayed in pivot

Basically, I have created a pivot query wherein it will display the total_work_hours per each costcode for each employee.
This is my desired output:
employeeno 8322.170 10184.2648 8321.169 10184.2649 <- costcodes
--------------------------------------------------------------------------
080418 10.00 1.50 NULL NULL
080441 6.50 NULL 1.00 3.00
but this is the result of my query:
employeeno 8322.170 10184.2648 8321.169 10184.2649 <- costcodes
--------------------------------------------------------------------------
080418 10.00 NULL NULL NULL
080418 NULL 1.50 NULL NULL
080441 NULL NULL 1.00 NULL
080441 6.50 NULL NULL NULL
080441 NULL NULL NULL 3.00
This is the result of my inner query:
employeeno costcoding hour_per_costcode
--------------------------------------------------
PH080418 8322.170 10.00
PH080418 10184.2648 1.50
PH080441 8321.169 1.00
PH080441 8322.170 6.50
PH080441 10184.2649 3.00
This is my query:
WITH PivotData AS
(SELECT wa.id,wa.sitecode, wa.companycode, wa.startdate, wa.enddate,
wa.description, wa.ratetypeid, wa_details.employeeno,
CAST(wa_details.costcode AS NVARCHAR(MAX)) + '.' +
CAST(wa_details.subcostcode AS NVARCHAR(MAX)) costcoding ,
wa_details.subcostcode, wa_details.hrswork [hour_per_costcode],
view_ttl_hours.ttl_work_hrs
FROM workallocation wa
INNER JOIN workallocation_details wa_details
ON wa.id = wa_details.workallocationid
INNER JOIN
(SELECT SUM(ttl_work_hrs) ttl_work_hrs, employee_id
FROM vwu_SUM_TIMESHEET_DAILY
WHERE TKSDATE BETWEEN '02-09-2012' AND '02-09-2012'
GROUP BY employee_id
) view_ttl_hours
ON wa_details.employeeno=view_ttl_hours.employee_id WHERE wa.id=99
)
SELECT employeeno, [8322.170], [10184.2648], [8321.169], [10184.2649]
FROM PivotData
PIVOT (MAX([hour_per_costcode])
FOR costcoding
IN ([8322.170], [10184.2648], [8321.169], [10184.2649])
) AS PivotResult
ORDER BY employeeno;
My question is how will I be able to unite the value for my multiple rows so that my desire output will be achieve? I also try the cross tab query but the result is also the same.
I need some guidance on how to solve this; can you help?
Remove the columns you don't need from the CTE. Something like this should work for you.
WITH PivotData AS
(SELECT wa_details.employeeno,
CAST(wa_details.costcode AS NVARCHAR(MAX)) + '.' +
CAST(wa_details.subcostcode AS NVARCHAR(MAX)) costcoding ,
wa_details.subcostcode, wa_details.hrswork [hour_per_costcode]
FROM workallocation wa
INNER JOIN workallocation_details wa_details
ON wa.id = wa_details.workallocationid
INNER JOIN
(SELECT SUM(ttl_work_hrs) ttl_work_hrs, employee_id
FROM vwu_SUM_TIMESHEET_DAILY
WHERE TKSDATE BETWEEN '02-09-2012' AND '02-09-2012'
GROUP BY employee_id
) view_ttl_hours
ON wa_details.employeeno=view_ttl_hours.employee_id WHERE wa.id=99
)
SELECT employeeno, [8322.170], [10184.2648], [8321.169], [10184.2649]
FROM PivotData
PIVOT (MAX([hour_per_costcode])
FOR costcoding
IN ([8322.170], [10184.2648], [8321.169], [10184.2649])
) AS PivotResult
ORDER BY employeeno;
Above query with your data:
WITH PivotData AS
(select 'PH080418' as employeeno, 8322.170 as costcoding, 10.00 as hour_per_costcode union all
select 'PH080418', 10184.2648, 1.50 union all
select 'PH080441', 8321.169, 1.00 union all
select 'PH080441', 8322.170, 6.50 union all
select 'PH080441', 10184.2649, 3.00
)
SELECT employeeno, [8322.170], [10184.2648], [8321.169], [10184.2649]
FROM PivotData
PIVOT (MAX([hour_per_costcode])
FOR costcoding
IN ([8322.170], [10184.2648], [8321.169], [10184.2649])
) AS PivotResult
ORDER BY employeeno;
Result:
employeeno 8322.170 10184.2648 8321.169 10184.2649
---------- ---------- ------------ ---------- -----------
PH080418 10.00 1.50 NULL NULL
PH080441 6.50 NULL 1.00 3.00