KG -
KG -

Reputation: 7170

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.

Upvotes: 4

Views: 8862

Answers (5)

Andriy M
Andriy M

Reputation: 77657

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))

Upvotes: 0

ErikE
ErikE

Reputation: 50201

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.

Upvotes: 0

KG -
KG -

Reputation: 7170

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.

Upvotes: 0

Cowan
Cowan

Reputation: 37533

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)
)

Upvotes: 4

Eric
Eric

Reputation: 95103

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.

Upvotes: 0

Related Questions