Outer join not showing extra entries - sql

I have made a query in which 3 tables are used. The first table has all the desired names which I need. The 2nd and 3rd table give me those names on which there is some bill amount. But I need all the names from the 1st table as well.
SELECT a.name,
nvl(c.bill_amount,0)
FROM table_1 a left outer join table_2 b
ON a.name = b.name
left outer join table_3 c on B.phone_number = C.phone_number
AND B.email = C.email
where b.status = 'YES'
and a.VALID = 'Y';
Now, the tables b and c give me limited number of names, lets say 5 on which bill is there. But in table_1, there are 10 names. I want to display them also with 0 bill_amount on their name. I'm using Oracle.

Applying a where clause on the right hand tale basiically makes it an inner join. To keep it OUTER, put the condition in the join conditions
Try:
SELECT a.name,
nvl(c.bill_amount,0)
FROM table_1 a
left outer join table_2 b
ON a.name = b.name
and b.status = 'YES' -- Put it here
left outer join table_3 c
on B.phone_number = C.phone_number
AND B.email = C.email
where a.VALID = 'Y'; -- Only items from the left hand table should go in the where clause

The answer above is right , I would just want to be more precise. The fact is when a left join does not match, the column of the right hand table are set to NULL.
Actually NULL is always propagating value in SQL, so b.status = 'YES' have the value NULL if the join does not math, and then the predicate does not match neither.
The general way to handle this would be (b.status = 'YES' or b.name IS NULL) : because b.name is the join column it is null if and only if join does not match, which may not be the case for b.status.
Because NULL is propagating you cannot use field = NULL but field IS NULL instead.
But it is okay to have it in the join clause when it is more clear.

Related

How can I show one element from one of two tables in a column

Table A has the id for a Company of which the name can be either in table B or table C.
What I'm trying to achieve is to show the fetched name in a new column, something like:
SELECT Bank.Name OR Company.Name AS 'CompanyName'
FROM Account
INNER JOIN Company
ON Account.CompanyID = Company.CompanyID OR
Account.CounterpartyID = Company.CompanyID
INNER JOIN Bank
ON Account.CompanyID = Bank.BankID OR
Account.CounterpartyID = Bank.BankID
WHERE ...
Can this work like this or do I need to use multiple SELECTs?
Thanks in advance!
Assumptions:
There will be ONE, or NO rows in TableA or TableB for the company
If there is a row in both TableA and TableB then taking the company name from TableA is acceptable
With those assumptions this code should do the trick:
SELECT A.CompanyId,
COALESCE(B.CompanyName, C.CompanyName)
FROM TableA A
LEFT
JOIN TableB B
ON A.CompanyId = B.CompanyId
LEFT
JOIN TableC C
ON A.CompanyId = C.CompanyId
WHERE TableA.CompanyId = 1234
Essentially what this code is doing is joining the tables together, using a LEFT JOIN so that rows from TableA don't get excluded by the absence of a row in either of TableB or TableC, then using COALESCE to get the first non-NULL value from either of the two tables for the CompanyName column.
The result of the query (if you were using SELECT *) where TableA doesn't contain a row would look a little like this:
Just to give an example of the data the query would be working against.
I would recommend LEFT JOIN. You seem to want names for both the company and the counterparty. That would be two columns and additional joins:
SELECT COALESCE(c.Name, b.Name) AS CompanyName,
COALESCE(cc.Name, bc.Name) AS CounterpartyName,
FROM Account a LEFT JOIN
Company c
ON a.CompanyID = c.CompanyID LEFT JOIN
Bank b
ON a.CompanyID = b.BankID AND
c.CompanyID IS NULL LEFT JOIN
Company cc
ON a.CounterpartyID = cc.CompanyID LEFT JOIN
Bank bc
ON a.CounterpartyID = bc.BankID AND
cc.CompanyID IS NULL
WHERE ...
SELECT
CASE
WHEN EXISTS(
SELECT b.name FROM tableB b WHERE b.name IS NOT NULL
) THEN b.name AS 'CompanyName'
WHEN EXISTS(
SELECT c.name FROM tableC c WHERE c.name IS NOT NULL
) THEN c.name AS 'CompanyName'
END
FROM tableA a
...
Hope this will work for you!
Yes, it can be done with COALESCE() function if you have NULLs, or even IIF() function or CASE expression
Instead of SELECT Bank.Name OR Company.Name AS 'CompanyName' you can do
SELECT COALESCE(Bank.Name, Company.Name) AS CompanyName
OR
SELECT IIF(Bank.Name IS NULL, Company.Name, Bank.Name) AS CompanyName
OR
SELECT CASE WHEN Bank.Name IS NULL
THEN Company.Name
ELSE Bank.Name
END AS CompanyName
If you mean '' then
SELECT CASE WHEN Bank.Name = ''
THEN Company.Name
ELSE Bank.Name
END AS CompanyName

Compare table one value with two different tables in postgresql

There are three tables, A,B and C having common columns(name and number)
Table A have 10 records(say x) which can be only from table B(say, y) and table C(say, z) (like, x = y+z).
In table A, there are some records whose value is 0 (zero)
I need to compare those zero value based records using column = name, with other two tables.
And check the column "number" for the same "name" is also zero (0) in table B and table C?
I tried to write the below sample query to test on my small set of 3 tables data- but for some reasons I am not able to get all the 10 records as a result?
SELECT a.name,a.number as A_number, b.number as B_number, c.number as C_number
from A a, B b, C c
WHERE a.name = b.name
The above query gives me data as follows in the sqlfiddle-
http://sqlfiddle.com/#!2/57f86/1
In the above data- theres no record name="hello"
Can anyone please correct me where I am going wrong? and how to get the exact result? I need all the records from Table A. I know if I use left join it will populate all the left table data even if no match.
Possibilities: Table A having records, some may be present in table B
and some in table C, but not on both.
I think this is what you want:
SELECT a.*, b.number as bnumber, c.number as cnumber
from a left outer join
b
on a.name = b.name left outer join
c
on a.name = c.name
where a.number = 0;
By the way, here is a Postgres SQL Fiddle.
It's been over 20 years since the JOIN keyword was added to SQL. Use it:
select
a.name,
a.number as A_number,
b.number as B_number,
c.number as C_number
from A a
left join B b on a.name = b.name
left join C c on a.name = c.name
where a.number = 0
The key here is the use of left join, which allows all rows in table A to be returned, even if there are no matching rows in the other tables.
If you want to just display true/false if the number is zero in the other tables, do this:
select
a.name,
a.number as A_number,
(b.number = 0 and c.number = 0) as zero_elsewhere
from A a
left join B b on a.name = b.name
left join C c on a.name = c.name
where a.number = 0
When you wrote WHERE a.name = b.name, that restricted the records returned from table A to only those that also exist in table B. This is not equivalent to a left join. If you used only a WHERE statement you would need to do:
WHERE ((a.name = b.name) OR (b.name is NULL))
AND
((a.name = c.name) OR (c.name is NULL))
In the comments and other answers, they have been using LEFT JOIN which is easier to write and read. I suggest you adopt that style as it is widely accepted.

How to use oracle outer join with a filter where clause

If i write a sql:
select *
from a,b
where a.id=b.id(+)
and b.val="test"
and i want all records from a where corresponding record in b does not exist or it exists with val="test", is this the correct query?
You're much better off using the ANSI syntax
SELECT *
FROM a
LEFT OUTER JOIN b ON( a.id = b.id and
b.val = 'test' )
You can do the same thing using Oracle's syntax as well but it gets a bit hinkey
SELECT *
FROM a,
b
WHERE a.id = b.id(+)
AND b.val(+) = 'test'
Note that in both cases, I'm ignoring the c table since you don't specify a join condition. And I'm assuming that you don't really want to join A to B and then generate a Cartesian product with C.
Move the condition into the JOIN clause and use the ANSI standard join pattern.
SELECT NameYourFields,...
FROM A
LEFT OUTER JOIN B
ON A.ID = B.ID
AND B.VAL = 'test'
INNER JOIN C
ON ...
A LEFT OUTER JOIN is one of the JOIN operations that allow you to specify a join clause. It preserves the unmatched rows from the first (left) table, joining them with a NULL row in the shape of the second (right) table.
So you can do as follows :
SELECT
FROM a LEFT OUTER JOIN b
ON a.id = b.id
--Note that you have used double quote "test" which is not used for varchar in SQL you should use single quote 'test'
AND b.val = 'test';
SELECT * FROM abc a, xyz b
WHERE a.id = b.id
AND b.val = 'test'

using an inner or left join in an update?

Though my sql knowledge is pretty good, I cannot get my head around the difference in a left vs inner join specifically when doing an update.
employee_table
column1:id
column2:socialsecurity
private_info_table
column1:id
column2:socialsecurity
I need to update employee_table.socialsecurity to be private_info_table.socialsecurity
should I do a left or inner join: ???
update e
set e.socialsecurity=p.socialsecurity
from employee_table e
join private_info_table p --should this be left or inner join?
on p.id=e.id
Left Join
Select *
from A
left join B on a.id = b.pid
In the conditions a.id = b.pid, every row is returned for both A and B BUT if the value from A (i.e. a.id) doesn't match the value from B (i.e., B), all fields of B will be null. On the other hand, for those rows where this condition is true, all values for B are shown. Notice that A's values have to be returned because it is on the left of the 'left' keyword.
Inner Join
Select *
from A
inner join B on a.id = b.pid
Rows are returned for rows where a.id = b.pid is true, otherwise no rows are returned if it is false. This is a mutually exclusive join.
In your case, don't use a left join because all records on the left of the 'left' keyword will be updated with null or non-null values. This means you will unintentionally update non-matched record with null values based upon my description of left joins.
It should be an inner join if you just want to update those records that are in both tables. If you do a left join it will update the employee table to be null wherever the id isn't found in the private info table.
The reason this matter is if you have some social security numbers in the employee table that already that aren't found in the private info table. You wouldn't want to erase them with a null value.

SQL LEFT outer join with only some rows from the right?

I have two tables TABLE_A and TABLE_B having the joined column as the employee number EMPNO.
I want to do a normal left outer join. However, TABLE_B has certain records that are soft-deleted (status='D'), I want these to be included. Just to clarify, TABLE_B could have active records (status= null/a/anything) as well as deleted records, in this case i don't want that employee in my result. If however there are only deleted records of the employee in TABLE_B i want the employee to be included in the result.I hope i'm making my requirement clear. (I could do a lengthy qrslt kind of thingy and get what I want, but I figure there has to be a more optimized way of doing this using the join syntax). Would appreciate any suggestions(even without the join). His newbness is trying the following query without the desired result:
SELECT TABLE_A.EMPNO
FROM TABLE_A
LEFT OUTER JOIN TABLE_B ON TABLE_A.EMPNO = TABLE_B.EMPNO AND TABLE_B.STATUS<>'D'
Much appreciate any help.
Just to clarify -- all records from TABLE_A should appear, unless there are rows in table B with statues other than 'D'?
You'll need at least one non-null column on B (I'll use 'B.ID' as an example, and this approach should work):
SELECT TABLE_A.EMPNO
FROM TABLE_A
LEFT OUTER JOIN TABLE_B ON
(TABLE_A.EMPNO = TABLE_B.EMPNO)
AND (TABLE_B.STATUS <> 'D' OR TABLE_B.STATUS IS NULL)
WHERE
TABLE_B.ID IS NULL
That is, reverse the logic you might think -- join onto TABLE_B only where you have rows that would exclude TABLE_A entries, and then use the IS NULL at the end to exclude those. This means that only those which didn't match (those with no row in TABLE_B, or with only 'D' rows) get included.
An alternative might be
SELECT TABLE_A.EMPNO
FROM TABLE_A
WHERE NOT EXISTS (
SELECT * FROM TABLE_B
WHERE TABLE_B.EMPNO = TABLE_A.EMPNO
AND (TABLE_B.STATUS <> 'D' OR TABLE_B.STATUS IS NULL)
)
The following query will get you the employee records that aren't deleted, or only the employ only has deleted records.
select
a.*
from
table_a a
left join table_b b on
a.empno = b.empno
where
b.status <> 'D'
or (b.status = 'D' and
(select count(distinct status) from table_b where empno = a.empno) = 1)
This is in ANSI SQL, but if I knew your RDBMS, I could give a more specific solution that may be a bit more elegant.
ah crud, this apparently works ><
SELECT TABLE_A.EMPNO
FROM TABLE_A
LEFT OUTER JOIN TABLE_B ON TABLE_A.EMPNO = TABLE_B.EMPNO
where TABLE_B.STATUS<>'D'
If you guys have any extra info to chime in with though, please feel free.
UPDATE:
Saw this question after sometime and thought i'll add more helpful info: This link has good info regarding ANSI syntax - http://www.oracle-base.com/articles/9i/ANSIISOSQLSupport.php
In particular this part from the linked page is informative:
Extra filter conditions can be added to the join to using AND to form a complex join. These are often necessary when filter conditions are required to restrict an outer join. If these filter conditions are placed in the WHERE clause and the outer join returns a NULL value for the filter column the row would be thrown away. if the filter condition is coded as part of the join the situation can be avoided.
SELECT A.*, B.*
FROM
Table_A A
INNER JOIN Table_B B
ON A.EmpNo = B.EmpNo
WHERE
NOT EXISTS (
SELECT *
FROM Table_B X
WHERE
A.EmpNo = X.EmpNo
AND X.Status <> 'D'
)
I think this does the trick. The left join is not needed because you only want to include employees with all (and at least one) deleted rows.
This is how I understand the question. You need to include only those employees for which either of the following is true:
an employee has only (soft-)deleted rows in TABLE_B;
an employee has only non-deleted rows in TABLE_B;
an employee has no rows in TABLE_B at all.
In other words, if an employee has both deleted and non-deleted rows in TABLE_B, omit that employee, otherwise include them.
This is how I think it could be solved:
SELECT DISTINCT a.EMPNO
FROM TABLE_A a
LEFT JOIN TABLE_B b1 ON a.EMPNO = b1.EMPNO
LEFT JOIN TABLE_B b2 ON b1.EMPNO = b2.EMPNO
AND (b1.STATUS = 'D' AND (b2.STATUS <> 'D' OR b2 IS NULL) OR
b2.STATUS = 'D' AND (b1.STATUS <> 'D' OR b1 IS NULL))
WHERE b2.EMPNO /* or whatever non-nullable column there is */ IS NULL
Alternatively, though, you could use grouping:
SELECT a.EMPNO
FROM TABLE_A a
LEFT JOIN TABLE_B b ON a.EMPNO = b1.EMPNO
GROUP BY a.EMPNO
HAVING 0 IN (COUNT(CASE b.STATUS WHEN 'D' THEN 1 ELSE NULL END),
COUNT(CASE b.STATUS WHEN 'D' THEN NULL ELSE 1 END))