Determining time ranges within pre-defined shifts in MySQL - mysql

I'm working with a timecard database and trying to determine how much time for each punch falls into each one of three distinct shift periods.
For example
shift 1 = 7AM - 3pm
shift 2 = 3pm - 11pm
shift 3 = 11pm - 7am
Joe clocks in at 6:45AM and out at 1:45PM
15 minutes of this would need to be calculated as time on shift 3, but I'm not sure how to go about slicing out that bit of time in MySQL. All I have are a time in and time out field.
There are three shift periods:
Shift TimeStart TimeEnd
1 07:00 15:00
2 15:00 23:00
3 23:00 07:00
Sample Data
ID TimeIn TimeOut Hours
100 2014-07-31 06:45 2014-07-31 13:45 7
Desired Result
ID Shift TimeWorked
100 1 06:45
100 2 00:00
100 3 00:15
SQL Fiddle

I was able to come up with a solution for this using PHP.
What I did was loop through each punch, minute by minute, and determine what shift each one minute time span applies to. Within the loop, I increment one of 4 variable for shifts 1, 2, 3 or 0(no shift pay), and at the end, dump those variables to the database for the record being analyzed.
$query = "SELECT * FROM source_filtered_timecard";
$result_set = mysqli_query($connection, $query);
while($record = mysqli_fetch_assoc($result_set)) {
$checkCount++;
$shift1_hours = 0; $shift2_hours = 0;
$shift3_hours = 0; $shift0_hours = 0;
$time = strtotime($record['in_time']);
$time_out = strtotime('-1 Minute',strtotime($record['out_time']));
while($time <= $time_out) {
$mysql_time = date('G:i:s',$time);
//SELECT SHIFT CODE THAT APPLIES TO CURRENT PIT//
$query = "SELECT shift FROM shift_rules WHERE STR_TO_DATE('{$mysql_time}','%H:%i:%S') BETWEEN start_time_24 AND end_time_24 LIMIT 1";
$current_shift_set = mysqli_query($connection, $query);
if(mysqli_num_rows($current_shift_set) == 1) {
$current_shift = mysqli_fetch_assoc($current_shift_set);
if($current_shift['shift'] == '1'){$shift1_hours++;}
elseif($current_shift['shift'] == '2'){$shift2_hours++;}
elseif($current_shift['shift'] == '3'){$shift3_hours++;}
else{$shift0_hours++;}
} else {
$shift0_hours++;
}
//INCRIMENT TIME BY 1 MINUTE//
$time = strtotime("+1 minute",$time);
}
$shift1_hours = $shift1_hours/60;
$shift2_hours = $shift2_hours/60;
$shift3_hours = $shift3_hours/60;
$shift0_hours = $shift0_hours/60;
//UPDATE TIMECARD ROWS WITH SHIFT HOURS//
$query = "UPDATE source_filtered_timecard
SET shift1_time = {$shift1_hours},
shift2_time = {$shift2_hours},
shift3_time = {$shift3_hours},
shift0_time = {$shift0_hours}
WHERE id = '{$record['id']}'";
$update = mysqli_query($connection, $query);
}

I'd do this in PHP. Looking at Joe's example, it is initially tempting to try to work out how his data maps onto the shift rules. However, I think it would be a neater solution to do it the other way around i.e. map the rules onto his data, until there is no data to classify.
The algorithm might go a bit like this:
Joe's remaining time is 6:45 - 13:45
Let's map the first rule onto it (i.e. how much of this rule contributes to that range?):
shift 1 = 7:00 - 15:00 (6:45 hours)
Now Joe's remaining time is:
6:45 - 7:00
Do the next rule:
shift 2 = 15:00 - 23:00 (0 hours)
Joe's remaining time is therefore unchanged. And finally the last rule:
shift 3 = 23:00 - 1d7:00 (0:15 hours)
There are a few things to note:
The amount of worked time could be stored in an array (a "worked time set"). It starts off as a simple start and end, but if a rule removes a chunk of time from the middle, it may split into two starts and two ends
When applying a rule, convert them to actual timestamps (i.e. a date and a time) so the wrapping to the next day works correctly
Write a function that takes a worked time set, plus a rule start and end timestamp, modifies a worked time set, and returns a number of hours for the rule

First you need to add day column for differentiate time from 23:59:59 to next day time.
id| shift_name | time_start | time_end | day
1 | Day Shift | '07:00:00' | '18:59:59' | 1
2 | Night Shift | '19:00:00' | '06:59:59' | 2
Procedure :
DELIMITER $$ CREATE PROCEDURE sp_check_shift(IN intime time) PROC: begin IF(intime>='00:00:01' AND intime<='23:59:59') THEN IF ( SELECT 1 FROM tbl_shift WHERE time_start<=intime AND time_end>=intime AND day=1) THEN SELECT shift_name FROM tbl_shift WHERE time_start<=intime AND time_end>=intime AND day=1;ELSEIF ( SELECT 1 FROM tbl_shift WHERE time_start<=intime AND day=2) THEN SELECT shift_name FROM tbl_shift WHERE time_start<=intime AND day=2;ELSEIF ( SELECT 1 FROM tbl_shift WHERE time_end>=intime AND day=2) THEN SELECT shift_name FROM tbl_shift WHERE time_end>=intime AND day=2;END IF;ELSE SELECT 'Invalid Time' shift_name;END IF;END$$ delimiter ;

Related

How to get the every week by google spread sheet

I want to have date of every weeks.
I put 07/02/2022 in F5 cell and put =EDATE(F5 + 7) in G5 cell
But N/A comes in G5
I guess maybe I put 07/02/2022 in F5 in a wrong way..
What should I do?
read the error message:
Wrong number of arguments to EDATE. Expected 2 arguments, but got 1 arguments.
correct G5 to =EDATE(F5,7)
In case you refer to tomorrow's date, that is 2/7/2022.
You are, if you read the help, adding 7 months with the command above.
The date of the next week starting on any day
function nextWeekDateStartingOnThis(day = 1) {//Sun(0) - Sat(6)
let dt = new Date();
let nd = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate() + 7 - dt.getDay() + day);
Logger.log(nd);
return nd;//this returns the date of the next monday because day = 1
}
If you want the date of the next tuesday then make day = 2

Checking to see if today is in the scheduled day utilizing a binary mapping table in SQL

I am pulling XML data from a 3rd party application using SQL Server 2008 (which I only have read-only access to the DB) and it stores the day or days of the week a job is supposed to run in one of those XML fields.
SQL uses a recursive day code of Sunday=1, Monday=2, Tuesday=4, Wednesday=8, Thursday=16, Friday=32, Saturday=64.
I am pulling the day from this XML field like the following.
case
when (job.SJDefn.value('(schedules/schedule/name)[1]', 'varchar(30)') ) like '%Week%'
then job.SJDefn.value('(/schedules/schedule/recurring/date/week/day_of_week)[1]', 'int')
else 0
end as JDOW,
I was originally utilizing the this to determine the current date of the week as but the values were obviously not compatible.
DATEPART(dw, getdate()) AS CDOW, -- Sun 1 Mon 2 Tue 3 Wed 4 Thu 5 Fri 6 Sat 7`
So I moved to:
case DATEPART(dw, getdate())
when 1 then 1 -- Sunday (1=1)
when 2 then 2 -- Monday (2=2)
when 3 then 4 -- Tueday (3=4
when 4 then 8 -- Wednesday (4=8)
when 5 then 16 -- Thursday (5=16)
when 6 then 32 -- Friday (6=32)
when 7 then 64 -- Saturday (7=64)
else NULL
end as CDOW,
The challenge is that it is an easy translation when the job just runs one day of a week.. but what about Monday/Wednesday/Friday well that is 42 which is an aggregate of the days Monday (2) + Wednesdays (8) + Friday (32).
I could translate this to text like in this question and do a string compare to a temp table but that seems inefficient.
I know that there is a table that can be built like this code to build a comparison table and I've checked the SQL Server Agent documentation (which this isn't but it is quite similar.
It seems like all possible combinations for Sunday - Saturday are basically a bitmap ranged from 1-127.. such as 42 = 0010 1010 which could be an on/off values for each day (first position always 0, 127 = 0111 111) and with that.
Position 1 = Always 0 ; binary 0000 0000
Position 2 = Sunday 1 ; binary 0000 0001
Position 3 = Monday 2 ; binary 0000 0010
Position 4 = Tueday 4 ; binary 0000 0100
Position 5 = Wednesday 8 ; binary 0000 1000
Position 6 = Thursday 16 ; binary 0001 0000
Position 7 = Friday 32 ; binary 0010 0000
Position 8 = Saturday 64 ; binary 0100 0000
I am thinking about how to potentially use a bitwise & operator but it compares the entire bit for an exact match and not a single position as I understand it so not thinking it will accomplish exactly what I want.
What I want is if the current day is in the schedule I get a true / false result from the comparison. I don’t care about interpreting the values into plain English.. For example if the string was 0011 1110 (Monday - Friday), then if the current day value is the equivalent of 0000 0010 (Monday) I be get true. If it was Sunday (0000 0001) it would be false if the reference was that 0010 1110.
I would really think there is a much simpler way of simply checking if the current day falls into the configuration in a few lines vs. building a temporary table to compare against.
So my question: given the information above, is there a simple function / query that I can execute to compare the two and return a boolean result (0/1 or Y/N) if the the current day matches the schedule?
This will give you a 1/0 result for the current date given a schedule bitmap:
declare #Today as Date = GetDate();
-- Assuming that ##DateFirst is correctly set:
declare #DoW as Int = DatePart( weekday, #Today );
-- Shift a bit to the appropriate position.
declare #Mask as Int = Power( 2, #DoW - 1 );
-- Sample schedule bitmap.
declare #Schedule as Int = 42;
-- Sign is used to convert the result of the bitwise-and to a 0 or 1.
-- Any positive value, indicating the corresponding bit is set, will return 1.
-- If there is no match, the result will be zero.
select #Today as Today, #DoW as Dow, #Mask as Mask, #Schedule as Schedule,
Sign( #Schedule & #Mask ) as IsScheduledToday;
As a professor once said, "you're always off by one in this business." It may need a tweak, but should be close.
It's a little difficult to tell exactly what you're trying to do here. You've explained the problem, but didn't provide any desired output. Since you have read-only access, however, I assume you're only interested in querying the data.
However, if you have an integer like 42, and you want to test bitwise if the bit for Monday is set (that is, 2's place), then you do this:
42 & 2 = 2
If you want to display days of the week you could do something like:
SELECT
CASE WHEN JDOW & 1 = 1 THEN 'U' ELSE '' END
+ CASE WHEN JDOW & 2 = 2 THEN 'M' ELSE '' END
+ CASE WHEN JDOW & 4 = 4 THEN 'T' ELSE '' END
+ CASE WHEN JDOW & 8 = 8 THEN 'W' ELSE '' END
+ CASE WHEN JDOW & 16 = 16 THEN 'R' ELSE '' END
+ CASE WHEN JDOW & 32 = 32 THEN 'F' ELSE '' END
+ CASE WHEN JDOW & 64 = 64 THEN 'S' ELSE '' END AS scheduled_days
FROM (VALUES (42),(84),(96), (4)) UnnamedTable (JDOW)
If you want you could create another table:
CREATE TABLE BitwiseWeekDay (
code tinyint primary key not null,
day_name nvarchar(10) not null,
day_short_name nvarchar(4) not null,
day_code nvarchar(1) not null
)
INSERT INTO BitwiseWeekDay VALUES
(1,'Sunday','Sun','U'),
(2,'Monday','Mon','M'),
(4,'Tuesday','Tue','T'),
(8,'Wednesday','Wed','W'),
(16,'Thurday','Thur','R'),
(32,'Friday','Fri','F'),
(64,'Saturday','Sat','U')
SELECT u.JDOW,
b.code,
b.day_name
FROM (VALUES (42),(84),(96), (4)) u (JDOW)
INNER JOIN BitwiseWeekDay b
ON u.JDOW & b.code = b.code
ORDER BY u.JDOW, b.code
But, I don't really know what you're looking for.

Octave Calculus with matrix

i'm developing a program in octave that i will explain as i put the code.
So i have this matrix in a file called matprec.m:
function [res1] = obtemDadosPrec()
res1 = [
1,2001,1,2,0.00;
1,2001,1,5,5.33;
2,2001,1,5,4.57;
3,2001,1,5,5.33;
4,2001,1,5,5.59;
5,2001,1,5,4.32;
2,2001,1,13,0.00;
3,2001,1,13,0.00;
4,2001,1,13,0.00;
3,2001,1,30,30.73;
2,2001,2,1,1.02;
3,2001,2,1,1.52;
4,2001,2,1,1.78;
5,2001,2,1,1.27;
1,2001,2,2,1.78;
2,2001,2,2,1.27;
3,2001,2,2,1.78;
4,2001,2,2,2.03;
5,2001,2,2,1.78;
1,2001,3,4,18.03;
3,2001,3,4,15.75;
5,2001,3,4,17.53;
1,2001,3,5,13.46;
2,2001,3,5,12.19;
3,2001,3,5,11.94;
4,2001,3,5,9.65;
5,2001,3,5,10.92;
2,2001,4,30,0.00;
4,2001,4,30,0.00];
format short g
return
endfunction
so in this matrix the first column is just the station where we measure the amount of precipitation, the second is the year, the third is the month, the fourth is the day and the fifth is the value of precipitation.
And what i want to do in another file is call this matrix and do the following calculus, in the month 1 i want do the average on all the days for example:
in month 1 day 5 i have 5 values 5.33, 4.57, 5.33, 5.59, 4.32, so i would do
(5.33 + 4.57 + 5.33 + 5.59 + 4.32)/5 = 5.028
And i want to do that for all the days and when i have all the days i would add them all to know the amount of precipitation in that month, and do that for all the 4 months.
I'm kind of stuck there if you could help me i would appreciate, thanks a lot!
First, get your array
>> Result = obtemDadosPrec();
Then get a logical array where rows corresponding to month == 1 are true (i.e. 1) and all others are false (i.e. 0)
>> month1Indices = Result(:,3) == 1;
Use this logical array to perform logical indexing and isolate only the 'true' rows.
>> month1Rows = Result(month1Indices, :);
Repeat same procedure to isolate 'day 5'
>> day5Indices = month1Rows(:,4) == 5;
>> day5Rows = month1Rows(day5Indices , :);
Calculate the average of the 5th column.
>> mean(day5Rows(:,5))
ans = 5.028

Weighted rotator table design

I am having a hard time with this very simple project. Maybe it's because it's Monday, I'm not sure.
I have a table that looks like this:
id | weight | hits
------------------------------------------------
1 | 4 | 0
2 | 1 | 0
3 | 2 | 0
Obviously, the hits column will increment by 1 each time that particular record is selected (We will run this update from within our PHP script).
How can I best retrieve these records by their weight? What I mean is that if I ran my query 15 times, I would want the following IDs returned:
1
1
1
1
3
3
2
1
1
1
1
3
3
2
1
We have a simple formula in place that we got online that retrieves a random weighted result, but we aren't running the formula enough to statistically balance out the results, so instead we need to do a simple rotation as described above.
I know that this is a simple problem to solve, but I'm having a hard time coming up with the best way to do it today.
I hope I've been clear enough in my description of the problem.
Unless I'm missing something (it is Monday here too after all), can't you just sort by hits / weight and LIMIT 1?
Run:
SELECT id FROM tbl WHERE weight - hits > 0 ORDER BY weight DESC LIMIT 0,1
With the last result, run:
UPDATE tbl SET hits = hits + 1, total_hits = total_hits + 1 WHERE id = (THE RESULT);
Then run
SELECT (Sum(weight-hits)) FROM tbl
If result = 0
UPDATE tbl SET hits = 0
EDIT:
It's possible to make it with two queries:
$query="SELECT id FROM tbl, weight - hits AS diff WHERE weight - hits > 0 ORDER BY weight DESC";
$result=mysql_query($query);
$num=mysql_numrows($result);
$i=0;
$id=mysql_result($result,$i,"id");
$diff =0;
while ($i < $num) {
$diff= $diff + mysql_result($result,$i,"diff");
++$i;
}
if( $diff = 1 and $i = 1);
$query2="UPDATE tbl SET hits = 0, total_hits = total_hits + IF(id='$id',1,0)";
else{
$query2="UPDATE tbl SET hits = hits + 1, total_hits = total_hits + 1 WHERE id = '$id'";
}
mysql_query($query2);

Calling a Scalar-valued Function in SSIS

Is there any way to execute a scalar-valued function from within a Derived Column transformation in SSIS?
-Scenario-
I have a function in my source DB that converts weights based on a UOM value in the record's UOM column. I want to utilize this function in the ETL process to ensure that my weight measures are always pounds. Can I call this function from within a Derived Column? If not, is there another transformation task I could utilize within the Data Flow (trying to avoid staging columns)?
dbo.Tasks table
id | Name | netWeight | grossWeight | UOM
12 Task12 30000 50000 10
dbo.MeasurementUnits table
id | Name | Shortname | Type | Precision
12 Kilogram kg 3 10000
14 Pound lb 3 10000
dbo.GetConvertedWeight function
ALTER FUNCTION [dbo].[GetConvertedWeight](#iWeight money, #ifromUOM int, #iToUOM int)
RETURNS money
AS
BEGIN
DECLARE #lConvertedWeight money,
#lKgToGrams money,
#lLbToGrams money,
#lOzToGrams money,
#lWeightInGrams money
--convert the weight to grams first.
SELECT #lWeightInGrams = CASE WHEN #iFromUOM = 12 THEN (ISNULL(#iWeight,0) * 1000)
WHEN #iFromUOM = 14 THEN (ISNULL(#iWeight,0) * 453.5924)
WHEN #iFromUOM = 15 THEN (ISNULL(#iWeight,0) * 28.3495)
WHEN #iFromUOM = 13 THEN (ISNULL(#iWeight,0))
ELSE ISNULL(#iWeight,0)
END
--Convert the converted weight to grams to the desired weight
SELECT #lConvertedWeight = CASE WHEN #iToUOM = 12 THEN (ISNULL(#lWeightInGrams,0) / 1000)
WHEN #iToUOM = 13 THEN ISNULL(#lWeightInGrams,0)
WHEN #iToUOM = 14 THEN (ISNULL(#lWeightInGrams,0)/453.5924)
WHEN #iToUOM = 15 THEN (ISNULL(#lWeightInGrams,0) / 28.3495 )
ELSE (ISNULL(#lWeightInGrams,0)/453.5924)
END
RETURN #lConvertedWeight
Example function call
dbo.GetConvertedWeight(dbo.Tasks.netWeight, dbo.Tasks.weightUOM, 14) AS netWeight
Nope. What you'll want is an OLE DB Command to do that. Send the results to an Output Column, and life should be peachy keen for you--at least with regards to SSIS.