LEFT OUTER JOIN returns no rows - abap

I have two tables with the same key. I want to, in one SELECT, without subsequent manipulation of data in an internal table, retrieve all records in the left-hand table where there is NO CORRESPONDING record in the right-hand table (i.e. columns from the right-hand table would be empty).
The most logical thing to do would be the following, but this does not compile, because you may not use a field from the right-hand side in an outer join in the WHERE clause:
select e~equnr into lt_equnr
from equi as e
left outer join eqbs as b on e~equnr = b~equnr
where e~matnr = material
and b~b_werk = space.
An alternative that looks promising and compiles is this, but it does not work, as it brings back even those that have corresponding entries in the right-hand table:
select e~equnr into table lt_equnr
from equi as e
left outer join eqbs as b on e~equnr = b~equnr
and b~b_werk = space
where e~matnr = material.
This option merely blanks out fields from the right-hand side, but still includes everything in the result set. This can be confirmed by selecting fields from the right-hand side.
Another option, which also does not work, is using a sub-select:
select e~equnr into table lt_equnr
from equi as e
where e~matnr = material
and e~equnr not in ( select equnr from equi where equnr = e~equnr ).

As pointed out in the comments on the question, there was a bug in my code. In my sub-select I was using the LHS table. (My sub-select was referencing EQUI instead of EQBS).
By fixing my sub-select thus, it works:
select e~equnr into table lt_equnr
up to max_entries rows
from equi as e
where e~matnr = material
and e~equnr not in ( select equnr from eqbs where equnr = e~equnr ).

You could try it with a subquery:
SELECT e~equnr INTO TABLE lt_equnr
FROM equi AS e
WHERE e~matnr = material
AND NOT EXISTS ( SELECT b~equnr
FROM eqbs as b
WHERE b~equnr = e~equnr ).

Related

Why is the left table not being preserved in a left outer Join?

I wrote this query against a redshift database
with wt_id(id) as (select cast(11646722011 as bigint))
select
w.id,
nullif(
trim(
regexp_replace(
json_extract_path_text(bn.foo, 'foo-id'), '\\["(.*)"\\]', '$$1'
)
), '') as foo-id
from wt_id w left outer join foo bn on w.id = bn.fid
where bn.day='2019-12-03'
limit 1;
This query returns an empty result set. But how is this possible? because I did a left out join, I should get 1 row where id is 11646722011 and the foo-id can be null if there was no match on the right hand side.
Your where clause is converting the outer join to an inner join because NULL fails the WHERE condition.
Conditions on the second table in a left join should be in the on clause. So you want:
from wt_id w left outer join
foo bn
on w.id = bn.fid and bn.day = '2019-12-03'
Your where clause has made your data loss. This is not the case about left join allows unmatched rows but about no data on that specific day after left join. So, you should replace where with and.
If we see the order of execution the where gets applied after the join as above. Hence, even if the rows gets null it gets removed if the other condition gets true as your day.

Entity Framework with Multiple Joins and Subquery

I have a complex query with multiple left joins and subqueries which I need to implement in Entify Framework. I've received the monster SQL and
my goal is to do it on a elegant way with EF. The query consumes multiple tables and creates a "WITH" subquery on top which
is included in the joins later. I've done a first approach with EF but when I inspect the output that EF sends to the DB, inner joins are sent when
I am expecting LEFT JOINs.
A summary of the SQL follows:
WITH SUB_QUERY
AS ( SELECT FIELD_A,
FIELD_B,
FIELD_C,
MAX (FIELD_D) MAX_FIELD_D
FROM TABLE_X
WHERE SOME FIELD_A = 'WHATEVER'
GROUP BY FIELD_A, FIELD_B, FIELD_C)
SELECT C.FIELD_A,
C.FIELD_B,
B.FIELD_X,
D.FIELD_S,
E.FIELD_J,
F.FIELD_Y
FROM TABLE_A A
LEFT JOIN SUB_QUERY B
ON A.FIELD_C = B.FIELD_C
LEFT JOIN TABLE_C C
ON B.FIELD_A = C.FIELD_A
LEFT JOIN TABLE_D D
ON A.FIELD_C = D.FIELD_C
LEFT JOIN TABLE_E E
ON A.FIELD_X = E.FIELD_X
LEFT JOIN TABLE_F F
ON A.FIELD_W = F.FIELD_W
WHERE A.FIELD_H = D.FIELD_H
AND A.FIELD_D = B.MAX_FIELD_D
As you see, a subquery on top filters and groups some data to be consumed in a join below. Then all the joins take place
and some fields are taken from different tables as the output of the query.
Which approach would you recommend me to accomplish this task? I've tried different approaches and no one of them works (either retrieve nothing, or many more rows than the SQL query on the DB, etc..)
Please note that the Domain Model in Entity Framework is properly setup: Primary Keys, collections, nested objects etc.. so I believe some of these
joins are not even required because my EF entities contain already references to the child collections and parent objects (navigation properties).
Thanks a lot!!
if you really need a left join you should mode the where condition related to a left joined table in the proper on clause
FROM TABLE_A A
LEFT JOIN SUB_QUERY B
ON A.FIELD_C = B.FIELD_C
LEFT JOIN TABLE_C C
ON B.FIELD_A = C.FIELD_A
LEFT JOIN TABLE_D D
ON A.FIELD_C = D.FIELD_C AND A.FIELD_D = B.MAX_FIELD_D
LEFT JOIN TABLE_E E
ON A.FIELD_X = E.FIELD_X
LEFT JOIN TABLE_F F
ON A.FIELD_W = F.FIELD_W
the use of a left join table column in where force the relation to work as a INNER JOIN

Left join, where clause

I'm wondering why this returns no values but if I place the "and r2.ref_nm = 'memberPrograStatusType" in the join clause it returns values. The 'memberPrograStatusType is null.
`select mp.mbr_id
,r1.ref_desc as program_name
,r2.ref_desc as program_status
,mp.nom_dt
,mp.enrl_dt
,mp.end_dt
from icue.mbr_pgm mp
left join icue.ref r1 on mp.pgm_typ_id = r1.ref_cd
left join icue.ref r2 on mp.mbr_pgm_sts_typ_id = r2.ref_cd
where '15-JAN-17' between mp.enrl_dt and nvl(mp.end_dt,sysdate)
and mp.mbr_id = 46714641
and r1.ref_nm = 'programType'
and r2.ref_nm = 'memberPrograStatusType'
'
The reason is because when you place a filter from a column in the left join in the where clause, you are turning it into an inner join.
However if you place the filter on the join clause then the filter applies only to the rows that have data in left join table.
The key here is order of operations
WHERE is applied after JOIN
If you put r2.ref_nm = 'x' into the JOIN predicate, it is applied against the right table, which is then joined to the left table. Because you are using a LEFT JOIN, this means that the rows joined from the right table are filtered on r2.ref_nm = 'x', but all rows from the left table are still preserved in the result set.
If you put r2.ref_nm = 'x' into the WHERE clause, then it is applied to the entire result set, after the joins have occured. This essentially makes your LEFT JOIN into an INNER JOIN if your filter is looking for anything other than NULL
TLDR: if you want to filter on the right table in a LEFT JOIN, you have to do so in the join predicate, otherwise you will lose all the NULL values that are created from the LEFT JOIN

SQL: Proper JOIN Protocol

I have the following tables with the following attributes:
Op(OpNo, OpName, Date)
OpConvert(OpNo, M_OpNo, Source_ID, Date)
Source(Source_ID, Source_Name, Date)
Fleet(OpNo, S_No, Date)
I have the current multiple JOIN query which gives me the results that I want:
SELECT O.OpNo AS Op_NO, O.OpName, O.Date AS Date_Entered, C.*
FROM Op O
LEFT OUTER JOIN OpConvert C
ON O.OpNo = C.OpNo
LEFT OUTER JOIN Source D
ON C.Source_ID = D.Source_ID
WHERE C.OpNo IS NOT NULL
The problem is this. I need to join the Fleet table on the previous multiple JOIN statement to attach the relevant S_No to the multiple JOIN table. Would I still be able to accomplish this using a LEFT OUTER JOIN or would I have to use a different JOIN statement? Also, which table would I JOIN on?
Please note that I am only familiar with LEFT OUTER JOINS.
Thanks.
I guess in your case you could use INNER JOIN or LEFT JOIN (which is the same thing as LEFT OUTER JOIN in SQL Server.
INNER JOIN means that it will only return records from other tables only if there are corresponding records (based on the join condition) in the Fleet table.
LEFT JOIN means that it will return records from other tables even if there are no corresponding records (based on the join condition) in the Fleet table. All columns from Fleet will return NULL in this case.
As for which table to join, you should really join the table that makes more logical sense based on your data structure.
Yes, you can use all tables mentioned before in your join conditions. Actually, JOINS (no matter of INNER, LEFT OUTER, RIGHT OUTER, CROSS, FULL OUTER or whatever) are left- associative, i. e. they are implicitly evaluated as if they would have been included in parentheses from the left as follows:
FROM ( ( ( Op O
LEFT OUTER JOIN OpConvert C
ON O.OpNo = C.OpNo
)
LEFT OUTER JOIN Source D
ON C.Source_ID = D.Source_ID
)
LEFT OUTER JOIN Fleet
ON ...
)
This is similar to how + or - would implicitly use parentheses, i. e.
2 + 3 - 4 - 5
is evaluated as
(((2 + 3) - 4) - 5)
By the way: If you use C.OpNo IS NOT NULL, then the LEFT OUTER JOIN Source D is treated as if it were an INNER JOIN, as you are explicitly removing all the "OUTER" rows.

Combining OUTER JOIN and WHERE

I'm trying to fetch some data from a database.
I want to select an employee, and if available, all appointments and other data related to that employee.
This is the query:
SELECT
TA.id,
TEI.displayname,
TA.threatment_id,
TTS.appointment_date,
TEI.displayname
FROM
tblemployee AS TE
LEFT OUTER Join tblappointment AS TA ON TE.employeeid = TA.employee_id
Inner Join tblthreatment AS T ON TA.threatment_id = T.threatmentid
Inner Join tblappointments AS TTS ON TTS.id = TA.appointments_id AND
TTS.appointment_date = '2009-09-28'
INNER Join tblemployeeinfo AS TEI ON TEI.employeeinfoid = TE.employeeinfoid
Inner Join tblcustomercard AS TCC ON TCC.customercardid = TTS.customercard_id
WHERE
TE.employeeid = 4
The problem is, it just returns null for all fields selected when there are no appointments. What am I not getting here?
Edit:
For clearity, i removed some of the collumns. I removed one too many. TEI.displayname should at least be displayed.
Looking at the list of columns returned by your query, you will notice that they all come from the "right" side of the LEFT OUTER JOIN. You do not include any columns from the "left" side of the join. Therefore, the expected result is the one you are observing — NULL values supplied for all right-hand columns in the result set for those rows that have no right-hand rows returned.
To see data even for those rows, include some columns from TE (tblemployee) in the result set.
Looking at your query I'm guessing that the situation is a bit more complex and that some of those tables on the right-hand side of the join should be moved to the left-hand side and, furthermore, that some of the other tables might possibly require their own OUTER joins to participate correctly in the query.
Edited w/ response to questioner's comment:
You have an odd situation (maybe not odd at all, depending on your application) in which you have an employee table and a separate employee information (employeeinfo) table.
Because you are joining the employeeinfo to the appointments table with an INNER join you can effectively think of them as a single table in terms of how they contribute to the final result set. Because this combined table REQUIRES a record in the appointments table and because this combined table is joined into the main result set with a LEFT OUTER join, the effect is that the employeeinfo record is not found if there's no appointment to link it to.
If you move the employeeinfo table to the left side of the join, or replace the employee table w/ the employeeinfo table, you should get the results you want.
In your query, you LEFT OUTER JOIN to the tblappointment table, but then you INNER JOIN to the tblthreatment and tblappointments tables.
You should try and structure your query in the order that you expect data to be there. Then in most simple queries, once you perform an OUTER join, most tables after that will be an OUTER join. This is by NO MEANS a rule and complex queries can vary, but in the marjority of simple queries its a good practice.
Try something like this for your query.
SELECT
TA.id,
TEI.displayname,
TA.threatment_id,
TTS.appointment_date
FROM
tblemployee AS TE
INNER Join
tblemployeeinfo AS TEI
ON
TEI.employeeinfoid = TE.employeeinfoid
LEFT OUTER Join
tblappointment AS TA
ON
TE.employeeid = TA.employee_id
LEFT OUTER JOIN
tblthreatment AS T
ON
TA.threatment_id = T.threatmentid
LEFT OUTER JOIN
tblappointments AS TTS
ON
TTS.id = TA.appointments_id
AND
TTS.appointment_date = '2009-09-28'
LEFT OUTER JOIN
tblcustomercard AS TCC
ON
TCC.customercardid = TTS.customercard_id
WHERE
TE.employeeid = 4
The issue is that the way you're joining (most of everything is joining to your left outer-joined table) whenever you're joining off of that, if the value in the outer joined table is nothing, there is nothing for the other fields to join to. Try to re-adjust your query so everything is joining off of your employeeID. I normally use left joined tables after I've limited everything down as much as possible with inner joins.
So my query would be something like:
SELECT
TA.id,
TEI.displayname,
TA.threatment_id,
TTS.appointment_date
FROM
tblemployee AS TE
INNER Join tblemployeeinfo AS TEI ON TEI.employeeinfoid = TE.employeeinfoid
Inner Join tblthreatment AS T ON TA.threatment_id = T.threatmentid
Inner Join tblappointments AS TTS ON TTS.id = TA.appointments_id AND
TTS.appointment_date = '2009-09-28'
Inner Join tblcustomercard AS TCC ON TCC.customercardid = TTS.customercard_id
LEFT OUTER Join tblappointment AS TA ON TE.employeeid = TA.employee_id
WHERE
TE.employeeid = 4
where the last outer join just gives me one column worth of information, not using it all to join more things onto. For speed, you also want to try to limit your information down as fast as possible with your first few inner joins, and then you do the outer joins last to join possible null values on to the smallest dataset you can. I hope this helps, if it's confusing, I'm sorry... I haven't had my caffeine yet.
The query is performing as it should.
A left out join will select all records from one table, join them with the records in another, and produce nulls where no records in the second table are found that match the join condition.
If you're looking for a separate behavior, you may want to think about two separate queries.