joining one table multiple times to other tables - sql

I have three tables:
Table User( userid username)
Table Key( userid keyid)
Table Laptop( userid laptopid)
i want all users who have either a key or a laptop, or both. How do i write the query so that it uses a join between table User and table Key, as well as a join between table User and table Laptop?
The main problem is that in the actual scenario, there are twelve or so table joins, sth like:
" select .. From a left join b on (...), c join d on (..),e,f,g where ...",
and i see that a could be joined to b, and a could also be joined to f. So assuming i can't make the tables a,b, and f appear side-by-side, how do i write the sql query?

You can use multiple joins to combine multiple tables:
select *
from user u
left join key k on u.userid = k.userid
left join laptop l on l.userid = u.userid
A "left join" also finds users which do not have a key or a laptop. If you replace both with "inner join", it would find only users with a laptop and a key.
When a "left join" does not find a row, it will return NULL in its fields. So you can select all users that have either a laptop or a key like this:
select *
from user u
left join key k on u.userid = k.userid
left join laptop l on l.userid = u.userid
where k.userid is not null or l.userid is not null
NULL is special, in that you compare it like "field is not null" instead of "field <> null".
Added after your comment: say you have a table Mouse, that is related to Laptop, but not to User. You can join that like:
select *
from user u
left join laptop l on l.userid = u.userid
left join mouse m on m.laptopid = l.laptopid
If this does not answer your question, you gotta clarify it some more.

select distinct u.userid, u.username
from User u
left outer join Key /* k on u.userid = k.userid */
left outer join Laptop /* l on u.userid = l.userid */
where k.userid is not null or l.userid is not null
EDIT
"The main problem is that in the actual scenario, there are twelve or so table joins, sth like:
" select .. From a left join b on (...), c join d on (..),e,f,g where ...",
and i see that a could be joined to b, and a could also be joined to f. So assuming i can't make the tables a,b, and f appear side-by-side, how do i write the sql query?"
You can have as many left outer joins as required. Join the table with the primary key to the rest of the tables or on any other field where field values of one table should match field values of other table.
eg will explain better than words
select *
from a
left outer join b on a.pk = b.fk -- a pk should match b fk
left outer join c on a.pk = c.fk -- a pk should match c fk
left outer join d on c.pk = d.fk -- c pk should match d fk
and so on

-- // Assuming that a user can have at max 1 items of each type
SELECT u.*
-- // Assuming that a user can have more then 1 items of each type, just add DISTINCT:
-- // SELECT DISTINCT u.*
FROM "User" u
LEFT JOIN "Key" u1 ON u.UserID = u1.UserID
LEFT JOIN "Laptop" u2 ON u.UserID = u2.UserID
LEFT JOIN "Server" u3 ON u.UserID = u3.UserID
-- // ...
WHERE COALESCE(u1.UserID, u2.UserID, u3.UserID /*,...*/) IS NOT NULL

As you described the case, you only wanted to know if someone has a laptop or a key. I would write the query with a subquery rather than a join:
select *
from user
where userid in (select userid from key union select userid from laptop)
The reason for this is that by a join a person with multiple laptops or multiple keys will be listed several times (unless you use distinct). And even you use distinct you end up with a less efficient query (at least on Oracle the query optimizer doesn't appear to be able to create an efficient plan).
[Edited to correct what Rashmi Pandit pointed out.]

Solution one:
SELECT * FROM [User] u
INNER JOIN [Key] k
ON u.userid = k.userid
UNION
SELECT * FROM [User] u
INNER JOIN Laptop l
ON u.userid = l.userid
[...]
Solution two:
SELECT * FROM [User] u
LEFT JOIN [Key] k
ON u.userid = k.userid
LEFT JOIN Laptop l
ON u.userid = l.userid
LEFT JOIN [...]
WHERE k.userid IS NOT NULL
OR l.userid IS NOT NULL
OR [...]
Just a guess, also you could check the execution plan for theses two to see if the UNION one is heavier or vice versa.

SELECT *
FROM User
LEFT JOIN Key ON User.id = Key.user_id
LEFT JOIN Laptop ON User.id = Laptop.user_id
WHERE Key.id IS NOT NULL OR Laptop.id IS NOT NULL

Related

Display Interactive Report for different roles sql query

I'm stuck on something, and need some hep with a SQL query. I've created an interactive report with the following SQL query:
SELECT *
FROM FORM f
LEFT OUTER JOIN MANAGER m ON m.MID = f.MID
LEFT OUTER JOIN USERS u ON u.ID = m.ID
LEFT OUTER JOIN MANAGER m2 ON m2.MID = f.MANID
LEFT OUTER JOIN USERS u2 ON u2.ID = c.ID
WHERE lower(u.username) = lower(:session_user_name)
OR lower(u2.username) = lower(:session_user_name);
The logged in user can have different roles in the form. Depending on the role the id of the manager will be set in different columns of the table.
However, all reports for which the user has been entered,regardless of the role in the form, should still be displayed.
With this query, however, I only see one line for which the has been entered as a Manager.
I don't know much about SQL and don't quite understand what the problem is, can someone help here?
Tried with union :
select *
FROM FORM f
LEFT OUTER JOIN MANAGER m ON m.MID = f.MID
LEFT OUTER JOIN USERS u ON u.ID = m.ID
where lower(u.username)=lower(:session_user_name)
union
select *
FROM FORM f2
LEFT OUTER JOIN MANAGER m2 ON m2.MID = f2.MANID
LEFT OUTER JOIN USERS u2 ON u2.ID = m2.ID
where lower(u2.username)=lower(:session_user_name)
Table form:
create table form (FORMID, COLUMN1 varchar2(50), MID NUMBER,MANID NUMBER);
MID and MANID are foreign keys to the table MANAGER
create table manager (MID number, COLUMN1 varchar2(30));
Example:
Here is a table with some sample data:
In the interactive report the manager with the id 15, should see both entries like this
But now he only gets to see one row, the upper row.

Join on two of the same foreign keys

I have this table below
Table Assignments
AssignmentID: int
LinkedTo: varchar(50)
AssignedUser: int
AssignedBy: int
where AssignedBy and AssignedUser are foreign keys from the table Users. Here is what Users looks like
UserKey:int
Username:varchar(50)
How can I do an inner join where I get both AssignedBy and AssignedUser?
The following gives me one of them but how can I get both? By the way AssignedBy and AssignedUser are two different Users. I'm trying to get Users.Username
select Users.Username
from Assignments
INNER JOIN Users ON Assignments.UserKey = Users.UserKey
If you want both user names on one row, you have to join Users twice, like
SELECT UA.Username AS AssignedUserName, UB.Username AS AssignedByUserName
FROM Assignments A
INNER JOIN Users UA ON A.AssignedUser = UA.UserKey
INNER JOIN Users UB ON A.AssignedBy = UB.UserKey;
If your goal is to have all user names regardless wether it comes from AssignedUser or from AssignedBy, you have to use UNION
SELECT U.Username
FROM Assignments A
INNER JOIN Users U ON A.AssignedUser = U.UserKey
UNION SELECT U.Username
FROM Assignments A
INNER JOIN Users U ON A.AssignedBy = U.UserKey;
Note however that this will remove duplicate user names. If you want to keep duplicates, use UNION ALL.
You need two joins:
SELECT assigned.username AS assigned_username
assigned_by.username AS assigned_by_username
FROM assignments a
JOIN users assigned ON a.assigneduser = assigned.userkey
JOIN users assigned_by ON a.assignedby = assigned_by.userkey

SQL Where on different table

SELECT * FROM student_mentor sm INNER JOIN users u
ON sm.student_id = u.user_id
WHERE sm.teacher_id = $teacher_id
Teacher_id being the session id,
I want to see all the students that have the same mentor.
Right now if I run this I just see all of the students twice, maybe one of you knows why?
My db scheme
You are not specifying on which columns you want to do the join, so you're getting a cross reference where all records are joined to all records.
You should do something like (not sure about your column names):
SELECT * FROM student_mentor sm INNER JOIN users u
ON sm.student_id = u.user_id
WHERE sm.teacher_id = $teacher_id

Sql match id's with names from a table that matches with another table

I have a table friend which has two columns with Id's that belong to specific usernames. See example:
Id_Gebruiker is the user and Id_Gebruiker2 is the friend of the user.
The table friend has id's that belong to the table user.
Then that table user has Foreign Keys (column Id_Client) to the table client (referencing the Id_Client column). The client table contains the usernames.
Now I want to get the corresponding usernames that belongs to the Id's that the Friend table contains. So far I got it working when I use only two tables but I can't get it to work with three tables.
Something like this?
select f.Id_Gebruiker, f.Id_Gebruiker2, u.Id_Client, c.Gebruikersnaam
from friend f
join [user] u on f.Id_Gebruiker2 = u.Id
join client c on u.id_client = c.id
EDIT Assuming the names of the users are on the same table (client) and that every friend.Id_Gebruiker corresponds to a client.Id (so there exists a FK relationship), you can find both names with an extra join:
select c.Gebruikersnaam as UserName, c2.Gebruikersnaam as FriendName
from friend f
join client c on f.Id_Gebruiker = c.Id
join [user] u on f.Id_Gebruiker2 = u.Id
join client c2 on u.id_client = c2.id
Here is another answer that you can check and verify at http://sqlfiddle.com/#!3/2b9dd/1.
The main query for getting names for userid and friendid in friends table is as below.
SELECT f1.userid,
f1.friendid,
y.username AS UserName,
x.username AS FriendName
FROM friends f1
INNER JOIN (SELECT DISTINCT f.friendid,
c.clientname AS UserName
FROM users u
INNER JOIN clients c
ON u.id_client = c.id
INNER JOIN friends f
ON f.friendid = u.id) x
ON f1.friendid = x.friendid
INNER JOIN (SELECT DISTINCT f.userid,
c.clientname AS UserName
FROM users u
INNER JOIN clients c
ON u.id_client = c.id
INNER JOIN friends f
ON f.userid = u.id) y
ON f1.userid = y.userid;

Query where foreign key column can be NULL

I want to get data if orgid = 2 or if there is no row at all for the uid. orgid is an integer. The closest thing I could think of is to do IS NULL but I'm not getting data for the uid that doesn't have an orgid row. Any idea?
select u.uid,u.fname,u.lname from u
inner join u_org on u.uid = u_org.uid
inner join login on u.uid = login.uid
where u_org.orgid=2 or u_org.orgid is NULL
and login.access != 4;
Basically the OR is if u_org.orgid row doesn't exist.
If there is "no row at all for the uid", and you JOIN like you do, you get no row as result. Use LEFT [OUTER] JOIN instead:
SELECT u.uid, u.fname, u.lname
FROM u
LEFT JOIN u_org o ON u.uid = o.uid
LEFT JOIN login l ON u.uid = l.uid
WHERE (o.orgid = 2 OR o.orgid IS NULL)
AND l.access IS DISTINCT FROM 4;
Also, you need the parenthesis I added because of operator precedence. (AND binds before OR).
I use IS DISTINCT FROM instead of != in the last WHERE condition because, again, login.access might be NULL, which would not qualify.
However, since you only seem to be interested in columns from table u to begin with, this alternative query would be more elegant:
SELECT u.uid, u.fname, u.lname
FROM u
WHERE (u.uid IS NULL OR EXISTS (
SELECT 1
FROM u_org o
WHERE o.uid = u.uid
AND o.orgid = 2
))
AND NOT EXISTS (
SELECT 1
FROM login l
WHERE l.uid = u.uid
AND l.access = 4
);
This alternative has the additional advantage, that you always get one row from u, even if there are multiple rows in u_org or login.