TSQL help my feeble mind grasp Pivot/Unpivot on two columns
-
05-10-2020 - |
Question
I've been trying to wrap my feeble mind around this one for some time, but I am having difficulty. I have looked at a few other examples like this SE.DBA answer, but I am having difficulty applying it to my exact situation. I do not care if the solution uses PIVOT/UNPIVOT or Cross Apply.
I am using GROUP BY because there are maybe 15-45 records per payroll, and I want all those records summed up per payroll. I am using SQLServer 2016.
The DDL and data below show the source data and the PIVOT query that I currently have
--Source data table
CREATE TABLE #EmployeeTaxes
(
Employee int
,Payroll int
,FIT DECIMAL(19,2)
,Employee_FICA DECIMAL(19,2)
,Employer_FICA DECIMAL(19,2)
,Employee_MEDI DECIMAL(19,2)
,Employer_MEDI DECIMAL(19,2)
,FIT_Gross DECIMAL(19,2)
,Employee_FICA_Gross DECIMAL(19,2)
,Employer_FICA_Gross DECIMAL(19,2)
,Employee_MEDI_Gross DECIMAL(19,2)
,Employer_MEDI_Gross DECIMAL(19,2)
)
--Data in source table
INSERT INTO #EmployeeTaxes
VALUES (1, 1, 100.00, 150.00, 50.00, 200.00, 200.00, 20000.00, 20000.00, 20000.00, 20000.00, 20000.00)
,(2, 1, 100.00, 150.00, 50.00, 200.00, 200.00, 20000.00, 20000.00, 20000.00, 20000.00, 20000.00)
,(3, 1, 100.00, 150.00, 50.00, 200.00, 200.00, 20000.00, 20000.00, 20000.00, 20000.00, 20000.00)
,(1, 2, 200.00, 250.00, 150.00, 300.00, 300.00, 25000.00, 25000.00, 25000.00, 25000.00, 25000.00)
,(2, 2, 200.00, 250.00, 150.00, 300.00, 300.00, 25000.00, 25000.00, 25000.00, 25000.00, 25000.00)
,(3, 2, 200.00, 250.00, 150.00, 300.00, 300.00, 25000.00, 25000.00, 25000.00, 25000.00, 25000.00)
,(1, 3, 300.00, 350.00, 250.00, 400.00, 400.00, 30000.00, 30000.00, 30000.00, 30000.00, 30000.00)
,(2, 3, 300.00, 350.00, 250.00, 400.00, 400.00, 30000.00, 30000.00, 30000.00, 30000.00, 30000.00)
,(3, 3, 300.00, 350.00, 250.00, 400.00, 400.00, 30000.00, 30000.00, 30000.00, 30000.00, 30000.00)
--Basic data layout as it currently is
SELECT PAYROLL
,sum(fit) AS SumFIT
,sum(fit_gross) AS SumFIT_Gross
,sum(employee_fica) AS Sum_EE_FICA
,sum(employee_fica_gross) AS Sum_EE_FICA_Gross
,sum(employer_fica) AS Sum_ER_FICA
,sum(employer_fica_gross) AS Sum_ER_FICA_Gross
,sum(employee_medi) AS Sum_EE_Medi
,sum(employee_medi_gross) AS Sum_EE_Medi_Gross
,sum(employer_medi) AS Sum_ER_Medi
,sum(employer_medi_gross) AS Sum_ER_Medi_Gross
FROM #EmployeeTaxes
GROUP BY Payroll
--The query as I have it now, missing the Gross column
SELECT PAYROLL
,[Taxes]
,[Tax]
FROM
(
SELECT PAYROLL
,sum(fit) AS SumFIT
,sum(fit_gross) AS SumFIT_Gross
,sum(employee_fica) AS Sum_EE_FICA
,sum(employee_fica_gross) AS Sum_EE_FICA_Gross
,sum(employer_fica) AS Sum_ER_FICA
,sum(employer_fica_gross) AS Sum_ER_FICA_Gross
,sum(employee_medi) AS Sum_EE_Medi
,sum(employee_medi_gross) AS Sum_EE_Medi_Gross
,sum(employer_medi) AS Sum_ER_Medi
,sum(employer_medi_gross) AS Sum_ER_Medi_Gross
FROM #EmployeeTaxes
GROUP BY Payroll
) CK
UNPIVOT
(
Tax FOR Taxes IN (SumFIT
,Sum_EE_FICA
,Sum_ER_FICA
,Sum_EE_Medi
,Sum_ER_Medi)
) AS Unpvt
ORDER BY PAYROLL
DROP TABLE #EmployeeTaxes
What I want to do is have the columns that are marked with "_gross" on the end to be in their own separate column as shown by the fake data below
--Fake table that holds structure of desired output
CREATE TABLE #FakeDesiredOutput
(
Payroll int
,Witholding varchar(100)
,Tax DECIMAL(19,2)
,Gross DECIMAL(19,2)
)
INSERT INTO #FakeDesiredOutput
VALUES (1,'FIT', 300.00, 60000.00)
,(1,'Employee_FICA', 450.00, 60000.00)
,(1,'Employer_FICA', 150.00, 60000.00)
,(1,'Employee_MEDI', 600.00, 60000.00)
,(1,'Employer_MEDI', 600.00, 60000.00)
,(2,'FIT', 600.00, 75000.00)
,(2,'Employee_FICA', 750.00, 75000.00)
,(2,'Employer_FICA', 450.00, 75000.00)
,(2,'Employee_MEDI', 900.00, 75000.00)
,(2,'Employer_MEDI', 900.00, 75000.00)
,(3,'FIT', 900.00, 90000.00)
,(3,'Employee_FICA', 1050.00, 90000.00)
,(3,'Employer_FICA', 750.00, 90000.00)
,(3,'Employee_MEDI', 1200.00, 90000.00)
,(3,'Employer_MEDI', 1200.00, 90000.00)
SELECT * FROM #FakeDesiredOutput
DROP TABLE #FakeDesiredOutput
Solution
The CROSS APPLY
way of doing this would be:
SELECT et.Payroll
, v.Item
, Tax = SUM(v.TaxValue)
, Gross = SUM(v.GrossValue)
FROM #EmployeeTaxes et
CROSS APPLY (
VALUES ('FIT', fit, fit_Gross, 1)
, ('Employee_FICA', Employee_FICA, Employee_FICA_Gross, 2)
, ('Employer_FICA', Employer_FICA, Employer_FICA_Gross, 3)
, ('Employee_MEDI', Employee_MEDI, Employee_MEDI_Gross, 4)
, ('Employer_MEDI', Employer_MEDI, Employer_MEDI_Gross, 5)
)v(Item, TaxValue, GrossValue, OrderByNum)
GROUP BY et.Payroll
, v.Item
, v.OrderByNum
ORDER BY et.Payroll
, v.OrderByNum;
The results:
+---------+---------------+---------+----------+ | Payroll | Item | Tax | Gross | +---------+---------------+---------+----------+ | 1 | FIT | 300.00 | 60000.00 | | 1 | Employee_FICA | 450.00 | 60000.00 | | 1 | Employer_FICA | 150.00 | 60000.00 | | 1 | Employee_MEDI | 600.00 | 60000.00 | | 1 | Employer_MEDI | 600.00 | 60000.00 | | 2 | FIT | 600.00 | 75000.00 | | 2 | Employee_FICA | 750.00 | 75000.00 | | 2 | Employer_FICA | 450.00 | 75000.00 | | 2 | Employee_MEDI | 900.00 | 75000.00 | | 2 | Employer_MEDI | 900.00 | 75000.00 | | 3 | FIT | 900.00 | 90000.00 | | 3 | Employee_FICA | 1050.00 | 90000.00 | | 3 | Employer_FICA | 750.00 | 90000.00 | | 3 | Employee_MEDI | 1200.00 | 90000.00 | | 3 | Employer_MEDI | 1200.00 | 90000.00 | +---------+---------------+---------+----------+
The CROSS APPLY
clause has a fourth column, OrderByNum
simply to allow the results to reflect your desired sort order, where FIT
comes before the other entries.
OTHER TIPS
Based on your update, I believe the following should work. The CTE allows me to select from your tax records once for each kind of tax.
WITH DesiredRows (TaxNumber, TaxName) AS
(SELECT 1, 'Fit' UNION ALL
SELECT 2, 'Employee Fica' UNION ALL
SELECT 3, 'Employer Fica' UNION ALL
SELECT 4, 'Employee Medi' UNION ALL
SELECT 5, 'Employer Medi' )
SELECT etx.Payroll,
dr.TaxNumber, dr.TaxName,
SUM(CASE dr.TaxNumber
WHEN 1 THEN etx.fit
WHEN 2 THEN etx.employee_fica
WHEN 3 THEN etx.Employer_FICA
WHEN 4 THEN etx.Employee_MEDI
WHEN 5 THEN etx.Employer_MEDI
ELSE 0 END) AS Tax,
SUM(CASE dr.TaxNumber
WHEN 1 THEN etx.FIT_Gross
WHEN 2 THEN etx.Employee_FICA_Gross
WHEN 3 THEN etx.Employer_FICA_Gross
WHEN 4 THEN etx.Employee_MEDI_Gross
WHEN 5 THEN etx.Employer_MEDI_Gross
ELSE 0 END) AS GrossAmt
FROM dbo.EmployeeTaxes etx
CROSS JOIN DesiredRows dr
GROUP BY etx.Payroll, dr.TaxNumber, dr.TaxName
ORDER BY etx.Payroll, dr.TaxNumber, dr.TaxName