Left outer join + SUM with group by - mysql

I have a table called Purchases which belongs to a Users (that is a Purchase has a foreign key to User).
The Purchases table has a column called quantity and a state_id column, both are integers.
I want to be able to order the Users by their completed purchases (state_id = 10) where the SUM of their quantities is bigger than > 100. That is, all the users which have a total of completed purchases > 100, should appear first, and the rest right after.
This is what I have tried:
SELECT users.id as user_id,
SUM(CASE WHEN purchases.state_id = 5
THEN purchases.quantity
ELSE 0
END) as quantity
FROM users
LEFT OUTER JOIN purchases ON purchases.user_id = users.id
GROUP BY purchases.user_id
But this is just returning me one User, not all of them. What am I missing?

You can JOIN only completed purchases using JOIN condition. And you should group by users.id not purchases.user_id as soon you can have users in a table without purchases at all:
SELECT users.id as user_id,
SUM(purchases.quantity) as quantity
FROM users
LEFT JOIN purchases ON (users.id=purchases.user_id)
AND (purchases.state_id = 10)
GROUP BY users.id
ORDER BY quantity DESC

Related

Trying to filter users that have any order with the status id equal to 2 yet getting other users total as 0

SELECT COUNT(*) AS Total, id
FROM (SELECT * FROM orders WHERE status_id=2)
RIGHT JOIN users ON users.id = orders.courier
I'm getting this error
#1054 - Unknown column 'orders.courier' in 'on clause'
Is this what you are looking for?
select user_id
from orders
having sum(status_id = 2) >= 1
group by user_id
This will give you all user_ids that have at least one order with status_id = 2 (assuming that the column in orders that store the id of the user is called user_id).
If you want to list all users that ordered and count how many orders they have with status_id = 2:
select user_id, sum(status_id = 2) count_status_2
from orders
group by user_id
And if you want even users that never ordered:
select u.user_id, coalesce(sum(o.status_id = 2), 0) count_status_2
from users u
left join orders o on o.user_id = u.user_id
group by u.user_id

Conditional INNER JOIN or LEFT JOIN based on the joining condition

I have a query
SELECT
users.email AS email,
addons.sku AS sku,
addons.quantity as quantity,
invoices.total as total
FROM addons
INNER JOIN users ON 1=1
and users.id = addons.user_id
LEFT JOIN invoices ON 1=1
AND invoices.user_id = users.id
AND invoices.status != 3
Here is what I need to happen:
if user doesn't have an invoice at all we should include them with NULL being returned in the total
if user has an invoice in status != 3 we should include them
if invoices exists and status = 3 we should exclude them.
So it's like I need both INNER JOIN and LEFT JOIN at the same time
How can I achieve that?
This is what you need:
SELECT
users.email AS email,
addons.sku AS sku,
addons.quantity as quantity,
invoices.total as total
FROM addons
INNER JOIN users
ON users.id = addons.user_id
LEFT JOIN invoices
ON invoices.user_id = users.id
WHERE invoices.status IS NULL OR invoices.status != 3
Explanation:
Users without invoices are included in the result, with "empty" invoice total. That's what the LEFT JOIN is for
Users with invoice status != 3 are included, and so are their invoices. So, add that to the where clause (remember the status could be NULL because of the above LEFT JOIN)
Users with invoice status = 3 are excluded. Do that with the WHERE clause

Count elements from a table with differents conditions in mySql?

I wanna count all the orders a user has and all the complete orders a user has. I came with this but it´s not working
select
count(a.id) as total,
count(b.id) as complete
from
user
join
orders a on user.id = a.user_id
join
orders b on user.id = b.user_id
where
a.id = 1
and
(b.id = 1 and b.complete = 'yes');
Any idea?
you could sum the order with yes and count the distinct id group by user
select user.id, sum(if(a.complete ='yes',1,0)), count(distinct a.id)
from user
INNER join orders a on user.id = a.user_id
group by user.id
I believe you are searching for grouping (MySQL GROUP BY) by the differents users, and then count all the orders related to each user plus the completed ones. For this approach, you will need to:
(1) Join users with they orders.
(2) Use GROUP BY clause on user.id column.
(3) Count all orders related to each user with COUNT()
(4) Sum all orders related to each user having some specific condition with SUM(CASE WHEN <specific_condition> THEN 1 ELSE 0 END).
In summary, a query like next one should work:
SELECT
u.id,
COUNT(o.id) AS total_orders,
SUM(CASE WHEN o.complete = "yes" THEN 1 ELSE 0 END) AS complete_orders
FROM
user AS u
INNER JOIN
orders AS o ON o.user_id = u.id
GROUP BY
u.id

SQL: exclude users where at least one row is matching

I have a database of users (id, name) which is connected to database of their purchases (id, userId, price).
What I want to do is to find all users who didn't make a purchase with price 500. At first I though about such query, but it would return all rows where price is not 500, not the users themselves.
select * from purchase p
join user u on u.id = p.userId
and price != 500
Does somebody have an idea how to group it so only the users who NEVER did 500 purchase would show up?
One way is to group by the user and take only those groups having no purchase with a price of 500
select u.id, u.name
from user u
left join purchase p on u.id = p.userId
group by u.id, u.name
having sum(p.price = 500) = 0

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!