in my component I have created an agenda, where user can save their appointments.
Agenda table is quite simple: there is a title, description and start/end datetime fields.
When a user adds a new event, I'd wish to hint him with the first empty spot.
How can I achieve that?
Is that possible with a single/bunch queries, or I have to create a loop until I find the first empty spot?
For example this is my table:
| ID | Start date | End date |
| 1 | 2012-06-14 09:00:00 | 2012-06-14 09:32:00 |
| 2 | 2012-06-14 15:00:00 | 2012-06-14 15:45:00 |
| 3 | 2012-06-14 18:20:00 | 2012-06-14 18:55:00 |
The first free datetime should be 2012-06-14 09:33:00, how can I fetch this date?
Interesting challenge to be done in one query :) Took me a while but I came with solution. With these assumptions:
minimum appointment duration is 30 minutes
appointment starts and ends in one day
day starts at 9:00 and ends at 17:00
minimum interval between ending and starting times is 1 minute
There are 4 cases to take into consideration:
There is some free time in the morning
There is some free time during the day
First free slot is next day to the last appointment in db
You have all free slots from now on
So yo have to select minimum form these dates.
And the query:
SELECT
MIN(next_start_date) AS next_start_date
FROM
(
(
SELECT
DATE_FORMAT(t1.start_date,'%Y-%m-%d 09:00:00') AS next_start_date
FROM
agenda t1
WHERE
t1.start_date > NOW()
AND
TIME(t1.start_date) > '09:30:00'
AND
NOT EXISTS(
SELECT 1 FROM agenda t2 WHERE DATE(t2.start_date) = DATE(t1.start_date) AND TIME(t2.start_date) <= '09:30:00'
)
LIMIT 1
)
UNION
(
SELECT
t3.end_date + INTERVAL 1 MINUTE AS next_start_date
FROM
agenda t3
WHERE
t3.start_date > NOW()
AND
TIME(t3.start_date) >= '09:00:00'
AND
TIME(t3.end_date) < '16:30:00'
AND NOT EXISTS
(SELECT 1 FROM agenda t4 WHERE TIMESTAMPDIFF(MINUTE,t3.end_date,t4.start_date) BETWEEN 0 AND 30 )
ORDER BY
t3.start_date ASC
LIMIT 1
)
UNION
(
SELECT CONCAT(CAST(DATE((SELECT MAX(t5.start_date) + INTERVAL 1 DAY FROM agenda t5 WHERE t5.start_date > NOW())) AS CHAR), ' 09:00:00') AS next_start_date
)
UNION
(
SELECT
IF(
TIME(NOW()) < '09:00:00',
DATE_FORMAT(NOW(),'%Y-%m-%d 09:00:00'),
IF(
TIME(NOW()) < '16:30',
NOW(),
DATE_FORMAT(NOW() + INTERVAL 1 DAY,'%Y-%m-%d 09:00:00' )
)
) AS next_start_date
FROM
(SELECT 1) t6
WHERE NOT EXISTS(
SELECT 1 FROM agenda t7 WHERE t7.start_date > NOW()
)
LIMIT 1
)
) t
Of course it is not perfect - when there next free slot occurs in the next day, there's a chance that it is Saturday or other day off from work. Taking it into consideration, the best way will be to check if returned is valid work day, if not then repeat the query with NOW() replaced by datestring of next valid work day.
Related
I have two tables in mysql (shop_details,booking)
shop_details
id shop_id open_time close_time
1 1 09:00Am 09:00Pm
2 5 10:00Am 09:00Pm
booking
id shop_id start_time end_time
1 1 10:30am 11:10Am
1 5 02:00pm 02:45pm
Now I want if I want to know about shop_id (1) then I want to get booked time (after shop open time) and avaliable time (till shop close) with every half hour, for example I want following result
shop_id start_time end_time status
1 09:00am 09:30am avaliable
1 09:30am 10:00am avaliable
1 10:00am 10:30am avaliable
1 10:30am 11:10am booked
1 11:10am 11:40am avaliable
...
1 08:30am 09:00pm booked
You need a list of times -- then you can get the information using a join or correlated subquery:
select t.tme,
(case when exists (select 1
from booking b
where b.start_time <= t.tme and
b.end_time > t.tme
)
then 'booked' else 'available'
end) as status
from (select cast('00:00' as time) as tme union all
select cast('00:30' as time) as t union all
select cast('01:00' as time) as t union all
. . .
select cast('23:50' as time) as t
) t join
(select sd.*
from shop_details sd
where sd.shop_id = 1
) sd1
on t.tme >= sd1.open_time and
t.tme <= sd1.close_time;
first, use while function to create a loop that will repeat the code until reaching close time,
inside while function, u can use IF statement to make Availability status base on dates
goold luck
Can anyone help me solve my complicated sql request?
I have an html page with a product list and to every item i want to join loading and sales information for last 4 months.
My desired result is:
Item1 - Month0 (12 in, 0 out), Month-1 (33 in, 36 out)......
Item2 - Month0 (10 in, 30 out), Month-1 (0 in, 66 out)......
My SQL Queries:
Products(simplified) in method "getProducts":
Select item_id, item_name
From Products
Loading
SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS YearMonth,SUM(load_qty) AS total,
FROM loading
GROUP BY YearMonth
WHERE item_id = ?
ORDER BY YearMonth DESC ', array($productId));
Sales
SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS YearMonth,SUM(load_qty) AS total,
FROM sales
GROUP BY YearMonth
WHERE item_id = ?
ORDER BY YearMonth DESC ', array($productId));
Then I got PHP file to call SQL method:
$products = $productManager->getProducts();
$this->data['portalProducts'] = $portalProducts;
And final HTML file with product list:
<?php foreach ($products as $product): ?>
<table>
<tr>
<td><?php echo date("Y - m", strtotime("now"))?></td> //current month
<td>//here goes current month load</td>
<td>//here goes current month sale</td>
</tr>
<tr>
<td><?php echo date("Y - m", strtotime("-1 month"))?></td> //last month
<td>//here goes last month load</td>
<td>//here goes last month sale</td>
</tr>
..............
</table>
Is it possible to combine all of the results from the above queries into one request and then display it as I mentioned in the beginning...
Thanks in advance.
You can combine your SQL queries as follows:
SELECT p.item_id, p.item_name, cal.year_month, COALESCE( q_in.total, 0 ) AS qty_in, COALESCE( q_out.total, 0 ) AS qty_out
FROM Products AS p
INNER JOIN
( SELECT CONCAT(YEAR(NOW()), MONTH(NOW())) AS `year_month`
UNION
SELECT CONCAT(YEAR(NOW() - INTERVAL 1 MONTH), MONTH(NOW() - INTERVAL 1 MONTH)) AS `year_month` ) AS `cal`
ON 1 = 1
LEFT JOIN
( SELECT `item_id`, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS `year_month`, SUM(`load_qty`) AS `total`,
FROM loading
WHERE (`load_date` BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
GROUP BY `item_id`, YEAR(`load_date`), MONTH(`load_date`)) AS q_in
ON p.item_id = q_in.item_id AND cal.year_month = q_in.year_month
LEFT JOIN
( SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS `year_month`, SUM(`load_qty`) AS `total`,
FROM sales
WHERE (`load_date` BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
GROUP BY `item_id`, YEAR(`load_date`), MONTH(`load_date`)) AS q_out
ON p.item_id = q_out.item_id AND cal.year_month = q_out.year_month
ORDER BY p.item_id, p.item_name, q_in.year_month
Output example:
-------------------------------------------------------
| item_id | item_name | year_month | qty_in | qty_out |
-------------------------------------------------------
| 1 | blah | 201604 | 0 | 16 |
-------------------------------------------------------
| 1 | blah | 201605 | 12 | 16 |
-------------------------------------------------------
Explanation:
I have used "Calendar" sub-query to generate a list of months so that your output will always have a row for the current and the previous months even when total quantity for a given month is 0 in and 0 out:
( SELECT CONCAT(YEAR(NOW()), MONTH(NOW())) AS YearMonth
UNION
SELECT CONCAT(YEAR(NOW() - INTERVAL 1 MONTH), MONTH(NOW() - INTERVAL 1 MONTH)) AS YearMonth ) AS Cal
Afterwards I use a left join to join the queries for "loading" and "sales".
I have added a
WHERE (load_date BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
to each of the sub-queries to restrict the calculation to current and last month only. This is to improve performance.
Note: I have not tested the query in MySQL
I have a table which keeps track of data from a game, here is an example of the table:
id | player_name | date | score | kills
1 | test1 | 2013-01-01 00:00:00 | 10000 | 200
2 | test1 | 2013-01-01 00:01:00 | 12000 | 300
I have a leaderboards for players, it ranks people who gain the most score/kills, etc. in a certain time period. At the moment I have only got it so that it ranks players in the previous 24 hours. I am doing this by selecting the first and last records in a specified time period and then subtracting them to get the difference.
This is my current query:
SELECT date, score FROM datapoints WHERE player_name = :player AND date = (SELECT MIN(date) FROM datapoints WHERE player_name = :player AND date > DATE_SUB(CURDATE(), INTERVAL 24 HOUR))
UNION ALL
SELECT date, score FROM datapoints WHERE player_name = :player AND date = (SELECT MAX(date) FROM datapoints WHERE player_name = :player AND date > DATE_SUB(CURDATE(), INTERVAL 24 HOUR))
After subtracting I use the PHP arsort() function to order them and then display them on the page.
However, I want to add another feature. I want to be able to see the on which day was the users best day for score/kills.
I have been thinking of how I could possibly do it and one was was using the above query but having a loop for each day and taking out the best day, however this probably isn't very efficient and I was wondering, if there was a better way of doing this?
Here is how you would get the score changes and kills that occur on any given calendar day:
select date(date) as thedate, max(score) - min(score) as DayScore,
max(kills) - min(kills) as DayKills
from datapoints dp
where player_name = :player
group by date(date);
To get the top day for scores, for instance, you would add an order by and limit clause like this:
order by DayScore desc
limit 1;
I have a table like this
id | date | content
1 | 09-16-2013 | content 1 here
2 | 09-23-2013 | content 2 here
3 | 09-30-2013 | content 3 here
I would like to display the content for a week from that date. For example, the first content should start on 9/16/2013 and then show until 9/22/2013 mid night. then on next day, it changes to the content 2.
Same way,when I am on content 2, I want to display like "previous week content" and then show just the previous ones..I think I can do this by checking the current date and then anything below that has to be displayed.
I am not very good at these kind of mysql queries, please advise!
Regards
I guess you're looking for something like this
SELECT *
FROM table1
WHERE date BETWEEN CURDATE() + INTERVAL 0 - WEEKDAY(CURDATE()) DAY
AND CURDATE() + INTERVAL 6 - WEEKDAY(CURDATE()) DAY
This query will grab a row(s) where date column is within the boundaries of the current calendar week (from Monday to Sunday).
WEEKDAY() function returns the weekday index for date (0 = Monday, 1 = Tuesday, … 6 = Sunday). The expression
CURDATE() + INTERVAL 0 - WEEKDAY(CURDATE()) DAY
returns a date for Monday of the current calendar week and
CURDATE() + INTERVAL 6 - WEEKDAY(CURDATE()) DAY
returns a date for Sunday of the current calendar week.
Using BETWEEN in WHERE clause makes sure that a query returns only rows with date values that falls between these two dates (Monday through Sunday).
Note: Make sure that you have an index on date column. This query is index-friendly.
Sample output for today's date (09/19/2013):
+------+------------+----------------+
| id | date | content |
+------+------------+----------------+
| 1 | 2013-09-16 | content 1 here |
+------+------------+----------------+
UPDATE: To get records for previous calendar week you just substract 1 week interval from both values in BETWEEN
SELECT *
FROM table1
WHERE date
BETWEEN CURDATE() + INTERVAL 0 - WEEKDAY(CURDATE()) DAY - INTERVAL 1 WEEK,
AND CURDATE() + INTERVAL 6 - WEEKDAY(CURDATE()) DAY - INTERVAL 1 WEEK
Try this
SELECT * FROM table WHERE date BETWEEN '09-16-2013' AND '09-22-2013';
keyword is WEEK()
SELECT id,date, CONCAT('content ',WEEK(date),' to here') as content FROM table_name
SELECT *
FROM table
WHERE date BETWEEN '9/16/2013 00:00:00.00' AND '9/22/2013 00:00:00.00'
You can replace the week offset to your needs
SET #weekOffset = +2;
SELECT * FROM test
WHERE WEEK(`date`) = WEEK(NOW()) + #weekOffset;
See a working demo here
To select it dynamically, try something like
SELECT * FROM `yourTable` WHERE NOW() >= STR_TO_DATE(`date`, '%m-%d-%Y') ORDER BY STR_TO_DATE(`date`, '%m-%d-%Y') DESC LIMIT 1
or t
SELECT * FROM `yourTable` WHERE CURDATE() >= STR_TO_DATE(`date`, '%m-%d-%Y') ORDER BY STR_TO_DATE(`date`, '%m-%d-%Y') DESC LIMIT 1
sqlfiddle example - http://sqlfiddle.com/#!2/62982/4
Hotel_id Room_id Room_type Start_date End_date Price
----------------------------------------------------------------
13 2 standard 2012-08-01 2012-08-15 7000
13 2 standard 2012-08-16 2012-08-31 7500
13 2 standard 2012-09-01 2012-09-30 6000
13 3 luxury 2012-08-01 2012-08-15 9000
13 3 luxury 2012-08-16 2012-08-31 10000
13 3 luxury 2012-09-01 2012-09-30 9500
Hi this is the structure and data of my table.
I need to create a mysql query for hotel booking, that would match in database user entered data:
Date when they want to checkin and checkout
Room type
For Ex:
If user selects Hotel with luxury room based on these dates (2012-08-30 to 2012-09-04)
the total cost would be (10000*2) for 30th and 31st Aug + (9500*3) for 1st,2nd and 3rd Sep(4th checkout day don't include)
that means total price will be 20000+28500=48500
So query should filter total price based on the Hotel_id,Room_id,Start_date,End_date and Price
Thanks
Use this solution:
SELECT SUM(
CASE WHEN a.Start_date = b.min_sd AND a.Start_date <> b.max_sd THEN
(DATEDIFF(a.End_date, '2012-08-30')+1) * a.Price
WHEN a.Start_date = b.max_sd AND a.Start_date <> b.min_sd THEN
DATEDIFF('2012-09-04', a.Start_date) * a.Price
WHEN (a.Start_date,a.Start_date) IN ((b.min_sd,b.max_sd)) THEN
(DATEDIFF('2012-09-04', '2012-08-30')+1) * a.Price
WHEN a.Start_date NOT IN (b.min_sd, b.max_sd) THEN
(DATEDIFF(a.End_date, a.Start_date)+1) * a.Price
END
) AS totalprice
FROM rooms a
CROSS JOIN (
SELECT MIN(Start_date) AS min_sd,
MAX(Start_date) AS max_sd
FROM rooms
WHERE Room_type = 'luxury' AND
End_date >= '2012-08-30' AND
Start_date <= '2012-09-04'
) b
WHERE a.Room_type = 'luxury' AND
a.End_date >= '2012-08-30' AND
a.Start_date <= '2012-09-04'
Replace occurances of 2012-08-30 and 2012-09-04 with your input start and end dates respectively.
This will account for start and end dates being in the same month as well as spanning across multiple months.
SQLFiddle Demo
You can use MySQL's BETWEEN ... AND ...
operator to find the date ranges in which the desired booking falls (remember to take one day off of the given checkout
date as, like you say, there is no night's stay), then group the results by room and take the
SUM() of price times number of nights (which can
be calculated using MySQL's LEAST() and
GREATEST() functions):
SELECT Room_id,
SUM(Price * (1 + DATEDIFF(
LEAST(End_date, '2012-09-04' - INTERVAL 1 DAY),
GREATEST(Start_date, '2012-08-30')
))) AS Total
FROM mytable
WHERE Room_type = 'luxury' AND (
'2012-09-04' - INTERVAL 1 DAY
BETWEEN Start_date AND End_date
OR '2012-08-30' BETWEEN Start_date AND End_date
)
GROUP BY Room_id
See it on sqlfidde.
try this:
set #Hotel_id :=13;
set #Room_id :=3;
set #Start_date :='2012-08-30' ;
set #End_date :='2012-09-04';
select sum(b.TotalPrice-b.deductions) as total_cost from
( select a.Price,a.StartDate,a.EndDate,price*(DATEDIFF(a.EndDate,a.StartDate)+1) as TotalPrice
,case when a.EndDate=#End_date then a.Price else 0 end as deductions
from
(select price,case when #Start_date>=Start_date then #Start_date else Start_date end as StartDate
,case when #End_date<=End_date then #End_date else End_date end as EndDate
from h_booking h1
where Hotel_id=#Hotel_id
and Room_id=#Room_id
and (#Start_date between Start_date and End_date or #End_date between Start_date and End_date ))a )b