Oracle SQL Conditional Outer Join using case statement - sql

Lets say that I have three tables:
1. TableA with columns TableAID (PK), Desc Nullable
2. TableB with columns TableBID (PK), TableAID(FK) Nullable
3. TableC with columns TableCID (PK), TableBID (FK) Nullable,
TableAID (FK) Nullable, Start_Date, End_Date
I need to return the Desc in Table A
if TableC.TableBID is not null then use the TableAID(FK) in TableB to retrieve Desc
else
use TableAID (FK) in TableC to retrieve Desc
Note: It's possible both TableC.TableBID or TableC.TableAID can be null. In all cases I still must be able to return the other columns in TableC.
Here is my code:
Select ta.desc, tc.start_date, tc.end_date
from TableC tc
Left outer join TableB tb
on case
when tc.TableBID is not null then (
tc.TableBID = tb.TableBID
Left outer join TableA ta
on tb.TableAID = ta.TableAID
--my concern here is that tb.TableAID can be null. Will it still work?
)
else tc.TableAID = ta.TableAID --my concern here is that tc.TableAID can be null.
--WIll it still work?
I'm also concern about syntax. If there is a better way to have a conditional join, please advise. I'm using oracle. This code will go into a view which will be used for a search procedure (that's why it has to return everything regardless of nulls).
Thanks for your help.

You can put your CASE in the SELECT clause and join the tables accordingly, like this:
SELECT CASE
WHEN tb.tableAID IS NOTNULL THEN tab.desc
ELSE tac.desc
END AS desc
-- or better: NVL(tab.desc, tac.desc) AS desc
, tc.start_date
, tc.end_date
FROM tableC tc
JOIN tableB tb ON tc.tableBID = tb.tableBID
LEFT JOIN tableA tab ON tab.tableAID = tb.tableAID
LEFT JOIN tableA tac ON tac.tableAID = tc.tableAID

Related

What is the correct way of writing an exclusive left-join on two tables?

Do I need to write condition 'is NULL' for two (or could be more) keys when doing an exclusive left join of two tables? The question is if the 'WHERE B.columnName1 IS NULL' is enough?
Join with one key condition:
SELECT columns
FROM TableA
LEFT OUTER JOIN TableB
ON A.columnName = B.columnName
WHERE B.columnName IS NULL
Is the following more accurate? Do I need condition for columnName2?
SELECT columns
FROM TableA
LEFT OUTER JOIN TableB
ON A.columnName1 = B.columnName1
AND A.columnName2 = B.columnName2
WHERE B.columnName1 IS NULL
AND B.columnName2 IS NULL
So far it worked with one condition but I just got confused, I see this is simple question, sorry for that
Single column is enough(as long as it is not nullable):
SELECT columns
FROM TableA A
LEFT OUTER JOIN TableB B
ON A.columnName = B.columnName
WHERE B.columnName IS NULL;
Best practice is to use primary key for tableB
The ON clause contains the condition for the join:
ON A.columnName = B.columnName
If this condition is not met then the result is that all columns of the unmatched row of table B will be NULL, so your 2nd query is not wrong.
But, this also means that B.columnName itself is NULL and this condition only in the WHERE clause is enough.
It simply means that in this row A.columnName has no matching B.columnName.
The best practice is to use a column that is part of the ON clause, which your code is doing:
SELECT columns
FROM TableA A LEFT OUTER JOIN
TableB B
ON A.columnName = B.columnName
WHERE B.columnName IS NULL;
The JOIN does not treat NULL values as equal. Equally, the primary key (or any component of the primary key) also works fine.
If you don't want to think about this, use NOT EXISTS:
SELECT columns
FROM TableA A
WHERE NOT EXISTS (SELECT 1
FROM TableB B
WHERE A.columnName = B.columnName
);

SQL Where Condition with IF Statement

So I'm trying to write a query to pull some data and I have one condition that needs to be met and I can't seem to figure out how to actually execute it. What I'm trying to achieve is that if a column is not null in one table, then I want to check another table and see if there is a specific value in one those columns. So in a psuedo code type of way I'm trying to do this
SELECT id, user_name, created_date, transaction_number
FROM TableA
WHERE (IF TableA.response_id IS NULL OR
IF (SELECT type_id from TableB WHERE (type_id NOT IN ('4)) AND (id = TableA.response_id))
So from here what I'm trying to do is pull all transactions for customers that have no responses in them, but from those that do have responses I still want to grab transaction that's don't have a specific code associated to them. I'm not sure if it's possible to do it in this manner or if I need to create some temporary tables that can then be manipulated but I'm stuck on this one condition.
At first I thought you wanted the CASE statement from the wording of your question, but I think you're just looking for an OUTER JOIN with an OR statement:
SELECT DISTINCT a.id, a.user_name, a.created_date, a.transaction_number
FROM TableA A
LEFT JOIN TableB B ON A.response_id = B.Id
WHERE A.response_id IS NULL
OR B.type_id NOT IN (4)
A Visual Explanation of SQL Joins
where TableA.Response_id is null or (select count(1) from TableB WHERE (type_id NOT IN ('4)) AND (id = TableA.response_id)) = 0
provided that your subquery is logically correct.
Well I'm not 100% certain I follow, but assuming what you want is to see if there are response entries for a particular ID in Table A I think you want something like this.
SELECT a.id, user_name, created_date, transaction_number
FROM TableA a
LEFT JOIN TableB b
ON a.id=b.id
LEFT JOIN TableC c
ON a.id=c.id
WHERE isnull(b.id,c.id) IS NOT NULL
GROUP BY a.id, user_name, created_date, transaction_number
ISNULL will return b.id if it is not null, c.id if c.id is not null and NULL otherwise. That will tell you if there's a response for a.id in either TableB or TableC. That's assuming TableB & TableC are more like logs. If you're saying those table will certainly have an entry for a.id then it's just a matter of replacing b.id & c.id with b.[response_column] & c.[response_column] respectively.

Multiple counts in a multi table query SQL

I would like to count The different requests by survey Id's and grouping it by SubjectValue
I have done this on just the one table with a sub query, but I'm not too sure to do it with several. Could anyone help me out?
This is how the 3 tables are joined. The only values of note are
subjectValue - Table A
Request_Id - Table A
Survey_Id - Table C
SELECT TableA.SubjectValue
FROM TableB INNER JOIN
TableA ON TableB.ID = TableA.Request_ID INNER JOIN
Table C ON TableB.Details_ID = TableC.ID
May I also add that all counts should be returned in the same row.
there are 3 different survey Id's so the count will need a where clause on the survey_id.
Hope that makes sense.
Many thanks in advance.
You can use generic Cross-tab method
select
TableA.SubjectValue,
SUM(case when somecol='request1' then 1 else 0 end) as request1,
SUM(case when somecol='request2' then 1 else 0 end) as request2,
.
TableB INNER JOIN
TableA ON TableB.ID = TableA.Request_ID INNER JOIN
Table C ON TableB.Details_ID = TableC.ID
group by
TableA.SubjectValue
You probably dont need to join to table C (surveys) assuming you have a foreign key to it on table B (Requests).
try this.
SELECT TableA.SubjectValue, COUNT(TableB.SurveyID)
FROM TableB
INNER JOIN TableA ON TableB.ID = TableA.Request_ID
Group by TableA.SubjectValue
EDIT: To Include SurveyID use this..
SELECT TableC.SurveyID, TableA.SubjectValue, COUNT(TableB.RequestId)
FROM TableA
INNER JOIN TableB ON TableB.SurveyID = TableA.SurveyID
INNER JOIN TableC ON TableC.RequestID = TableB.RequestID
Group by TableA.SubjectValue, TableC.SurveyID
(hope i didnt get my A, B's and C's mixed up.)

SQL Case statement to check for NULLS and Non-existent records

I am doing a join between two tables and want to select the columns based on whether they have a record or not. I'm trying to avoid having multiple of the same field and am trying to condense them into single columns. Something like:
Select
id = (CASE WHEN a.id IS NULL THEN b.id ELSE a.id END),
name = (CASE WHEN a.name IS NULL THEN b.name ELSE a.name END)
From Table1 a
Left Join Table2 b
On a.id = b.id
Where a.id = #id
I'd like id to populate from Table1 if a record exists, but if not pull from Table2. The previous code returns no records because there are no NULL values in Table1 so my question is how do I run a check to see if any records even exist? Also if anyone knows of a better way to accomplish what I am trying to do I appreciate guidance and constructive criticism.
EDIT
It looks like COALESCE will work for what I'm trying to accomplish. I'd like to give a little more info on exactly what I am working with and get some advice on whether I am using the best method.
I have a bloated table Table2 and it is in production. I'm working on building new web applications for this system but can't justify a complete database redesign so I am trying to do one "on the fly". I've created a new table Table1 and I am writing stored procedures for the following methods Get(Select), Set(Update), Add(Insert), Remove(Delete). This way, to my code, it will seem that I am working with a single table that is not bloated. My code will simply call one of the SP methods and then the stored procedure will handle the data between the old table and the new. I am currently working on the Get method and I need to check the old table Table2 for a record if it doesn't exist in Table1.
Thanks to the suggestions here my query currently looks like this:
Select
id = coalesce(a.id, b.student_number),
first_name = coalesce(a.first_name, b.first_name),
last_name = coalesce(a.last_name, b.last_name),
//etc
From Table1 a
Full Outer Join Table2 b
On a.id = b.student_number
Where (a.id = #id Or b.student_number = #id)
This works for what I'm trying to accomplish, I'd like to throw it out there to the experienced crowd for any tips or suggestions if there are better or more correct ways to go about this.
Thanks
I suspect your problem may come from doing a left join. Try again using a full outer join, like this:
Select
id = coalesce(a.id, b.id),
name = coalesce(a.name, b.name)
From Table1 a
full outer Join Table2 b
On a.id = b.id
Where a.id = #id
Select id = coalesce(a.id, b.id),
name = coalesce(a.name, b.name)
From Table2 b
Left Join Table1 a On a.id = b.id
Where b.id = #id
You may need to use ISNULL or CASE instead of COALESCE depending on your database platform.
First, you don't need a case statement for that:
Select ISNULL(a.id,b.id) AS id, ISNULL(a.name,b.name) AS name,
From Table1 a
Left Join Table2 b
On a.id = b.id
Where a.id = #id
Second, if I get it right, the id field can contain nulls, and in that case you are screwed. I mean, the ID is a unique value that identify a row, if it can be null, you can't identify that row.
But if what you want is getting records from Table1 and Table2 and avoid duplicates, a simple UNION will work fine, since it discards duplicates:
select id, name
from Table1
where id = #id
union
select id, name
from Table2
where id = #id
You could do something like:
select id, name from Table1 a where a.id not in (select id from Table2)
UNION
select id, name from Table2 b
This would give you all the records from table1 that didn't have a corresponding match in table2 plus all of table2's records. The union would then combine the results.
In your first CASE statement, a.id and b.id will always be same value, except for instances in which a.id has a value and b.id generates a NULL value because of the LEFT JOIN. There will never be a row in the result set with a NULL a.id value and a non-NULL b.id value. You could just use a.id for this column.
For the second CASE statement, you may find the name column in either or both tables with a value (and, of course, the values may be different). You said you want to "condense" the these column values; the SQL function for that is COALESCE:
COALESCE(a.id, b.id)
which returns the first non-NULL value (a.id if it isn't NULL, otherwise b.id). It won't tip you off to different names in the two tables.

sql select statement with a group by

I have data in 2 tables, and I want to create a report.
Table A:
tableAID (primary key)
name
Table B:
tableBID (primary key)
grade
tableAID (foreign key, references Table A)
There is much more to both tables, but those are the relevant columns.
The query I want to run, conceptually, is this:
select TableA.name, avg(TableB.grade) where TableB.tableAID = TableA.tableAID
The problem of course is that I'm using an aggregate function (avg), and I can rewrite it like this:
select avg(grade), tableAID from TableB group by tableAID
but then I only get the ID of TableA, whereas I really need that name column which appears in TableA, not just the ID.
Is it possible to write a query to do this in one statement, or would I first need to execute the second query I listed, get the list of id's, then query each record in TableA for the name column... seems to me I'm missing something obvious here, but I'm (quite obviously) not an sql guru...
You can do this:
SELECT avg(b.grade), a.tableAID, a.name
FROM TableA a
JOIN TableB b
ON b.tableAID = a.tableAID
GROUP BY a.tableAID, a.name
Just adding it to the group will work fine in your case.
SELECT AVG(TableB.grade), TableB.tableAID, TableA.Name
FROM TableA INNER JOIN TableB
ON TableA.TableAID = TableB.TableAID
GROUP BY TableA.tableAID, TableA.Name
Alternative answer:
SELECT AVG(b.grade), a.tableAID, MAX(a.name )
FROM TableA a
JOIN TableB b
ON b.tableAID = a.tableAID
GROUP BY a.tableAID
Just to get you thinking.