MySQL query help (join, subselect) - mysql

I have 2 tables: orders and bookings.
The orders table contain unique orders from customers. Each customer has booked a locker for a period of 1 to 5 years and therefore the bookings table can contain 1 to 5 rows pr. order (1 row for each year). Each row in the booking table contains and end_date which is the same date every year (20XX-06-30).
I want to select all the orders where the corresponding final end_date in the bookings table is this year (2014-06-30).
SELECT DISTINCT orders.id
FROM orders,
bookings
WHERE orders.id = bookings.order_id
AND bookings.end_date = '2014-06-30'
The problem with this query is that it also selects the orders where the end_date in the booking rows continue the following years (2015-06-30, 2016-06-30 etc).

I am not sure I understood well, but here's a solution for what I understood, this should get you the order ids where there last end date (max) is 2014-06-30.
SELECT orders.id, MAX(bookings.end_date)
FROM orders INNER JOIN bookings
ON orders.id = bookings.order_id
GROUP BY bookings.order_id
HAVING MAX(bookings.end_date) = '2014-06-30'

Maybe join to the bookings again, checking for a larger booking date for the same order id:-
SELECT orders.id
FROM orders
INNER JOIN bookings ON orders.id = bookings.order_id
LEFT OUTER JOIN bookings2 ON orders.id = bookings2.order_id AND bookings2.end_date > bookings.end_date
WHERE bookings.end_date = '2014-06-30'
AND bookings2.end_date IS NULL

Related

SQL List data from table dependant on data from another table

I'm writing a CMS for a client who wishes for a sorting of his data by upcoming activities.
There is a table named customers which contains all of the customers, identified by the cust_id column.
There is also a table named activities, in which there is a cust_id column to pair it with a customer, and a date_last_done field to specify when the task was completed. The customer can have many activities to their record.
I want to fetch all customers where the most recent (date_last_done desc) activity was completed more than 11 months ago.
Here's my attempt:
SELECT
*
FROM
customers
INNER JOIN
activities
ON
activities.cust_id = customers.cust_id
WHERE
(SELECT
date_last_done
FROM
activities
WHERE
cust_id = ???
ORDER BY
date_last_done
DESC)
< date() - 11months
How would I edit my query to do this?
Join the customers table with a subquery that returns the customers with activity that fits the criteria.
SELECT c.*
FROM customers AS c
JOIN (SELECT cust_id
FROM activities
GROUP BY cust_id
HAVING MAX(date_last_done) < DATE_SUB(NOW(), INTERVAL 11 MONTH)) AS a
ON c.cust_id = a.cust_id

Optimising MySql Query with LEFT JOINS

I am trying to get a list of customer who haven't ordered for 6months or more. I have 4 tables which I have used in the query
accounts (account_id)
stores (store_id, account_id)
customers (store_id, customer_id)
orders (order_id, customer_id, store_id)
The customer and orders table are very big, 3M and 26M rows respectively, so using left joins in my query make the query time extremely long. I believe I have index my tables correctly
here is my query i have used
SELECT cus.customer_id, MAX(o.order_date), cus.store_id, s.account_id, store_name
FROM customers cus
LEFT JOIN stores s ON s.store_id=cus.store_id
LEFT JOIN orders o ON o.customer_id=cus.customer_id AND o.store_id=cus.store_id
WHERE account_id=26 AND
(SELECT order_id
FROM orders o
WHERE o.customer_id=cus.customer_id
AND o.store_id=cus.store_id
AND o.order_date < CURRENT_DATE() - INTERVAL 6 MONTH
ORDER BY order_id DESC LIMIT 0,1) IS NOT NULL
GROUP BY cus.customer_id, cus.client_id;
I need to get the last order date and this is the reason why I have joined the orders table, however since the customers can have multiple orders it is returning multiple rows of the customer and that is why I have used the group by clause.
If anyone can assist me with my query.
Start with this:
SELECT customer_id, MAX(order_date) AS last_order_date
FROM orders
GROUP BY customer_id
HAVING last_order_date < NOW() - INTERVAL 6 MONTH;
Assuming that gives you the relevant customer_ids, then move on to
SELECT ...
FROM ( that-select-as-a-subquery ) AS old
JOIN other-tables-as-needed ON USING(customer_id)
If necessary, JOIN back to orders to get more info. Do not try to get other columns in that subquery. (That's a "groupwise max" problem.)
Your strategy of using an ordered and limited subquery on your orders table is probably responsible for your poor performance.
This subquery will generate a virtual table showing the date of the most recent order for each distinct customer. (I guess a distinct customer is distinguished by the pair customer_id, store_id).
SELECT MAX(order_date) recent_order_date,
customer_id, store_id
FROM orders
GROUP BY customer_id, store_id
Then, you can use that subquery as if it were a table in your query.
SELECT cus.customer_id, summary.recent_order_date,
cus.store_id, s.account_id, store_name
FROM customers cus
JOIN stores s ON s.store_id=cus.store_id
JOIN (
SELECT MAX(order_date) recent_order_date,
customer_id, store_id
FROM orders
GROUP BY customer_id, store_id
) summary ON summary.customer_id = cus.customer_id
AND summary.store_id = s.store_id
WHERE summary.recent_order_date < CURRENT_DATE - INTERVAL 6 MONTH
AND store.account_id = 26
This approach moves the GROUP BY to an inner query, and eliminates the wasteful ORDER BY ... LIMIT query pattern. The inner query doesn't have to be remade for every row in the outer query.
I don't understand why you used LEFT JOIN operations in your query.
And, by the way, most people, when they're new to SQL, don't have great intuition about which indexes are useful and which aren't. So, when asking for help, it's always good to show your indexes. In the meantime, read this:
http://use-the-index-luke.com/

mysql - Finding count of 0 when using joined tables

EDIT I've put up an sqlfiddle with this schema here: http://sqlfiddle.com/#!2/0726f2. I'm trying to select customers 3, 4, 5, 6.
Consider a db with three tables:
customers
---------
id
seats
-----
id
buyer_id (fk to customers)
flight_id
flights
-------
id
datetime (This is the UTC time of the flight)
I'm trying to find customers who have not booked seats on any flight in March.
This query provides a list of customers who have not booked seats on any flight:
SELECT customers.id, count(seats.id) as seat_count FROM `customers`
LEFT JOIN `seats` ON `seats`.`buyer_id` = `customers`.`id`
LEFT JOIN `flights` ON `flights`.`id` = `seats`.`flight_id`
GROUP BY customers.id
HAVING seat_count=0
I tried this query to find a list of customers who have not booked seats on any flight in March
SELECT customers.id, count(seats.id) as seat_count FROM `customers`
LEFT JOIN `seats` ON `seats`.`buyer_id` = `customers`.`id`
LEFT JOIN `flights` ON `flights`.`id` = `seats`.`flight_id`
WHERE flights.datetime >= '2014-03-01 00:00:00'
AND flights.datetime <= '2014-04-01 00:00:00'
GROUP BY customers.id
HAVING seat_count=0
But it returns an empty list. I understand why: I'm selecting a list of customers who have booked seats in March then finding customers in that list who have not booked seats. Clearly an empty set.
Likewise with adding this to the WHERE clause
AND seats.is is null
I can't figure a proper way to do this.
I've tried:
Flipping the JOINs every which way
Using a subquery in the LEFT JOIN statement. Performance was prohibitively bad.
Trying SELECT customers.id from customers where id not in ([above query]) MySql uses a correlated subquery and performance is also prohibitively awful.
Because this is wrapped up in a larger search feature, I can't come at this from another direction (selecting from seats and going from there, for example). Schema changes are not possible.
Thanks.
You can use NOT EXISTS like
SELECT *
FROM customers
WHERE NOT EXISTS (
SELECT * FROM seats
INNER JOIN flights ON flights.id = seats.flight_id
WHERE flights.datetime >= '2014-03-01 00:00:00'
AND flights.datetime <= '2014-04-01 00:00:00'
AND seats.buyer_id = customers.id
)
here is a corresponding SQLFiddle.
By the way you should at least add an index on seats.buyer_id, since this is a column you need to join on. With the named index the execution plan does not look that bad.
this works:
SELECT customers.id, count(seats.id) as seat_count FROM `seats`
INNER JOIN (SELECT id FROM flights WHERE DATE(flights.datetime) >= '2014-03-01'
AND DATE(flights.datetime) <='2014-04-01') `flights` ON `flights`.`id` = `seats`.`flight_id`
RIGHT JOIN customers ON customers.id=seats.buyer_id
GROUP BY customers.id
HAVING seat_count=0
here's the fiddle
here's another way to do it:
SELECT customers.id FROM customers WHERE id NOT IN (SELECT seats.buyer_id FROM seats
INNER JOIN `flights` ON `flights`.`id` = `seats`.`flight_id`
WHERE flights.datetime >= '2014-03-01 00:00:00'
AND flights.datetime <= '2014-04-01 00:00:00')
second fiddle

One-to-many query with subtoals and limits

Requirements: Select customers with 5 or more invoices totaling more than $3000 with transactions from Sept 2011 to current date.
DBMS: MySQL 5.6
Tables:
customers: customerID (...)
invoice: customerID,invoice_no,order_date,order_total (...)
I wrote several MySQL queries. The one that comes "closest" to working appears below. The problem with the results is twofold:
It looks at the total of all invoices per custopmer, not just those within the date range.
It pulls in some (but not all) records that are outside of the date range.
Here is the query:
#Customers with 5 or more invoices Totaling more than $3000 From Sept 2011 to current
SELECT distinct c2.customerID,c2.firstname,c2.lastname,c2.company,c2.address,c2.address2,c2.city,c2.state,c2.country,c2.phone,c2.email,SUM(c1.order_total)
FROM
customers c2 LEFT JOIN invoice c1
ON c2.customerID = c1.customerID
AND ((date(c1.order_date)) between '2011-09-01' and date(now()))
GROUP BY
c1.customerID
HAVING
COUNT(c1.invoice_no)>=7 and sum(c1.order_total) >=3000
Any help would be appreciated greatly.
Thanks.
This should do it:
select c.*, SUM(i.order_total) total, COUNT(*) order_count
FROM customers c
JOIN invoice i ON c.customerID = i.customerID
WHERE i.order_date >= '2011-09-01'
GROUP BY c.customerID
HAVING order_count >= 5 and total > 3000

inner join with empty result from right table

I have 2 tables, restaurants and orders, each restaurant can have many orders
restaurants table
id
name
orders table
id
restaurant_id
date
I need to find the restaurants that have no orders on some date range. In orders table I save the order dates like - each row represents one day. So, I need to make inner join, but with no results from the orders table. Say, I need to find restaurants that are free from 2013-08-09 to 2013-08-11 date range. How can I achieve this ? How to make a query, that will give the restaurants with no matching in the orders table - according to the date range ?
Actually I can do it saving all the dates in the orders table with status not_ordered, and make inner join with not_ordered = true condition, but in that case I will have to populate all the table with the dates, which is not a good thing in my case.
Thanks
select r.*
from restaurant r
left join orders o on r.id = o.restaurant_id and o.date between '...' and '...'
where o.id is null;
Or you can do it using not exists as shown in other answers.
You don't want to use an inner join for this. You can do it with an outer join, or with NOT EXISTS and a sub-query.
Here's an example of the latter approach:
select r.id,r.name
from restaurants r
where not exists (
select NULL
from orders o
where o.restaurant_id = r.id
and o.date >= '2013-08-09'
and o.date <= '2013-08-11'
);
I don't know mysql very well, but this should work as general SQL:
SELECT *
FROM restaurants
WHERE NOT EXISTS(SELECT 1
FROM order
WHERE restaurant_id=id AND
date BETWEEN '2013-08-09' AND '2013-08-11')