I have a requirement based on dates, where I need to show the count for the staff on the particular date range, Sundays & government holidays should not be counted.
------------------------------------------
|Staff name| less | Full | more | Absent |
------------------------------------------
| name 1 | 1 | 3 | 1 | 1 |
------------------------------------------
| name 2 | 2 | 2 | 2 | 0 |
------------------------------------------
This is my requirement:
Sundays should not be counted
Saturdays will have 4 hours of effort
Week days will have 8 hours of effort
But staff can enter lower or higher than the fixed one.
Here in the table if the staff has worked for 7 hours will be counted +1 in less column, if he works more than 8 hours will be counted +1 in more, if he works exactly 8 hours will be counted +1 in full column, if he absent on the particular date +1 will be counted in absent column.
For Saturday: same as the above but 8 hours will be considered as 4 hours.'
Sunday should not be counted in anyways.
For this requirement do I need to have a new MySQL table consists of all the 365 days of the year with Sundays & govt holidays with in active status??
or we can do it without having table? if so how to do it, pls explain.
Thanks in advance..
$day_of_week = date("D");
That will show you the current day of the week so that will help you know if it is "Sat" or "Sun".
as for the govt holidays, you will need to store those in a table. Probably the easiest way is to have day, month, year. Then you can find out the current day like this:
$mon = date("m");
$day = date("d");
$year = date("Y");
Just use those variables to construct a query and make sure the current date isnt a holiday:
$sql = "SELECT * FROM holidays WHERE day='".$day."' AND month='".$month."' AND year='".$year."'";
....execute query
Related
I have my php mysql currently that get the entire months sales and groups it by days. I am now trying to take that further and separate am vs pm sales. The AM shift is 10am-7pm and PM shift is 7pm-2am. I Know I can group by day then by hour and iterate through and get the am that way but I am sure their is a better way directly in sql.
Thanks for any insight.
SELECT DATE(a_tabs.strDate - INTERVAL 16 HOUR) as day ,
DATE_FORMAT(a_tabs.strDate, '%h') AS hour ,
sum(a_invoices.Total) as total
FROM a_tabs
Right JOIN a_invoices on a_tabs.TabId = a_invoices.TabId
WHERE a_tabs.strDate BETWEEN '2022-03-01 09:00:00' and '2022-03-31 18:00:00'
AND a_invoices.status='c'
and a_tabs.status<>'v'
GROUP BY day , hour
result from this query
So in a given day you have 3 periods:
02:00 to 10:00 [8h, no shift]
10:00 to 19:00 [9h, AM shift]
19:00 to 02:00 [7h, PM shift]
But the trouble with a naieve solution is that the PM shift crosses over the date boundary.
Assuming a simplified table like:
CREATE TABLE sales (
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
rep_id INTEGER UNSIGNED,
dt DATETIME,
amount INTEGER
);
We can correctly align the shifts with the date boundary with dt - INTERVAL 2 HOUR and then something like:
SELECT
DATE(dt - INTERVAL 2 HOUR) 'day',
IF( HOUR(dt - INTERVAL 2 HOUR) BETWEEN 0 AND 7, 'UN',
IF( HOUR(dt - INTERVAL 2 HOUR) BETWEEN 8 AND 16, 'AM', 'PM' )
) AS 'shift',
SUM(amount) AS 'sales'
FROM sales
GROUP BY day, shift;
Sample data omitted for brevity, see on sqlfiddle: http://sqlfiddle.com/#!9/d4fbcb/22/0, but for a sale every 15 minutes on the dot, beginning at 2022-01-01 00:00:00:
| day | shift | sales |
|------------|-------|-------|
| 2021-12-31 | PM | 8 |
| 2022-01-01 | AM | 36 |
| 2022-01-01 | PM | 28 |
| 2022-01-01 | UN | 32 |
| 2022-01-02 | AM | 36 |
| 2022-01-02 | PM | 28 |
| 2022-01-02 | UN | 32 |
You can see that it correctly assigns the sales between 00:00 and 02:00 to the previous day's PM shift, and sales outside the defined shifts as "UN" for undefined.
However, with regard to maintainability, extensibility, and performance: I would not really recommend this approach of calculating the shift duringreport generation at all.
Maintainability: At some point in the future the shift boundary changes, now this query is returning incorrect shift data going forward. The naieve fix is to just change the hours in the query, but now it returns incorrect results for past data.
Extensibilty: At some point in the future a "cover" shift is added for 4PM to 10PM to account for demand. It is not possible to compute using a query like this anymore.
Performance: All of those dt - INTERVAL 2 HOUR and IF() statements add overhead and make it difficult or impossible to use indexes depending on what the requirements off your query are.
What I would suggest is making the "shift" into metadata that is associated with the sale record and calculated at insert time. Depending on your particular requirements, it might just be a string in the sale record, eg: 20220101_AM, or a foreign key relation into more robust schema.
Given both your group by clauses are related to the time. Start by shifting the date so that AM is truly AM and PM is truly PM.
a_tabs.strDate - INTERVAL 7 HOUR
(7 chosen as 7pm end of AM shift).
Use UNIX_TIMESTAMP to get this down to a second value (hours would be better but there isn't a function for that). And then div by a 12 hr interval.
So
SELECT ...
GROUP BY UNIX_TIMESTAMP(a_tabs.strDate - INTERVAL 7 HOUR) DIV (60*60*12)
Is it possible to execute a different select query for each day of the week. I currently have the following columns: id, station_name, week_type and service.
The week_type is an enom value with the following options: 'Mon-Thur', 'Fri', 'Sat', 'Sun', 'Special'.
The service column only has a varchar value of the time of day. It needs to apply as the service operates the same on a weekly schedule depending on the week_type.
+-----------------------------------+------------+-----------+-----------+
| id |station_name| week_type | service |
+-----------------------------------+------------+-----------+-----------+
| 1 | Station1 | Mon-Thur | 08:15:00 |
| | | | |
| 2 | Station2 | Sat | 10:15:00 |
+-----------------------------------+------------+-----------+-----------+
As seen in the table above, when it is Saturday in my timezone and is equal to the week_type, then it should only show Saturday rows. And etc. for the other columns.
Any help would be much appreciated, as I am new to SQL.
I think you really need to work out on the table. Why don't you normalize your table.
station_services
id|station_name
station_working_days
id|station_id|weekday_id|working_hours
If you dont want week days as seperate table then you can hardcode from 1 as sunday to saturday as 7
station_working_days
id|station_id|weekday|working_hours
By normalising you will get all the flexibility in future too.
In case if the stations all the time have the same working hours then use the following table normalisation so that it may help you.
station_services
id|station_name|working_hours
station_working_days
id|station_id|weekday_id
I'm trying to figure out an efficient query for a project I'm working on.
We're recording a switch state into a table, each time it changes, a row is added with the new value (0 or 1).
Here's a simplified structure of the table:
day | hour | state
-----+------+-------
10 | 1 | 1 # day 10
10 | 6 | 0
10 | 21 | 1
11 | 3 | 0 # day 11
11 | 6 | 1
13 | 13 | 0 # day 13
....
Now we need to make a daily overview, something like this:
Day 11 : Switch was on during 0-3, 6-24
SELECT * FROM log WHERE day = 11 will give us only [3,0] and [6,1]. From those we can guess that it started ON and ended ON, but how about day 12?
SELECT * FROM log WHERE day = 12 gives nothing, obviously - there's no clue to guess from.
What is an efficient and reliable way to get the starting and ending state for a given day? Something like "Select one entry before day 12 and one after day 12"?
SELECT
day,
hour,
state
FROM
log
WHERE
day*100+hour
BETWEEN
(SELECT max(day*100+hour) FROM log WHERE day < 12)
AND
(SELECT min(day*100+hour) FROM log WHERE day > 12)
Will give you everything between (including) the last entry before day 12 and the first entry after day 12.
The second part might be unnecessary if you don't need to know when the state changed, and it's enough to know the state didn't change until at least midnight of the selected day.
I need some people to battle a "best practice" for a PHP/MySQL appointment system for a hairdresser I'm currently working on. Hopefully, together we can clear some things up to avoid having to re-do the system afterwards. I've been looking at SO and Google for some tutorials, best practices etc. but I haven't found anything that fits the needs I have.
Basic Information
There are multiple hairdressers available each day, each hairdresser has his/her own agenda containing his/her appointments with customers. Connected to a hairdresser is a table containing the times he/she is available during the week.
Table: people
+----+------+-------+-----------+
| id | name | email | available |
+----+------+-------+-----------+
| 1 | John | i#a.c | Y |
| 2 | Sara | c#i.a | N |
+----+------+-------+-----------+
Table: times (there is a primary key for this table, I left it out of the schedule below)
+-----------+-------------+-----------+-----------+
| people_id | day_of_week | start | end |
+-----------+-------------+-----------+-----------+
| 1 | 1 | 08:00 | 12:00 |
| 1 | 1 | 12:30 | 17:00 |
| 1 | 3 | 09:00 | 12:00 |
| 1 | 4 | 10:30 | 16:00 |
| 1 | 5 | 09:00 | 17:00 |
| 1 | 6 | 09:00 | 12:00 |
| 2 | 1 | 09:00 | 12:00 |
| 2 | 2 | 09:00 | 12:00 |
| 2 | 3 | 09:00 | 12:00 |
+-----------+-------------+-----------+-----------+
As you can see there is a one to many relationship between people and times (obviously, since there are 7 days in a week). But besides that there is also an option to add a break between the hours of a day (see people_id = 1, day_of_week = 1: 08:00-12:00 and 12:30-17:00).
Furthermore there is a table called 'hairdress_types', this is a table containing the various types of appointments one can make (like coloring hair, cutting hair, washing, etc). This table contains the amount of time this appointment takes in minutes.
At last I have a table appointments which is pretty simple:
id, people_id, types_id, sex, fullname, telephone, emailaddress, date_start, date_end
Date start and date end would be full DATETIME fields in MySQL making it easier to calculate using MySQL query functions.
What's the best practice?
So, the way I set things up, a front-end user would select a date in a field triggering an ajax/jquery function that finds all hairdressers available at the choosen date. The user then specificies a hairdresser (this is not mandatory: a user can also choose to select 'Any' hairdresser option) and the type of appointment he/she wants to make.
After submitting the first form, the following information can be used:
date (day, month and year)
people_id (can be 0 for 'Any' or an ID if a hairdresser was selected)
hairdress_type (which is linked to an amount of minutes the appointment takes)
Using this information I would either select the available dates from the selected hairdresser OR I would loop al available hairdressers and their available dates.
This is where my minds gets a mental breakdown! Because what is the best way to check the available dates. The way I thought would be the best was:
Query the hairdressers times for the given date (1 at a time)
Query the appointments table using the startdate of the result of query1 + the amount in minutes the appointment type takes (so: SELECT * FROM appointments WHERE ((date_start >= $start AND date_start <= ($start+$time)) OR (date_end > $start AND date_end <= ($start+$time)) AND people_id = 1)
As soon as no results are found I assume this spot is free and this is presented as an option to the user
The biggers problem I'm facing is point 2: My mind is really going crazy on this query, is this the complete query I need to find appointments matching a specific timespan?
Thanks for reading & thinking along! :-)
// Edit: a little more "testdata":
John - Monday - 12:00 - 17:00.
Appointments:
- 12:00 - 12:30
- 14:30 - 15:30
A user wants to have an appointment which takes 2 hours, in the exampel above I would check:
Is there an appointment between 12:00 and 14:00? Yes,.. proceed to next spot
Is there an appointment between 14:00 and 16:00? Yes,.. proceed to next spot
Is there an appointment between 16:00 and 18:00? Error, not available after 17:00
Thus.. it might be a better option to use "timeblocks" of 10/15 minutes. Making the checks:
12:00 - 14:00
12:10 - 14:10
12:20 - 14:20 etc..
This would find the available spot between 12:30 and 14:30.
// Edit 2: Possbile query to step 2
I've been working out some stuff on paper (a table with appointments and possible empty spots to use). And I came up with the following. An appointment can not be made in case:
There is an appointment with start_date BETWEEN $start and $end
There is an appointment with end_date BETWEEN $start and $end
There is an appointment with start_date < $start and end_date > $end
Querying the above to the appointment table together with people_id would result in either no rows (= free spot) or one/multiple row(s) in which case the spot is taken.
I guess the best way to find open spots is to query the database for blocks of X minutes with a start interval of 10 minutes. The bad side of this solution is that I would neet 6 queries for every hour, which would be about 48 queries for every hairdresser... Any ideas on how to reduce that amount of queries?
In the end I went for a system that generated timestamps for the start and end dates in the database. While checking I added one second to the start and subtracted one second from the end to avoid any overlapping time for appointments.
What did I end up doing
Obviously I'm not sure this is the best practice, but it did work for me. The user starts by selecting their sex and a preference for the day. This sends an AJAX request to get the available personel and various kinds of appointment types (f.e. coloring hair, cutting hair, etc).
When all settings have been choosen (sex, date, personel and type) I start by some simple validations: checking the date, checking if the date("N") is not 7 (sunday). If everything is okay, the more important stuff is started:
1) The appointment type is fetched from the database including the total amount of time this type takes (30 minutes, 45 minutes, etc)
2) The available personal is fetched (a complete list of people on that day or just a single person if one is chosen) including their available times
The personel (or one person) is then looped, starting with their own starting time. At this point I have a set of data containing:
$duration (of the appointment type)
$startTime (starting time of the person selected in the loop)
$endTime (= $startTime + $duration)
$personStart (= starting time of the person)
$personEnd (= end time of the person)
Let's take this demo data:
$duration = 30 min
$startTime = 9.00h
$endTime = 9.30h
$personStart = 9.00h
$personEnd = 12.00h
What I'm doing here is:
while( $endTime < $personEnd )
{
// Check the spot for availability
$startTime = $endTime;
$endTime = $startTime + $duration;
}
Obviously, it's al simplified in this case. Because when I check for availability, and the spot is not free. I set the $startTime to be equal to the latest appointment found and start from there in the loop.
Example:
I check for a free spot at 9.00 but the spot is not free because there's an appointment from 9.00 till 10.00, then 10.00 is returned and $startTime is set to 10.00h instead of 9.30h. This is done to keep the number of queries to a minimum since there can be quiet a lot.
Check availability function
// Check Availability
public static function checkAvailability($start, $end, $ape_id)
{
// add one second to the start to avoid results showing up on the full hour
$start += 1;
// remove one second from the end to avoid results showing up on the full hour
$end -= 1;
// $start and $end are timestamps
$getAppointments = PRegistry::getObject('db')->query("SELECT * FROM appointments WHERE
((
app_start BETWEEN '".date("Y-m-d H:i:s", $start)."' AND '".date("Y-m-d H:i:s", $end)."'
OR
app_end BETWEEN '".date("Y-m-d H:i:s", $start)."' AND '".date("Y-m-d H:i:s", $end)."'
)
OR
(
app_start < '".date("Y-m-d H:i:s", $start)."' AND app_end > '".date("Y-m-d H:i:s", $end)."'
))
AND
ape_id = ".PRegistry::getObject('db')->escape($ape_id));
if(PRegistry::getObject('db')->num_rows($getAppointments) == 0) {
return true;
} else {
$end = 0;
foreach(PRegistry::getObject('db')->fetch_array(MYSQLI_ASSOC, $getAppointments, false) as $app) {
if($app['app_end'] > $end) {
$end = $app['app_end'];
}
}
return $end;
}
}
Since I'm storing appointments as "From:10.00 Till:11.00" I have to make sure to check spots from 11:00:01 till 11:59:59, because otherwise the appointment at 11:00 will show in the results.
At the end of the function, in case an appointment is found, I loop the results and return the latest end. This is the next start in the loop I mentioned above.
Hopefully this can be of any help to anyone. Just as info: ape_id is the ID of the "Appointment Person" it is linked with.
Use MySQL keyword BETWEEN.
SELECT * FROM mytable WHERE mydate BETWEEN start_date AND end_date;
If you want to see if an appointment is available between now and a day, you could do this:
$enddate = strtotime('+1 day', time());
SELECT * FROM mytable WHERE mydate BETWEEN NOW() AND {$enddate};
Source
I need this for rent a car price calculation. Cars prices are different according to seasons.
I have a season_dates table like this
id slug start end
1 low 2011-01-01 00:00:00 2011-04-30 00:00:00
2 mid 2011-05-01 00:00:00 2011-06-30 00:00:00
3 high 2011-07-01 00:00:00 2011-08-31 00:00:00
4 mid 2011-09-01 00:00:00 2011-10-31 00:00:00
5 low 2011-11-01 00:00:00 2011-12-31 00:00:00
Users selecting days, for example:
start_day 08/20 end_day 08/25
My query like that:
SELECT * from arac_donemler
where DATE_FORMAT(start, '%m/%d') <= '08/20'
and DATE_FORMAT(end, '%m/%d') >= '08/25'
This gives me high season that's correct.
But what I couldn't handle is: what if user selects a date range between 2 seasons?
For example from 20 August to 05 September.
This time I have to find that date ranges belongs to which seasons?
And I have to calculate how many days per each seasons?
For the example above,
high season ending at 31 August. So 31-20 = 11 days for high season, 5 days for mid season.
How can I provide this separation?
I hope I could explain it.
I tried so many things like join table inside but couldn't succeed it.
I'll let others chime in with the right way to do date comparisons in SQL (yours almost certainly kills indexing for the table), but for a start, you can get exactly the seasons that are relevant by
select * from arac_donemler
where end >= [arrival-date]
and start <= [departure-date]
Then you should do the rest of your processing (figure out how many days in each season and so forth) in the business logic instead of in the database query.
I would store all single days within a table.
This is a simple example.
create table dates (
id int not null auto_increment primary key,
pday date,
slug tinyint,
price int);
insert into dates (pday,slug,price)
values
('2011-01-01',1,10),
('2011-01-02',1,10),
('2011-01-03',2,20),
('2011-01-04',2,20),
('2011-01-05',2,20),
('2011-01-06',3,30),
('2011-01-07',3,30),
('2011-01-08',3,30);
select
concat(min(pday),'/',max(pday)) as period,
count(*) as days,
sum(price) as price_per_period
from dates
where pday between '2011-01-02' and '2011-01-07'
group by slug
+-----------------------+------+------------------+
| period | days | price_per_period |
+-----------------------+------+------------------+
| 2011-01-02/2011-01-02 | 1 | 10 |
| 2011-01-03/2011-01-05 | 3 | 60 |
| 2011-01-06/2011-01-07 | 2 | 60 |
+-----------------------+------+------------------+
3 rows in set (0.00 sec)
EDIT. Version with grandtotal
select
case
when slug is null then 'Total' else concat(min(pday),'/',max(pday)) end as period,
count(*) as days,
sum(price) as price_per_period
from dates
where pday between '2011-01-02' and '2011-01-07'
group by slug
with rollup;
+-----------------------+------+------------------+
| period | days | price_per_period |
+-----------------------+------+------------------+
| 2011-01-02/2011-01-02 | 1 | 10 |
| 2011-01-03/2011-01-05 | 3 | 60 |
| 2011-01-06/2011-01-07 | 2 | 60 |
| Total | 6 | 130 |
+-----------------------+------+------------------+
4 rows in set (0.00 sec)
edit. Stored procedure to populate table
delimiter $$
create procedure calendario(in anno int)
begin
declare i,ultimo int;
declare miadata date;
set i = 0;
select dayofyear(concat(anno,'-12-31')) into ultimo;
while i < ultimo do
select concat(anno,'-01-01') + interval i day into miadata;
insert into dates (pday) values (miadata);
set i = i + 1;
end while;
end $$
delimiter ;
call calendario(2011);
If you have a table RENTAL too (the real version would need a lot of other details in it):
CREATE TABLE Rental
(
start DATE NOT NULL,
end DATE NOT NULL
);
and you populate it with:
INSERT INTO rental VALUES('2011-08-20', '2011-09-05');
INSERT INTO rental VALUES('2011-08-20', '2011-08-25');
then this query produces a plausible result:
SELECT r.start AS r_start, r.end AS r_end,
s.start AS s_start, s.end AS s_end,
GREATEST(r.start, s.start) AS p_start,
LEAST(r.end, s.end) AS p_end,
DATEDIFF(LEAST(r.end, s.end), GREATEST(r.start, s.start)) + 1 AS days,
s.id, s.slug
FROM rental AS r
JOIN season_dates AS s ON r.start <= s.end AND r.end >= s.start;
It yields:
r_start r_end s_start s_end p_start p_end days id slug
2011-08-20 2011-09-05 2011-07-01 2011-08-31 2011-08-20 2011-08-31 12 3 high
2011-08-20 2011-09-05 2011-09-01 2011-10-31 2011-09-01 2011-09-05 5 4 mid
2011-08-20 2011-08-25 2011-07-01 2011-08-31 2011-08-20 2011-08-25 6 3 high
Note that I'm counting 12 days instead of 11; that's the +1 in the days expression. It gets tricky; you have to decide whether if the car is returned on the same day as it is rented, is that one day's rental? What if it is returned the next day? Maybe the time matters? But that gets into detailed business rules rather than general principles. Maybe the duration is the larger of the raw DATEDIFF() and 1? Also note that there is only the rental start and end dates in this schema to identify the rental; a real schema would have some sort of Rental Agreement Number in the rental table.
(Confession: simulated using IBM Informix 11.70.FC2 on MacOS X 10.7.1, but MySQL is documented as supporting LEAST, GREATEST, and DATEDIFF and I simulated those in Informix. The most noticeable difference might be that Informix has a DATE type without any time component, so there are no times needed or displayed.)
But [...] seasons period always same every year. So I thought to compare only days and months. 2011 isn't important. Next years just 2011 will be used. This time problem occurs. For example low season includes November, December and then go to January, February, March, April. If a user selects a date range 01.05.2011 to ...2011 There is no problem. I just compare month and day with DATE_FORMAT(end, '%m/%d'). But if he chooses a range from December to next year January, how am I gonna calculate days?
Notice that 5 entries per year in the Season_Dates table is not going to make an 8" floppy disk break sweat over storage capacity for a good few years, let alone a 500 GiB monster disk. So, by far the simplest thing is to define the entries for 2012 in 5 new rows in the Season_Dates table. That also allows you to handle the fact that in December, the powers-that-be decide the rules will be different (20th December to 4th January will be 'mid', not 'low' season, for example).