How to calculate value with multiple condition and how to design database? - sql

I need to calculated data with multiple condition
MinOTHrs MaxOTHs DayType Rate
------------------------------ -----------
3 - Working_day 18
4 11 Weekend 18
11 - Weekend 36
For example
if employee do OT on working_day > 3 Hrs. they will get rate 18 (MAX 18 for working_day)
if employee do OT on weekend > 4 Hrs. but < 11 Hrs. they will get rate 18
if employee do OT on weekend > 11 Hrs. they will get rate 36(MAX 36 for weekend)
Could you suggest me about coding and design database ?
Thank you very much

I'd probably do most of the work as multiple joins between Work and OT tables. That way you can be fairly specific about the scenario you want.
Below is a nice primer for you. It's not perfect but it should get you a long way towards where I think you want to go! (IDs included are just for debugging purposes - they're not required of course).
//setup
CREATE TABLE overTime (
MinOTHrs integer,
MaxOTHrs integer,
DayType varchar(max),
payRate integer
);
CREATE TABLE workLog (
logID integer,
empID integer,
DayType varchar(max),
hoursWorked integer
);
insert into overTime values (3, null, 'Working_day', 18);
insert into overTime values (4, 11, 'Weekend', 18);
insert into overTime values (11, null, 'Weekend', 36);
insert into workLog values (567, 1234, 'Working_day', 2);
insert into workLog values (568, 1234, 'Working_day', 5);
insert into workLog values (569, 1234, 'Weekend', 2);
insert into workLog values (570, 1234, 'Weekend', 9);
insert into workLog values (571, 1234, 'Weekend', 14);
//query
select
wl.logID
, wl.empID
, wl.DayType
, wl.hoursWorked
, coalesce(weekdayOverTime.MinOTHrs, weekendLowOverTime.MinOTHrs, weekendHighOverTime.MinOTHrs) as MINOTHrs
, coalesce(weekdayOverTime.MaxOTHrs, weekendLowOverTime.MaxOTHrs, weekendHighOverTime.MaxOTHrs) as MAXOTHrs
, coalesce(weekdayOverTime.payRate, weekendLowOverTime.payRate, weekendHighOverTime.payRate) as maxOTPayBracket
from workLog wl
left join overTime weekdayOverTime on wl.DayType = weekdayOverTime.dayType and wl.hoursWorked > weekdayOverTime.MinOTHrs and wl.dayType = 'Working_day'
left join overTime weekendLowOverTime on wl.DayType = weekendLowOverTime.dayType and wl.hoursWorked > weekendLowOverTime.MinOTHrs and wl.hoursWorked < weekendLowOverTime.MaxOTHrs and wl.dayType= 'Weekend'
left join overTime weekendHighOverTime on wl.DayType = weekendHighOverTime.dayType and wl.hoursWorked > weekendHighOverTime.MinOTHrs and weekendHighOverTime.MaxOTHrs is null and wl.dayType= 'Weekend'

Related

Need query for somewhat complicated aggregation

I haven't found a way to solve this (in an elegant way), so I'd like to ask for your help with calculating the average week-hours for specific month.
declare #person table (pers_id int, [from] date, [to] date, hrs decimal(4, 2));
insert into #person values (72, '2017-09-01', '2017-11-13', 20);
insert into #person values (72, '2017-11-14', null, 35);
declare #months table (ym date);
insert into #months values ('2017-09-01');
insert into #months values ('2017-10-01');
insert into #months values ('2017-11-01');
insert into #months values ('2017-12-01');
/* so I need a query whouch would output average week-hours: */
2017-09-01 = 20
2017-10-01 = 20
2017-11-01 = 28.5
= (13/30)*20 + (17/30)*35 ; (assumed each month has 30 days)
2017-12-01 = 35
Anybody willing to help me out?
(am on Azure SQL)
This assumes at most one change in hours per month. If you have more, then you'd need more complex calculations around the proportions
select
start,
round(sum(
case
when [from]>[start] then (datediff(d,[from],finish)+1) * hrs/days
when [to]<[finish] then (datediff(d,start,[to])+1) * hrs/days
else hrs end
),2)
from
(select ym as start, EOMONTH(ym) as finish,
30
--datepart(d, EOMONTH(ym))
as days from #months) months
inner join ( select pers_id, [from], isnull([to], EOMONTH(getdate()))as[to], hrs from #person) p
on months.finish>=p.[from] and months.start<=p.[to]
group by start

Calculate year over year increase SQL Server 2008R2

Below is my table structure. I need to calculate rent for length of lease for each properties:
Let's look at PropertyID = 12077:
Area = 1280
StartDate = 2023-02-01
EndDate = 2027-10-31
BaseRent = 21.53
RentIncreasePercent = .04 (4 percent)
IncreaseRepeatMonths = 12 months (NOTE: First 12 months there won't be any increase)
Since this property lease starts and ends between year 2023 and 2028, I'd like to know (in separate row per year) amount of rent to be collected each year. This would take percent increase every 12 months (compound rent increase) into consideration.
Example:
21.53 * 1280 would give rent for first 12 months. However, lease started in February so year 2023 total rent amount would be = ((21.23 * 1280)/12) * 11
For year 2024, first month rent would be = (21.23 * 1280)/12 because rent only increases every 12 months. For next 11 months of 2024, rent would be ((12.23 * 1.04 * 1280)/12)* 11.
For year 2025, first month rent would be (12.23 * 1.04 *1280)/12). However, next 11 months of 2025 would be ((12.72 * 1.04 * 1280)/12)*11. 12.72 comes from compound increase.
How would I go about coming up with a view to do this? Most confusing part to me is not knowing how to accommodate for lease start date when it is not starting on January.
declare #table table
(
PropertyID int,
area int,
StartDate date,
EndDate date,
BaseRent decimal(12,2),
RentIncreaseBasis varchar(30),
RentIncreasePercent decimal(5,2),
IncreaseRepeatMonths int
)
insert #table values (12076, 5627, '2024-01-01', '2028-12-31', '16.52', '% Increase', 0.03, 12)
insert #table values (12077, 1280, '2023-02-01', '2027-10-31', '21.53', '% Increase', 0.04, 12)
insert #table values (12078, 1000, '2017-03-01', '2025-11-30', '23.52', '% Increase', 0.01, 12)
insert #table values (12079, 2000, '2020-02-01', '2024-09-30', '15.57', '% Increase', 0.05, 12)
insert #table values (12080, 3000, '2018-05-01', '2020-08-31', '18.58', '% Increase', 0.04, 12)
insert #table values (12081, 4000, '2019-08-01', '2020-12-31', '22.56', '% Increase', 0.03, 12)
insert #table values (12082, 5000, '2017-02-01', '2028-03-31', '19.53', '% Increase', 0.02, 12)
select * from #table
I recommend to use a calendar table which containts all the months from your table.
I hope my example will work in SQL 2008.
-- here is your code
-- the calendar table
DECLARE #MonthCalendar table(
[Month] date PRIMARY KEY
)
DECLARE #MinDate date,#MaxDate date
-- get min and max date
SELECT
#MinDate=MIN(StartDate),
#MaxDate=MAX(EndDate)
FROM #table
-- fill the calendar table
;WITH monthCTE AS(
SELECT CAST(#MinDate AS date) [Month]
UNION ALL
SELECT DATEADD(MONTH,1,[Month])
FROM monthCTE
WHERE [Month]<#MaxDate
)
INSERT #MonthCalendar([Month])
SELECT [Month]
FROM monthCTE
OPTION(MAXRECURSION 0);
-- final query
SELECT
*,
(BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12 MonthRentAmount,
(1+RentIncreasePercent*IncreaseCount) TotalPercent
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
-- query for total amounts by PropertyIDs and Years
SELECT
PropertyID,
YEAR(StartDate) [Year],
SUM((BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12) YearRentAmount
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
GROUP BY PropertyID,YEAR(StartDate)
ORDER BY PropertyID,[Year]

Reference table from subquery in Oracle

I have simplified my tables but essentially I have a table of accounts that have a cycle_no and end date. This end date is always set to the first of the month but I need to get the real end date by looking in the calendar details table. The real end date is the next date for this cycle_no.
To create the simplified tables and enter a few rows of data:
CREATE TABLE DATA_OWNER.ACCOUNT
(
ACCNO NUMBER(4),
CYCLE_NO NUMBER(4),
ENDDATE DATE
);
CREATE TABLE DATA_OWNER.CALENDAR_DETAILS
(
CALENDAR_DT DATE,
BILL_CYCL_NO NUMBER(4)
);
INSERT INTO calendar_Details
VALUES
('18/DEC/2017',
17);
INSERT INTO calendar_Details
VALUES
('23/DEC/2017',
20);
INSERT INTO calendar_Details
VALUES
('18/JAN/2018',
17);
INSERT INTO calendar_Details
VALUES
('23/JAN/2018',
20);
INSERT INTO calendar_Details
VALUES
('20/FEB/2018',
17);
INSERT INTO calendar_Details
VALUES
('21/FEB/2018',
20);
INSERT INTO account
VALUES
(1, 17, '01/DEC/2107');
INSERT INTO account
VALUES
(2, 20, '01/DEC/2107');
If we run this query though we get "ACC". "ENDDATE": invalid identifier:
SELECT accno, cycle_no, enddate, actual_date
FROM account acc
JOIN
(
SELECT MIN(calendar_dt) actual_date
FROM calendar_details cal
WHERE calendar_dt > acc.enddate
)
ON acc.cycle_no = cal.bill_cycl_no;
Can anyone give us some pointers on the best way to achieve this please?
You cannot refer to outer table references in a subquery in the FROM. Just use a correlated subquery:
SELECT accno, cycle_no, enddate,
(SELECT MIN(cal.calendar_dt) as actual_date
FROM calendar_details cal
WHERE cal.calendar_dt > acc.enddate AND acc.cycle_no = cal.bill_cycl_no
) as actual_date
FROM account acc;

Get the information in terms of time about people who are free on a particular day

I have the following data:
CREATE TABLE Table1
(
ID varchar(10),
StudentName varchar(30),
Course varchar(15),
SECTION varchar(2),
DAY varchar(10),
START_TIME time,
END_TIME time,
actual_starttime time,
actual_endtime time
);
INSERT INTO Table1
VALUES (111, 'Mary', 'Science', 'A', 'Mon', '13:30:00.0000000', '16:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
INSERT INTO Table1
VALUES (111, 'Mary', 'Maths', 'A', 'Tue', '12:30:00.0000000', '13:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
INSERT INTO Table1
VALUES (111, 'Mary', 'Physics', 'C', 'Tue', '10:30:00.0000000', '11:10:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
INSERT INTO Table1
VALUES (112, 'Robert', 'Maths', 'A', 'Mon', '13:30:00.0000000', '16:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
The scenario is as follows: the student can have class from morning 9 to night 9:30 from Monday to Friday. My requirement is I have to identify a timeslot where all the students in the same section are free so that a teacher can reschedule a class.
Example: both Mary and Robert are free in the morning from 9:00 to 1:30 in the afternoon on Monday. I would like to write query for this.
Please help.
Thanks in advance!
To return the full list of the timeslots available, you need to build a set of all the timeslots for each day of the week and then find if any of these slots have students being taught within it.
This is easily achieved with a recursive CTE to build your full timeslot set, from which you can JOIN into your Students data. The output of the query below is the day and time of each vacant session:
-- Build the dummy data sets:
declare #Data table
(
ID varchar(10),
StudentName varchar(30),
Course varchar(15),
SECTION varchar(2),
DAY varchar(10),
START_TIME time,
END_TIME time,
actual_starttime time,
actual_endtime time
);
insert into #Data values
(111, 'Mary', 'Science', 'A', 'Mon', '13:30:00.0000000', '16:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
,(111, 'Mary', 'Maths', 'A', 'Tue', '12:30:00.0000000', '13:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
,(111, 'Mary', 'Physics', 'C', 'Tue', '10:30:00.0000000', '11:10:00.0000000', '09:00:00.0000000', '21:20:00.0000000')
,(112, 'Robert', 'Maths', 'A', 'Mon', '13:30:00.0000000', '16:20:00.0000000', '09:00:00.0000000', '21:20:00.0000000');
-- Query the data:
with TimeSlots as -- Recursive CTE builds a table of all timeslots in TIME data type.
(
select cast('09:00:00' as time) as TimeSlotStart
,cast('09:30:00' as time) as TimeSlotEnd
union all
select dateadd(minute,30,TimeSlotStart)
,dateadd(minute,30,TimeSlotEnd)
from TimeSlots
where TimeSlotStart < cast('21:00:00' as time)
)
, TeachingDays as -- Used to return all the time slots above for each day of the week in CROSS JOIN below.
(
select 1 as DaySort
,'Mon' as TeachingDay
union all
select 2 as DaySort
,'Tue'
union all
select 3 as DaySort
,'Wed'
union all
select 4 as DaySort
,'Thu'
union all
select 5 as DaySort
,'Fri'
)
select td.TeachingDay
,t.TimeSlotStart
,t.TimeSlotEnd
from TimeSlots t -- Select all timeslots.
cross join TeachingDays td -- For each day.
left join #Data d -- And find all students that are being taught on that day at the specified time.
on(td.TeachingDay = d.DAY
and t.TimeSlotStart <= d.END_TIME
and t.TimeSlotEnd > d.START_TIME
)
where d.ID is null -- Then only return data where there are no students being taught at this timeslot.
order by td.DaySort
,t.TimeSlotStart;
You could create a Stored Procedure with following steps.
Step 1: Predefine timeslots in a different table.(09:00-10:00, 10:00-11:00 etc)
Step 2: Select count of students
Step 3:
for all the slots
Begin
for all the students
Begin
if(students.actual_starttime =slots.actual_starttime and
students.actual_endtime =slots.actual_endtime
break;
else count=count+1;
End
End
Step 4: if above count matches with count of total students, then slot is free for all the students else slot is not foree for all the students.
Hope this helps. Let me know if you find difficulty with it.
You should have three more tables to make it more simple
i.e. Student, Section and slots
I tried to create 1 more table with half hour slots
create table table2(timeslot time);
insert into table2 values ('9:00:00.0000000');
insert into table2 values ('9:30:00.0000000');
insert into table2 values ('10:00:00.0000000');
insert into table2 values ('10:30:00.0000000');
insert into table2 values ('11:00:00.0000000');
insert into table2 values ('11:30:00.0000000');
insert into table2 values ('12:00:00.0000000');
insert into table2 values ('12:30:00.0000000');
insert into table2 values ('13:00:00.0000000');
insert into table2 values ('13:30:00.0000000');
insert into table2 values ('14:00:00.0000000');
insert into table2 values ('14:30:00.0000000');
insert into table2 values ('15:00:00.0000000');
insert into table2 values ('15:30:00.0000000');
insert into table2 values ('16:00:00.0000000');
insert into table2 values ('16:30:00.0000000');
insert into table2 values ('17:00:00.0000000');
insert into table2 values ('17:30:00.0000000');
insert into table2 values ('18:00:00.0000000');
insert into table2 values ('18:30:00.0000000');
insert into table2 values ('19:00:00.0000000');
insert into table2 values ('19:30:00.0000000');
insert into table2 values ('20:00:00.0000000');
insert into table2 values ('20:30:00.0000000');
insert into table2 values ('21:00:00.0000000');
insert into table2 values ('21:30:00.0000000');
Following SQL will give you free slot and name of student:
Query:
select t1.StudentName,t2.timeslot
from Table2 t2,
Table1 t1
where t2.timeslot<t1.start_time
and t2.timeslot<t1.end_time
and t1.section='A'
group by t1.StudentName,t2.timeslot
order by t2.timeslot
Output:
StudentName timeslot
1 Mary 09:00:00
2 Robert 09:00:00
3 Mary 09:30:00
4 Robert 09:30:00
5 Mary 10:00:00
6 Robert 10:00:00
7 Mary 10:30:00
8 Robert 10:30:00
9 Mary 11:00:00
10 Robert 11:00:00
11 Mary 11:30:00
12 Robert 11:30:00
13 Mary 12:00:00
14 Robert 12:00:00
15 Mary 12:30:00
16 Robert 12:30:00
17 Mary 13:00:00
18 Robert 13:00:00
This is just half task done, I just showed you way to achieve it. Introduce two more joins with student and section table to achieve this.
Shred the day (09:00 to 21:30 interval) into minutes, find free minutes with respect to students of the group and days of interest and group minutes found back as intervals.
CREATE TABLE Table1 (ID varchar(10),StudentName varchar(30), Course varchar(15) ,SECTION varchar(2),DAY varchar(10),
START_TIME time , END_TIME time, actual_starttime time, actual_endtime time);
INSERT INTO Table1 VALUES (111, 'Mary','Science','A','Mon','13:30:00.0000000','16:20:00.0000000','09:00:00.0000000','21:20:00.0000000')
INSERT INTO Table1 VALUES (111, 'Mary','Maths','A','Tue','12:30:00.0000000','13:20:00.0000000','09:00:00.0000000','21:20:00.0000000')
INSERT INTO Table1 VALUES (111, 'Mary','Physics','C','Tue','10:30:00.0000000','11:10:00.0000000','09:00:00.0000000','21:20:00.0000000')
INSERT INTO Table1 VALUES (112, 'Robert','Maths','A','Mon','13:30:00.0000000','16:20:00.0000000','09:00:00.0000000','21:20:00.0000000')
;
-- parameters
declare #tds time = '09:00';
declare #tde time = '21:30';
declare #section varchar(2) = 'A';
create table #daysofinterest (DAY varchar(10) primary key);
insert #daysofinterest (DAY) values ('Mon'),('Tue'),('Fri');
create table #groupmembers(ID int primary key);
insert #groupmembers(ID) values (111),(112);
-- query
select DAY, startt = dateadd(minute, min(n), #tds), endt = dateadd (minute, max(n), #tds)
from (
select DAY, n, grp = n - row_number() over(partition by DAY order by n)
from (
-- all minutes of the day, #tds till #tde
select top (datediff(minute, #tds, #tde)) n = row_number() over(order by (select null))
from sys.all_objects
) tally
cross join #daysofinterest dd
join #groupmembers gm on
not exists (select 1 from table1 t
where t.ID = gm.ID and t.DAY = dd.DAY and SECTION = #section and
dateadd (minute, n, #tds) between t.START_TIME and t.END_TIME )
group by DAY, n
--this minute is free for every group member
having count(*) = (select count(*) from #groupmembers)
) g
group by DAY, grp
order by DAY, min(n)

SQL Counting Total Time but resetting total if large gap

I have a table containing device movements.
MoveID DeviceID Start End
I want to find out if there is a way to sum up the total movement days for each device to the present. However if there is a gap 6 weeks bewtween an end date and the next start date then the time count is reset.
MoveID DeviceID Start End
1 1 2011-1-1 2011-2-1
2 1 2011-9-1 2011-9-20
3 1 2011-9-25 2011-9-28
The total for device should be 24 days as because there is a gap of greater than 6 weeks. Also I'd like to find out the number of days since the first movement in the group in this case 28 days as the latest count group started on the 2011-9-1
I thought I could do it with a stored proc and a cursor etc (which is not good) just wondered if there was anything better?
Thanks
Graeme
create table #test
(
MoveID int,
DeviceID int,
Start date,
End_time date
)
--drop table #test
insert into #test values
(1,1,'2011-1-1','2011-2-1'),
(2,1,'2011-9-1','2011-9-20'),
(3,1,'2011-9-25','2011-9-28')
select
a.DeviceID,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /*6 weeks = 42 days*/ then 0 else datediff(dd,a.Start, a.End_time)+1 /*we will count also the last day*/ end) as movement_days,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /6 weeks = 42 days/ then 0 else datediff(dd,a.Start, a.End_time)+1 /we will count also the last day/ end + case when b.MoveID is null then datediff(dd, a.Start, a.End_time) + 1 else 0 end) as total_days
from
#test a
left join #test b
on a.DeviceID = b.DeviceID
and a.MoveID + 1 = b.MoveID
group by
a.DeviceID
Let me know if you need some explanation - there can be more ways to do that...
DECLARE #Times TABLE
(
MoveID INT,
DeviceID INT,
Start DATETIME,
[End] DATETIME
)
INSERT INTO #Times VALUES (1, 1, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (2, 1, '9/1/2011', '9/20/2011')
INSERT INTO #Times VALUES (3, 1, '9/25/2011', '9/28/2011')
INSERT INTO #Times VALUES (4, 2, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (5, 2, '3/1/2011', '4/20/2011')
INSERT INTO #Times VALUES (6, 2, '5/1/2011', '6/20/2011')
DECLARE #MaxGapInWeeks INT
SET #MaxGapInWeeks = 6
SELECT
validTimes.DeviceID,
SUM(DATEDIFF(DAY, validTimes.Start, validTimes.[End]) + 1) AS TotalDays,
DATEDIFF(DAY, MIN(validTimes.Start), MAX(validTimes.[End])) + 1 AS TotalDaysInGroup
FROM
#Times validTimes LEFT JOIN
#Times timeGap
ON timeGap.DeviceID = validTimes.DeviceID
AND timeGap.MoveID <> validTimes.MoveID
AND DATEDIFF(WEEK, validTimes.[End], timeGap.Start) > #MaxGapInWeeks
WHERE timeGap.MoveID IS NULL
GROUP BY validTimes.DeviceID