Sql-server – TSQL help the feeble mind grasp Pivot/Unpivot on two columns

cross-applypivotsql servert-sqlunpivot

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

Best Answer

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.