EDIT . I say below that the syntax is illegal; on further thought that BS for my part, I don’t know what it really is (I can’t indicate where in the language the definition of aliases is required for self-connection). I still believe that the explanation below is probably correct, whether for the “error” or for the “undefined” behavior, which I mentioned below.
*
The syntax is illegal (you knew this - you were just curious to know what would happen, and if you can figure out the solution). I agree with jarlh that you should have received an error message. Obviously, Oracle did not code it that way.
Since this is an invalid syntax, what you see cannot be called an error (therefore, I disagree with Nick's comment). Undefined behavior - when you use syntax that is not supported by the Oracle language definition, you can get any crazy results for which Oracle does not bear any responsibility.
Well, from this point of view, is there any explanation for what you see? I believe this is truly a Cartesian association, not a union, as Nick suggested.
Put yourself in the shoes of an optimizer. He sees the first table in the FROM list, she scans it, how good it is.
Then it reads the second table and has a list of such columns:
tabNULL.val, tabNULL.descr, tabNULL.val, tabNULL.descr
tabNULL.val = tabNULL.val condition tabNULL.val = tabNULL.val
The optimizer is dumb, it is not smart. He, unlike you, does not understand at this moment that tabNULL intended for two different incarnations of the table. He thinks tabNULL.val on both sides of the equation is the EXACT value, and both of them refer to the first "incarnation" of the table. The only time this fails is that tabNULL.val is NULL, so REWRITES REWRITES with the sentence becomes tabNULL.val IS NOT NULL .
Only the FIRST table is checked for tabNULL.val IS NOT NULL ; the optimizer does not "know" tabNULL.val appears in the list again and may have VARIOUS values! Then a connection occurs; at this moment there are no other conditions, therefore BOTH rows in the second incarnation of the table will create rows in the join, for A, ONE CHAR from the first table.
Then in the projection, only FIRST tabNULL.val will be read tabNULL.val and will fill the BOTH columns at the output. You request that the query mechanism return tabNULL.val twice, and, in your opinion, it is from different places, but there is only one memory cell labeled tabNULL.val and stores what was in the first table.
Of course, very few know with certainty what the optimizer and query mechanism do, but in this case I think this is a pretty safe assumption.