SQL Pivot to be used in a View - sql-server

I am trying to create a view that will sum the total hours of different work types, as well as the quantity of different products used to a RefNumber.
I have successfully pivoted and summed the hours of work grouped by RefNumber, but now I am scratching my head on how to sum the total product use by Ref Number.
Sample Data
Download Excel of data
Keep in mind, on some RefNumbers, there will be multiple entries of the same ProductID. We may have had a few guys using the same product in different quantities.
Here is what I am trying to the Pivot the data too:
Pivot Data
So far, I have been able to accomplish summing the hours of each work type, and pivoting that data into a single line item grouped by the Ref Number using the following SQL code:
SELECT RefNumber,
SUM (CASE WHEN WorkType = 'Blast' THEN THours ELSE NULL END) AS TBlast,
SUM (CASE WHEN WorkType = 'Wheel' THEN THours ELSE NULL End) As TWheel,
SUM (CASE WHEN WorkType = 'Painting' THEN THours ELSE NULL END) AS TPainter,
SUM (CASE WHEN WorkType = 'Mask/Prep' THEN Thours ELSE NULL END) As TMask,
SUM (CASE WHEN WorkType = 'Demask/Touch Up' THEN Thours ELSE NULL END) As TDMask,
SUM (CASE WHEN WorkType = 'Handling: Raw' THEN THours ELSE NULL END) As TRHand,
SUM (CASE WHEN WorkType = 'Handling: Product' THEN Thours ELSE NULL END) As TPHand,
SUM (CASE WHEN WorkType = 'Wheel:Assist' THEN Thours ELSE NULL END) As TAWheel,
SUM (CASE WHEN WorkType = 'Metalizing' THEN Thours ELSE NULL END) As TMetal
FROM (
SELECT [RefNumber], [WorkType], SUM (Hours)/60 As THours
FROM [dbo].[Vw_Beta_CostLog]
GROUP BY RefNumber, WorkType
) sub
GROUP BY RefNumber
ORDER BY RefNumber
Any Ideas on how to modify this code base to Pivot the the distinct product ID's into there own column, and sum the use of those products in a second column?
Also, I want to be able to use this as a view, so I am trying to avoid dynamic pivots.
EDIT: Forgot to mention, there will be at most 4 unique products used per ref number.
RAW DATA
GUID EmpName RefNumber DateInt Hours WorkType ProductID PQty
P-3468 Gary Hahn 114204 20181008 132 Painting NULL NULL
P-3473 Gary Hahn 114204 20181009 204 Painting NULL NULL
P-3475 Gary Hahn 114204 20181009 120 Painting NULL NULL
F-31915 Jose Flores 114204 20181007 60 Handling: Raw NULL NULL
F-31941 Jose Flores 114204 20181008 30 Handling: Raw NULL NULL
F-31951 Chris Pollock 114204 20181008 30 Handling: Raw NULL NULL
F-32076 Chris Pollock 114204 20181010 120 Handling: Product NULL NULL
F-32109 Chris Pollock 114204 20181011 90 Handling: Product NULL NULL
F-32301 Daryl Underwood 114204 20181015 15 Handling: Product NULL NULL
B-6594 David Martinez 114204 20181007 150 Blast NULL NULL
B-6599 Emiliano Barrios 114204 20181008 66 Blast NULL NULL
B-6617 Jose Molina 114204 20181009 30 Blast NULL NULL
P-3468 Gary Hahn 114204 20181008 NULL Primer 11 3
P-3473 Gary Hahn 114204 20181009 NULL Intermediate 890 2
P-3475 Gary Hahn 114204 20181009 NULL Finish 134HG 2
Output I am looking for
RefNumber Blast Painting Handling: Raw Handling: Product Product1 P1Qty Product2 P2Qty Product3 P3Qty
114204 246 456 120 225 11 3 890 2 134HG 2

You can "pivot" non-numeric data by using MAX() instead of SUM() and continue to use case expressions as you are already doing.
SELECT
RefNumber
, SUM (CASE WHEN WorkType = 'Blast' THEN Hours/60 ELSE NULL END) AS TBlast
, SUM (CASE WHEN WorkType = 'Wheel' THEN Hours/60 ELSE NULL End) As TWheel
, SUM (CASE WHEN WorkType = 'Painting' THEN Hours/60 ELSE NULL END) AS TPainter
, SUM (CASE WHEN WorkType = 'Mask/Prep' THEN Hours/60 ELSE NULL END) As TMask
, SUM (CASE WHEN WorkType = 'Demask/Touch Up' THEN Hours/60 ELSE NULL END) As TDMask
, SUM (CASE WHEN WorkType = 'Handling: Raw' THEN Hours/60 ELSE NULL END) As TRHand
, SUM (CASE WHEN WorkType = 'Handling: Product' THEN Hours/60 ELSE NULL END) As TPHand
, SUM (CASE WHEN WorkType = 'Wheel:Assist' THEN Hours/60 ELSE NULL END) As TAWheel
, SUM (CASE WHEN WorkType = 'Metalizing' THEN Hours/60 ELSE NULL END) As TMetal
, MAX (CASE WHEN rn = 1 THEN WorkType END) As WorkType1
, MAX (CASE WHEN rn = 1 THEN ProductID END) As Product1
, MAX (CASE WHEN rn = 1 THEN PQty END) As Qty1
, MAX (CASE WHEN rn = 2 THEN WorkType END) As WorkType2
, MAX (CASE WHEN rn = 2 THEN ProductID END) As Product2
, MAX (CASE WHEN rn = 2 THEN PQty END) As Qty2
, MAX (CASE WHEN rn = 3 THEN WorkType END) As WorkType3
, MAX (CASE WHEN rn = 3 THEN ProductID END) As Product3
, MAX (CASE WHEN rn = 3 THEN PQty END) As Qty3
FROM (
SELECT
[RefNumber]
, [WorkType]
, [ProductID]
, Hours
, PQty
, ROW_NUMBER() OVER (PARTITION BY RefNumber, case when ProductID IS NULL then 0 else 1 end ORDER BY ProductID) rn
FROM [Vw_Beta_CostLog]
) sub
GROUP BY
RefNumber
ORDER BY
RefNumber
Note that you cannot create a view which dynamically increases or decreases the number of columns based on how many products are used.
see online demo: https://rextester.com/SBQUP24417

Related

How can separate morning and evening shifts patients with gender wise using SQL stored procedure

I want to separate the morning and evening patient checked-in count gender-wise and also if gender age less then show the Female child and M child on the basis of gender.
SELECT
COUNT(ch.EnteredOn) AS counter,
CONVERT(date, ch.EnteredOn) AS sessionDay,
SUM(CASE WHEN CAST(CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) < 12 THEN 1 ELSE 0 END) AS 'Morning',
SUM(CASE WHEN CAST(CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) >= 12 THEN 1 ELSE 0 END) AS 'Evening',
SUM(CASE WHEN p.Gender = 1 THEN 1 ELSE 0 END) AS Male,
SUM(CASE WHEN p.Gender = 2 THEN 1 ELSE 0 END) AS Fmale,
SUM(CASE WHEN DATEDIFF(hour, P.DOB, GETDATE()) / 8766 <= 18
AND P.Gender = 1 THEN 1 ELSE 0 END) AS MChild,
SUM(CASE WHEN DATEDIFF(hour, P.DOB, GETDATE()) / 8766 <= 18
AND P.Gender = 2 THEN 1 ELSE 0 END) AS FChild
FROM
Patient.CheckIn ch
INNER JOIN
Patient.Patients P ON ch.PatientId = p.PatientId
GROUP BY
Ch.EnteredOn
I have only three columns like Gender, Time and DOB. Gender id 1 shows the male and Gender id 2 is using for females.
enter image description here
SELECT convert(date, ch.EnteredOn) AS sessionDay, CAST( CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) <12 as morning, count(ch.EnteredOn) AS counter
,sum(case when p.Gender=1 Then 1 ELSE 0 end) as Male
,sum(case when p.Gender=2 Then 1 ELSE 0 end) as Fmale
,Sum( case when DATEDIFF(hour,P.DOB,GETDATE())/8766<=18 AND P.Gender=1 Then 1 ELSE 0 end ) as MChiled
,Sum( case when DATEDIFF(hour,P.DOB,GETDATE())/8766<=18 AND P.Gender=2 Then 1 ELSE 0 end ) as FChiled
FROM Patient.CheckIn ch
inner join Patient.Patients P on ch.PatientId=p.PatientId
Group by Ch.EnteredOn
Group BY can be
Group by convert(date, ch.EnteredOn) AS sessionDay, CAST( CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) <12

How can I combine the below 2 sql queries when there are multiple case statements with SUM involved

This is the first sub-query:
SELECT SUM(CASE WHEN TRADE_TYPE='SELL' THEN (QUANTITY*PRICE) END) -
SUM(CASE WHEN TRADE_TYPE='BUY' THEN (QUANTITY*PRICE) END) AS NET_PL, TRADINGSYMBOL AS STOCK_NAME
FROM dbo.[Table1]
GROUP BY TRADINGSYMBOL, TRADE_DATE
This is the second sub-query:
SELECT SUM(CASE WHEN TRADE_TYPE='BUY' THEN QUANTITY END) -
SUM(CASE WHEN TRADE_TYPE='SELL' THEN QUANTITY END) AS NET_QUANTITY, TRADINGSYMBOL AS STOCK_NAME
FROM dbo.[Table1]
GROUP BY TRADINGSYMBOL
This is the query result of second sub-query:
NET_QUANTITY STOCK_NAME
---------------------- --------------------------------------------------
NULL ABCL
0 ADAT
NULL BAF
NULL BEGE
0 CRECC
NULL CIEN
NULL DFMXA
NULL DFJL
-50 HDANK
1000 MEHD
NULL PRAK
0 TNTS
Every TRADINGSYMBOL that exists as part of second query result WITH NET QUANTITY 0 has to be combined/merged with the first query result. I mean apart from TRADINGSYMBOL(s) included in query-1, the query-2 TRADING SYMBOLS also have to get calculated for their (QUANTITY*PRICE) and should be part of final output.
Please guide me. Thank you.
A quick and easy way would be to just treat them as two sub-queries.
SELECT
PL.STOCK_NAME
,PL.NET_PL
,Q.NET_QUANTITY
FROM
(SELECT SUM(CASE WHEN TRADE_TYPE='SELL' THEN (QUANTITY*PRICE) END) -
SUM(CASE WHEN TRADE_TYPE='BUY' THEN (QUANTITY*PRICE) END) AS NET_PL, TRADINGSYMBOL AS STOCK_NAME
FROM dbo.[Table1]
GROUP BY TRADINGSYMBOL, TRADE_DATE) AS PL
FULL OUTER JOIN
(SELECT SUM(CASE WHEN TRADE_TYPE='BUY' THEN QUANTITY END) -
SUM(CASE WHEN TRADE_TYPE='SELL' THEN QUANTITY END) AS NET_QUANTITY, TRADINGSYMBOL AS STOCK_NAME
FROM dbo.[Table1]
GROUP BY TRADINGSYMBOL) AS Q ON PL.STOCK_NAME = Q.STOCK_NAME

SQL Server 2012: Categorizing NULL values as missing and pending based on occurence

I have a data related to student week wise attendance in a study center. It will have Centerid, studentid, week1, week2 and so on.
This is the Pivoted Schema link for my data and this is the link for my original data.
The student may have missed few weeks in between or few weeks yet to come in future.
What I need is to categorize the missing dates into Missed weeks and Futuree Weeks.
If you check the above screenshot, the NULL values highlighted in red are the missing ones and the NULL values in green are the future ones. When The student attends the class, we will have the date when he attended the class for each week. Hence when he misses the class, there wont be date and will be null. similarly when the week is of future, it will be null as he is yet to attend the class.
I want to classify the red ones as missing and the green ones as future. I was able to do this using a case comparison like
WHEN WEEK1 IS NULL AND (WEEK2 is NOT NULL OR WEEK3 is NOT NULL OR WEEK 4 is NOT NULL OR WEEK 5 is NOT NULL OR WEEK6is NOT NULL OR WEEK7 is NOT NULL THEN 'WEEK1_Missing'
WHEN WEEK7 IS NULL AND (WEEK2 is NOT NULL OR WEEK3 is NOT NULL OR WEEK 4 is NOT NULL OR WEEK 5 is NOT NULL OR WEEK6is NOT NULL OR WEEK1 is NOT NULL THEN 'WEEK7_Future'
Is there any better approach to this? Please suggest me.
Thanks in advance.
The query below will calculate some kind of binary string from the weeks.
The binary string is used to determine if the records have future and/or missing weeks.
Which is then used to get the totals.
select [CENTERID],
sum(case when calc like '0%' then 1 else 0 end) as future,
sum(case when calc like '%10%' then 1 else 0 end) as missing
from (
SELECT [CENTERID], [STUDENTID],
case when WEEK7 is null then '0' else '1' end +
case when WEEK6 is null then '0' else '1' end +
case when WEEK5 is null then '0' else '1' end +
case when WEEK4 is null then '0' else '1' end +
iif(WEEK3 is null,'0','1') + -- iif as alternative if you want to golf code this
iif(WEEK2 is null,'0','1') +
iif(WEEK1 is null,'0','1') as calc
FROM week_data
) q
group by [CENTERID];
Returns:
CENTERID future missing
1001 0 2
1002 1 1
1003 1 2
1004 1 1
1005 0 2
1006 1 0
1007 3 3
To apply such trick directly on the original data:
select [CENTERID],
sum(case when calc like '0%' then 1 else 0 end) as future,
sum(case when calc like '%10%' then 1 else 0 end) as missing
from (
SELECT [CENTERID], [STUDENTID],
max(case when [WEEKNAME] = 'week 7' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 6' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 5' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 4' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 3' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 2' and [WEEKDATE] is not null then '1' else '0' end) +
max(case when [WEEKNAME] = 'week 1' and [WEEKDATE] is not null then '1' else '0' end) as calc
FROM original_data
GROUP BY [CENTERID], [STUDENTID]
) q
group by [CENTERID];
If you just want to replace the nulls by something like 'Missing' or 'Future', then realise that you can use a CASE WHEN within a CASE WHEN.
For example:
SELECT [CENTERID], [STUDENTID]
,(CASE
WHEN WEEK1 IS NULL
THEN (CASE
WHEN COALESCE(WEEK2, WEEK3, WEEK4, WEEK5, WEEK6, WEEK7) IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK1
END) as [WEEK1]
,(CASE
WHEN WEEK2 IS NULL
THEN (CASE
WHEN COALESCE(WEEK3, WEEK4, WEEK5, WEEK6, WEEK7) IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK2 END) as [WEEK2]
,(CASE
WHEN WEEK3 IS NULL
THEN (CASE
WHEN COALESCE(WEEK4, WEEK5, WEEK6, WEEK7) IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK3
END) as [WEEK3]
,(CASE
WHEN WEEK4 IS NULL
THEN (CASE
WHEN COALESCE(WEEK5, WEEK6, WEEK7) IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK4
END) as [WEEK4]
,(CASE
WHEN WEEK5 IS NULL
THEN (CASE
WHEN COALESCE(WEEK6, WEEK7) IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK5
END) as [WEEK5]
,(CASE
WHEN WEEK6 IS NULL
THEN (CASE
WHEN WEEK7 IS NULL
THEN 'Future'
ELSE 'Missing'
END)
ELSE WEEK6
END) as [WEEK6]
,(CASE
WHEN WEEK7 IS NULL
THEN 'Future'
ELSE WEEK7
END) as [WEEK7]
FROM week_data

Count of rows for 2 days

My data is in below format:
employee order id date
a 123 01/06/2013
b 124 02/06/2013
a 125 02/06/2013
a 129 02/06/2013
I need the data in below format:
employee day 1 day 2
a 1 2
b 0 1
Try this one -
DECLARE #temp TABLE
(
dtStart DATETIME
, employees CHAR(1)
)
INSERT INTO #temp (employees, dtStart) VALUES('a','01/06/2013')
INSERT INTO #temp (employees, dtStart) VALUES('a','01/06/2013')
INSERT INTO #temp (employees, dtStart) VALUES('b','02/06/2013')
SELECT
employees
, day1 = COUNT(CASE WHEN DAY(dtStart) = 1 THEN 1 END)
, day2 = COUNT(CASE WHEN DAY(dtStart) = 2 THEN 1 END)
FROM #temp
--WHERE dtStart BETWEEN '01/06/2013' AND '30/06/2013'
GROUP BY employees
Something along these lines should work (depending on whether you have a fixed amount of days or not):
select employee,
SUM(CASE WHEN date = '01/06/2013' THEN 1 ELSE 0 END) as day1,
SUM(CASE WHEN date = '02/06/2013' THEN 1 ELSE 0 END) as day2
from table
group by employee
select distinct employees,
SUM(CASE WHEN dtStart = '01/06/2013' THEN 1 ELSE 0 END) as day1,
SUM(CASE WHEN dtStart = '02/06/2013' THEN 1 ELSE 0 END) as day2
from yourTable
group by dtStart,employees
see your demo

Best way to aggregate data - Update query or subquery?

I'm trying to aggregate some customer order data into one table for analysis. The data is the number of products a customer orders, then trying to determine whether the order is a small, medium, or large order, then determine the total products they bought and the cost of the order by OrderSize.
Small order - 1 - 2 products
Medium order - 3 - 4 products
Large Order - >=5 products
Here is the data:
CustomerID OrderID OrderSize OrderTotal
1 800 1 $20
2 801 1 $10
3 802 4 $85
1 803 1 $30
2 804 8 $120
3 805 1 $40
Here is the table I am attempting to build (easier to just post an image):
I could run an update query like this to populate the table:
-- Build the table --
CREATE TABLE Customers (
CustomerID varchar(10) PRIMARY KEY,
SmallOrderCount int,
SmallOrderProducts int,
SmallOrderTotal money,
MedOrderCount int,
MedOrderProducts int,
MedOrderTotal money,
LargeOrderCount int,
LargeOrderProducts int,
LargeOrderTotal money
);
-- Insert the unique customers --
INSERT INTO Customers
SELECT CustomerID FROM Orders GROUP BY CustomerID;
-- Update to populate the order information --
UPDATE Customers
SET SmallOrderCount = (SELECT count(*) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
UPDATE Customers
SET SmallOrderProducts = (SELECT sum(OrderSize) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
UPDATE Customers
SET SmallOrderTotal = (SELECT sum(OrderTotal) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
Then I could repeat this for the remaining 6 columns.
However, this seems like a lot of work. Is there a way to populate my Customer table using a subquery that may be less work? Or is my approach above the most direct?
You can do all of it in one INSERT command with CASE statements:
INSERT INTO Customers
SELECT CustomerID
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN 1 ELSE 0 END) AS SmallOrderCount
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderSize ELSE 0 END) AS SmallOrderProducts
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderTotal ELSE 0 END) AS SmallOrderTotal
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN 1 ELSE 0 END) AS MedOrderCount
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderSize ELSE 0 END) AS MedOrderProducts
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderTotal ELSE 0 END) AS MedOrderTotal
,sum(CASE WHEN OrderSize > 4 THEN 1 ELSE 0 END) AS LargeOrderCount
,sum(CASE WHEN OrderSize > 4 THEN OrderSize ELSE 0 END) AS LargeOrderProducts
,sum(CASE WHEN OrderSize > 4 THEN OrderTotal ELSE 0 END) AS LargeOrderTotal
FROM Orders
GROUP BY CustomerID;
See this working demo for the full query on data.SE.
One insert should be enough:
INSERT INTO Customers(CustomerID,SmallOrderCount,SmallOrderProducts,
SmallOrderTotal)
SELECT a.CustomerID, COUNT(a.*) as cnt, sum(a.OrderSize) as OrderSize,
sum(a.OrderTotal) as OrderTotal
FROM Orders a
WHERE a.OrderSize BETWEEN 1 AND 2
GROUP BY a.CustomerID
The query above will insert only customers whose order size is between 1 and 2. If you need to insert others as well, you can use :
INSERT INTO Customers(CustomerID,SmallOrderCount,SmallOrderProducts,
SmallOrderTotal)
SELECT a.CustomerID,
COUNT(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN 1 END) as cnt,
sum(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN a.OrderSize ELSE 0 END) as OrderSize,
sum(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN a.OrderTotal ELSE 0 END ) as OrderTotal
FROM Orders a
GROUP BY a.CustomerID
I'd only create a single query (which can be used as view) for that and not a whole new persisted table.
WITH cteOrders AS (
SELECT CustomerID,
CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderSize END SmallOrderProducts,
CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderTotal END SmallOrderTotal,
CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderSize END MediumOrderProducts,
CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderTotal END MediumOrderTotal,
CASE WHEN OrderSize > 4 THEN OrderSize END LargeOrderProducts,
CASE WHEN OrderSize > 4 THEN OrderTotal END LargeOrderTotal
FROM Orders
)
SELECT CustomerID,
COUNT(SmallOrderProducts) SmallOrderCount,
COALESCE(SUM(SmallOrderProducts), 0) SmallOrderProducts,
COALESCE(SUM(SmallOrderTotal), 0) SmallOrderTotal,
COUNT(MediumOrderProducts) MediumOrderCount,
COALESCE(SUM(MediumOrderProducts), 0) MediumOrderProducts,
COALESCE(SUM(MediumOrderTotal), 0) MediumOrderTotal,
COUNT(LargeOrderProducts) LargeOrderCount,
COALESCE(SUM(LargeOrderProducts), 0) LargeOrderProducts,
COALESCE(SUM(LargeOrderTotal), 0) LargeOrderTotal
FROM cteOrders
GROUP BY CustomerID
You can do this by using single INSERT INTO SELECT statement
INSERT INTO Customers
SELECT * ,
(SELECT count(*) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = CustomerID),
(SELECT sum(OrderSize) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = CustomerID)
FROM CUSTOMERS

Resources