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.
Related
I have to do sum from 2 different tables and show it using MySQL:
total comments from table 1, total comments from table 2: What i have tried so far is,
SELECT u.name as name, u.username as username,
( SELECT SUM(total) FROM (SELECT (COUNT(nc.id)) as total FROM table1 as nc WHERE nc.user_id = u.id) UNION ALL (SELECT COUNT(pc.id) AS total FROM table2 pc WHERE pc.user_id = u.id) as finalTotal ) as total_comments
FROM user as u
GROUP BY u.id
It is giving me this error:
Every derived table must have its own alias
If I understand what you need, you have to modify the query like this :
select u.name,u.username,total as total_comments
from user as u
left join (
select id,sum(total) as total
from(
select nc.id,count(1) as total
from table1 as nc
group by nc.id
union all
select pc.id,count(1) as total
from table2 as pc
group by pc.id
) as t group by id
) comments on comments.id = u.id
Before UNION ALL put alias like you giving for all..
SELECT u.name as name, u.username as username,
( SELECT SUM(total) FROM (SELECT (COUNT(nc.id)) as total FROM table1 as nc WHERE nc.user_id = u.id) as vr UNION ALL (SELECT COUNT(pc.id) AS total FROM table2 pc WHERE pc.user_id = u.id) as finalTotal ) as total_comments
FROM user as u
GROUP BY u.id
SELECT u.name as name, u.username as username,
( SELECT SUM(total) FROM
(SELECT (COUNT(nc.id)) as total FROM table1 as nc WHERE nc.user_id = u.id
UNION ALL SELECT COUNT(pc.id) AS total FROM table2 pc WHERE pc.user_id = u.id) as finalTotal ) as total_comments
FROM user as u
GROUP BY u.id
Remove brackets before and after UNION ALL and check.
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!
I am looking to load all posts, sorted by newest first, but also limit them to three per user. I have no idea how to do that though! Here's the SQL I have currently to create the table and select the posts.
SELECT p.title, u.firstname, u.lastname
FROM post p
JOIN user u
ON p.user_id=u.id
ORDER
BY u.id, p.ctime DESC
#LIMIT TO 3 by user
;
This is what you can do
select
u.*,
p.*
from
user u
left join
(
select
p1.*
FROM
post p1
where
(
select
count(*)
from
post p2
WHERE
p1.user_id = p2.user_id
AND p1.id <= p2.id
) <= 3
order by p1.id desc
) p ON u.id = p.user_id
order by u.id
Took help from MySQL Limit LEFT JOIN Subquery after joining
Something like this might work
SELECT u.*,up.* FROM user u LEFT JOIN
(
SELECT `p`.`title`, `u`.`firstname`, `u`.`lastname`
FROM `post` `p`
ORDER
BY `p`.`ctime` DESC
LIMIT 3
)
up on up.user_id = u.id
I have three tables: userProfile, loginTimes, orders.
I am trying get each user's profile row, his last login time, and his last order row.
Here's my query:
Select u.*, t.loginTime, orders.* From userProfiles u
Inner Join
(Select userId, MAX(time) loginTime From loginTimes Group By userID) t
On u.userId = t.userID
Inner Join
(Select userId, MAX(enterDate) orderDate From orders Group By userId) o
On u.userID = o.userID
Inner Join
orders On orders.userId = u.userId And orders.enterDate = o.orderDate
Is there any way to rewrite without so many sub queries?
OP I think this is the query you are going for, this still requires 2 subqueries, but I don't believe your original query functioned as intended.
You could remove the loginTimes subquery, and use MAX(loginTime) in the outer SELECT list, but then you'd need to GROUP BY every field in the order table, which is arguably just as unclean.
The following query retrieves the UserId, latest LoginTime and the entire order record for the user's most recent order:
SELECT u.userId,
u.userName,
l.loginTime,
o.*
FROM userProfiles u
INNER JOIN ( SELECT userId,
loginTime = MAX(time)
FROM loginTimes
GROUP BY userID) l ON u.userId = l.userId
INNER JOIN ( SELECT *,
rowNum = ROW_NUMBER() OVER (PARTITION BY userId
ORDER BY enterDate DESC)
FROM orders) o ON u.userId = o.userId AND o.rowNum = 1
Working on SQLFiddle
You can easily re-write the aggregation with the following.
-- Aggregation
(
SELECT
T.userId,
MAX(time) as loginTime,
MAX(enterDate) as orderDate
FROM
loginTimes as T INNER JOIN orders as O
ON
T.userId = O.userId
GROUP BY
T.userId
)
However, I do not understand why you are calculating MAX(enterDate) and not using it.
The two tables that are joined without aggregation is easy also. You should stay away from using *. It is just wasted overhead if all the fields are not being used.
SELECT
U.*, O.*
FROM
userProfiles as U
INNER JOIN
orders as O
ON O.userId = U.userId
Please explain what you are trying to return as values from the Query. What is the business logic?
I believe this will do:
SELECT u.userID
,u.otherColumn
,MAX(t.time) AS loginTime
,MAX(o.enterDate) AS orderDate
FROM userProfiles u
JOIN loginTimes t ON t.userID = u.userID
JOIN orders o ON o.userID = u.userID
GROUP BY u.userID, u.otherColumn
For every other column in userProfiles you add to the SELECT clause, you need to add it to the GROUP BY clause as well..
Update:
Just because it can be done.. I tried it without any subquery :)
SELECT u.userID
,MAX(t.time) AS loginTime
,o.*
FROM userProfiles u
JOIN loginTimes t ON t.userID = u.userID
JOIN orders o ON o.userID = u.userID
LEFT JOIN orders o1 ON o.userID = o1.userID AND o.enterDate < o1.enterDate
WHERE o1.orderID IS NULL
GROUP BY u.userID
,o.* --write out the fields here
You'll have to write down the fields of the orders table you want in the select clause in your GROUP BY clause also.
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'].'"