left join with empty rows from right table mysql - mysql

I have restaurants and orders tables, in orders table I have restaurant_id, status and date fields - for each day I save one row in orders table. If for some day there is no order - it means the is no row for that day in orders table.
I want to show on the calendar the data for the current month for each restaurant, according to these 2 separate conditions.
1) in first case show only those restaurants that have at least one free
day during this month(which means for this month at least one date is missing in orders table).
2) in second case show only those restaurants that are free for today
(which means there is no row for today in orders table)
for both cases, if the condition is satisfied, I should fetch all the orders for the current month - this is the tricky part.
The usual anti-join with left, or inner join do not give the desired result.
Thanks.
edit
outputs should be like this
1) http://img826.imageshack.us/img826/3114/e6zt.png
2) http://img13.imageshack.us/img13/6397/44l0.png

This is all the listings for this month for all restaurants that are free today:
SELECT r.`id`, r.`name`, o.`date`, o.`status`, o.`id` order_id
FROM restaurants r
INNER JOIN orders o
ON r.id = o.restaurant_id
LEFT JOIN
( SELECT DISTINCT o2.Restaurant_ID
FROM orders o2
WHERE o2.date = DATE(CURRENT_TIMESTAMP)
) o2
ON r.id = o2.restaurant_id
WHERE o.Date >= DATE_FORMAT(CURRENT_TIMESTAMP ,'%Y-%m-01')
AND o.Date <= DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01')
AND o2.Restaurant_ID IS NULL;
This simply gets all the restaurants with bookings today (subquery o2), then excludes these restaurants:
AND o2.Restaurant_ID IS NULL;
This is all the listings for this month for all restaurants that have at least one free day this month:
SELECT r.`id`, r.`name`, o.`date`, o.`status`, o.`id` order_id
FROM restaurants r
INNER JOIN orders o
ON r.id = o.restaurant_id
AND o.date BETWEEN '2013-08-10' AND '2013-08-31'
INNER JOIN
( SELECT o2.Restaurant_ID
FROM orders o2
WHERE o2.Date >= DATE_FORMAT(CURRENT_TIMESTAMP ,'%Y-%m-01')
AND o2.Date <= DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01')
GROUP BY o2.Restaurant_ID
HAVING COUNT(DISTINCT o2.Date) < DAY(DATE_ADD(DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01'), INTERVAL -1 DAY))
) o2
ON r.id = o2.restaurant_id
WHERE o.Date >= DATE_FORMAT(CURRENT_TIMESTAMP ,'%Y-%m-01')
AND o.Date <= DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01');
The trick is to get the number of days in this month:
DAY(DATE_ADD(DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01'), INTERVAL -1 DAY))
Then limit the results to restaurant_id's that have less bookings than this:
HAVING COUNT(DISTINCT o2.Date) < DAY(DATE_ADD(DATE_FORMAT(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 MONTH) ,'%Y-%m-01'), INTERVAL -1 DAY))
Example of Both on SQL Fiddle

Related

Counting number of rows between 2 dates and 1 where clause

I've a mysql table with me
Now we want to do some calculations like this
count date wise for all courses enrolled
count where course id = 2 for date > start_date AND date < end_date
Expected output where we calculate all courses enrolled
Expected output where we calculate all courses enrolled where course id = 2
*
expected output where course_id = 2 AND date range is between 2022-11-15 to 2022-11-13
The query which I've right now
SELECT COUNT(*), DATE(registered_on)
FROM courses_enrolled
WHERE course_id = 1
GROUP BY DATE(registered_on), course_id
ORDER BY registered_on desc;
You need to use some kind of calendar table approach here:
SELECT d.dt AS date, COUNT(ce.id) AS cnt
FROM (
SELECT '2022-11-12' AS dt UNION ALL
SELECT '2022-11-13' UNION ALL
SELECT '2022-11-14' UNION ALL
SELECT '2022-11-15'
) d
LEFT JOIN courses_enrolled ce
ON DATE(ce.registered_on) = d.dt AND
ce.course_id = 2
GROUP BY d.dt
ORDER BY d.dt;
The calendar table ensures that all dates you want in the output appear. In practice, you may replace the subquery in d with a bona-fide table containing all dates of interest. The left join ensures that no dates are dropped which have no matching courses on that day.
If you are using MySQL 8 you can use a recursive CTE to create your date range.
For all enrolled courses for given date range -
WITH RECURSIVE calendar (date) AS (
SELECT '2022-11-13' # start date
UNION ALL
SELECT date + INTERVAL 1 DAY FROM calendar
WHERE date + INTERVAL 1 DAY <= '2022-11-15' # end date
)
SELECT COUNT(ce.id) count_all, c.date
FROM calendar c
LEFT JOIN courses_enrolled ce
ON ce.registered_on BETWEEN c.date AND (c.date + INTERVAL 1 DAY - INTERVAL 1 SECOND)
GROUP BY c.date
ORDER BY c.date DESC;
Note the use of BETWEEN start AND end of day in the join criteria. For a small dataset this offers negligible benefit but on a large dataset it would allow for use of an index on registered_on, which could offer significantly improved performance.
Or for just the selected course -
WITH RECURSIVE calendar (date) AS (
SELECT '2022-11-13' # start date
UNION ALL
SELECT date + INTERVAL 1 DAY FROM calendar
WHERE date + INTERVAL 1 DAY <= '2022-11-15' # end date
)
SELECT COUNT(ce.id) count_selected_course, c.date
FROM calendar c
LEFT JOIN courses_enrolled ce
ON ce.registered_on BETWEEN c.date AND (c.date + INTERVAL 1 DAY - INTERVAL 1 SECOND)
AND ce.course_id = 2
GROUP BY c.date
ORDER BY c.date DESC;
Or counting both at the same time -
WITH RECURSIVE calendar (date) AS (
SELECT '2022-11-13' # start date
UNION ALL
SELECT date + INTERVAL 1 DAY FROM calendar
WHERE date + INTERVAL 1 DAY <= '2022-11-15' # end date
)
SELECT COUNT(ce.id) count_all, COUNT(IF(ce.course_id = 2, ce.id, NULL)) count_selected_course, c.date
FROM calendar c
LEFT JOIN courses_enrolled ce
ON ce.registered_on BETWEEN c.date AND (c.date + INTERVAL 1 DAY - INTERVAL 1 SECOND)
GROUP BY c.date
ORDER BY c.date DESC;

Mysql : Get cumulative purchase of products for same day last week based on the current date

Based on current date which is march 17 2022 (thrusday) i am trying to find the same day last week(10th march 2022)(thrusday) sale cumulative product wise
i used following query but unfortunately had an issue with duplicates
select t1.order_date,t1.product as product, t1.total_order_demand as 'order_demand', t2.total_order_demand as 'lastweek same_day demand'
from(
SELECT date(od.CREATED_DATE) as order_date, p.name as product, Sum(od.QUANTITY) as total_order_demand
from order_detail od left join product p on p.id=od.PRODUCT
where date(od.CREATED_DATE)<=CURDATE() and date(od.CREATED_DATE)>=date(curdate()) - interval 1 WEEK
and od.STATUS='Y' and (od.ORDER_END_DATE>=od.ORDER_START_DATE or od.ORDER_END_DATE is null) group by date(od.created_date)
) t1
left join(
SELECT date(od.CREATED_DATE) as order_date, p.name as product, Sum(od.QUANTITY) as total_order_demand
from order_detail od left join product p on p.id=od.PRODUCT
where date(od.CREATED_DATE)<=CURDATE() and date(od.CREATED_DATE)>=date(curdate()) - interval 1 WEEK
and od.STATUS='Y' and (od.ORDER_END_DATE>=od.ORDER_START_DATE or od.ORDER_END_DATE is null) group by date(od.created_date)
) t2
on WEEKDAY(DATE_SUB(CURDATE() , INTERVAL 1 WEEK)) = WEEKDAY(CURDATE()) and t1.product =t2.product
where t1.order_date >= date(curdate()) - interval 1 WEEK group by t1.product,t2.product
order by t1.order_date desc ;

Sub-queries, self-joins or View-Joins?

I'm new here, so I hope I'll do everything right.
I'm trying to get a table on MySQL where I would get the sum of orders from a certain productline per month, and then comparing it with the orders from the same month in the previous year, to calculate the delta.
For that i need to group by each orders with the productline and month, and also month of last year. Of course, sometimes, there are no orders in certain months for certain productlines.
The tables columns would look like this :
ProductLine - MONTH(date) - SUM(ORDERS) - LAST_YEAR-MONTH(date) - LAST_YEAR_SUM(ORDERS) - DELTA
I can get current year table and last year table separately in views, but I can't effectively join them without messing the numbers, maybe because of the group-by clauses
Here what I tried (I seriously tried a 100 possibilities) :
Creating two views and left join the last-year table
Creating a subquery for the LAST_YEAR orders
Here is an example of what I tried, (forget about the delta, that part is the easy one) :
SELECT p.productLine pL
, MONTH(o.orderDate) oD
, SUM(od.quantityOrdered)
, MONTH(date_sub(o.orderDate, interval 1 year)) previous_year
, (SELECT SUM(orderdetails.quantityOrdered)
FROM orderdetails
LEFT
JOIN orders
ON orderdetails.orderNumber = orders.orderNumber
LEFT
JOIN products
on products.productCode = orderdetails.productCode
WHERE orderDate in (previous_year)
) previous_order
FROM products p
JOIN orderdetails od
ON p.productCode = od.productCode
JOIN orders o
ON od.orderNumber = o.orderNumber
GROUP
BY pL
, oD
, previous_year
ORDER
BY oD DESC
If you are running MySQL 8.0, you can use window functions and a range specification:
select
p.productline,
date_format(o.orderdate, '%Y-%m-01') as ordermonth
sum(od.quantityordered) quantityordered,
max(sum(od.quantityordered)) over(
partition by productline
order by date_format(o.orderdate, '%Y-%m-01')
range between interval 1 year preceding and interval 1 year preceding
) previousquantityordered
from products as p
join orderdetails as od on p.productcode = od.productcode
join orders as o on od.ordernumber = o.ordernumber
group by p.productline, ordermonth
order by ordermonth desc
For simplified comparison based on a calendar year comparison, I would start Jan 1 of the prior year to now of current year. Doing this, you would get Jan 2019/2020, Feb 2019/2020, etc to current month. Since Oct, Nov, Dec have not happened yet, you could add in an "and month( order date ) is LESS than the current month we are in" since it did not happen yet this year to compare against
select o.*
from orders o
where o.orderDate >= '2019-01-01'
AND month( o.orderDate ) < month( curdate())
Now that the underlying record base is simplified, lets try to get your details.
To differentiate between last year and current, I am doing a conditional SUM(). So within the sum is a CASE/WHEN test. If the year of the order is same as current year, sum its qty, otherwise 0. Likewise, if less than current year, put into the last year qty bucket total. This way, you get all product lines, even those that may have sold last year and not this and vice-versa too. This would result in zero values in those respective columns. But obviously below is a much more simplified query to follow.
select
p.productLine,
MONTH(o.orderDate) OrderMonth,
SUM( case when year( o.orderDate ) = year( curdate())
then od.quantityOrdered else 0 end ) CurYearQty,
SUM( case when year( o.orderDate ) < year( curdate())
then od.quantityOrdered else 0 end ) LastYearQty
from
orders o
JOIN orderdetails od
on o.orderNumber = od.orderNumber
JOIN products p
on od.productCode = p.productCode
where
o.orderDate >= '2019-01-01'
AND month( o.orderDate ) < month( curdate())
group by
MONTH(o.orderDate),
p.productLine
order by
MONTH(o.orderDate),
p.productLine

mysql how to mulitply some values in the same column but not others if it meets a condition

Is it possible to mulitply some values in the same column but not others if the value meets a certain condition? I don't want to create another column.
Query I am working with:
SELECT
name ,
ROUND(SUM(orderline_sales.amount * orderline_sales.price) * orders_sales.discount * customers.annual_discount) AS total_revenue
FROM
orderline_sales
JOIN
orders_sales ON orders_sales.id = orderline_sales.orders_sales_id
JOIN
employee ON orders_sales.empoyee_id = employee.id
JOIN
customers ON orders_sales.customer_id = customers.id
WHERE
date BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL 365 DAY) AND CURRENT_DATE
GROUP BY employee.name
ORDER BY totale_omzet DESC
LIMIT 1;
The orders_sales table contains a date attributetype and the orders_sales table has a 1:n cardinal relationship with orderline_sales. I only want to multiply the SUM result with customers.annual_discount if the YEAR of the order is higher than 2017. How would I go about doing this?
you can use CASE.
SELECT
CASE WHEN YEAR > 2017 THEN
ROUND(SUM(orderline_sales.amount * orderline_sales.price) *
orders_sales.discount *
customers.annual_discount)
ELSE
(orderline_sales.price * orders_sales.discount * customers.annual_discount)
END AS total_revenue FROM orderline_sales
JOIN
orders_sales ON orders_sales.id = orderline_sales.orders_sales_id
JOIN
employee ON orders_sales.empoyee_id = employee.id
JOIN
customers ON orders_sales.customer_id = customers.id
WHERE
date BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL 365 DAY) AND CURRENT_DATE
GROUP BY employee.name
ORDER BY totale_omzet DESC

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)