How to combine natural join and left outer join in SQL - sql

I have 3 tables students, enrollments, and classes. I want to list every student and the department code of each class they have taken (department code is in classes) including the students who have not taken any classes (department code would be blank in this case). This is necessary because of future computations that will be done.
Here is how I've thought about this. The following query does a left join of students and enrollments so that I include students who have not enrolled in any classes
select s.B#, s.firstname, e.classid
from students s, enrollments e
where s.B#=e.B#(+)
This query works as expected.
Next, I join enrollments and classes so that I can see the corresponding dept_code for each enrollment:
select B#, dept_code
from enrollments natural join classes
This is also working as expected.
When I try to bring the two together though I run into problems
select s.B#, s.firstname, dept_code
from students s, (enrollments e natural join classes)
where s.B# = e.B#(+)
Attempting to run this query I get the error:
ORA-25156: old style outer join (+) cannot be used with ANSI joins
Can anyone please explain what's going on here?

You will be better off using explicit JOIN instead of old-style comma-separated joins with where clause. Same goes here for (+).
Write your query like that instead:
select s.B#, s.firstname, dept_code
from students s
left join (
select * -- if there are same names of columns in both tables, specify them explicitly
from enrollments e
join classes c on e.? = c.? -- specify here your natural join columns
) e on s.B# = e.B#
Btw, the error tells you exactly that you can't combine old style joins with ANSI joins. Probably time to switch to the newer format which exists for a long time now.

the brackets are not really needed, I put them there just to emphasize the structure
select s.B#, s.firstname, c.dept_code
from students s
left join ( enrollments e
join classes c
on c.classId = e.classId
)
on s.B# = e.B3
;
Might as well be written like this (only a different format)
select s.B#, s.firstname, c.dept_code
from students s
left join enrollments e
join classes c
on c.classId = e.classId
on s.B# = e.B3
;
P.s.
If you have doubt about this syntax, you might want to try this:
create table t1 (i int);
create table t2 (i int);
create table t3 (i int);
create table t4 (i int);
create table t5 (i int);
insert into t1 (i) values (1);
insert into t2 (i) values (1);
insert into t3 (i) values (1);
insert into t4 (i) values (1);
insert into t5 (i) values (1);
select *
from t1
join t2
join t3
join t4
join t5
on t5.i=t4.i
on t4.i=t3.i
on t3.i=t2.i
on t2.i=t1.i
;

Your query should look something like this:
select s.B#, s.firstname, c.dept_code
from students s left join
enrollments e
on s.B# = e.B3 left join
classes c
on e.classId = b.classId; -- I am guessing what the `JOIN` column is
In other words, you need two left joins to keep all the students. Otherwise, the on clause might filter rows out.
I also strongly encourage you to avoid and un-learn NATURAL JOIN. It is just a bug waiting to happen in code. It matches columns only based on their names -- not even taking into account declared foreign key relationships.
It hides the columns being joins, which makes debugging and understanding queries much more difficult. For instance, because tables I create almost always have columns such as CreatedAt and CreatedBy, it can't be used.
EDIT: (For lgrade)
You would put the condition in the ON clause:
select s.B#, s.firstname, c.dept_code
from students s left join
enrollments e
on s.B# = e.B3 and e.lgrade is not null left join
classes c
on e.classId = b.classId; -- I am guessing what the `JOIN` column is

I strongly encourage you to avoid and un-learn outer join because it produces nulls. Having nulls is just a bug waiting to happen in code. Outer join isn't really a join at all, rather a 'unnatural' union, using nulls to force things together.
Suggested alternative approach: do the union yourself using an appropriate default value:
select B#, firstname, dept_code
from students
natural join enrollments
natural join classes
union
select B#, firstname, '{{NONE}}' as dept_code
from students
where B# not in ( select B# from enrollments );

Related

Join three tables on two columns with blanks where they don't match

I am trying to join three tables on two different columns, with the caveat that I want there to be blanks when certain columns don't match up. The tables hold data on math and English test, so I am joining two test tables based on a common test column with a student table through ID column that they all have in common.
As you will see from the images below, in some cases, the same test contains both math and English, so this appears in both tables, but in other cases, separate tests are written.
The three tables are:
As you can see, the MEG10 test is shared between both English and Math, as they are written together and are in the same test. This is where I am getting tripped up, as I want the join to display all the results when the tests appear in both English and math. When the tests are separate, however, I want it to have blanks in the English or Math columns with "stuff" (because there isn't actually information in those supposed-to-be-blank columns), respectively. Essentially, when I join them, I want the resultant table to be the one shown below.
The code that I am using to join them is:
SELECT
m.ID, m.Test, m.M_Col1, m.M_Col2, m.M_Col3,
e.E_Col1, e.E_Col2, e.E_Col3,
s.First, s.Last
FROM M_Table AS m
INNER JOIN E_Table AS e ON (m.Test = r.Test)
JOIN S_Table AS s ON (m.ID = s.ID AND e.ID = s.ID)
WHERE m.Test LIKE '%2013%';
This gives me:
Now, I understand that I am joining on the Test column, which means that I should only be getting tests that they both share, but I am not sure how to join it such that I get the results that I want. Moreover, I know I am only taking the test column from the math table, but I don't want two test columns.
I am sure there is a way to do this, I am just not sure how. If I need to clarify my question, please let me know.
You can use FULL OUTER JOIN to bring records from both ends of the relationship between the Math table and the English table. Basically, FULL OUTER JOIN returns all records from both tables, combining them when the join conditions do match, else filling the columns of the missing relation to NULL.
SELECT
COALESCE(m.ID, e.ID) id, COALESCE(m.Test, e.Test) Test,
m.M_Col1, m.M_Col2, m.M_Col3,
e.E_Col1, e.E_Col2, e.E_Col3,
s.First, s.Last
FROM M_Table AS m
FULL OUTER JOIN E_Table AS e ON m.Test = e.Test AND e.ID = e.ID
INNER JOIN S_Table AS s ON COALESCE(m.ID, e.ID) = s.ID
WHERE COALESCE(m.Test, e.Test) LIKE '%2013%';
Tip: merging together tables Math and English would probably simplify your database design. What is the benefit of splitting the information over two tables that have the same structure, and how will it scale when more subjects come into play? You could just use a unique table, with an additional column to store the name of the subject (Math, English, ...).
You need a list of all the id and tests -- you can get that like this
SELECT m.ID, m.Test FROM Math_Table as M
UNION
SELECT e.ID, e.Test FROM English_Table as E
Run that and understand the output
Then you take that and join it to the names and left join it to the tests like this
SELECT m.ID, m.Test, m.M_Col1, m.M_Col2, m.M_Col3, e.E_Col1, e.E_Col2, e.E_Col3, s.First, s.Last
FROM (
SELECT m.ID, m.Test FROM Math_Table as M
UNION
SELECT e.ID, e.Test FROM English_Table as E
) as base
JOIN S_Table as S ON Base.ID = S.ID
LEFT JOIN M_Table AS m ON m.ID = base.ID AND m.Test = base.Test
LEFT JOIN E_Table AS e ON e.ID = base.ID AND e.Test = base.Test
WHERE base.Test LIKE '%2013%'

SQLITE3 Inner Join Returning Too Many Tuples

I have 4 tables in sqlite3:
students(student_id, student_name)
instructors(instructor_id, instructor_name)
courses(course_id, course_name)
and
enrollments(enroll_id, student_id, instructor_id, course_id)
With the last 3 columns having a foreign key reference to the relevant columns in the first 3 tables
When I try to do a query that shows only the student’s name, instructor’s name and course name where an enroll_id is equal to 001 for example
SELECT students.student_name, instructors.instructor_name, courses.course_name
FROM users, instructors, courses INNER JOIN enrollments
WHERE enrollments.enroll_id = 001;
I'm getting a lot of data from all tables, but not a single tuple with just the relevant enroll data assigned to enroll_id 001. Any help is appreciated
Never use commas in the FROM clause. Always use proper, explicit JOIN syntax. The right way to express this query is:
SELECT s.student_full_name, i.instructor_name, c.course_name
FROM enrollments e JOIN
instructors i
ON e.instructor_id = i.instructor_id JOIN
courses c
ON e.course_id = c.course_id JOIN
students s
ON e.student_id = s.student_id
WHERE e.enroll_id = 001;
The use of table aliases is highly recommended. It makes the query easier to write and to read.
Try this:
SELECT students.student_full_name, instructors.instructor_name, courses.course_name
FROM users, instructors, courses, enrollments
WHERE enrollments.enroll_id = 001
and enrollments.student_id = users.student_id
and enrollments.instructor_id = instructors.instructor_id
and enrollments.course_id = courses.course_id;

How can i apply left outer join conditions on four tables?

i was trying to apply joins on 4 tables. but i could not find proper result for that.
i have 4 tables like 1.students,2.college,3.locations,4.departments. so i have same column sid in all tables which can be used to join conditions.
i want all matched rows from four tables as mentioned columns in select statement below and unmatched rows in left table which is left outer join work.
i have tried this syntax.
select
students.sname,
college.cname,
locations.loc,
department.dept
from students, college, locaions, departments
where student.sid=college.sid(+)
and college.sid=locations.sid(+)
and locations.sid=department.sid(+);
is this right ?
This is an old-fashioned way of outer-joining in an Oracle database. For Oracle this statement is correct; in other DBMS it is invalid.
Anyway, nowadays (as of Oracle 9i; in other DBMS much longer) you should use standard SQL joins instead.
select
s.sname,
c.cname,
l.loc,
d.dept
from students s
left outer join college c on c.sid = s.sid
left outer join locations l on l.sid = c.sid
left outer join departments d on d.sid = l.sid;
Given what you've shown it would appear that what you really want is
select s.sname,
c.cname,
l.loc,
d.dept
from students s
LEFT OUTER JOIN college c
ON c.SID = s.SID
LEFT OUTER JOIN locations l
ON l.SID = s.SID
LEFT OUTER JOIN departments d
ON d.SID = s.SID
The issue in your original query is that because an OUTER JOIN is an optional join, you can end up with NULL values being returned in one of the join fields, which then prevents any of the downstream values being joined. I agree with #ThorstenKettner who observes in a comment that SID is apparently a "student ID", but it's not reasonable or appropriate to have a "student ID" field on tables named COLLEGE, LOCATIONS, or DEPARTMENTS. Perhaps you need to update your design to allow any number of students to be associated with one of these entities, perhaps using a "join" table.
Best of luck.

What Join to use against 2 Tables for All Data

Hi I am looking to find out what join I would use if I wanted to join 2 tables together. I currently have a list of all students so 25 students to 1 class and the other table only shows 7 of those names with their test results.
What I would like is to have 1:1 join for the ones with the test results and the other ones without I would like to show them underneath so all in all I have 20 records.
If somebody could please advise on how I could achieve this please.
Thanks in advance.
It sounds like you want an OUTER JOIN.
For this example, we'll assume that there is a table named student and that it contains a column named id which is UNIQUE (or PRIMARY) KEY.
We'll also assume that there is another table named test_result which contains a column named student_id, and that column is a foreign key referencing the id column in student.
For demonstration purposes, we'll just make up some names for the other columns that might appear in these tables, name and score.
SELECT s.id
, s.name
, r.score
FROM student s
LEFT
JOIN test_result r
ON r.student_id = s.id
ORDER
BY r.student_id IS NULL
, s.score DESC
, s.id
Note that if student_id is not unique in test_result, there is potential to return multiple rows that match a row in student.
To get (at most) one row returned from test_result per student, we could use an inline view.
SELECT s.id
, s.name
, r.score
FROM student s
LEFT
JOIN ( SELECT t.student_id
, MAX(t.score) AS score
FROM test_result t
GROUP BY t.student_id
) r
ON r.student_id = s.id
ORDER
BY r.student_id IS NULL
, s.score DESC
, s.id
The expressions in the ORDER BY clause are designed to return the students that have matching row(s) in test_result first, followed by students that don't.
This is just a demonstration, and very likely excludes some important criteria, such as which test a score should be returned for. But without a sample schema and some example data, we're just guessing.
You are looking for a left outer join or a full outer join.
The left outer join will show all students and their tests if they have them.
select *
from Students as s
left outer join Tests as t
on s.StudentId = t.StudentId
The full outer join will show all students with their tests if they have them, and tests even if they do not have students.
select *
from Students as s
full outer join Tests as t
on s.StudentId = t.StudentId

In PostgreSQL how do you join two tables select separate information

Having trouble with joins.
I have a table called subjects
subno subname
30006 Math
31445 Science
31567 Business
I also have a another table called enrollment
subno sno
30009 980008
4134 988880
etc..
how to list subject numbers and subject names for student 9800007 ?
If you want to return zero rows for students without an enrolment, use a LEFT [OUTER] JOIN, eg:
SELECT e.sno, s.subno, s.subname
FROM enrollment e LEFT OUTER JOIN subjects s ON s.subno = e.subno
WHERE e.sno=988880;
To return no rows for students without enrolments, use an INNER JOIN:
SELECT e.sno, s.subno, s.subname
FROM enrollment e INNER JOIN subjects s ON s.subno = e.subno
WHERE e.sno=988880;
Note that join order is important for outer joins (RIGHT [OUTER] JOIN and LEFT [OUTER] JOIN - the OUTER keyword is optional) but not for INNER JOIN. For that reason, #swetha's answer has a problem: the join order is reversed if you're looking for information about a student.
See this SQLFiddle
Try this
select *
from subjects s
left join enrollment e on s.subno = e.subno
where sno=9800007