mysql getting all not exists from 2 tables - mysql

Hi I've got 2 tables:
customers
invoices
I'd like to find all customers who hasn't bought from me monthly for the past 6 months. This is what I've got at the moment:
SELECT * FROM customers WHERE NOT EXISTS (SELECT * FROM invoices WHERE invoices.customerid = customers.customerid AND month(invoices.fdate) = '2')
it kept running for 10 mins and there are still no results. i'm thinking that even if my syntax works, i'd have to go about it monthly, and crossing it off my customers table manually.
is there a better solution? all help appreciated. Thanks

You query doesn't quite do what you want. The correctly phrased one probably won't work any better:
SELECT c.*
FROM customers c
WHERE NOT EXISTS (SELECT 1
FROM invoices i
WHERE i.customerid = c.customerid AND
i.fdate >= CURDATE() - interval 6 month
);
For this version, you want an index on invoices(customerid, fdate).
You might also give this a shot:
SELECT c.*
FROM customers c JOIN
(SELECT customerid, MAX(fdate) as max_fdate
FROM invoices i
GROUP BY customerid
) i
ON i.customerid = c.customerid
WHERE i.max_fdate < CURDATE() - interval 6 month;

Related

Total orders by new customers Each day and week

I'm fairly new to SQL and I'm trying get total orders by new customers every day and each week in Mysql ( feel free to answer in any sql versions). First I tried to get total orders by new customers every day by writing below query, however Im getting incorrect results.
select COUNT(u.orderId), date(u.createdAt) as ord_dt, u.userId
from userorder u
inner join
(
select userId, min(date(createdAt)) as first_date
from userorder
group by 1
) g
on g.userId = u.userId
where g.first_date < date(u.createdAt)
group by 3
Link to the dataset https://docs.google.com/spreadsheets/d/1fA6hAkDJgp28BF0G0aSe9Ml64S9cIwch/edit?usp=sharing&ouid=104686423957654811582&rtpof=true&sd=true
Any help is appreciated
I wrote this query to get the daily answer.
select first_order_date, sum(first_total_order) as tot from
(select user_id,min(order_date) as first_order_date,total_order as
first_total_order
from
(select userId as user_id,date(createdAt) as order_date,count(orderId)
as total_order
from userorder
group by 1,2
) as uo
group by 1) z
group by 1;

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: Get last 2 orders of users with conditions

I realized my first explanation was a bit off so I recollected my thoughts and rephrased my first question. I'd like to thank #Jitendra Sanghani and #StanislavL for providing the first answers.
Is it possible in MySQL to get the last 2 orders of users provided their last order made by them was on March 2016 and the 2nd to last was made on Dec 2015 (or any other older date)? This means if they made an order now but their previous one was January 2016 then they won't count (since we're looking at Dec 2015).
I currently have a sample orders table with fields id, fullname, and created.
you can write two queries and then have inner join on both like
select a.* from orders where orderdate > <> as a inner join (select b.orderdate from orders where orderdate <= <>)
in you case query will be
select a.* from orders where a.orderdate >= '2016-03-01' as a
inner join
(select b.orderdate from orders where orderdate <= '2015-12-31') as b
ON a. orderid = b.orderid
I think this should solve your problem.
select o.id,
sum(when case o.created>'2016-03-01' then 1 else 0 end) as current_count,
sum(when case o.created<='2016-03-01' then 1 else 0 end) as prev_count
from orders o
group by o.id
having current_count>0 and prev_count>0
corrected

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