SQL join help for friend list - sql

I have three database tables: users, user_profiles and friends:
users
id
username
password
user_profiles
id
user_id
full_name
friends
id
usera_id
userb_id
What would be a query which finds the friends list of any user and also joins the table users and user_profiles to get the profile and user information of that friend?

Use:
SELECT f.username,
up.*
FROM USERS f
JOIN USER_PROFILES up ON up.user_id = f.id
JOIN FRIENDS fr ON fr.userb_id = f.id
JOIN USERS u ON u.id = fr.usera_id
WHERE u.username = ?
...assuming userb_id is the friend id.

This may not be the best way to do it, but this felt like the logical way:
select a.id , a.friend_id ,
Users.username
from
( SELECT id , IF(usera_id = 1, userb_id , usera_id) friend_id
FROM friends
where usera_id = 1 OR userb_id = 1 ) a
left join Users on a.friend_id = Users.id
this uses a mySQL function so probably wont work in Oracle/MSSQL

Falling back on bad habits (not using JOIN notation in the FROM clause):
SELECT a.id, a.username, a.full_name,
b.id, b.username, b.full_name
FROM friends AS f, users AS ua, users AS ub,
user_profiles AS a, user_profiles AS b
WHERE f.usera_id = ua.id
AND f.userb_id = ub.id
AND a.user_id = ua.id
AND b.user_id = ub.id
The key point is using table aliases (all those 'AS' clauses) and referencing the same table more than once when necessary.
Someone could write this with JOIN instead.

Some modification to eugene y's answer, will this work?
SELECT * FROM users u
JOIN friends f ON (f.userb_id = u.id OR f.usera_id = u.id)
JOIN user_profiles p ON u.id = p.user_id
WHERE u.id = ?

Related

Multiple SQL jointure

I will try to be the most intelligible possible as I'm a very beginner in SQL.
Here is my issue, I have a database with answers from questionnaries.
Each database have a user.id, unique.id, date, final score. And I would like to extract those data with a simple table which group the score of each questionnary by user.id and by date.
I have tried this :
SELECT A.user_id, SUBSTRING(A.created_at,1,10), A.SCORE, B.SCORE,
C.SCORE, D.SCORE, E.SCORE
FROM A
LEFT JOIN B
ON A.user_id = A.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(B.created_at,1,10)
LEFT JOIN C
ON A.user_id = C.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(C.created_at,1,10)
LEFT JOIN D
ON A.user_id = D.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(D.created_at,1,10)
LEFT JOIN E
ON A.user_id = E.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(E.created_at,1,10)
This is almost successful but I find out if some one did not participate to the A questionnary then I don't have anything about it.
I hope, I have been clear enough.
Thank you all,
You must manage your query with USER table (if you have it), as follow:
SELECT U.user_id, SUBSTRING(A.created_at,1,10), A.SCORE, B.SCORE,
C.SCORE, D.SCORE, E.SCORE
FROM USER U
LEFT JOIN A
ON U.user_id = A.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(A.created_at,1,10)
LEFT JOIN B
ON U.user_id = B.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(B.created_at,1,10)
LEFT JOIN C
ON U.user_id = C.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(C.created_at,1,10)
LEFT JOIN D
ON U.user_id = D.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(D.created_at,1,10)
LEFT JOIN E
ON U.user_id = E.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(E.created_at,1,10)
I would suggest two things, depending on how restricted you are to modify your tables.
Just use one table. Add a column testType or something like this. You can ever foreign key it and use only the keys there. That would make the recordset smaller.
Then put all your results in that one table with the matching keys.
Use UNION if the tables are all the same and have just different entries, then query this union.
There is no need for a JOIN in your case, it's better to adapt the structure.
Otherwise you can try FULL JOIN to keep all the data.

transform union query to join query

I have a simple database schema composed of 3 tables
User
id
name
matricule
Document
id
serial
user_id(owner of document : foreign key to User(id))
User_Document (join table)
user_id
document_id
I want all document serial from both user sources : owner of document and join table . The query is filtered by a list of users matricule
I managed to achieve the desired goal with union query :
select d.serial from Document d
INNER JOIN users u ON u.id = d.user_id
where u.matricule IN ('1234')
UNION
select d.serial from Document d
inner join User_Document ud on d.id = ud.document_id
inner join users u on ud.user_id = u.id
where u.matricule IN ('1234')
How to arrive to the same result with only a join query ? I need as well skip document with no serial ( this case is possible)
Thank you very much
Try this:
SELECT d.serial
FROM Document d
LEFT JOIN User_Document ud
ON d.id = ud.document_id
LEFT JOIN users u
on ud.user_id = u.id
LEFT JOIN users u2
ON d.user_id = u2.id
WHERE d.serial IS NOT NULL
AND
(
ISNULL(u.matricule,'') IN ('1234')
OR ISNULL(u2.matricule,'') IN ('1234')
)
Why do you want to remove the joins? Probably the most efficient query would use exists:
select d.*
from documents d
where d.serial is not null and
(exists (select 1
from users u
where u.id = d.user_id and u.matricule = '1234'
) or
exists (select 1
from user_document ud join
users u
on u.id = d.user_id
where ud.document_id = d.id and u.matricule = '1234'
)
);
For this, you would then want indexes on users(id, matricule), user_documents(document_id, user_id), and documents(serial, user_id, document_id).
The use of indexes saves the elimination of duplicates -- which should be a big win for this type of query.

Exclude record if duplicate record has certain value

I have a table of users who have certain roles. A user is entered into the table one time for each role they have. I need to get a count of all users who have certain roles but I need to exclude any duplicate record that also has another role. Below is what is populated in my table
Name Role
Steve ROLE_8
Steve ROLE_9
Steve ROLE_1
And this is the query I have to select users who have certain roles. What I need to do is check to see if a user has ROLE_1 but also check if there is another instance of that user who has a role that I do not wish to include and exclude that user from the return set.
SELECT COUNT(DISTINCT c.user_id)
FROM users c
WHERE email_addr != ''
AND email_addr IS NOT NULL
AND EXISTS
(SELECT r.role_id
FROM ROLE r, user_role ur
WHERE c.user_id = ur.user_id
AND ur.role_id = r.role_id
AND (r.name = 'ROLE_1'
OR r.name = 'ROLE_2'
OR r.name = 'ROLE_3'
OR r.name = 'ROLE_4'
OR r.name = 'ROLE_5'
OR r.name = 'ROLE_6'))
Using the DISTINCT in your COUNT makes the EXISTS unnecessary - you can just join to the user_role table. At that point you just need to exclude those users who also have one of the roles that you don't want:
SELECT
COUNT(DISTINCT U.user_id)
FROM
Users U
INNER JOIN User_Role UR ON UR.user_id = U.user_id AND
INNER JOIN Role R ON
R.role_id = UR.role_id AND
R.name IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
U.email_addr IS NOT NULL AND U.email_addr <> '' AND
NOT EXISTS
(
SELECT *
FROM
User_Role UR2
INNER JOIN Role R2 ON
R2.role_id = UR2.role_id AND
R2.name IN ('Some_Excluded_Role')
WHERE
UR2.user_id = U.user_id
)
If you want to exclude any user who has any role outside of your list then you can do the following:
SELECT
COUNT(DISTINCT U.user_id)
FROM
Users U
INNER JOIN User_Role UR ON UR.user_id = U.user_id AND
INNER JOIN Role R ON
R.role_id = UR.role_id AND
R.name IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
U.email_addr IS NOT NULL AND U.email_addr <> '' AND
NOT EXISTS
(
SELECT *
FROM
User_Role UR2
INNER JOIN Role R2 ON
R2.role_id = UR2.role_id AND
R2.name NOT IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
UR2.user_id = U.user_id
)
I believe this will get you what you're looking for (without knowing exactly what you expect the results to look like). The concept being to first find the ones that match the 'good' roles and then filter with the other sub-select the ones that also have 'bad' roles.
SELECT COUNT(DISTINCT c.[user_id])
FROM users AS c
INNER JOIN user_role AS ur
ON ur.[user_id] = c.[user_id]
WHERE c.email_addr != ''
AND c.email_addr IS NOT NULL
AND ur.role_id IN
(SELECT sub_r.role_id
FROM [role] AS sub_r
WHERE sub_r.name IN ('ROLE_USER_ADMIN', 'ROLE_1')
)
AND c.[user_id] NOT IN
(SELECT sub2_ur.[user_id]
FROM user_role AS sub2_ur
INNER JOIN [role] AS sub2_r
ON sub2_r.role_id = sub2_ur.role_id
WHERE sub2_r.name IN ('ROLE_NOT_TO_USE','ANOTHER_NOT_TO_USE')
AND sub2_ur.[user_id] = c.[user_id]
)
If I wanted to get a dataset in which I'm including users that have either role 1, 2, or 3 and I wanted to exclude users that had role 4 and 5, I would do the following, if "Name" is unique:
SELECT COUNT(DISTINCT Name)
FROM ROLE
WHERE Role IN ('ROLE_1','ROLE_2','ROLE_3')
AND Name NOT IN (SELECT DISTINCT Name FROM ROLE WHERE Role IN ('ROLE_4','ROLE_5');

sql join Many-To-Many - 3 tables

I have 3 tables in sqlserver :
tbl_Users - User_ID, User_Name
tbl_Roles - Role_ID, Role_Name
tbl_Users_Roles_MTM - User_ID, Role_ID
A user can have multiple roles assigned to him, and that will show in the Many-To-Many table.
In my stored-procedure I need Role_Name which are NOT assigned to a specific User_ID (which is given as a parameter).
I guess I should use an INNER JOIN (or a LEFT one...).
There are numerous entries in SO and other forums with questions nearly similar to this but not quite. I experimented a lot but by now I completely lost my hands and feet!
Thank you all.
EDIT :
With the help of the good people of SO, I got it to work :
SELECT r.Role_Name
FROM tbl_Roles r
WHERE NOT EXISTS(
SELECT 1
FROM tbl_Users_Roles_MTM ur
WHERE ur.User_ID = #User_ID
AND ur.Role_ID = r.Role_ID);
SO people are awesome!!!!!
Try this query:
SELECT r.Role_Name
FROM tbl_Roles r
WHERE NOT EXISTS (
SELECT 1
FROM tbl_Users_Roles_MTM ur
WHERE ur.User_ID = #User_ID
AND ur.Role_ID = r.Role_ID);
You can do this:
SELECT *
FROM Roles
WHERE Role_Name = #rolename
AND NOT EXISTS(SELECT 1
FROM tbl_Roles AS r
INNER JOIN tbl_Users_Roles_MTM AS ur ON ur.Role_ID = r.Role_ID
WHERE r.role_Name = #rolename
AND ur.User_ID = #User_ID);
The NOT EXISTS predicate will ensure that the role is not assigned to any user.
SELECT r.*
FROM tbl_Roles r
LEFT JOIN tbl_Users_Roles_MTM ur
ON r.Role_ID = ur.Role_ID
AND ur.User_ID = #User_ID
WHERE ur.User_ID IS NULL
I would do something like this
select roles.Role_Name
from tbl_Roles as roles
left join tbl_Users_Roles_MTM as userRoles
on userRoles.Role_ID = roles.Role_ID
and userRoles.[User_ID] = #User_ID
where userRoles.Role_ID is null
Basically left join and then select only those rows, where it has not been joined

SQL SELECT with m:n relationship

I have m:n relationship between users and tags. One user can have m tags, and one tag can belong to n users. Tables look something like this:
USER:
ID
USER_NAME
USER_HAS_TAG:
USER_ID
TAG_ID
TAG:
ID
TAG_NAME
Let's say that I need to select all users, who have tags "apple", "orange" AND "banana". What would be the most effective way to accomplish this using SQL (MySQL DB)?
SELECT u.*
FROM (
SELECT user_id
FROM tag t
JOIN user_has_tag uht
ON uht.tag_id = t.id
WHERE tag_name IN ('apple', 'orange', 'banana')
GROUP BY
user_id
HAVING COUNT(*) = 3
) q
JOIN user u
ON u.id = q.user_id
By removing HAVING COUNT(*), you get OR instead of AND (though it will not be the most efficient way)
By replacing 3 with 2, you get users that have exactly two of three tags defined.
By replacing = 3 with >= 2, you get users that have at least two of three tags defined.
In addition to the other good answers, it's also possible to check the condition in a WHERE clause:
select *
from user u
where 3 = (
select count(distinct t.id)
from user_has_tag uht
inner join tag t on t.id = uht.tag_id
where t.name in ('apple', 'orange', 'banana')
and uht.user_id = u.userid
)
The count(distinct ...) makes sure a tag is counted only once, even if the user has multiple 'banana' tags.
By the way, the site fruitoverflow.com is not yet registered :)
You can do it all with joins...
select u.*
from user u
inner join user_has_tag ut1 on u.id = ut1.user_id
inner join tag t1 on ut1.tag_id = t1.id and t1.tag_name = 'apple'
inner join user_has_tag ut2 on u.id = ut2.user_id
inner join tag t2 on ut2.tag_id = t2.id and t2.tag_name = 'orange'
inner join user_has_tag ut3 on u.id = ut3.user_id
inner join tag t3 on ut3.tag_id = t3.id and t3.tag_name = 'banana'
SELECT *
FROM USER u
INNER JOIN USER_HAS_TAG uht
ON u.id = uht.user_id
INNER JOIN TAG t
ON uht.TAG_ID = t.ID
WHERE t.TAG_NAME IN ('apple','orange','banana')