MySQL Select, join, comparing dates fails - mysql

I have these two tables:
DateRanges
some_id start_date end_date
---------------------------------
1 2012-12-01 2012-12-15
1 2013-01-01 2013-01-15
3 2013-01-03 2013-01-10
Items
id name
----------------
1 Some name
2 Other name
3 So on...
What I try to achieve is to get, for each element in Items table, the biggest start_date (ignoring the smaller dates/date ranges for that Item) and check if the current date is in that range, like in the next table (let's say today's 02 January 2013):
id name TodayIsInTheRange
---------------------------------------------
1 Some name true
2 Other name false
3 So on... false
I have tried to obtain the 3rd table with the next query:
SELECT A.*, (B.`start_date` <= CURRENT_DATE AND CURRENT_DATE <= B.`end_date`) AS `TodayIsInTheRange`
FROM `Items` as A
LEFT JOIN `DateRanges` as B ON
A.id = B.some_id
SORT BY B.`end_date` DESC
But with this query my items repeat themselves because I have two records in DateRanges for the same item.

I use SQL Server, but I think something like this should be pretty close:
SELECT
I.Id,
I.Name,
(DR.start_date <= CURRENT_DATE AND CURRENT_DATE <= DR.end_date) AS `TodayIsInTheRange`
FROM `Items` AS I
LEFT JOIN
(SELECT Some_Id, MAX(Start_Date) as MaxStartDate
FROM `DateRanges`
GROUP BY Some_ID) AS HDR ON I.Id = HDR.Some_Id
LEFT JOIN `DateRanges` AS DR ON HDR.Some_Id = DR.Some_Id AND HDR.MaxStartDate = DR.Start_Date

select * from items join date_ranges dr0 on items.id = dr0.some_id
where start_date =
(select max(start_date) from date_ranges dr1 where dr0.some_id = dr1.some_id);

SELECT
a.*,
( b.start_date <= CURRENT_DATE
AND CURRENT_DATE <= b.end_date ) AS TodayIsInTheRange
FROM
Items AS a
LEFT JOIN
( SELECT some_id, MAX(start_date) AS start_date
FROM DateRanges
GROUP BY some_id
) AS m
JOIN
DateRanges AS b
ON b.some_id = m.some_id
ON a.id = m.some_id
ORDER BY b.end_date DESC ;

try to use GROUP BY and MAX function. The first provide you only one row for each item.id, the second tell you if there is at least one date in your range
SELECT A.*, MAX(B.`start_date` <= CURRENT_DATE AND CURRENT_DATE <= B.`end_date`) AS `TodayIsInTheRange`
FROM `Items` as A
LEFT JOIN `DateRanges` as B ON
A.id = B.some_id
GROUP BY A.id
ORDER BY B.`end_date` DESC

Related

How to set default value from mysql join interval yearmonth

I have problem with my query. I have two tables and I want join them to get the results based on primary key on first table, but I missing 1 data from first table.
this my fiddle
as you can see, I missing "xx3" from month 1
I have tried to change left and right join but, the results stil same.
So as you can see I have to set coalesce(sum(b.sd_qty),0) as total, if no qty, set 0 as default.
You should cross join the table to the distinct dates also:
SELECT a.item_code,
COALESCE(SUM(b.sd_qty), 0) total,
DATE_FORMAT(d.sd_date, '%m-%Y') month_year
FROM item a
CROSS JOIN (
SELECT DISTINCT sd_date
FROM sales_details
WHERE sd_date >= '2020-04-01' - INTERVAL 3 MONTH AND sd_date < '2020-05-01'
) d
LEFT JOIN sales_details b
ON a.item_code = b.item_code AND b.sd_date = d.sd_date
GROUP BY month_year, a.item_code
ORDER BY month_year, a.item_code;
Or, for MySql 8.0+, with a recursive CTE that returns the starting dates of all the months that you want the results, which can be cross joined to the table:
WITH RECURSIVE dates AS (
SELECT '2020-04-01' - INTERVAL 3 MONTH AS sd_date
UNION ALL
SELECT sd_date + INTERVAL 1 MONTH
FROM dates
WHERE sd_date + INTERVAL 1 MONTH < '2020-05-01'
)
SELECT a.item_code,
COALESCE(SUM(b.sd_qty), 0) total,
DATE_FORMAT(d.sd_date, '%m-%Y') month_year
FROM item a CROSS JOIN dates d
LEFT JOIN sales_details b
ON a.item_code = b.item_code AND DATE_FORMAT(b.sd_date, '%m-%Y') = DATE_FORMAT(d.sd_date, '%m-%Y')
GROUP BY month_year, a.item_code
ORDER BY month_year, a.item_code;
See the demo.

How to query a hotel database to return the query for a single room available for three consecutive nights?

I'm trying to find an answer to the following query:
A customer wants a single room for three consecutive nights. Find the first available date in December 2016.
As per the question, this should be the right answer. But I don't know how to solve it.
+-----+------------+
| id | MIN(i) |
+-----+------------+
| 201 | 2016-12-11 |
+-----+------------+
The link is from question number 14 here.
This is the ER diagram of the database:
I apologize that I'm a bit rusty with this kind of query and I can't guarantee that I got all of the syntax correct, but I think that something like the following might work:
SELECT id, DATE_ADD(b.booking_date, INTERVAL (end_date + 1 DAY) as date
FROM (
SELECT r.id, STR_TO_DATE('2016-01-01', '%Y-%m-%d') as start_of_month, b.booking_date as start_date, DATE_ADD(b.booking_date, INTERVAL (nights - 1) DAY) as end_date
FROM room r
LEFT JOIN booking b ON r.id = b.room_no
ORDER BY r.id, b.booking_date
) as room_bookings
WHERE DATE_DIFF(room_bookings.start_of_month, room_bookings.start_date) >= 3
OR DATE_DIFF(room_bookings.end_date, (
SELECT b2.booking_date FROM booking b2
WHERE b2.room_no = room_bookings.id AND b2.booking_date > room_bookings.start_date
ORDER BY b2.booking_date LIMIT 1)
) >= 3
In fact, now that I type that all out, you might be able to tweak the WHERE of the main query so that you don't even need the room_bookings subselect. Hopefully this helps and isn't too far off the mark.
This seems very hard to do without a calendar table -- because an appropriate room might have no booking at all during the month. Without any booking, there is no record in the month to start with.
select r.id, dte
from rooms r cross join
(select date('2018-12-01') as dte union all
select date('2018-12-02') as dte union all
. . .
select date('2018-12-32') as dte
) d
where not exists (select 1 from bookings b where b.room_no = r.id and b.booking_date = d.dte) and
not exists (select 1 from bookings b where b.room_no = r.id and b.booking_date = d.dte + interval 1 day) and
not exists (select 1 from bookings b where b.room_no = r.id and b.booking_date = d.dte + interval 2 day)
order by d.dte
limit 1;
This assumes that booking_date is the start of the stay. You need to provide the logic for a "single room".
select distinct top 1 alll.i,alll.room_no,
case
when (select count(*) from booking where room_no = alll.room_no and booking_date between dateadd(day,1,alll.i) and dateadd(day,3,alll.i)) > 0 then 'Y'
else 'N'
end as av3
from
(select c.i,b.room_no,b.booking_date
from calendar c cross join booking b
where month(c.i) = 12 and year(c.i) = 2016 and b.room_type_requested = 'single'
) as alll
join
(
select distinct c.i, b.room_no
from calendar c join booking b
on c.i between b.booking_date and DATEADD(day,b.nights-1,b.booking_date)
where month(c.i) = 12 and year(c.i) = 2016 and b.room_type_requested = 'single'
) as booked
on alll.i = booked.i
and alll.room_no <> booked.room_no
order by 1
This works. It is a little complicated but basically first checks all the rooms that are booked and then does a comparison between rooms not booked on each day of the month till the next 3 days.
My solution is separate problem into 2 parts (in the end was 2 queries joined together). May not be the most efficient but the solution is correct.
1) Of the single rooms, look at the last check-out date, and see which one is vacant first (i.e. no more bookings for the rest of the month)
2) check in between current reservations - and see if there's a 3 day gap between them
3) join those together - grab the min
WITH subquery AS( -- existing single-bed bookings in Dec
SELECT room_no, booking_date,
DATE_ADD(booking_date, INTERVAL (nights-1) DAY) AS last_night
FROM booking
WHERE room_type_requested='single' AND
DATE_ADD(booking_date, INTERVAL (nights-1) DAY)>='2016-12-1' AND
booking_date <='2016-12-31'
ORDER BY room_no, last_night)
SELECT room_no, MIN(first_avail) AS first_avail --3) join the 2 together
FROM(
-- 1) check the last date the room is booked in December (available after)
SELECT room_no, MIN(first_avail) AS first_avail
FROM(
SELECT room_no, DATE_ADD(MAX(last_night), INTERVAL 1 DAY) AS first_avail
FROM subquery q3
GROUP BY 1
ORDER BY 2) AS t2
UNION
-- 2) check if any 3-day exist in between reservations
SELECT room_no, DATE_ADD(MIN(end2), INTERVAL 1 DAY) AS first_avail
FROM(
SELECT q1.booking_date AS beg1, q1.room_no, q1.last_night AS end1,
q2.booking_date AS beg2, q2.last_night AS end2
FROM subquery q1
JOIN subquery q2
ON q1.room_no = q2.room_no AND q2.booking_date > q1.last_night
GROUP BY 2,1
ORDER BY 2,1) AS t
WHERE beg2-end1 > 3) AS inner_t
This works conceptually as the first avaiable date should always be the end of the previous booking.
SELECT MIN(DATE_ADD(a.booking_date, INTERVAL nights DAY)) AS i
FROM booking AS a
WHERE DATE_ADD(a.booking_date, INTERVAL nights DAY)
>= '2016-12-01'
AND room_type_requested = 'single'
AND NOT EXISTS
(SELECT 1 FROM booking AS b
WHERE b.booking_date BETWEEN
DATE_ADD(a.booking_date, INTERVAL nights DAY)
AND DATE_ADD(a.booking_date, INTERVAL nights+2 DAY)
AND a.room_no = b.room_no)

Retrieving last record in each group filtering by date - MySQL

I need an upgrade from a problem already solved in another question: Retrieving the last record in each group - MySQL
My problem is very similar yet I cannot achieve the results I need.
In my first table VAT_types I define what kind of rates are available by their names
id type
--------------
1 ordinaria
2 ridotta
3 minima
4 esente
In my second table VAT_rates I have multiple VAT rates according to when the law will make them official and those rates will be once in a while updated but a record of all rates must always be available
id date type rate
-----------------------------
1 2013-01-01 1 22.0000
2 2013-01-01 2 10.0000
3 2013-01-01 3 4.0000
4 2000-01-01 4 0.0000
9 2019-01-01 2 11.5000
10 2021-01-01 2 12.0000
11 2019-01-01 1 24.2000
12 2021-01-01 1 25.0000
So if I want to filter them accordin to the current date (or a future date) I just have to query them like this:
SELECT VAT.id, TYPE.type, VAT.date, VAT.rate
FROM VAT_rates VAT JOIN VAT_types TYPE on TYPE.id = VAT.type
WHERE cast(VAT.date as date) <= cast("2022-11-22" as date)
ORDER BY VAT.type ASC, VAT.date DESC
"2022-11-22" can be any date, and in fact if I change it to CURDATE() it will display all available rates until that date.
Now I want to group them by vat type and retrieve just the last updated one. So I looked up here and found that solution linked above which I tweaked like this:
SELECT T1.*
FROM (
SELECT VAT.id, TYPE.type, VAT.date, VAT.rate
FROM VAT_rates VAT JOIN VAT_types TYPE on TYPE.id = VAT.type
WHERE cast(VAT.date as date) <= cast("2022-11-22" as date)
ORDER BY VAT.type ASC, VAT.date DESC
) T1
LEFT JOIN (
SELECT VAT.id, TYPE.type, VAT.date, VAT.rate
FROM VAT_rates VAT JOIN VAT_types TYPE on TYPE.id = VAT.type
WHERE cast(VAT.date as date) <= cast("2022-11-22" as date)
ORDER BY VAT.type ASC, VAT.date DESC
) T2
ON (T1.type = T2.type AND T1.id < T2.id)
WHERE T2.id IS NULL
ORDER BY T1.rate DESC;
The result will be:
id type date rate
--------------------------------
12 Ordinaria 2021-01-01 25,0000
10 Ridotta 2021-01-01 12,0000
3 Minima 2013-01-01 4,0000
4 Esente 2000-01-01 0,0000
It seems to work, but of course it's way too complicated. I also wish to use this query in my php and tweaking the date just once in order to retrieve the right rates and then the specific rate needed.
How can I simplify the query above?
You could use a inner join on subquery for max date group by type
select VAT.id, TYPE.type, VAT.date, VAT.rate
from VAT_rates VAT
inner JOIN VAT_types TYPE on TYPE.id = VAT.type
inner join (
select max(VAT.date) max_date, TYPE.type
from VAT_rates VAT
INNER JOIN VAT_types TYPE on TYPE.id = VAT.type
WHERE str_to_date(VAT.date, '%Y-%m-%d') <= str_to_date("2022-11-22", '%Y-%m-%d')
group by TYPE.type
) T on T.max_date = VAT.date and T.type = TYPE.type
It is common to find the greatest per group using the following approach
select VAT.id, TYPE.type, VAT.date, VAT.rate
from VAT_rates VAT
join VAT_types TYPE on VAT.type = TYPE.id
join
(
select type, max(date) max_date
from VAT_rates
where cast(date as date) <= cast("2022-11-22" as date)
group by type
) t on VAT.type = t.type and
VAT.date = t.max_date and
cast(VAT.date as date) <= cast("2022-11-22" as date)

How can I optimise this sql query?

Here are 4 tables....
tbl_std_working_hour
tbl_attendance
tbl_holiday
tbl_leave
I want to find out the employees absentee reports by this query....but it takes times when I have applied this for many employees...is there any way to simplify this query?
SELECT date
FROM tbl_std_working_hour
WHERE date NOT IN (SELECT date FROM tbl_attendance WHERE emp_id = '$emp_id')
AND date NOT IN (SELECT date FROM tbl_holiday)
AND date NOT IN (SELECT date FROM tbl_leave WHERE emp_id = '$emp_id')
AND total_hour <> '00:00:00'
AND date >= '$start'
AND date <= '$end'
AND emp_id = '$emp_id'
First, I would rewrite using NOT EXISTS:
SELECT wh.date
FROM tbl_std_working_hour wh
WHERE NOT EXISTS (SELECT 1
FROM tbl_attendance a
WHERE a.date = wh.date AND a.emp_id = wh.emp_id
) AND
NOT EXISTS (SELECT 1
FROM tbl_holiday h
WHERE h.date = wh.date
) AND
NOT EXISTS (SELECT 1
FROM tbl_leave l
WHERE l.emp_id = wh.emp_id and l.date = wh.date
)
WHERE wh.total_hour <> '00:00:00' AND
wh.date >= '$start' AND
wh.date <= '$end' AND
wh.emp_id = '$emp_id';
Then add the following composite (multi-column) indexes:
tbl_std_working_hour(emp_id, date, total_hour)
tbl_attendance(emp_id, date)
tbl_holiday(date) (might already exist if date is the primary key or unique)
tbl_leave(emp_id) (might already exist if emp_id is the primary key or unique)
Note that I changed the subqueries to refer to the emp_id in the outer query. This makes it easier to change the emp_id. In addition, your query should be using parameters for the values in the WHERE clause.
This is also a better way that can work better using UNION ALL
SELECT date
FROM tbl_std_working_hour AS tswh
WHERE NOT EXISTS(
SELECT date FROM tbl_attendance ta WHERE ta.date = tswh.date AND ta.emp_id = tswh.emp_id
UNION ALL
SELECT date FROM tbl_holiday th WHERE th.date = tswh.date
UNION ALL
SELECT date FROM tbl_leave tl WHERE tl.date = tswh.date AND tl.emp_id = tswh.emp_id)
AND total_hour <> '00:00:00'
AND date >= '$start'
AND date <= '$end'
AND emp_id = '$emp_id'

MySQL query with join or subquery

I have such a schema and queries:
http://sqlfiddle.com/#!2/7b032/3
Seperately I have these queries:
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid ORDER BY times DESC LIMIT 0,2;
SELECT * FROM details WHERE 1;
By comparing userid columns of both table I need to join them.
I need an output having these columns:
"times, userid, name, age, location"
Also order, group and limits should be considered.
I would be happy if you can write one query with JOIN and one query with subquery.
I have a 60k table and I will compare the performances.
How about this:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with Demo
edit:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
ORDER BY times DESC
LIMIT 0,2
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with demo