Select latest record on many to many relationship - sql

I have a many to many relationship like below. A user can change their role, and I want to get all users with their last role equals to role2 (example).
users
user_id | user_name | user_password
1 | user1 | *hashed password*
2 | user2 | *hashed password*
3 | user3 | *hashed password*
roles
role_id | role_name | role_description
1 | role1 | *description*
2 | role2 | *description*
user_roles
user_roles_id | user_id | role_id | created_at
1 | 1 | 1 | 2018-04-10 01:01:01
2 | 2 | 2 | 2018-04-10 01:01:02
3 | 3 | 1 | 2018-04-10 01:01:03
4 | 1 | 2 | 2018-04-12 01:01:01
5 | 1 | 1 | 2018-04-13 01:01:02
6 | 2 | 1 | 2018-04-14 01:01:01
7 | 3 | 2 | 2018-04-14 01:01:02
8 | 2 | 2 | 2018-04-15 01:01:01
9 | 1 | 2 | 2018-04-15 01:01:02
10 | 1 | 1 | 2018-04-16 01:01:01
From those tables, I want to get things like
user_id | role_id
2 | 2
3 | 2
But all this time I got
user_id | role_id
1 | 2
2 | 2
3 | 2
user1 should not be there because its last role was role1.
So what I'm trying to do is, get users where last role = 2.
Any help would be appreciated! Thanks in advance!
Addition
The results that I wanted is all data from users table. So it might be like
user_id | user_name | <and all of the rest>
2 | user2 | ...
3 | user3 | ...
So that, the field role_id I mentioned above is just reference to select the user.

This is one solution. Essentially, you are looking at each user's latest role, and only include it in the output, if the role is 2.
SELECT
ur.user_id, ur.role_id
FROM
user_roles ur
WHERE
ur.created_at = (SELECT MAX(created_at)
FROM user_roles
WHERE user_id = ur.user_id)
AND ur.role_id = 2
GROUP BY
ur.user_id, ur.role_id;
EDIT
Based on the additional information from the comments, the following will return all information from the Users table:
SELECT
u.*
FROM
users u INNER JOIN (
SELECT
ur.user_id
FROM
user_roles ur
WHERE
ur.created_at = (SELECT MAX(created_at)
FROM user_roles
WHERE user_id = ur.user_id)
AND ur.role_id = 2
GROUP BY
ur.user_id, ur.role_id) tmp ON tmp.user_id = u.user_id;

Use subquery with correlation approach and do the joins
select u.user_id, u.user_name, u.user_password,
r.role_id, r.role_name, r.role_description
from (select t.user_id, (select role_id from user_roles
where user_id = t.user_id
order by created_at desc LIMIT 1) as role_id
from user_roles t
group by t.user_id) tt
inner join users u on u.user_id = tt.user_id
inner join roles r on r.role_id = tt.role_id
where r.role_id <> 1;

Related

How to generate multiple rows based on a single record when join a table?

I have two tables User and Post. User has many posts, so user_id is the foreign key.
User:
id | name | base
---+------+-------
1 | User1| 5
Post:
id | content | min | max | user_id
---+---------+------+-----+--------
1 | Test | 8 | 10 | 1
2 | Test2 | 10 | 15 | 1
There could also have other table to join(Which is not show in the example). In general, I can joins the posts and other tables:
SELECT users.id, users.content, posts.min, posts.max, posts.content, ...
FROM users
JOIN table_1
ON condition_1
JOIN table_2
ON condition_2
JOIN posts
ON users.id = posts.user_id
Now I want to get users, but join posts and extend the posts based on user.base, which will seperate id=1 post into two records. So finally I want to get 3 records:
user.id | user.name | user.base | post.id | post.content | post.min | post.max | post.user_id
--------+-----------+-----------+---------+--------------+----------+----------+----------
1 | User1 | 5 | 1 | Test | 5 | 8 | 1
1 | User1 | 5 | 1 | Test | 8 | 10 | 1
1 | User1 | 5 | 2 | Test2 | 10 | 15 | 1
Is it possible to get the result via SQL?
I guess below query would do the job if i understood it correctly ,
(select u.id as uid,u.name,u.base,p.id,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
and min = (select min(min) from posts p1 where p1.user_id = u.id))
union
select u.id as uid,u.name,u.base,p.id,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id
order by uid,min
dbfiddle example with whole solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=592d8eb5803c11b2a30cac388344a402
UPDATE : To join the results with other tables,use the above query as inline view and join with tables with conditions as desired.
Lets assume another table user_addr that has address details for all users and user_id is the foreign key that references user.id
select uid,name,base,pid,content,min,max,magic_table.user_id,addr_typ,addrline1,addrline2,city,postcode,prov,country
from
((select u.id as uid,u.name,u.base,p.id as pid,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
order by min asc
fetch first 1 rows only)
union
select u.id as uid,u.name,u.base,p.id as pid,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id) magic_table, user_addr
where magic_table.user_id = user_addr.user_id
Output :
dbfiddle link with solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=ea697bbf1502857834b461edd8ccf447

SQL count referrals for each user

My query:
SELECT COUNT(referrer) as refs, SUM(amount) as total, contracts.id, userid, fine
FROM contracts
JOIN users ON contracts.userid = users.id
WHERE active = 1
GROUP BY userid
my users table :
id | username | referrer (int)
1 | test | 2
2 | drekorig |
3 | maximili | 2
my contracts table:
id ! userid | amount | fine | active
1 | 1 | 50 | 23/10/2018 | 1
2 ! 2 | 120 | 24/10/2018 | 1
3 | 2 | 150 | 24/10/2018 | 1
How do I get the count of referrals for each User? My query actually gets the number of contracts instead...
Expected result:
refs | total | id | userid | fine
0 | 0 | 1 | 1 | 23/10/2018
2 | 270 | 2 | 2 | 24/10/2018
http://sqlfiddle.com/#!9/0a464d/5
SELECT r.count as refs,
SUM(amount) as total,
MAX(c.id),
u.id,
MAX(fine)
FROM users u
LEFT JOIN
(SELECT referrer, COUNT(*) `count`
FROM users
GROUP BY referrer
) r
ON u.id = r.referrer
JOIN contracts c
ON c.userid = u.id
WHERE active = 1
GROUP BY u.id

Inner queries on the same table - is there a better way?

I have these two tables (simplified versions)
Orders
owner_id | user_1 | user_2 | amount | order_id
-----------------------------------------------
1 | 2 | 3 | 100 | AAA
1 | 7 | 2 | 200 | BBB
2 | 3 | 5 | 400 | CCC
Users
user_id | username
------------------
1 | John
2 | Robert
3 | Sally
4 | Mario
5 | Albert
6 | Hernest
7 | Homer
I need to get, in one query, all the info related to a particular order, including the owner_id, user_1, user_2 usernames.
The result I'm trying to achieve is the following:
owner_id | owner_username | user_1_id | user_1_username | user_2_id | user_2_username | order_total
----------------------------------------------------------------------------------------------------
1 | John | 2 | Robert | 3 | Sally | 100
So far I'm getting all I need with a query like this:
SELECT o.owner_id AS owner_id, (SELECT username FROM Users where user_id = 1) AS owner_username,
o.user_1 AS user_1_id, (SELECT username FROM Users where user_id = 2) AS user_1_username,
o.user_2 AS user_2_id, (SELECT username FROM Users where user_id = 3) AS user_2_username,
o.amount AS order_total
FROM Orders.o
WHERE o.order_id = 'AAA'
This is an example to retrieve the info for the first order.
I'm not very satisfied by the inner queries I have to do to get each username, I think it's kinda ugly.
Is there a more elegant or more performant way to get the usernames?
Thank you
This may help
SELECT od.*,
U1.username AS 'User_1_Name',
U2.username AS 'User_2_Name',
U3.username AS 'User_3_Name'
FROM Orders od
LEFT OUTER JOIN Users U1
ON od.Owner_Id = U1.User_Id
LEFT OUTER JOIN Users U2
ON od.User_1 = U2.User_Id
LEFT OUTER JOIN Users U3
ON od.User_2 = U3.User_Id
WHERE order_id = 'AAA'

display basic information from tables

I have two tables i.e.
Users
uid | firstname
1 | John
2 | Bob
3 | Paul
4 | Peter
Calls
cid | assigned_to | caller_id
1 | 2 | 1
2 | 1 | 3
3 | 2 | 4
4 | 4 | 2
assigned_to and caller_id are just the uid in users.
I just want to display the results of each call:
call_id | username(assigned_to) | username(caller_id)
How can I do this in SQL?
Thanks,
Try this:
select
cid as call_id,
A.username, -- assingned to
B.username -- caller id
from calls
left join users A on calls.assigned_to = A.uid
left join users B on calls.caller_id = B.uid

Include admin role in users table from roles table

Is there a way to query users table like this:
| id | username |
-----------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
and user_roles table:
| id_user | id_role |
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 3 | 1 |
assuming that role with id = 1 is an admin role, to produce result that looks like this:
| id | username | admin |
-------------------------
| 1 | user1 | Y |
| 2 | user2 | N |
| 3 | user3 | Y |
I think it can be done using nested SELECT statements, but I was wondering if it's doable using JOIN.
Edit:
The admin column value doesn't have to be Y or N, it can be admin role id (1) or NULL or whatever that will let me know if user is an admin
I would try this:
select u.id, u.username, if (id_role is null, 'N', 'Y') as is_admin
from users u
left outer join user_roles r
on u.id = r.id_user and r.id_role = 1
But I'm not 100% sure.
Well, you can join like this, which will give you the id_role in the result.
SELECT u.*, r.id_role
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user
You can add WHERE r.id_role=1 to get just the admins, etc.
But to get the admin as "Y" or "N" as you wanted, you can use an IF on whether the id_role is 1 or not.
SELECT u.*, IF(r.id_role = 1, "Y", "N") as admin
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user
select u.id_user, if(count(1) > 0, 'Y', 'N')
from user u left outer join user_roles ur on u.user_id = ur.user_roles
where ur.id_role=1
group by u.id_user
I'm not using mysql, so just googled it has the 'if' function, if that's wrong, replace with DECODE, COALESCE or alike.