Remove duplicate rows displayed in pivot - sql

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

Related

UNPIVOT not getting NULL values

I have the following :
SELECT Custid, Shipperid, Freight
FROM Sales.FreightTotals
UNPIVOT( Freight FOR Shipperid IN([1],[2],[3]) ) AS U;
I have the following result :
custid shipperid freight
1 1 25
1 3 100
2 2 75
My expected output after getting NULL values :
custid shipperid freight
1 1 25
1 2 NULL
1 3 100
2 1 NULL
2 2 75
2 3 NULL
Use a CTE to get NULL as 0.00 for example then replace the 0.00 by NULL using NULLIF() :
WITH C AS
(
SELECT custid,
ISNULL([1], 0.00) AS [1],
ISNULL([2], 0.00) AS [2],
ISNULL([3], 0.00) AS [3]
FROM Sales.FreightTotals
)
SELECT custid, shipperid, NULLIF(freight, 0.00) AS freight
FROM C
UNPIVOT( freight FOR shipperid IN([1],[2],[3]) ) AS U;
That is considered a feature of unpivot. Use apply instead:
SELECT ft.custid, v.shipperid, v.freight
FROM Sales.FreightTotals ft CROSS APPLY
(VALUES (1, ft.[1]), (2, ft.[2]), (3, ft.[3])) AS v(shipperid, freight);

SQL Server - Transpose Date from One Table To Another

Can't figure out how to transpose data from one table to another. Do I use a cursor?
Sample Data:
Build Part SN DateShipped
A 1 123 2017-01-01
A 2 234 2017-02-02
A 3 345 2017-03-03
B 1 987 2017-01-01
B 2 876 2017-02-02
B 3 765 2017-03-03
Desired Result:
Build Part1SN Part1Ship Part2SN Part2Ship Part3SN Part3Ship
A 123 2017-01-01 234 2017-02-02 345 2017-03-03
B 987 2017-01-01 876 2017-02-02 765 2017-03-03
Since you are mixing data types (date & int) in the Pivot, I'll give a working example of a dynamic Pivot. Date note of what we are doing within the Cross Apply.
I'm also assuming Part is sequential within build, otherwise we would need to apply/nest a Row_Number()
Example
Declare #SQL varchar(max) = '
Select *
From (
Select A.Build
,B.*
From YourTable A
Cross Apply ( values (concat(''Part'',A.Part,''SN''), concat('''',A.SN))
,(concat(''Ship'',A.Part,''Ship''),concat('''',A.DateShipped))
) B (Item,Value)
) A
Pivot (max([Value]) For [Item] in (' + Stuff((Select ','+QuoteName(concat('Part',Part,'SN'))
+','+QuoteName(concat('Ship',Part,'Ship'))
From (Select Distinct Part From YourTable ) A
Order By 1
For XML Path('')),1,1,'') + ') ) p'
Exec(#SQL)
--Print #SQL
Returns
The Generated SQL Looks Like This
Select *
From (
Select A.Build
,B.*
From YourTable A
Cross Apply ( values (concat('Part',A.Part,'SN'), concat('',A.SN))
,(concat('Ship',A.Part,'Ship'),concat('',A.DateShipped))
) B (Item,Value)
) A
Pivot (max([Value]) For [Item] in ([Part1SN],[Ship1Ship],[Part2SN],[Ship2Ship],[Part3SN],[Ship3Ship]) ) p
select Build,
max(case Part when 1 then SN end) 'Part1SN', max(case Part when 1 then DateShipped end) 'Part1Ship',
max(case Part when 2 then SN end) 'Part2SN', max(case Part when 2 then DateShipped end) 'Part2Ship',
max(case Part when 3 then SN end) 'Part3SN', max(case Part when 3 then DateShipped end) 'Part3Ship'
from TempTable
group by Build
You should try using pivot Table.
For reference follow the below link
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

Find all products were purchased along with specific item

I want to write a query to find all products that were purchased along with specific item such as coffee in my Order table, I have an order table as below:
OrderID ItemCode ItemName Price
-------------------------------------------------------------------
1000001 100 Apple 5
1000001 101 Salad 15
1000001 102 Coffee 5.5
1000002 110 Bread 2.5
1000002 120 Banana 7.5
1000003 105 Meat 115
1000003 108 Fish 75
1000004 115 Cake 3.5
1000004 102 Coffee 5.5
1000004 144 CupCake 10
So how am suppose to get the result, keeping in mind OrderID such as "1000001" is one order and so on?!
Here is one way to do it..
Select * from
(
Select *,
cofExistence = max(case when ItemName = 'Coffee' then ItemName end)
Over(Partition by OrderID)
from yourtable
) a
where cofExistence = 'Coffee'
My first thought is a self join:
select tother.itemName, count(*) as NumOrders
from t join
t tother
on t.orderid = tother.orderid and
t.itemName = 'Coffee' and
tother.itemName <> 'Coffee'
group by tother.itemName
order by count(*) desc;
For a single product, you can do the same thing using window functions:
select t.itemName, count(*) as NumOrders
from (select t.*,
max(case when itemName = 'Coffee' then 1 else 0 end) as hasCoffee
from t
) t
where t.itemName <> 'Coffee' -- probably not interested in coffee in the output
group by t.itemName
order by count(*) desc;
The self join generalizes more easily to more than one product.
Another option (just for fun) is a dynamic pivot.
Example
Declare #Fetch varchar(100) = 'Coffee'
Declare #SQL varchar(max) = '
Select *
From (
Select OrderID -- << Remove if you want a 1 line Total
,ItemName
,Value = 1
From YourTable A
) A
Pivot (Sum([Value]) For [ItemName] in (' + Stuff((Select Distinct ','+QuoteName(ItemName)
From YourTable
Where OrderID in (Select Distinct OrderID from YourTable Where ItemName =#Fetch)
Order By 1
For XML Path('')),1,1,'') + ') ) p
Where '+quotename(#Fetch)+' is not null
'
Exec(#SQL);
--Print #SQL
Returns

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 pivot with variable lines

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.