About SQL pivot table - sql

I'm trying to figure out how to produce this result in SQL using pivot table Sorted by SubID
units
Sub
SubCode
AM
PM
3
Math
M2201
Monday / 7:00AM-8:00AM
Tuesday / 1:00PM-2:00PM
3
Science
S2203
Monday / 9:00AM-10:00AM
Tuesday / 3:00PM-4:00PM
3
Comp (lab)
C2203
Friday / 9:00AM-10:00AM
Wednesday / 3:00PM-4:00PM
2
Comp (lec)
C2203
Thursday / 9:00AM-10:00AM Friday / 7:00AM-8:00AM
Tuesday / 3:00PM-4:00PM
Originally, I have 3 tables where I pull out data.
table SetSub
ssID
AY
Prog
YLev
Sem
SubCode
1
2022-2023
Intermediate
3
2
M2201
2
2022-2023
Intermediate
3
2
S2203
2
2022-2023
Intermediate
3
2
C2203
table Sched
schedID
Prog
Sem
SubCode
Sub
Units
Shift
SubType
Day
Sched
isLecLab
1
Intermediate
2
M2201
Math
3
AM
Lec
Monday
7:00AM-8:00AM
0
2
Intermediate
2
M2201
Math
3
PM
Lec
Tuesday
1:00PM-2:00PM
0
3
Intermediate
2
S2203
Science
3
AM
Lec
Monday
9:00AM-10:00AM
0
4
Intermediate
2
S2203
Science
3
PM
Lec
Tuesday
3:00PM-4:00PM
0
5
Intermediate
2
C2203
Comp
2
AM
Lec
Thursday
9:00AM-10:00AM
1
6
Intermediate
2
C2203
Comp
2
AM
Lec
Friday
7:00AM-8:00AM
1
7
Intermediate
2
C2203
Comp
2
PM
Lec
Tuesday
3:00PM-4:00PM
1
8
Intermediate
2
C2203
Comp
3
AM
Lab
Friday
9:00AM-10:00AM
1
9
Intermediate
2
C2203
Comp
3
PM
Lab
Wednesday
3:00PM-4:00PM
1
table Subjects
subid
Sub
SubCode
Units
isLecLab
1
Math
M2201
3
0
2
Science
S2203
3
1
3
Comp
C2203
5
0
Added an image since table get messed up upon saving the post
But created a new table for this.
subid
units
sub
UserCode
Shift
Sched
1
3
Math
M2201
AM
Monday / 7:00AM-8:00AM
1
3
Math
M2201
PM
Tuesday / 1:00PM-2:00PM
2
3
Science
S2203
AM
Monday / 9:00AM-10:00AM
2
3
Science
S2203
PM
Tuesday / 3:00PM-4:00PM
3
3
Comp (lab)
C2203
AM
Friday / 9:00AM-10:00AM
3
2
Comp (lab)
C2203
PM
Wednesday / 3:00PM-4:00PM
3
3
Comp (lec)
C2203
AM
Thursday / 9:00AM-10:00AM
3
2
Comp (lec)
C2203
PM
Tuesday / 3:00PM-4:00PM
3
2
Comp (lec)
C2203
PM
Friday / 7:00AM-8:00AM
I tried several queries and the closest I've got is this
units
Sub
Code
AM
PM
3
Math
M2201
Monday / 7:00AM-8:00AM
Tuesday / 1:00PM-2:00PM
3
Science
S2203
Monday / 9:00AM-10:00AM
Tuesday / 3:00PM-4:00PM
3
Comp (lab)
C2203
Thursday / 9:00AM-10:00AM
Tuesday / 3:00PM-4:00PM
3
Comp (lab)
C2203
Friday / 9:00AM-10:00AM
Wednesday / 3:00PM-4:00PM
2
Comp (lec)
C2203
Thursday / 9:00AM-10:00AM
Tuesday / 3:00PM-4:00PM
2
Comp (lec)
C2203
Friday / 9:00AM-10:00AM
Wednesday / 3:00PM-4:00PM
Second data for AM of comp (lec) didn't appear.
Here's the code I've tried
select a.usercode, a.sub, a.Units, a.am, b.pm, a.Schedid from
(select * from
(select distinct subid, usercode, sub, units, shift, sched from Table1 where shift= 'am') as src
pivot (max(sched) for shift in ("am")) as pvt ) as A
inner join
(select * from
(select distinct subid, usercode, sub, units, shift, sched from table1 where shift= 'pm') as src2
pivot (max(sched) for shift in ("pm")) as pvt2 ) as B on a.shift= b.shift

Conditional agg will perform a pivot, and it's eaier to perform more advanced operations using it than PIVOT does, so it's a pattern worth learning.
To see how a conditional agg works, remove the GROUP BY and any mention of a MIN/MAX/STRING_AGG or other agregating operation. It makes it easier to see that a vertical arrangement of data:
A, 1
B, 2
C, 1
Becomes diagonal when CASE WHEN'd:
--CASE WHEN letter = 'A' then number end as a,
--CASE WHEN letter = 'B' then number end as b,
--CASE WHEN letter = 'C' then number end as c,
A, B, C
1, -, -
-, 2, -
-, -, 3
The GROUP/MAX then flattens the nulls out, so the "diagonal" data is fully rotated to horizontal
--MAX(CASE WHEN letter = 'A' then number end) as a,
--MAX(CASE WHEN letter = 'B' then number end) as b,
--MAX(CASE WHEN letter = 'C' then number end) as c,
A, B, C
1, 2, 3
In your requirement, using STRING_AGG allows multiple values per cell rather than just one
This form is for use on your joined table because I cannot make assumptions about the 3 tables that generated it (no detail)
Something like this should work for SQLS..
SELECT
CASE WHEN Sub = 'Comp (lec)' THEN MIN(Units) ELSE MAX(Units) END as Units,
Sub,
MAX(UserCode) as UserCode,
STRING_AGG(CASE WHEN Shift = 'AM' THEN Sched END, CHAR(10)) as AM,
STRING_AGG(CASE WHEN Shift = 'PM' THEN Sched END, CHAR(10)) as PM
FROM
t
GROUP BY
Sub
..but it'd be good to see the source tables/the query that generated t. Getting the data in the exact order per cell (making sure that the cell says "Thursday..Friday" rather then "Friday..Thursday" might be really messy. It would help if these things were numeric/dates somewhere. Post the original data so we can see if it helps)

Related

How to Find Week number, Period and year from Date in Redshift? (Week Starting with Wednesday and ends up with Tuesday)

Need to find weekNumber like 1,2,3,4 but the week starts with Wednesday and ends with Tuesday from date column and after the 4th week, again the week restart by again as the 1st week and so on (no need to consider month).
Need to find the Period based on weekNumber only, 4 weeks as 1 Period and Periods end with 13 (period 1-13) will restart again 1st period.
(4 weeks = 1 period) (no need to consider month).
Now need to calculate the businessyear based on Period. 13 Periods as One businessyear. (13 periods = 1 year)
Calculation logic:
7 days * 4 weeks = 28 days = 1 period
13 periods = 1 businessyear
Example:
A year has 365 days normally
In my scenario, 4 weeks * 7 days = 28 days
28 days *13 periods = 364 days
The remaining days will come as the 5th week and period 14.
Datekey date Year semistor Quarter Month DayName DayNum Wnumber
20090101 01-01-2009 2009 1 1 January 1 Thursday 1 0
20090102 02-01-2009 2009 1 1 January 1 Friday 2 0
20090103 03-01-2009 2009 1 1 January 1 Saturday 3 0
20090104 04-01-2009 2009 1 1 January 1 Sunday 0
20090105 05-01-2009 2009 1 1 January 1 Monday 0
20090106 06-01-2009 2009 1 1 January 1 Tuesday 6 0
20090107 07-01-2009 2009 1 1 January 1 Wednesday 0 0
20090108 08-01-2009 2009 1 1 January 1 Thursday 1 1
20090109 09-01-2009 2009 1 1 January 1 Friday 2 1
20090110 10-01-2009 2009 1 1 January 1 Saturday 3 1
20090111 11-01-2009 2009 1 1 January 1 Sunday 4 1
20090112 12-01-2009 2009 1 1 January 1 Monday 5 1
20090113 13-01-2009 2009 1 1 January 1 Tuesday 6 1
20090114 14-01-2009 2009 1 1 January 1 Wednesday 0 1
No need to consider the month in my scenario, need to consider leap year also (2016, 2020).
The traditional way to do this type of thing is to create a calendar table in the database. Then, your queries can simply JOIN to the calendar table to extract the relevant value.
I find that the easiest way to create the calendar table is to use Excel. Simply write some formulas that provide the desired values and Copy Down for the next decade or so. Then, save the sheet as CSV and load it into the database.
This way, you can totally avoid complex calculations involving database functions and you can use whatever rules you wish.

Excel: No. of Weekdays in a given week

My Data
A B C
1 Created Date Week No. of WorkDays
2 6/20/2018 11:36 06-w4 5
3 6/26/2018 12:56 06-w5 5
4 7/6/2018 23:01 07-w1 5
5 6/18/2018 18:11 06-w4 5
6 6/15/2018 12:01 06-w3 5
7 6/1/2018 13:31 06-w1 1
8 6/8/2018 12:17 06-w2 5
9 6/1/2018 13:32 06-w1 1
10 7/30/2018 13:32 07-w5 2
I have a week function that says whether an issue was created in Week 1 or 2 or so on. But for my calculation I need to consider workdays in that week, how do I do it. I'm calculating the no. of weekdays in that week manually now.
For example: June Week 1: No. of Weekdays is 1, because June 1 is Friday and June 2 is Saturday. Similarly for July Week 5 it is 2 days because July 29 is Sunday, July 30 & 31 are the weekdays.
My current formula for Week is
=CONCATENATE(TEXT(A1,"MM"),"-w",WEEKNUM(A1,1)-WEEKNUM(DATE(YEAR(A1),MONTH(A1),1),1)+1)
I'm trying out to use Workday function, but it does not provide my desired result.
Kindly help me out with this.
If the date in the A column is always a weekday, you can use this:
If that date can also be a weekend day, it will take the working days of the previous week. If you want to take the workinf days of the next week, you have to fiddle around still a bit.
=MIN(5,IF(MONTH(A2-WEEKDAY(A2,3))<MONTH(A2),7-WEEKDAY(DATE(YEAR(A2),MONTH(A2),1),1),IF(MONTH(A2+5-WEEKDAY(A2,2))>MONTH(A2),WEEKDAY(DATE(YEAR(A2),MONTH(A2)+1,0),2),5)))
First MIN: restrict to max 5 working days
First IF(): check if monday before or on date in A2 is in previous month
If so: take 7 minus weekday of first of month (sunday being 1)
If not so: second IF: check if friday this week is in next month
If so: take the weekday of the last of this month (monday being 1)
If not so: week in the middle of month, return 5
This of course does not take into account public holidays, only weekends.
For an inclusive # or workdays (e.g. Friday is 1 workday) try,
=NETWORKDAYS.INTL(A2, A2+5-WEEKDAY(A2,2), 1)
NETWORKDAYS.INTL allows for an optional holiday list if you want to create one.
Wrote my own VBA Formula - Results are as below
A B C D
1 Created Date Week No. of WorkDays No. of Days - Formula
2 6/20/2018 11:36 06-w4 5 5
3 3/2/2018 12:56 03-w1 2 2
4 7/6/2018 23:01 07-w1 5 5
5 6/18/2018 18:11 06-w4 5 5
6 6/15/2018 12:01 06-w3 5 5
7 6/1/2018 13:31 06-w1 1 1
8 6/8/2018 12:17 06-w2 5 5
9 6/1/2018 13:32 06-w1 1 1
10 7/30/2018 13:32 07-w5 2 2
Formula
=CalculateWorkdaysInWeek(A2)
VBA Code
Function CalculateWorkdaysInWeek(WeekRange As Range) As Variant
'Assume Week 2, 3 & 4 will always have 5 days
Dim WeekNo As Double
'Check if Week is 1 or 5
WeekNo = (Application.WorksheetFunction.WeekNum(WeekRange, 1) - _
Application.WorksheetFunction.WeekNum(DateSerial(Year(WeekRange), Month(WeekRange), 1))) + 1
Dim NoOfWeekDays As Integer
If WeekNo = 1 Then
FirstWeekDay = 7 - Weekday(DateSerial(Year(WeekRange), Month(WeekRange), 1), vbSunday)
If FirstWeekDay > 5 Then
FirstWeekDay = 5
End If
NoOfWeekDays = FirstWeekDay
ElseIf WeekNo = 5 Then
'Check the last day of the month as Monday as Start
LastWeekDay = Weekday(DateSerial(Year(WeekRange), Month(WeekRange) + 1, 0), vbMonday)
If LastWeekDay > 5 Then
LastWeekDay = 5
End If
NoOfWeekDays = LastWeekDay
Else ' Week 2,3 & 4 Return 5
NoOfWeekDays = 5
End If
CalculateWorkdaysInWeek = NoOfWeekDays
End Function

Take one month back in teradata sql

I have some tables (samples are brought here) like this
scores (the score is calculated once in each month for each branch_cust in the 28 for specific month)
Branch_cust model_date score
1 28/12/2013 4
1 28/01/2014 3
1 28/02/2014 2
1 28/03/2014 7
1 28/04/2014 3
1 28/05/2014 5
1 28/06/2014 6
2 28/12/2013 9
2 28/01/2014 10
2 28/02/2014 12
2 28/03/2014 11
2 28/04/2014 10
2 28/05/2014 7
2 28/06/2014 8
loans:
Branch_cust agreement_date
1 05-01-2014
1 29-01-2014
2 27-02-2014
2 28-02-2014
Loans:
desired output:
Branch_cust agreement_date loan_open_score
1 05-01-2014 4
1 29-01-2014 3
2 27-02-2014 10
2 28-02-2014 12
Logic to create the loan_open_score :
If the day in the month of the agreement_date is less then "28" then bring the score of the month previous to the month of the agreement date.
If the day is greater or equal to "28" then bring the score for the month equal to the month of the agreement date.
Example: In the sample data for branch_cust = 1 the agreement_date was 05-01-2014 - meaning - day = 5 so I need to go back to Dec 2013 and take the score from there.
Any help how to do this? thank's. I was thinking of "join" and then substract 1 in "case of.." but I don't know how to handle the case when the date is 'dd-01-YYYY' in sql-teradata.
updated : column data type of the dates are dates.
trunc(agreement_date,'mon') + 27 returns the 28th of the current month. Now you can apply some logic and join on this calculated date:
case when trunc(agreement_date,'mon') + 27 > agreement_date
then add_months(trunc(agreement_date,'mon') + 27,-1)
else trunc(agreement_date,'mon') + 27
end
Another option would be to get the latest model_date per agreement date and join it to the scores table. This way you don't have to manipulate dates.
select t.branch_cust,t.agreement_Date,s.score
from scores s
join (select distinct l.branch_cust,l.agreement_Date
,max(s.model_Date) over(partition by l.branch_cust,l.agreement_Date) as max_model_Date
from scores s
join loans l on s.branch_cust=l.branch_cust and l.agreement_Date >= s.model_Date
) t
on s.branch_cust=t.branch_cust and s.model_Date=t.max_model_Date
select *
from scores as s
join loans as l
on l.Branch_cust =
s.Branch_cust
and l.model_date =
add_months
(
trunc(S.agreement_date,'mm')+27
,case when extract(day from s.agreement_date) < 28 then -1 else 0 end
)

Add date of first day of week to date dimension

I want to add a column to my date dimension that contains the date of the first day of that week. here's a piece of what it looks like
CalendarDate DayOfWeekNumber DayKey DayOfWeekName WeekNumber
1996-01-01 2 19960101 MONDAY 1
1996-01-02 3 19960102 TUESDAY 1
1996-01-03 4 19960103 WEDNESDAY 1
1996-01-04 5 19960104 THURSDAY 1
1996-01-05 6 19960105 FRIDAY 1
1996-01-06 7 19960106 SATURDAY 1
1996-01-07 1 19960107 SUNDAY 1
1996-01-08 2 19960108 MONDAY 2
1996-01-09 3 19960109 TUESDAY 2
1996-01-10 4 19960110 WEDNESDAY 2
1996-01-11 5 19960111 THURSDAY 2
1996-01-12 6 19960112 FRIDAY 2
1996-01-13 7 19960113 SATURDAY 2
1996-01-14 1 19960114 SUNDAY 2
So basically i would want a column WeekStartDate that, for each WeekNumber would have the CalendarDate of the first dayOfWeekNumber (dayOfWeekNumber =1)
It would look like
CalendarDate DayOfWeekNumber DayKey DayOfWeekName WeekNumber WeekStart
1996-01-01 2 19960101 MONDAY 1 1996-01-01
1996-01-02 3 19960102 TUESDAY 1 1996-01-01
1996-01-03 4 19960103 WEDNESDAY 1 1996-01-01
1996-01-04 5 19960104 THURSDAY 1 "
1996-01-05 6 19960105 FRIDAY 1 "
1996-01-06 7 19960106 SATURDAY 1 "
1996-01-07 1 19960107 SUNDAY 1 "
1996-01-08 2 19960108 MONDAY 2 1996-01-08
1996-01-09 3 19960109 TUESDAY 2 "
1996-01-10 4 19960110 WEDNESDAY 2 "
1996-01-11 5 19960111 THURSDAY 2 "
1996-01-12 6 19960112 FRIDAY 2
1996-01-13 7 19960113 SATURDAY 2
1996-01-14 1 19960114 SUNDAY 2
so something like
update myTable set WeekStartDate = CalendarDate where dayofweeknumber=2 (monday) for each weeknumber (pseudocode, i know that's not what literally what i want it to do).
Thanks for suggestions. Pretty sure I need to window on weeknumber.
UPDATE C
SET WeekStartDate = week_start
FROM
(
select *, week_start = min(CalendarDate)
over (partition by year(CalendarDate), WeekNumber)
from Calendar
) AS C

Using Sum() with multiple where clauses

I'm pretty new to this, so forgive if this has been posted (I had no idea what to even search on).
I have 2 tables, Accounts and Usage
AccountID AccountStartDate AccountEndDate
-------------------------------------------
1 12/1/2012 12/1/2013
2 1/1/2013 1/1/2014
UsageId AccountID EstimatedUsage StartDate EndDate
------------------------------------------------------
1 1 10 1/1 1/31
2 1 11 2/1 2/29
3 1 23 3/1 3/31
4 1 23 4/1 4/30
5 1 15 5/1 5/31
6 1 20 6/1 6/30
7 1 15 7/1 7/31
8 1 12 8/1 8/31
9 1 14 9/1 9/30
10 1 21 10/1 10/31
11 1 27 11/1 11/30
12 1 34 12/1 12/31
13 2 13 1/1 1/31
14 2 13 2/1 2/29
15 2 28 3/1 3/31
16 2 29 4/1 4/30
17 2 31 5/1 5/31
18 2 26 6/1 6/30
19 2 43 7/1 7/31
20 2 32 8/1 8/31
21 2 18 9/1 9/30
22 2 20 10/1 10/31
23 2 47 11/1 11/30
24 2 33 12/1 12/31
I'd like to write one query that gives me estimated usage for each month (starting now until the last month that we serve an account) for all accounts being served during that month.
The results would be as follows:
Month-Year Total Est Usage
------------------------------
Oct-12 0 (none being served)
Nov-12 0 (none being served)
Dec-12 34 (only accountid 1 being served)
Jan-13 23 (accountid 1 & 2 being served)
Feb-13 24 (accountid 1 & 2 being served)
Mar-13 51 (accountid 1 & 2 being served)
...
Dec-13 33 (only accountid 2 being served)
Jan-14 0 (none being served)
Feb-14 0 (none being served)
I'm assuming I need to sum and then do a Group By...but not really sure logically how I'd lay this out.
Revised Answer:
I've created a Months table with columns MonthID, Month with values like (201212, 12), (201301, 1), ...
I've also reorganised the usage table to have a month column rather than the start date and end date, as it makes the idea clearer.
See http://sqlfiddle.com/#!3/f57d84/6 for details
The query is now:
Select
m.MonthID,
Sum(u.EstimatedUsage) TotalEstimatedUsage
From
Accounts a
Inner Join
Usage u
On a.AccountID = u.AccountID
Inner Join
Months m
On m.MonthID Between
Year(a.AccountStartDate) * 100 + Month(a.AccountStartDate) And
Year(a.AccountEndDate) * 100 + Month(a.AccountEndDate) And
m.Month = u.Month
Group By
m.MonthID
Order By
1
Previous answer, for reference which assumed usages ranges were full dates rather than just months.
Select
Year(u.StartDate),
Month(u.StartDate),
Sum(Case When a.AccountStartDate <= u.StartDate And a.AccountEndDate >= u.EndDate Then u.EstimatedUsage Else 0 End) TotalEstimatedUsage
From
Accounts a
Inner Join
Usage u
On a.AccountID = u.AccountID
Group By
Year(u.StartDate),
Month(u.StartDate)
Order By
1, 2