SQL Server 2008 CASE Logic in SELECT statement - sql-server-2008

Hello again SQL Server 2008 gurus.
I need to apply the following rules to the setting of a worker's start and end times for their work day (hourly employees) in a SELECT statement. I apologize in advance for my SQL ignorance.
The rule is to set their start time to a value stored in a table field for that worker, if they login on or before their start time (a time stored in the worker starttime column) and therefore get credit for starting at their start time.
If they log out within a 10 minute period before or anytime after their end time stored in a column for the worker, they get credit for their full day, another value stored in a column of the worker table, otherwise they are penalized some percentage of an hour, i.e. their log out time rounded to .25 of an hour less closest to the time they logged out. i.e. if they are set to log out at 4:30, and they log out at 4:18, their log out time is 4:15. If they log out at 4:20, and are set to log out at 4:30, their log out time is 4:30.
The first rule applies to all hourly employees where their workday hours is less than or equal to their expected workday value. The caveat is, for those where overtime is ok (a bit value set to 1). If overtime is permitted, the number of billable hours can exceed the full day value stored for them, and therefore the value of their logout - login time can exceed their fullday value.
The question is, can these rules be calculated in the SELECT statement and if so can I get some help with the code?
The columns containing the information are:
worker.startime (TIME)
worker.endtime (TIME)
worker.overtimeallowed (BIT)
worker.workdayhours (decimal (12,2))
worker.penaltyvalue (decimal (12,2))
If it requires a UDF or stored procedure (since I'm using the Telerik ReportViewer) I'm not sure it would be supported, but that's probably another question.
So far I've gotten some help with applying some CASE logic - calculating whether a worker get's credit for their 1/2 lunch. The code that was supplied works as promised. This, I believe may be an extension to that logic - so I'll provide the code I have here:
-- for testing purposes only.
DECLARE #StartDate AS DateTime
SET #StartDate = CAST('03/25/2012' AS DATE)
DECLARE #EndDate AS DateTime
SET #EndDate = CAST('04/10/2012' AS DATE)
SELECT
w.Firstname
,w.Lastname
,wf.Login
,wf.logout
,ROUND(CAST(DATEDIFF(MI, wf.Login, wf.Logout) AS DECIMAL)/60,2)
- CASE
WHEN DATEDIFF(hour, wf.Login, wf.Logout) < w.MinimumHours THEN
w.LunchDeduction
ELSE
0
END AS [Hours Credited]
FROM Workers AS w
JOIN Workflow AS wf
ON wf.LoggedInWorkerid = w.ID
JOIN Roles AS r
ON w.RoleID = r.RoleID
WHERE (r.Descript = 'Hourly')
AND wf.Login >= #StartDate AND wf.Logout <= #EndDate
ORDER BY w.Lastname, w.Firstname

Here is a sample select dealing with constraints you described. CTEs create tables for testing purposes. Main query shows the calculations. You have worked with datediffs and dateadds so there is no mistery. If you haven't use % before, it is modulo operator used to round time to 15 minutes.
;with worker (ID, overtime, startTime, endTime) as
(
select 1, 1, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 2, 0, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 3, 0, CAST ('08:30' as time), CAST ('16:30' as time)
),
-- Test table of workflows
wf (workerID, login, logout) as
(
select 1, CAST ('2012-03-11 08:20' as datetime), CAST ('2012-03-11 19:33' as datetime)
union all
select 2, CAST ('2012-03-11 08:50' as datetime), CAST ('2012-03-11 16:20' as datetime)
union all
select 3, CAST ('2012-03-11 08:22' as datetime), CAST ('2012-03-11 16:18' as datetime)
)
select wf.workerID,
wf.login,
wf.logout,
-- if starttime > login return startTime else login
case when DATEDIFF(MI, w.startTime, cast (wf.login as time)) < 0
then cast(CAST (wf.login AS date) AS datetime) + w.startTime
else wf.login
end roundedLogin,
case when w.overtime = 1 -- Round to 15 minutes whenever finished
OR
-- Round to 15 minutes if left ten or more minutes before endTime
DATEDIFF(MI, cast (wf.logout as time), dateadd (MI, -10, w.endTime)) > 0
then dateadd (MI, -(DATEPART (MI, wf.logout) % 15), wf.logout)
-- stop at endTime if overtime = 0 OR left job at apropriate time
else cast(CAST (wf.logout AS date) AS datetime) + w.endTime
end roundedLogout
from worker w
inner join wf
on w.ID = wf.workerID
There will be a problem with this approach. When you start to integrate mathematics into original query you will notice that you have to write expressions evaluating roundedLogin and roundedLogout again to calculate billable hours. You cannot reuse alias defined in the same scope, but you can create derived table or view or even calculated fields. View returning columns from workflows and all additional expressions would probably be the best.
Using this view in other queries would simplify things by encapsulating logic at one place.

Related

Finding the area available for the date range

Suppose you have a room which is 100sqft and you want to rent it from 1st Aug to 31st Aug.
Bookings Table schema
startdate|enddate|area|storageid
you have following bookings
06-Aug|25-Aug|50|'abc'
05-Aug|11-Aug|40|'xyz'
18-Aug|23-Aug|30|'pqr'
13-Aug|16-Aug|10|'qwe'
Now somebody requests for booking from 08-Aug to 20-Aug. For this date range the maximum area available is 10sqft (Since, for dates 8,9,10 and 11 Aug only 10sq ft is available.)
How would you create an efficient SQL query to get this? Right now I have very messy and inefficient query which gives wrong results for some cases. I am not posting the query because It is so messy that I can't explain it myself.
I don't necessarily want to solve it using SQL only. If there is an algorithm that can solve it efficiently I would extract all the data from database.
Someone removed SQL Server, but here is the algorithm:
DECLARE #startDate date = '2016-08-09';
DECLARE #endDate date = '2016-08-20';
DECLARE #totalArea decimal(19,2) = 100;
WITH Src AS --Your source table
(
SELECT * FROM (VALUES
('2016-08-06', '2016-08-25', 50, 'abc'),
('2016-08-05', '2016-08-11', 40, 'xyz'),
('2016-08-18','2016-08-23',30,'pqr'),
('2016-08-13','2016-08-16',10,'qwe')
)T(startdate, enddate, area, storageid)
), Nums AS --Numbers table 0..N, N must be greater than ranges calculated
(
SELECT 0 N
UNION ALL
SELECT N+1 N FROM Nums
WHERE N<DATEDIFF(DAY,#startDate,#endDate)
) --Query
--You can use total-maxUsed from range of days
SELECT #totalArea-MAX(Used) FROM
(
--Group by day, sum all used areas
SELECT MidDate, SUM(Used) Used FROM
(
--Join table with numbers, split every day, if room used, return area
SELECT DATEADD(DAY, N, #startDate) MidDate, CASE WHEN DATEADD(DAY, N, #startDate) BETWEEN startDate AND endDate THEN area END Used
FROM Src
CROSS APPLY Nums
) T
GROUP BY MidDate
) T

How to create a loop in a case statement

i have the following SQL statement:
DECLARE #time datetime;
SELECT #time = (select min(CreationDate) from TABLE);
DECLARE #time2 int;
SELECT #time2 = 15;
select ColumnA,
(case when CreationDate between #time and DATEADD(MINUTE,#time2***1**,#time)
then cast (#time2*1 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***2**,#time)
then cast (#time2*2 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***3**,#time)
then cast (#time2*3 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***4**,#time)
then cast (#time2*4 as int)
else 0
end) as 'interval', count(1)
from TABLE
group by
ColumnA,
(case when CreationDate between #time and DATEADD(MINUTE,#time2***1**,#time)
then cast (#time2*1 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***2**,#time)
then cast (#time2*2 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***3**,#time)
then cast (#time2*3 as int)
when CreationDate between #time and DATEADD(MINUTE,#time2***4**,#time)
then cast (#time2*4 as int)
else 0
end)
How can i write the case statement in a loop so the bold number will be a parameter
2.i need that the loop/function will be able to write as many case row as needed according to the parameter in Q.1
thanks a lot !
Hello everyone,
Thanks for your response and comments.
I realized that maybe I did not explain my questions properly. Let me rephrase my questions again.
I have an ETL process that runs and fills a table consisting of column called "ColumnA" which displayed codes, and creation time column called "CreationDate".
I want to divide the results by time of creation. Sometimes by 15 minutes, sometimes by 20 minutes or any other time interval.
So I established a variable called "#time" that say what is the interval length.
The first problem: I do not know how long the ETL process will run, so I do not know how many lines to produce in the CASE statement.
The second problem: the number of CASE statement lines also depends on the interval length in which I choose in the variable "#time". That is, if the process takes an hour and "#time" selected intervals is 15 minutes then I must produce 4 CASE statement lines but if I select "#time" to be 10 minutes then I must produce 6 CASE statement lines…
Who can I do it with parameters?
Thanks in advance for your time and efforts.
Regards,
Alan B.
Don't think of loops when working with SQL. Think of results you want to see.
If I interprete your request correctly, you are selecting all rows with a creation date between the given #time and the following hour. For these you determine the time slice. In your case it's 15 minutes and you want to know, whether a creation date is within the first 15 minutes (then you output 15), or second (then you output 30) and so on. Records with another creation date, no matter whether before or after the hour in question, are given an output 0.
So the only problem is to find the correct formula, which is more or less: get the time difference in minutes, divide by the minute slice, and the resulting full integer will tell you which slice it is, starting with 0 for the first slice in the hour.
That should be more or less:
select columna, interval_end_minute, count(*)
from
(
select
columna,
case when creationdate >= #time and creationdate < dateadd(hour,1,#time) then
(truncate((time_to_sec(datediff(creationdate, #time)) / 60) / #time2, 0) + 1) * #time2
else
0
end as interval_end_minute
from table
) data
group by columna, interval_end_minute;

MySql - Calculating distance in time using 2 values from 1 column (Poor design workaround)

I was granted access to a legacy database in order to do some statistics work.
I've so far gotten everything I need out of it, except I am trying to calculate a distance in time, using 5 values, stored in 4 columns (ARGGGHHH)
Above is a subsection of the database.
As you can see, I have start and stop date and time.
I would like to calculate the distance in time from str_date + str_time to stp_date + stp_time
The issue I have is, the calculation should be performed differently depending on the second value in stp_time.
IFF second value = "DUR".... THen I can just take the first value "01:04:51" in this scenario
IFF second value = anything else. stp_time represents a timecode and not a duration. This must then calculate stp_time - str_time (accounting for date if not same date)
All data is 24 hour format. I have done work with conditional aggregation, but I have not figured this one out, and I have never worked with a malformed column like this before.
Any and all advice is welcome.
Thanks for reading
SELECT
CASE WHEN RIGHT(stp_time,3)="DUR"
THEN
TIMEDIFF(LEFT(stp_time,8), '00:00:00')
ELSE
TIMEDIFF(
STR_TO_DATE(CONCAT(stp_date," ",LEFT(stp_time,8)), '%d/%b/%Y %H:%i:%s'),
STR_TO_DATE(CONCAT(str_date," ",LEFT(str_time,8)), '%d/%b/%Y %H:%i:%s')
)
END AS diff
FROM so33289063
Try this out, you might want a where condition for the subquery
With left and right:
SELECT IF(dur,stp,timediff(str,stp)) FROM(
SELECT STR_TO_DATE(CONCAT(str_date," ",LEFT(str_time,8)), 'd%/%b/%Y %H:%i:%s') as str,
STR_TO_DATE(CONCAT(stp_date," ",LEFT(stp_time,8)), 'd%/%b/%Y %H:%i:%s') as stp,
if(RIGHT(stp_time,3)="DUR",1,0) as dur
FROM my_table
) AS times

Parallelism in stored procedure in mysql?

I have a stored procedure to insert few records on daily basis. Same logic gets executed for each day but in a sequential manner. So to improve the performance, I was thinking to introduce parallelism. So is there a way or could some one point me to some example where I can run some logic in a stored procedure in parallel.
EDIT:
The query I am using in my stored procedure is :
INSERT INTO tmp (time_interval, cnt, dat, txn_id) SELECT DATE_FORMAT(d.timeslice, '%H:%i') as time_interval
, COUNT(m.id) as cnt
, date(d.timeslice) as dat
, "test" as txn_id
FROM ( SELECT min_date + INTERVAL n*60 MINUTE AS timeslice
FROM ( SELECT DATE('2015-05-04') AS min_date
, DATE('2015-05-05') AS max_date) AS m
CROSS
JOIN numbers
WHERE min_date + INTERVAL n*60 MINUTE < max_date
) AS d
LEFT OUTER
JOIN mytable AS m
ON m.timestamp BETWEEN d.timeslice
AND d.timeslice + INTERVAL 60 MINUTE
GROUP
BY d.timeslice;
This query groups the records on hour basis for each day and inserts in tmp table. So I want to run this query in parallel for each day instead of sequential.
Thanks.
Is d a set of DATETIMEs that represent the 24 hours of one day? My gut says it can be simplified a bunch. It can be sped up by adding WHERE n BETWEEN 0 AND 23. Perhaps:
SELECT '2015-05-04' + INTERVAL n*60 MINUTE AS timeslice
FROM numbers
WHERE n BETWEEN 0 AND 23
What is in mytable? In particular, is the 'old' data static or changing? If it is unchanging, why repeatedly recompute it? Compute only for the last hour, store it into a permanent (not tmp) table. No need for parallelism.
If the data is changing, it would be better to avoid
ON m.timestamp BETWEEN d.timeslice
AND d.timeslice + INTERVAL 60 MINUTE
because (I think) it will not optimize well. Let's see the EXPLAIN SELECT....
In that case, use a stored procedure to compute the start and end times and construct (think CONCAT) the ON clause with constants in it.
Back to your question...
There is no way in MySQL, by itself, to get parallelism. You could write separate scripts to do the parallelism, each with its own parameters and connection.

SQL Statement Database

I have a Mysql Table that holds dates that are booked (for certain holiday properties).
Example...
Table "listing_availability"
Rows...
availability_date (this shows the date format 2013-04-20 etc)
availability_bookable (This can be yes/no. "Yes" = the booking changeover day and it is "available". "No" means the property is booked for those dates)
All the other dates in the year (apart from the ones with "No") are available to be booked. These dates are not in the database, only the booked dates.
My question is...
I have to make a SQL Statement that first calls the Get Date Function (not sure if this is correct terminology)
Then removes the dates from "availability_date" WHERE "availability_bookable" = "No"
This will give me the dates that are available for bookings, for the year, for a property.
Can anyone help?
Regards M
Seems like you've almost written the query.
SELECT availability_date FROM listing_availability
WHERE availability_bookable <> 'NO'
AND availability_date >= CURDATE()
AND YEAR(CURDATE()) = YEAR(availability_date)
I think I understand, and you'll obviously confirm. Your "availability_booking" has some records in it, but not every single day of the year, only those that may have had something, and not all are committed, some could have yes, some no.
So, you want to simulate All dates within a given date range... Say April 1 - July 1 as someone is looking to book a party within that time period. Instead of pre-filling your production table, you can't say that April 27th is open and available... since no such record exists.
To SIMULATE a calendar of days for a date range, you can do it using MySQL variables and join to "any" table in your database provided it has enough records to SIMULATE the date range you want...
select
#myDate := DATE_ADD( #myDate, INTERVAL 1 DAY ) as DatesForAvailabilityCheck
from
( select #myDate := '2013-03-31' ) as SQLVars,
AnyTableThatHasEnoughRows
limit
120;
This will just give you a list of dates starting with April 1, 2013 (the original #myDate is 1 day before the start date since the field selection adds 1 day to it to get to April 1, then continues... for a limit of 120 days (or whatever you are looking for range based -- 30days, 60, 90, 22, whatever). The "AnyTableThatHasEnoughRows" could actually be your "availability_booking" table, but we are just using it as a table with rows, no join or where condition, just enough to get ... 120 records.
Now, we can use this to join to whatever table you want and apply your condition. You just created a full calendar of days to compare against. Your final query may be different, but this should get it most of the way for you.
select
JustDates.DatesForAvailabilityCheck,
from
( select
#myDate := DATE_ADD( #myDate, INTERVAL 1 DAY ) as DatesForAvailabilityCheck
from
( select #myDate := '2013-03-31' ) as SQLVars,
listing_availability
limit
120 ) JustDates
LEFT JOIN availability_bookable
on JustDates.DatesForAvailabilityCheck = availability_bookable.availability_date
where
availability_bookable.availability_date IS NULL
OR availability_bookable.availability_bookable = "Yes"
So the above uses the sample calendar and looks to the availability. If no such matching date exists (via the IS NULL), then you want it meaning there is no conflict. However, if there IS a record in the table, you only want those where YES, you CAN book it, the entry on file might not be committed and CAN be in your result query of available dates.