Join 3 tables in one column and sort them - sql

I'm completely new to SQL and need a bit of help.
I have 3 tables in my SQL Server 2008 database. In each table there are different kinds of users (coworkers, students, teachers). All of them have Nicknames. There is also a table with all users.
I would like to join all of them into one single table with one column called nickname. In this column the three different types of users are supposed to be sorted in packages like so regardless of their nickname:
coworker1 coworker2 coworker2 student1 student2 student3 teacher1 teacher2
I tried the following way:
SELECT A.Nickname, B.Nickname, C.Nickname, D.Nickname
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname
Well that does not work at all and gives me a table with 4 columns each full with NULLs and unsorted users
Thanks HABO this did it for me:
select Nickname from (
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname,
case
when B.Nickname is not NULL then 1
when C.Nickname is not NULL then 3
when D.Nickname is not NULL then 2
else 0 end as Package
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname ) as Elmer
order by Package, Nickname

It sounds like you could just union and order your three user type tables instead of doing a bunch of joins.
Try something like this:
SELECT s.nickname nickname, ..., 'student' type FROM students s
UNION
SELECT t.nickname nickname, ..., 'teacher' type FROM teachers t
UNION
...
ORDER BY type;
Does this closer to what you are trying to accomplish?

If you only want the non-NULL column output:
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname
Note that the columns have been rearranged so that the nickname from users is the last choice.
Your comment regarding sorting into packages is unclear to me. Do you mean something like this:
select Nickname from (
SELECT Coalesce( B.Nickname, C.Nickname, D.Nickname, A.Nickname ) as Nickname,
case
when B.Nickname is not NULL then 1
when C.Nickname is not NULL then 3
when D.Nickname is not NULL then 2
else 0 end as Package
FROM users AS A LEFT OUTER JOIN coworkers AS B ON
A.Nickname = B.Nickname
LEFT OUTER JOIN teachers AS C ON
A.Nickname = C.Nickname
LEFT OUTER JOIN students AS D ON
A.Nickname = D.Nickname ) as Elmer
order by Package, Nickname

Related

case expression inside this left join?

so I have this left join
LEFT JOIN LATERAL (SELECT d.country FROM db.patient_info d
WHERE d.id IN (SELECT DISTINCT st.category FROM db.surgery_types st, db.surgery_record sr
WHERE sr.id = st.surgery_record_id AND sr.surgery_type_id = m.id)
ORDER BY d.priority, d.country
LIMIT 1
) c ON TRUE
the issue is that sometimes d.country comes back null. How can I add a case statement in the left join so that when d.country IS NULL then 'USA'?
My results look like this
Patient Name
Surgery Type
Dave
USA
Richard
EU
Ben
EU
Sally
JP
Bob
null
Dicky
null
I want to modify the left join so that it looks more like this
Patient Name
Surgery Type
Dave
USA
Richard
EU
Ben
EU
Sally
JP
Bob
USA
Dicky
USA
Thoughts?
Use coalesce which returns the first non-null value.
-- I have no idea if this lateral join is valid.
LEFT JOIN LATERAL (
SELECT coalesce(d.country, 'USA')
FROM db.patient_info d
WHERE d.id IN (
SELECT DISTINCT st.category
FROM db.surgery_types st, db.surgery_record sr
WHERE sr.id = st.surgery_record_id AND sr.surgery_type_id = m.id
)
ORDER BY d.priority, d.country
LIMIT 1
) c ON TRUE
Though the order by will still use null so it might not sort properly. You might want to split this into a CTE.
-- Again, no idea if the lateral join is valid,
-- just showing a technique.
with countries as(
SELECT coalesce(d.country, 'USA') as country
FROM db.patient_info d
WHERE d.id IN (
SELECT DISTINCT st.category
FROM db.surgery_types st
JOIN db.surgery_record sr ON sr.id = st.surgery_record_id
-- Don't know what m is
WHERE sr.surgery_type_id = m.id
)
)
with first_country as (
select country
from countries
order by priority, country
limit 1
)
select
...
LEFT JOIN LATERAL countries on true
Finally, it might be simpler and faster to update the table to set all null countries to USA, and then make the column not null.
Not looking into your business logic and whether a lateral join is needed at all or a scalar subquery in the select list of expressions would be enough, here is my suggestion.
CROSS JOIN LATERAL
(
select coalesce
(
( /* your lateral subquery in the brackets here */),
'USA'
) as country
) as c
You do not need left join anymore. Please note that this will only work if the subquery is scalar.

Senior Manager of Employee sql

I have an employee table:
I have a category table which joins userid and category id:
Category with value 1 is senior manager.
I want to find senior manager of each employee.Seniors Managers with category value of 1.
I need the output like this:
How can we achieve this in SQL Server 2008?
Any help appreciated.
Try:
WITH Emp_CTE AS (
Select ID,EmployeeName,Manager from employee
UNION ALL
SELECT ecte.ID,ecte.EmployeeName,c.Manager
FROM employee c
INNER JOIN Emp_CTE ecte ON ecte.Manager = c.ID
)
SELECT a.ID,a.EmployeeName,b.EmployeeName
FROM Emp_CTE a
left join employee b
on a.Manager = b.ID
left join category c
on a.Manager = c.UserID
where c.Category = '1'
As this query calls for a Self Join, this should work:
SELECT a.EMPLOYEENAME as Employee,
b.EMPLOYEENAME as Senior_Manager
FROM employee a
LEFT JOIN
employee b ON a.ID = b.Manager
LEFT JOIN
category c ON b.ID = c.UserID
WHERE c.Category = 1

SQL LEFT JOIN for joining three tables but one with to exclude content

I have 3 tables
STUDENTS
FEES_PAID
SUSPENDED
I want to get the details of the students who have paid the fees but not from SUSPENDED.
SELECT
ID
FROM
STUDENTS s
LEFT JOIN
SUSPENDED p ON s.ID = p.ID
INNER JOIN
FEES_PAID f ON f.ID = s.ID
WHERE
s.ID IS NULL
Unfortunately this does not work. Can any one suggest an efficient query?
Thanks in advance
You need to check if the second table is missing from the LEFT JOIN. So, you need to look at a column in that table. Change the WHERE to:
WHERE p.ID IS NULL
Alternatively, use NOT EXISTS:
SELECT s.ID
FROM STUDENTS s INNER JOIN
FEES_PAID f
ON f.ID = s.ID
WHERE NOT EXISTS (SELECT 1 FROM SUSPENDED p WHERE s.ID = p.ID);
Note that for both these queries, you will need to qualify the ID in the SELECT to specify the table where it comes from.
This should work:
SELECT
s.ID
FROM
STUDENTS s
LEFT JOIN
SUSPENDED p
ON s.ID=p.ID
INNER JOIN
FEES_PAID f
ON f.ID= s.ID
WHERE
p.ID IS NULL

SQL: a better way to include & exclude on the same field than, both, WHERE and NOT EXISTS?

I have this query which needs to, both, include and exclude on the same role_id field, so I'm calling the same table for both the NOT EXISTS() subquery and the INNER JOIN.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
WHERE ur.role_id = 3
AND NOT EXISTS (SELECT 1
FROM users_roles
WHERE role_id = 4
AND user_id= u.id)
ORDER BY c.country, c.postal
This approach works, but seems kinda clunky, so I'm wondering whether there's a more standard (performant) approach that I'm not aware of?
I recommend running performance analysis on both queries, but you can also do this with a left outer join.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
LEFT OUTER JOIN users_roles ur2
ON (u.id = ur2.user_id)
AND role_id = 4
WHERE ur.role_id = 3
AND ur2.user_id IS NULL
ORDER BY c.country, c.postal
This will end up with NULL in place of values for the ur2 table if no matching rows in the table are found; if you assert in the WHERE clause that the value is NULL, it will exclude anything that did have a match.
This one makes just one join, it uses grouping, sums the role_ids and checks on the aggregate result, the execution time on sqlfiddle is 6ms vs 24ms
SELECT
u.fname
,u.lname
,c.country
,c.postal
,sum(ur.role_id)
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
and role_id in(3,4)
group BY u.fname, u.lname, c.country, c.postal
having sum(ur.role_id) = 3
go
You could join user_roles just once and it might help performance a tiny bit. I cannot make up my mind right now which query I like more.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
CROSS APPLY (
SELECT SUM (IIF(role_id = 3, 1, 0)) AS role3Count, SUM (IIF(role_id = 4, 1, 0)) AS role4Count
FROM users_roles ur
WHERE role_id BETWEEN 3 AND 4 AND u.id = ur.user_id
) ur
WHERE role3Count > 0 AND role4Count = 0
ORDER BY c.country, c.postal
This has the chance of being slightly faster because a single index seek on users_roles with range role_id BETWEEN 3 AND 4 is enough to retrieve the roles. In your query, there were two seeks. On the other hand this query does some aggregates and computations. You'd need to measure.
I'm not convinced this is a nicer query. It is more complex than yours. I'll propose it anyway. You decide.

SQL query on table that has 2 columns with a foreign id on the same table

I have a table let's say in the form of: match(id, hometeam_id, awayteam_id) and team(id, name). How do I build my SQL query in order to get a result table in the form of (match_id, hometeam_name, awayteam_name), since they both (hometeam_id, awayteam_id) reference the same table (team)?
Thank you
You would just join to the team table multiple times:
SELECT m.id, away.name, home.name
FROM match m
INNER JOIN team away ON away.id = m.awayteam_id
INNER JOIN team home ON home.id = m.hometeam_id
You join to the team table twice.
select matchdate, t1.teamname, t2,teamname from
match m
join team t1 on m.hometeamId = t1.teamid
join team t2 on m.awayteamid = t2.teamid
Join to the team table twice, once for the Home Team, and again for the Away Team, using aliases after the table names in the query:
select m.match_id, homeTeam.name as HomeTeamName, awayTeam.name as AwayTeamName
from
team homeTeam join
match m on m.hometeam_id = homeTeam.hometeam_id join
team awayTeam on awayTeam.hometeam_id = m.awayteam_id
select m.id, h.name as hometeam_name, a.name as awayteam_name
from match m left join team h on m.hometeam_id = h.id
left join team a on m.awayteam_id = a.id