SQL Statement Construction: Selecting Unique Records - mysql

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

Related

MySQL merge columns from multiple tables

i'm trying to select some data in the following way:
field:
+----------+------------+-----------+
| id | room_id | server_id |
+----------+------------+-----------+
| 1 | 34 | 0 |
| 2 | 34 | 0 |
| 3 | 35 | 1 |
+----------+------------+-----------+
user_position:
+----------+------------+-----------+
| user_id | server_id | position |
+----------+------------+-----------+
| 11 | 0 | 2 |
| 17 | 1 | 25 |
| 19 | 0 | 28 |
+----------+------------+-----------+
room:
+----------+------------+-----------+
| id | server_id | background|
+----------+------------+-----------+
| 34 | 0 | #d91a1a |
| 35 | 1 | #f81b2a |
| 36 | 0 | #191b4a |
+----------+------------+-----------+
RESULT:
(I hope I didn't mess it up)
+----------+------------+-----------+------------+------------+
| id | server_id | background| room_id | user_id |
+----------+------------+-----------+------------+------------+
| 1 | 0 | #d91a1a | 34 | null |
| 2 | 0 | #d91a1a | 34 | 11 |
| 3 | 1 | #f81b2a | 35 | null |
| 25 | 1 | null | null | 17 |
| 28 | 0 | null | null | 19 |
+----------+------------+-----------+------------+------------+
Unfortunately i couldn't write the right query to achieve this result. The best I could get was that the field.id, user_position.position and field.room_id, user_position.room_id columns were separated. I have no idea how to merge them together.
Can somebody help me?
UPDATE
OK, so after some trying I got this:
SELECT field.id, field.server_id, field.room_id, null AS user_id, room.background
FROM field
LEFT JOIN room ON room.id = field.room_id
WHERE field.server_id = 0
UNION
SELECT user_position.position, user_position.server_id, null, user_position.user_id, room.background
FROM user_position
LEFT JOIN room ON room.id = (SELECT field.room_id FROM field WHERE field.id = user_position.position)
WHERE user_position.server_id = 0
Now it is working I just want to ask if there isn't a better way to achieve the same result. Or do you think this query is good enough?
With the given set of data, you may try below -
SELECT COALESCE(F.id, UP2.position) id
,COALESCE(F.server_id, UP2.server_id) server_id
,UP2.background
,F.room_id
,UP2.user_id
FROM field F
LEFT OUTER JOIN (SELECT UP.server_id, R.background, UP.position, UP.user_id
FROM (SELECT server_id, position, user_id, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY user_id) rn
FROM user_position) UP
JOIN (SELECT server_id, background, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY id) rn
FROM room) R ON UP.rn = R.rn
AND UP.server_id = R.server_id) UP2 ON F.id = UP2.position
UNION
SELECT COALESCE(F.id, UP3.position) id
,COALESCE(F.server_id, UP3.server_id) server_id
,UP3.background
,F.room_id
,UP3.user_id
FROM field F
RIGHT OUTER JOIN (SELECT UP.server_id, R.background, UP.position, UP.user_id
FROM (SELECT server_id, position, user_id, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY user_id) rn
FROM user_position) UP
JOIN (SELECT server_id, background, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY id) rn
FROM room) R ON UP.rn = R.rn
AND UP.server_id = R.server_id) UP3 ON F.id = UP3.position
ORDER BY id
Here is the demo.

How can I get ids of grouped rows?

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.

How to select working time by calling time, group id?

I need to select working time - SUM(users_worktime.length) as working_time) when users was calling by the calling time and users group id. How to do that?
Here is my select:
SELECT
users_groups.name as name,
COUNT(DISTINCT calls.id) as calls,
SUM(calls.status = 'ended') as answers,
COUNT(DISTINCT orders.id) as deals,
ROUND(COUNT(DISTINCT orders.id) * 100 / SUM(calls.status = 'ended'),2) as rate,
SUM(case when calls.status = 'ended' then calls.call_length else 0 end) as talking_time,
//SUM(users_worktime.length) as working_time
FROM
users_groups
LEFT JOIN users ON users.group_id = users_groups.id
LEFT JOIN calls ON (calls.user_id = users.id AND calls.created_at >= '2015-12-30 00:00:00' AND calls.created_at <= '2015-12-31 23:59:59')
LEFT JOIN orders ON orders.id = calls.order_id AND orders.status = 'finished'
WHERE 1
AND users.group_id = 1
GROUP BY
users_groups.id
I need result like this:
| name | calls | answers | deals | rate | talking_time| working_time |
------------------------------------------------------------------------
| Group | 4 | 3 | 2 | 75 % | 180 | 355.00 |
And here are my data tables:
users_worktime:
| id | user_id | length | start |
-----------------------------------------------
| 1 | 2 | 130 | 2015-12-30 07:53:38 |
| 2 | 8 | 55 | 2015-12-30 12:53:38 |
| 3 | 8 | 170 | 2015-12-31 22:53:38 |
users:
| id | username | group_id |
----------------------------
| 2 | Thomas | 1 |
| 8 | Haroldas | 1 |
groups:
| id | name |
-------------
| 1 | Group |
calls:
| id | user_id | order_id | status | call_length | created_at |
-------------------------------------------------------------------------
| 1 | 2 | 3 | ended | 35 | 2015-12-30 07:53:38 |
| 2 | 8 | 4 | ended | 100 | 2015-12-31 12:53:38 |
| 3 | 8 | NULL | started | 15 | 2015-12-31 14:53:38 |
| 4 | 8 | NULL | ended | 45 | 2015-12-31 20:53:38 |
orders:
| id | user_id | call_id | start |
-----------------------------------------------
| 3 | 2 |1 | 2015-12-30 07:53:38 |
| 4 | 8 |2 | 2015-12-31 12:53:38 |
Thank you
EDIT:
I trying with subquery like this but is not correct because SUM is only one user of group
SELECT
users_groups.name as name,
COUNT(DISTINCT calls.id) as calls,
SUM(calls.status = 'ended') as answers,
COUNT(DISTINCT orders.id) as deals,
ROUND(COUNT(DISTINCT orders.id) * 100 / SUM(calls.status = 'ended'),2) as rate,
SUM(case when calls.status = 'ended' then calls.call_length else 0 end) as talking_time,
(SELECT SUM(users_worktime.length) FROM users_worktime WHERE users_worktime.user_id = users.id AND users_worktime.start >= '2015-12-30 00:00:00' AND users_worktime.start <= '2015-12-31 23:59:59') as working_time
FROM
users_groups
LEFT JOIN users ON users.group_id = users_groups.id
LEFT JOIN calls ON (calls.user_id = users.id AND calls.created_at >= '2015-12-30 00:00:00' AND calls.created_at <= '2015-12-31 23:59:59')
LEFT JOIN orders ON orders.id = calls.order_id AND orders.status = 'finished'
WHERE 1
AND users.group_id = 1
GROUP BY
users_groups.id

how to merge tables and sum

I have two tables with some specific data:
users:
+----+------------+
| id | username |
+----+------------+
| 1 | rob |
| 2 | john |
| 3 | jane | <--- jane never has donated
+----+------------+
donations:
+--------------------+------------+
| uid | amount | date |
+---------+----------+------------+
| 1 | 20 | 2013-10-10 |
| 2 | 5 | 2013-10-03 |
| 2 | 50 | 2013-09-25 |
| 2 | 5 | 2013-10-01 |
+---------+----------+------------+
Result I want:
+---------+-------------+---------+-------------+
| id | username | amount | monthly | <- sum of donations this month
+---------+-------------+---------+-------------+
| 1 | rob | 20 | 1 |
| 2 | john | 60 | 3 |
| 3 | jane | 0 | 0 | <- jane added
+---------+-------------+-----------------------+
This is my query:
SELECT t1.*, sum(t2.amount) amount, count(*) as monthly
FROM users t1
inner join donations t2
on t2.uid = t1.id
group by t1.username
EDIT: forgot to add jane, he never has donated.
How I can do this?
Your output is wrong as you didn't filter out the september record in your results (only October 2013 should be taken into account).
Your expected output should be this:
| ID | USERNAME | AMOUNT | MONTHLY |
|----|----------|--------|---------|
| 1 | rob | 20 | 1 |
| 2 | john | 10 | 2 |
| 3 | jane | 0 | 0 |
The query to get the output is:
SELECT
u.id,
u.username,
COALESCE(sum(d.amount), 0) amount,
COUNT(d.uid) monthly
FROM users u
LEFT JOIN donations d
ON u.id = d.uid
AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
GROUP BY u.id
Assuming users.ID holds unique values you can leave the group by as u.id if it is not then you will have to group by u.id, u.username.
Fiddle here.
SELECT t1.*, sum(t2.amount) as amount, count(t2.amount,t2.`date`) as monthly
FROM users t1
inner join donations t2
on t2.uid = t1.id
where month(t2.`date`)=month(curdate()) and year(t2.`date`)=year(curdate())
group by t1.username
How about this use current date and extract the month and match with the month from column date
CURDATE() , MONTH()
SELECT t1.*, (CASE WHEN SUM(t2.amount) IS NULL THEN 0 ELSE SUM(t2.amount) END) amount,
count(*) as monthly
FROM users t1
left join donations t2
on t2.uid = t1.id
WHERE MONTH(t2.`date`)=MONTH(CURDATE()) AND YEAR(t2.`date`)=YEAR(CURDATE())
group by t1.id

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 |