How can I get ids of grouped rows? - mysql

I have a table like this:
// notifications
+----+--------+-----------+---------+--------------------+
| id | money | post_id | user_id | belongs_to_user_id |
+----+--------+-----------+---------+--------------------+
| 1 | 5 | 1 | 123 | 101 |
| 2 | 10 | 2 | 123 | 101 |
| 3 | -2 | 4 | 456 | 101 |
| 5 | -2 | 2 | 456 | 101 |
| 6 | -2 | 3 | 123 | 101 |
| 7 | 5 | 4 | 789 | 101 |
| 8 | 10 | 4 | 789 | 101 |
+----+--------+-----------+---------+--------------------+
And here is my query:
SELECT * FROM notifications
WHERE belongs_to_user_id = 101
GROUP BY post_id, user_id
ORDER BY id DESC
LIMIT 3
The current output should be something like this:
+----+--------+-----------+---------+--------------------+
| 5 | -2 | 2 | 456 | 101 |
| 6 | -2 | 3 | 123 | 101 |
| 8 | 10 | 4 | 789 | 101 |
+----+--------+-----------+---------+--------------------+
The seventh row is grouped and we cannot see it in the result. That's exactly the problem. Here is the expected result:
+----+--------+-----------+---------+--------------------+
| 5 | -2 | 2 | 456 | 101 |
| 6 | -2 | 3 | 123 | 101 |
| 7 | 5 | 4 | 789 | 101 |
| 8 | 10 | 4 | 789 | 101 |
+----+--------+-----------+---------+--------------------+
If I remove GROUP BY, then the fifth will be omitted. So here is the logic:
I want to the last three rows (regardless grouping). In other word, Emm ... it's hard to say, I want to select grouped rows (but not counting in LIMIT).
Any idea how can I do that?

It shows comma separated id by groups
SELECT
GROUP_CONCAT(id),
post_id
FROM notifications
WHERE belongs_to_user_id = 101
GROUP BY post_id, user_id
ORDER BY id DESC
LIMIT 3

Please try this query. It will get the last three "groups", and then extract all the rows of those groups (using a join):
SELECT t.*
FROM notifications t
INNER JOIN (SELECT s.post_id, s.user_id
FROM notifications s
WHERE belongs_to_user_id = 101
GROUP BY post_id, user_id
ORDER BY post_id DESC, user_id DESC
LIMIT 3) u
ON u.post_id = t.post_id
AND u.user_id = t.user_id
WHERE t.belongs_to_user_id = 101
ORDER BY t.id
Update: same query using DISTINCT in the subquery:
SELECT t.*
FROM notifications t
INNER JOIN (SELECT DISTINCT s.post_id, s.user_id
FROM notifications s
WHERE belongs_to_user_id = 101
ORDER BY post_id DESC, user_id DESC
LIMIT 3) u
ON u.post_id = t.post_id
AND u.user_id = t.user_id
WHERE t.belongs_to_user_id = 101
ORDER BY t.id

Please try this
SELECT * FROM notifications WHERE belongs_to_user_id = 101 GROUP BY id, money ORDER BY id DESC

So, you want correlation subquery if i am not wrong
select * from table t
where belongs_to_user_id = 101 and
user_id = (select max(user_id) from table where post_id = t.post_id)
Additionally, you could add limit clause to limit the records that you want.

Related

SQL Query: join with condition

I have the following tables:
Customer
| c_id | name |
| -------- | -------------- |
| 1 | Adam |
| 2 | Bradley |
| 3 | Chandler |
| 4 | Damian |
| 5 | Eric |
| 6 | Frank |
orders
| order_id | c_id | amount
| -------- | -------------- | -------------- |
| 1 | 1 | 50
| 2 | 1 | 2
| 3 | 2 | 15
| 4 | 2 | 22
| 5 | 2 | 10
| 6 | 2 | 7
| 7 | 3 | 7
| 8 | 3 | 2
| 9 | 5 | 18
| 10 | 5 | 24
| 11 | 6 | 60
| 12 | 6 | 1
I want to create a list of users who have order amounts over 50.
This list should include c_id, name and the sum of all their orders including those under 50.
so it should look like this:
| c_id | name | amount
| -------- | -------------- | -------------- |
| 1 |Adam | 52
| 6 | Frank | 61
You can use group by and having:
select c.c_id, c.name, sum(o.amount)
from orders o join
customers c
on o.c_id = c.c_id
group by c.c_id, c.name
having max(o.amount) > 50;
SELECT
c_id
, name
, SUM(amount) AS total_amount
FROM
orders a
INNER JOIN customer b
ON b.c_id = a.user_id
WHERE
c_id IN (
SELECT
user_id
FROM
orders
WHERE
amount >= 50)
GROUP BY c_id, name
Best to break this down into chunks:
Customers who have a total amount over 50:
SELECT user_id FROM orders GROUP BY user_id HAVING sum(amount) >= 50;
Sum of the amounts for each order for customers that meet the criteria above:
SELECT user_id, sum(amount) as order_total
FROM orders
WHERE user_id IN (SELECT user_id FROM orders HAVING sum(amount) >= 50 GROUP BY user_id)
GROUP BY user_id;
You can just join over to your customer table to grab the name. Didn't include since that is the more straightforward ask here.

MySQL sort by sum multiple columns in different tables

I have 3 tables:
Users
| id | name |
|----|-------|
| 1 | One |
| 2 | Two |
| 3 | Three |
Likes
| id | user_id | like |
|----|---------|-------|
| 1 | 1 | 3 |
| 2 | 1 | 5 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
Transations
| id | user_id | transaction |
|----|---------|-------------|
| 1 | 1 | -1 |
| 2 | 2 | 5 |
| 3 | 2 | -1 |
| 4 | 3 | 10 |
I need get sum of likes.like and transations.transation for each user and then sort it by its result.
I was able to do it for users and likes:
select users.*, sum(likes.like) as points
from `users`
inner join `likes` on `likes`.`user_id` = `users`.`id`
group by `users`.`id`
order by points desc
But then I add transactions table like this:
select users.*, (sum(likes.like)+sum(transactions.`transaction`)) as points
from `users`
inner join `likes` on `likes`.`user_id` = `users`.`id`
inner join `transactions` on `transactions`.`user_id` = `users`.`id`
group by `users`.`id`
order by points desc
It is show wrong results.
I expecting to see:
| id | name | points |
|----|-------|--------|
| 3 | Three | 12 |
| 1 | One | 7 |
| 2 | Two | 5 |
But get this instead:
| id | name | points |
|----|-------|--------|
| 3 | Three | 12 |
| 1 | One | 6 |
| 2 | Two | 5 |
So, how sort users by sum likes.like and transations.transation?
Thank you!
Since there's not a 1-to-1 relationships between transactions and likes, I think you need to use subqueries:
select users.*,
(select sum(points) from likes where user_id = users.id) as points,
(select sum(transaction) from transactions where user_id = users.id) as transactions
from users
order by points desc
Updated after more explanation of requirements:
select users.*,
(select sum(points) from likes where user_id = users.id) +
(select sum(transaction) from transactions where user_id = users.id) as points
from users
order by points desc

use standard values if there is no overlap in table columns

I have a table ce_relations and a table ce_values which i want to combine to a table ce_combined_values. The ce_combined_values table should have the exact same amount of rows as ce_relations. The query stated below does however only return the rows where the user_id and friend_id is existing in the ce_values.user_id column. I tried to solve this problem by using the IFNULL statement, but i guess there is also an extra condition missing in the WHERE clausule... any help is welcome!
INSERT INTO ce_combined_values (user_id, friend_id, relation_degree, user_value, friend_value, relation_value)
SELECT a.user_id, a.friend_id, a.relation_degree, IFNULL(b.1d_value, 0) as user_value, IFNULL(c.1d_value, 0) as friend_value, Least(b.1d_value, c.1d_value) as relation_value
FROM ce_relations a, ce_values b, ce_values c
WHERE a.relation_degree = 1 AND b.user_id = a.user_id AND c.user_id = a.friend_id AND b.user_id <> c.user_id
Union all
//same select query is used for relation_degree 2 with 2d_values and relation_degree 3 with 3d_values.
EDIT:
For example this is what I want to achieve:
table ce_relations:
+---------+-----------+-----------------+
| user_id | friend_id | relation_degree |
+---------+-----------+-----------------+
| 1 | 3 | 1 |
| 2 | 1 | 1 |
| 3 | 4 | 1 |
+---------+-----------+-----------------+
table ce_values:
+---------+----------+----------+----------+
| user_id | 1d_value | 2d_value | 3d_value |
+---------+----------+----------+----------+
| 1 | 5 | 10 | 33 |
| 2 | 10 | 12 | 44 |
| 3 | 20 | 13 | 55 |
+---------+----------+----------+----------+
should become ce_combined values ( deleted relation_degree and relation_value for readability)
+---------+-----------+------------+--------------+
| user_id | friend_id | user_value | friend_value |
+---------+-----------+------------+--------------+
| 1 | 3 | 5 | 20 |
| 2 | 1 | 10 | 5 |
| 3 | 4 | 20 | 0 |
+---------+-----------+------------+--------------+
but currently returns ( the row with friend_id = 4 is missing because it doesn't exist in ce_values)
+---------+-----------+------------+--------------+
| user_id | friend_id | user_value | friend_value |
+---------+-----------+------------+--------------+
| 1 | 3 | 5 | 20 |
| 2 | 1 | 10 | 5 |
+---------+-----------+------------+--------------+
If I understand your problem correctly, you need to LEFT JOIN your tables. And I don't think you need to UNION ALL the same query three times with just a different filter condition.
Maybe that helps you:
SELECT a.user_id, a.friend_id, a.relation_degree,
IFNULL(b.value, 0) as user_value,
IFNULL(c.value, 0) as friend_value,
Least(b.value, c.value) as relation_value
FROM ce_relations a
LEFT
JOIN ce_values b
ON a.user_id = b.user_id
LEFT
JOIN ce_values c
ON a.friend_id = c.user_id
AND b.user_id <> c.user_id
WHERE a.relation_degree IN (1, 2, 3)

SQL Statement Construction: Selecting Unique Records

I currently have a table of Users, and at what time they connected to a device (e.g. a Wifi Router).
+-------------+-----------+---------+------------+---------------------+
| location_id | device_id | user_id | dwell_time | date |
+-------------+-----------+---------+------------+---------------------+
| 14 | 1 | 1 | 27.000000 | 2014-01-04 00:51:12 |
| 15 | 2 | 1 | 12.000000 | 2014-01-04 01:08:56 |
| 16 | 1 | 1 | 12.000000 | 2014-01-04 01:09:26 |
| 17 | 2 | 1 | 318.000000 | 2014-01-04 01:09:38 |
| 18 | 1 | 2 | 20.000000 | 2014-01-04 01:30:03 |
| 19 | 2 | 3 | 20.000000 | 2014-01-04 01:30:03 |
+-------------+-----------+---------+------------+---------------------+
I need to write a query title "Get Latest User Connections".
Basically, it needs to go through the history table shown above, and pick the latest record (based on Date) for each user and display it. In the example above, the result should be:
+-------------+-----------+---------+------------+---------------------+
| location_id | device_id | user_id | dwell_time | date |
+-------------+-----------+---------+------------+---------------------+
| 17 | 2 | 1 | 318.000000 | 2014-01-04 01:09:38 |
| 18 | 1 | 2 | 20.000000 | 2014-01-04 01:30:03 |
| 19 | 2 | 3 | 20.000000 | 2014-01-04 01:30:03 |
+-------------+-----------+---------+------------+---------------------+
Can someone please help me write a SQL statement that does this?
Assuming the combination of user_id and date is unique in the table, you could
SELECT
tablename.*
FROM tablename
INNER JOIN (
SELECT user_id, MAX(`date`) AS maxdate
FROM tablename
GROUP BY user_id
) AS selector
ON tablename.user_id=selector.user_id AND tablename.`date`=selector.maxdate
select *
from users
inner join (select user_id,max(date) as maxdate
from users
group by user_id)T1
on T1.user_id = users.user_id
AND T1.maxdate = users.date
or if you don't want to have a subquery, you can user #variables like this query below
SELECT location_id,device_id,user_id,dwell_time,date,
IF(#prevUserId IS NULL OR #prevUserId != user_id,#row:=1,#row:=#row+1) as row,
#prevUserId := user_id
FROM users
HAVING row = 1
ORDER BY user_id,date DESC
here's the sqlFiddle
Try this:
SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.datec
FROM (SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.date
FROM users u ORDER BY u.user_id, u.date DESC
) A
GROUP BY u.user_id
OR
SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.date
FROM users u
INNER JOIN (SELECT u.user_id, MAX(u.date) AS latestDate
FROM users u GROUP BY u.user_id
) A ON u.user_id = A.user_id AND A.latestDate = u.date

Get all actions of the last-three users for each post

In a last Question, i asked about geting all actions of the last three users from a history table that stores all actions done by users on deferments posts, now what i want is to get the same thing but for each post.
all actions of donne by the last-three users for each posts
history table
id | post_id | action | user_id
1 | 5 | 1 | 3
1 | 23 | 2 | 1
2 | 24 | 2 | 6
3 | 34 | 1 | 7
4 | 35 | 1 | 1
5 | 36 | 1 | 1
6 | 23 | 2 | 3
7 | 24 | 2 | 1
8 | 23 | 1 | 4
9 | 24 | 1 | 5
10 | 24 | 1 | 1
11 | 23 | 1 | 2
12 | 23 | 4 | 1
thanks and sorry if it seem to be a duplicate post
I think this will work:
SELECT a.user_ID, a.post_id, a.action
FROM tableName a
INNER JOIN
(
SELECT DISTINCT
#curRow:=IF(#prevRow=post_Id,#curRow+1,1) rn,
user_ID,
Post_Id,
#prevRow:=Post_Id
FROM (
SELECT DISTINCT Post_Id, User_Id
FROM TableName
ORDER BY Post_Id, ID DESC
) t
JOIN (SELECT #curRow:= 0) r
) b ON a.post_id = b.post_id AND a.user_id = b.user_id
WHERE b.rn <= 3
ORDER BY a.post_id, a.User_ID
And the Fiddle.
Coudl this be what you are looking for?
SQLFiddle
Code:
SELECT a.user_ID,
group_concat(a.post_id),
group_concat(a.action)
FROM tableName a
INNER JOIN
(
SELECT DISTINCT user_ID
FROM tableName
ORDER BY ID DESC
LIMIT 3
) b ON a.user_ID = b.user_ID
group by a.user_id
ORDER BY a.User_ID;
| USER_ID | GROUP_CONCAT(A.POST_ID) | GROUP_CONCAT(A.ACTION) |
--------------------------------------------------------------
| 2 | 7 | 3 |
| 3 | 5,5,4 | 1,2,5 |
| 6 | 7 | 2 |