How can I find users don't bought products by specific date?
I have three tables:
USERS
id, name, surname, email
PRODUCTS
id, name, description
TRANSACTIONS
id, id_user, id_product, date
I would like to know users doesn't bought products with id 1,3,4 in the last 3 months for example.
I've tried with these:
SELECT u.* FROM users u LEFT JOIN transactions t ON u.id = t.id_user
WHERE t.id_product != 1 AND t.id_product != 3 AND t.id_product != 4
AND t.date >= "2019-04-01";
SELECT u.* FROM users u LEFT JOIN transactions t ON u.id = t.id_user
WHERE t.id_product NOT IN(1,3,4) AND t.date >= "2019-04-01";
The LEFT JOIN will work if you set the conditions in the ON clause and select only the non matching rows from the table transactions:
SELECT u.*
FROM users u LEFT JOIN transactions t
ON u.id = t.id_user AND t.date >= '2019-04-01' AND t.id_product IN (1, 3, 4)
WHERE t.id_user IS NULL
Also NOT EXISTS works:
SELECT u.* FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM transactions t
WHERE u.id = t.id_user AND t.date >= '2019-04-01' AND t.id_product IN (1, 3, 4)
)
Related
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!
Let's say we have the following model :
A table of exercises (id, name)
A table of users (id, name, email)
A tables of exams (id, user_id, day)
A join table between exams and exercises (exam_id, exercise_id)
Each user can make one exam each day.
Each exam is made of many exercises (has and belongs to many).
For each user (when logged), I would like to display a table displaying all the exercises and for each the count and percent of exams the user made during a given period of time (using the day attribute of the exams).
At this time I can display all the exercises the user made and their percent during a given period with the following query.
SELECT
o.user_id,
o.exercise_id,
o.exercise_name,
COUNT(*) AS nb_exams,
(COUNT(*) * 100 / (
SELECT COUNT(*)
FROM exams
LEFT JOIN users ON exams.user_id = users.id
WHERE users.id = 1 AND exams.day >= "2015-09-01" AND exams.day <= "2015-09-07"
)) AS percent
FROM (
SELECT
exercises.id AS exercise_id,
exercises.name AS exercise_name,
exams.id AS exam_id,
users.id AS user_id
FROM exercises
LEFT JOIN exercises_exams ON exercises_exams.exercise_id = exercises.id
LEFT JOIN exams ON exercises_exams.exam_id = exams.id
LEFT JOIN users ON exams.user_id = users.id
WHERE users.id = 1 AND exams.day >= "2015-09-01" AND exams.day <= "2015-09-07"
GROUP BY exercise_id,exam_id
) AS o
GROUP BY exercise_id;
But I also want to display the exercises he did not make yet with a value of 0.
Is it possible to do this with one query of mysql ?
EDIT-
Here is the sqlfiddle for the current query. You will notice that only the 3 exercises which are used in exams are returned. I would like to list all the exercises with the count and percent (even the fourth with 0)
Firstly, you should remove the where condition to the join clause in the inner query to see a 0 result when an exercise wasn't performed. Getting a user_id corresponding to the 0 result doesn't make sense. This is the closest solution i can think of.
Fiddle
SELECT
o.user_id,
o.exercise_id,
o.exercise_name,
sum(case when exam_id is null then 0 else 1 end) AS nb_exams,
(sum(case when exam_id is null then 0 else 1 end) * 100 / (
SELECT COUNT(*)
FROM exams
LEFT JOIN users ON exams.user_id = users.id
where exams.day >= "2015-09-01" AND exams.day <= "2015-09-07"
)) AS percent
FROM (
SELECT
exercises.id AS exercise_id,
exercises.name AS exercise_name,
exams.id AS exam_id ,
users.id AS user_id
FROM exercises
LEFT JOIN exercises_exams ON exercises_exams.exercise_id = exercises.id
LEFT JOIN exams ON exercises_exams.exam_id = exams.id
and exams.day >= "2015-09-01" AND exams.day <= "2015-09-07"
LEFT JOIN users ON exams.user_id = users.id
) AS o
GROUP BY exercise_id,exercise_name
I have three tables, users, activities and purchases.
Users has many activities and purchases, activities has 4 types.
I need to query users like this:
[
{
user_id: 1,
// from activities
post_count: 2,
updated_count: 3,
print_count: 4,
share_count: 5,
// from purchases
purchase_count: 6
},
...
]
I use this sql:
SELECT u.id, post.post_count, updated.update_count, print.print_count, share.share_count, purchase.purchase_count
FROM users as u
LEFT JOIN (
SELECT user_id, activity_type, count(*) as post_count
FROM activities
WHERE activity_type = 1
GROUP BY user_id
) post
ON u.id = post.user_id
LEFT JOIN (
SELECT user_id, activity_type, count(*) as update_count
FROM activities
WHERE activity_type = 2
GROUP BY user_id
) updated
ON u.id = updated.user_id
LEFT JOIN (
SELECT user_id, activity_type, count(*) as print_count
FROM activities
WHERE activity_type = 3
GROUP BY user_id
) print
ON u.id = print.user_id
LEFT JOIN (
SELECT user_id, activity_type, count(*) as share_count
FROM activities
WHERE activity_type = 4
GROUP BY user_id
) share
ON u.id = share.user_id
LEFT JOIN (
SELECT user_id, count(*) AS purchase_count
FROM purchases
GROUP BY user_id
) purchase
ON u.id = purchase.user_id
how can i optimize performance with this query?
Great thanks to Eugen Rieck
I modified his query to this, then it works.
SELECT
users.id AS user_id,
SUM(IF((activities.activity_type=1),1,0)) AS post_count,
SUM(IF((activities.activity_type=2),1,0)) AS update_count,
SUM(IF((activities.activity_type=3),1,0)) AS print_count,
SUM(IF((activities.activity_type=4),1,0)) AS share_count,
IFNULL(purchase.count,0) AS purchase_count
FROM
users
LEFT JOIN activities ON activities.user_id=users.id
LEFT JOIN (
SELECT user_id, count(*) AS count
FROM purchases
GROUP BY user_id
) purchase
ON users.id = purchase.user_id
GROUP BY users.id
Currently you run the activities table 4 times - this could be folded into one:
SELECT
users.id AS user_id,
SUM(IF(activites.activity_type=1,1,0)) AS post_count,
SUM(IF(activites.activity_type=2,1,0)) AS update_count,
SUM(IF(activites.activity_type=3,1,0)) AS print_count,
SUM(IF(activites.activity_type=4,1,0)) AS share_count,
IFNULL(COUNT(purchases.id),0) AS purchase_count
FROM
users
INNER JOIN activities ON activities.user_id=users.id
LEFT JOIN purchases ON purchases.user_id=users.id
GROUP BY users.id
I have the following query, in which I used JOINs. It says:
unknown column m.bv ..
Could you please take a look and tell me what I'm doing wrong?
$query4 = 'SELECT u.*, SUM(c.ts) AS total_sum1, SUM(m.bv) AS total_sum
FROM users u
LEFT JOIN
(SELECT user_id ,SUM(points) AS ts FROM coupon GROUP BY user_id) c
ON u.user_id=c.user_id
LEFT JOIN
(SELECT user_id ,SUM(points) AS bv FROM matching GROUP BY user_id) r
ON u.user_id=m.user_id
where u.user_id="'.$_SESSION['user_name'].'"
GROUP BY u.user_id';
You are selecting SUM(points) AS bv from the table with the alias r, there is no tables with the alias m. So that it has to be r.bv instead like so:
SELECT
u.*,
SUM(c.ts) AS total_sum1,
SUM(r.bv) AS total_sum
FROM users u
LEFT JOIN
(
SELECT
user_id,
SUM(points) AS ts
FROM coupon
GROUP BY user_id
) c ON u.user_id=c.user_id
LEFT JOIN
(
SELECT
user_id,
SUM(points) AS bv
FROM matching
GROUP BY user_id
) r ON u.user_id = m.user_id
where u.user_id="'.$_SESSION['user_name'].'"
GROUP BY u.user_id
Replace m., with r. Look at second Join
You have aliased the derived table with r and you reference that table (twice) with m. Correct one or the other.
Since you group by user_id in the two subqueries and user_id is (I assume) the primary key of table user, you don't really need the final GROUP BY.
I would write it like this, if it was meant for all (many) users:
SELECT u.*, COALESCE(c.ts, 0) AS total_sum1, COALESCE(m.bv, 0) AS total_sum
FROM users u
LEFT JOIN
(SELECT user_id, SUM(points) AS ts FROM coupon GROUP BY user_id) c
ON u.user_id = c.user_id
LEFT JOIN
(SELECT user_id, SUM(points) AS bv FROM matching GROUP BY user_id) m
ON u.user_id = m.user_id
and like this in your (one user) case:
SELECT u.*, COALESCE(c.ts, 0) AS total_sum1, COALESCE(m.bv, 0) AS total_sum
FROM users u
LEFT JOIN
(SELECT SUM(points) AS ts FROM coupon
WHERE user_id = "'.$_SESSION['user_name'].'") c
ON TRUE
LEFT JOIN
(SELECT SUM(points) AS bv FROM matching
WHERE user_id = "'.$_SESSION['user_name'].'") m
ON TRUE
WHERE u.user_id = "'.$_SESSION['user_name'].'"
The last query can also be simplified to:
SELECT u.*,
COALESCE( (SELECT SUM(points) FROM coupon
WHERE user_id = u.user_id)
, 0) AS total_sum1,
COALESCE( (SELECT SUM(points) FROM matching
WHERE user_id = u.user_id)
, 0) AS total_sum
FROM users u
WHERE u.user_id = "'.$_SESSION['user_name'].'"
I have a users table and a payments table, for each user, those of which have payments, may have multiple associated payments in the payments table. I would like to select all users who have payments, but only select their latest payment. I'm trying this SQL but i've never tried nested SQL statements before so I want to know what i'm doing wrong. Appreciate the help
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*
FROM payments AS p
ORDER BY date DESC
LIMIT 1
)
ON p.user_id = u.id
WHERE u.package = 1
You need to have a subquery to get their latest date per user ID.
SELECT u.*, p.*
FROM users u
INNER JOIN payments p
ON u.id = p.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON p.user_ID = b.user_ID AND
p.date = b.maxDate
WHERE u.package = 1
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
SELECT id
FROM payments AS p2
WHERE p2.user_id = u.id
ORDER BY date DESC
LIMIT 1
)
Or
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
SELECT 1
FROM payments AS p2
WHERE
p2.user_id = p.user_id AND
(p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)
These solutions are better than the accepted answer because they work correctly when there are multiple payments with same user and date. You can try on SQL Fiddle.
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC
Check out this sqlfiddle
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM payments AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
ORDER BY p.user_id ASC, date DESC)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
You can try this:
SELECT u.*, p.*
FROM users AS u LEFT JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
FROM payments
) AS p ON u.userid = p.userid AND p.RowNo=1
There are two problems with your query:
Every table and subquery needs a name, so you have to name the subquery INNER JOIN (SELECT ...) AS p ON ....
The subquery as you have it only returns one row period, but you actually want one row for each user. For that you need one query to get the max date and then self-join back to get the whole row.
Assuming there are no ties for payments.date, try:
SELECT u.*, p.*
FROM (
SELECT MAX(p.date) AS date, p.user_id
FROM payments AS p
GROUP BY p.user_id
) AS latestP
INNER JOIN users AS u ON latestP.user_id = u.id
INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
WHERE u.package = 1
#John Woo's answer helped me solve a similar problem. I've improved upon his answer by setting the correct ordering as well. This has worked for me:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN (
SELECT user_ID, MAX(date) as maxDate FROM
(
SELECT user_ID, date
FROM payments
ORDER BY date DESC
) d
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
I'm not sure how efficient this is, though.
SELECT U.*, V.* FROM users AS U
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id
This will get it working
Matei Mihai given a simple and efficient solution but it will not work until put a MAX(date) in SELECT part so this query will become:
SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
And order by will not make any difference in grouping but it can order the final result provided by group by. I tried it and it worked for me.
My answer directly inspired from #valex very usefull, if you need several cols in the ORDER BY clause.
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
This is quite simple do The inner join and then group by user_id and use max aggregate function in payment_id assuming your table being user and payment query can be
SELECT user.id, max(payment.id)
FROM user INNER JOIN payment ON (user.id = payment.user_id)
GROUP BY user.id
If you do not have to return the payment from the query you can do this with distinct, like:
SELECT DISTINCT u.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
This will return only users which have at least one record associated in payment table (because of inner join), and if user have multiple payments, will be returned only once (because of distinct), but the payment itself won't be returned, if you need the payment to be returned from the query, you can use for example subquery as other proposed.