cannot create a single result set oracle joins or subqueries - sql

I have three basic queries that are related to each other and I need a single result set to return.
Simplified:
Query1 (possible return not a show stopper return null):
SELECT * FROM monkey WHERE monkey.ENDDATE IS NULL AND monkey.TEMPLATEID = 1
Query2 (possible return not a show stopper return null):
SELECT * FROM banana WHERE banana.ENDDATE IS NULL AND banana.TEMPLATEID = 1
Query3 (must return something):
SELECT * FROM tree WHERE tree.TEMPLATEID = 1
Query 1 and 2 may or may not return a value (come back null).
The third one will need to return a result (or not) IF the third query returns something I and query 1 or 2 fail I still want to return something.
I can’t do an outer join with 2 queries, because oracle won’t let me the error said… a.b (+) = b.b and a.c(+) = c.c is not allowed instead turn b+c into a view.
I think I understand the logical reason, never-the-less I need to return query 3 and maybe query 1, 2 or 1 and 2 along with 3 as a single result set.
I hope this makes sense.

You seem to be hitting ORA-01417. Using made up tables and data as you haven't provided any, or the join conditions, I can get the same effect by trying to outer join monkey to both tree and banana - in a completely contrived way, of course:
with banana as (select 'yellow' as colour, 1 as template_id, null as enddate
from dual),
monkey as (select 'capuchin' as monkeytype, 1 as template_id, null as enddate
from dual),
tree as (select 'tropical' as treetype, 1 as template_id from dual)
select t.treetype, b.colour, m.monkeytype
from tree t, banana b, monkey m
where t.template_id = 1
and b.template_id (+) = t.template_id
and b.enddate (+) is null
and m.template_id (+) = t.template_id
and m.enddate (+) is null
and m.template_id (+) = b.template_id;
Error at Command Line:10 Column:22
Error report:
SQL Error: ORA-01417: a table may be outer joined to at most one other table
01417. 00000 - "a table may be outer joined to at most one other table"
*Cause: a.b (+) = b.b and a.c (+) = c.c is not allowed
*Action: Check that this is really what you want, then join b and c first
in a view.
if you use the 'new' (since 9i, I think) ANSI join syntax, rather the Oracle-specific (+) notation, you can do more:
with banana as (select 'yellow' as colour, 1 as template_id, null as enddate
from dual),
monkey as (select 'capuchin' as monkeytype, 1 as template_id, null as enddate
from dual),
tree as (select 'tropical' as treetype, 1 as template_id from dual)
select t.treetype, b.colour, m.monkeytype
from tree t
left join banana b on b.template_id = t.template_id
and b.enddate is null
left join monkey m on m.template_id = t.template_id
and m.enddate is null
and m.template_id = b.template_id
where t.template_id = 1;
TREETYPE COLOUR MONKEYTYPE
-------- ------ ----------
tropical yellow capuchin
See the documentation for some of the restrictions on (+); Oracle recommend using the ANSI version, though they seem to use their own most of the time in examples in the documentation.

I suspect, that the tables, "monkey", "banana", and "tree" all have different column types, otherwise you can easily obtain what you want using a "UNION" or "UNION ALL" operator.
Also since you haven't shared the table defn. of these tables, I don't know if you have any foreign keys to join these tables or not. SO I am assuming they are completely unrelated.
So with these 2 assumptions....
Here's another way, of returning combined resultsets from different queries.
SELECT CURSOR(QRY1), CURSOR(QRY2), CURSOR(QRY3) FROM DUAL;
This will give you all three result-sets in one query. But each result-set is a ref-cursor which you'll have to navigate in your application.
If using java, the java type of each of the 3 columns, is a ResultSet, which you'll have to navigate, to get individual query results.
e.g.
String qry1= "SELECT * FROM monkey WHERE monkey.ENDDATE IS NULL";
String qry2 = " SELECT * FROM banana WHERE banana.ENDDATE IS NULL AND banana.TEMPLATEID = 1";
String qry3= "SELECT * FROM tree WHERE tree.ENDDATE IS NULL AND tree.TEMPLATEID = 1";
String qry = "SELECT CURSOR("+qry1+"), CURSOR("+qry2+"), CURSOR("+qry3+") FROM DUAL";
ResultSet rs = conn.createStatement().executeQuery(qry);
if(rs.next()) { //no while loop necessary, as we are expecting only one row
ResultSet rs1 = (ResultSet) rs.getObject(1);
ResultSet rs2 = (ResultSet) rs.getObject(2);
ResultSet rs3 = (ResultSet) rs.getObject(3);
while(rs1.next()) {
// retrive results of qry1
}
// same for rs2 and rs3
}
Hope that helped.

Related

Sub-query works but would a join or other alternative be better?

I am trying to select rows from one table where the id referenced in those rows matches the unique id from another table that relates to it like so:
SELECT *
FROM booklet_tickets
WHERE bookletId = (SELECT id
FROM booklets
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3)
With the bookletNum/seasonId/bookletTypeId being filled in by a user form and inserted into the query.
This works and returns what I want but seems messy. Is a join better to use in this type of scenario?
If there is even a possibility for your subquery to return multiple value you should use in instead:
SELECT *
FROM booklet_tickets
WHERE bookletId in (SELECT id
FROM booklets
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3)
But I would prefer exists over in :
SELECT *
FROM booklet_tickets bt
WHERE EXISTS (SELECT 1
FROM booklets b
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3
AND b.id = bt.bookletId)
It is not possible to give a "Yes it's better" or "no it's not" answer for this type of scenario.
My personal rule of thumb if number of rows in a table is less than 1 million, I do not care optimising "SELECT WHERE IN" types of queries as SQL Server Query Optimizer is smart enough to pick an appropriate plan for the query.
In reality however you often need more values from a joined table in the final resultset so a JOIN with a filter WHERE clause might make more sense, such as:
SELECT BT.*, B.SeasonId
FROM booklet_tickes BT
INNER JOIN booklets B ON BT.bookletId = B.id
WHERE B.bookletNum = 2000
AND B.seasonId = 9
AND B.bookletTypeId = 3
To me it comes down to a question of style rather than anything else, write your code so that it'll be easier for you to understand it months later. So pick a certain style and then stick to it :)
The question however is old as the time itself :)
SQL JOIN vs IN performance?

Record exist in one DB2 table but not in another table

I have 2 DB2 tables. I want to find out records that are in table A is not Table B with the following condition
I wrote this query and it is not working
SELECT A.CL_BATCH_DEPT,
A.CL_TRANS_CODE, A.CL_CUR_DOC_NO
FROM DBPA60AC.TB_ACCOUNT_EVENT A
LEFT JOIN DBPA60AC.TB_DOCUMENT B ON A.CL_CUR_DOC_NO = B.CL_DOCNO
WHERE A.CL_BATCH_DEPT = 'R07' AND A.CL_TRANS_CODE = '210'
AND A.CL_CUR_DOC_NO = "PI%" AND
B CL_DOCNO IS NULL
I am guessing that you want:
SELECT e.*
FROM DBPA60AC.TB_ACCOUNT_EVENT e LEFT JOIN
DBPA60AC.TB_DOCUMENT d
ON e.CL_CUR_DOC_NO = d.CL_DOCNO
WHERE e.CL_BATCH_DEPT = 'R07' AND
e.CL_TRANS_CODE = '210' AND
e.CL_CUR_DOC_NO LIKE 'PI%' AND
d.CL_DOCNO IS NULL;
That is, the comparison to PI% suggests that you really want LIKE.
Note that I also changed the aliases so they are meaningful.

Difference between combination of AND and OR when placed inside the parentheses vs when placed stand alone in a conditional statement

I am getting different number of results when I have the script like the following:
select count(distinct(t1.ticketid)),t1.TicketStatus from ticket as t1
inner join Timepoint as t2 on t1.TicketID=t2. ticketid
where
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10
)
And when I use it like this:
select count(distinct(t1.ticketid)),t1.TicketStatus from ticket as t1
inner join Timepoint as t2 on t1.TicketID=t2. ticketid
where
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
t1.TicketBuildStatusID<>12 AND
t1.TicketBuildStatusID<>11 AND
t1.TicketBuildStatusID<>10
Can someone tell me why there is a difference, to me the logic is the same!
Thanks,
In your example, it won't matter because you have all AND clauses. That said, you need to be aware of precedence (ie order of operations) where NOT comes before AND, AND comes before OR and so on.
So just like 3 + 3 x 0 means 3 + (3 x 0), A or B and C means A or (B and C), even if that's not what you meant.
So in cases where you have mixed AND and OR clauses, it matters a lot.
Consider this example:
select *
from A, B
where A.id = B.id and A.family_code = 'ABC' or A.family_code = 'DEF'
It's horrible code, I admit, but for illustrative purposes, bear with me.
You may have meant this:
select *
from A, B
where A.id = B.id and (A.family_code = 'ABC' or A.family_code = 'DEF')
but you said this:
select *
from A, B
where (A.id = B.id and A.family_code = 'ABC') or A.family_code = 'DEF'
Which in the construct above completely blows away your join, resulting in a cartesian product for all cases where the family code is DEF.
So bottom line: when you mix clauses (AND, OR, NOT), it's best to use parentheses to be explicit about what you mean, even when it's not necessary.
Food for thought.
-- EDIT --
The question was changed after I wrote this so that the queries were NOT the same (ands were changed to ors).
Hopefully my explanation still helps.
After the edited to your question there will now be a difference.
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10
)
This query will return values where t1.TicketBuildStatusID is 10, 11 and 12. It states that it should not be 10 (so 11 and 12), or not be 11 (so 10 and 11), or not be 12 (so 10 and 11).
Yes, those queries will produce different results. In fact, the first query will return every value of TicketBuildStatusID unless it has a value of NULL.
When TicketBuildStatusID has a value or 12 it doesn't have a value of 11 or 12 so the expression (t1.TicketBuildStatusID<>12 OR t1.TicketBuildStatusID<>11 OR t1.TicketBuildStatusID<>10), is true. If it has a value of 11, then the same applies again, and for every other possible value, apart from NULL (as {expression}<>NULL = NULL which is not true).
when you do this
AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10)
you are basically doing no filter because any of the condition evaluated to true will make all the condition true e.i.
true AND (true or false or false) = true
when you do this all conditions should match like status should not be 12,11,10
AND
t1.TicketBuildStatusID<>12 AND
t1.TicketBuildStatusID<>11 AND
t1.TicketBuildStatusID<>10
OR isn't the logic that you want. Because if x = 12, then it is not 11. So, all values match x <> 12 and x <> 11.
So, just simply the logic and use not in:
select count(distinct t1.ticketid), t1.TicketStatus
from ticket t1 inner join
Timepoint t2
on t1.TicketID = t2.ticketid
where t2.BuilderAnalystID = 10 and
t1.SubmissionDT >= '2018-04-01' and
t1.TicketBuildStatusID not in (12, 11, 10)
Notes:
distinct is not a function, so there is no need to place the following expression in parentheses.
Use standard date formats. Either 'YYYYMMDD' or 'YYYY-M-DD'.

When to add joins to a query

Background
We have a bunch of Java code which generates an SQL query. When generating the conditions for the WHERE part, we are having some difficulties inducing the necessary joins.
The purpose of the query is always to return the zoo's id (see Database Structure).
Database Structure
Context
In the UI the user defines separately the filtering logic and the WHERE clause pieces. An example
Filtering logic: "1 OR (2 AND 3) OR (4 AND 5)"
WHERE clause pieces:
1: animal.name = "jack"
2: zoo.city = "Los Angeles"
3: animal.species = "bear"
4: animal.species = "fish"
5: animal.name = "henrietta"
Problem
The problem is that depending on the filtering logic and the WHERE clause pieces, extra joins might be necessary. At the very least new join is required when you have a query with like "(species = A AND name = B) AND (species = C AND name = D)" (both species and name columns are compared to different values while being in AND relation). Note that this query only makes sense because it is suppose to return the zoo's id where all of these conditions are true.
So my problem is that I do not know how many joins are absolutely necessary. I don't know the logic with which I will arrive at the answer. Because an extra join is not necessary if you have a query like "(species = A OR species = B)".
Expected Behavior
The following query should return the IDs of all zoos where there are two animals with names jack and tanya.
SELECT zoo.id FROM zoo
INNER JOIN animal a1 ON zoo.id = a1.zoo_id
INNER JOIN animal a2 ON zoo.id = a2.zoo_id
WHERE
a1.name = "jack"
AND a2.name = "tanya";
This query illustrates the need for two joins. The next one illustrates the case where only one join is sufficient.
SELECT zoo.id FROM zoo
INNER JOIN animal ON zoo.id = animal.zoo_id
WHERE
animal.name = "jack"
OR animal.name = "tanya";
Naive Solution
The simplest possible solution is to add one join for each animal table reference but there are serious performance consequences; increase of thousands of percents when compared to minimal joins.
This baseline for any query of what you have would be as follows...
SELECT
a.species,
a.name,
z.city
from
animals a
join zoo z
ON a.zoo_id = z.id
Then, just add your query criteria into the WHERE clause, such as
where
( a.name = 'jack' )
OR ( a.species = 'bear' AND z.city = 'Los Angeles' )
OR ( a.species = 'fish' AND a.name = 'henrietta' )
It goes through all the animals entries ONCE and pulls out those that qualify. I would also have an index on animals by (species, name), and the zoo table indexed on (id, city)
You don't need extra joins.
Just join on the Zoo ID and you can add to the end of your statement "WHERE...."
EDIT: Are you doing "JOIN ON" ? If so, take the filter out of the join and just put it at the end.

SQL - identifying exact matches across multiple records

Table Parent
Column1
S1
S2
S3
Table Child
Column1 Column2
S1 P1
S1 P2
S2 P1
S2 P2
S3 P1
Where parent.column1 = child.column1
Given the above tables, I need to identify the parents whose children have the same records in column2 as parent S1 does.
For example, S1 and S2 both have P1 and P2, so that would meet the condition. S3, however, does not have P2, and should therefore be excluded.
New to SQL, so I'm having some trouble. Tried it by using a not in statement, but since S3 has P1, it's not being excluded.
You need a join. This will vary by SQL dialect. Something like:
select child.column1, child.column2 from (
select column2 as parentsColumn2Value from child where column1='S1'
) as parentsColumn2Table
left join child on parentsColumn2Table.column2=child.column2
Solving this for the general case of finding all the matching parents is more fun :) See note at the bottom for how this can be used for the simpler case (one of the parent keys is fixed).
I'll give what I think is a "proper" solution, but I don't have a copy of Access (or SQL Server) to hand to see if it works in there. (Yes, I have tested this against a DB here though...)
SELECT p1.column1, p2.column1
FROM parent p1 JOIN parent p2 ON p1.column1 < p2.column1
WHERE NOT EXISTS (SELECT 1
FROM (SELECT c1.column1, c1.column2 FROM child c1 WHERE c1.column1 = p1.column1) c1f
FULL OUTER JOIN
(SELECT c2.column1, c2.column2 FROM child c2 WHERE c2.column1 = p2.column1) c2f
ON c1f.column2 = c2f.column2
WHERE c1f.column1 IS NULL OR c2f.column1 IS NULL
);
So hopefully you can see how what I said above is tied together in this :) I'll try to explain...
The "outer" (first) select generates combinations of column1 values (p1.column1 and p2.column1). For each of the combinations, we list the rows in "child" for those values (these are c1f and c2f: c1f means "child 1 filtered") and do a FULL OUTER JOIN. Which is a comparatively rare construct, in my experience. We want to match up all the entries in c1f and c2f (using their column1 values), and find any on either side that doesn't have a corresponding entry on the other side. If there are any such non-matchers then they will manifest as rows from the join with a null for their column1 value. So the parent query selects only combinations of column1 values where no such rows in the subquery exist, i.e. every child row for p1's column1 value has a corresponding child row for p2's column1 value and vice versa.
So for instance, for the iteration where p1.column1 is 'S1' and p2.column2 is 'S3', that subquery (without aggregation) would produce:
c1f__column1 | c1f__column2 | c2f__column1 | c2f__column2
--------------+--------------+--------------+--------------
S1 | P1 | S3 | P1
S1 | P2 | |
and it's those nulls in the second row that flag this combination as not matching. Some twisty thinking involved, it's tempting to get fixated on finding matching combinations, when finding non-matching ones is easier.
As a final bonus, when I created some test tables for this, I made (column1,column2) the primary key of child, which just so happened to be exactly what you need to drive the full outer join of the filtered tables efficiently. Win! (So do note I haven't tried to cope with duplicate combinations in child... but you could just slap "distinct" in the c1f and c2f derivations)
NB based on Matt's comment, if one of your parent values was known (i.e. you just wanted to list all the parent values with the same children as S1) then you can just slap "and p1.column1 = 'S1'" on the end of this. But replace "parent p1 JOIN parent p2 ON p1.column1 < p2.column1" with just "parent p1, parent p2" in that case... remember that otherwise the query as written will only output half of all the possible pairs...
This is an Access, rather than an SQL solution in that it makes use of a User Defined Function (UDF).
SELECT p.Column1,
ConcatList("SELECT Column2 FROM c WHERE Column1='S1'","|") AS S1,
ConcatList("SELECT Column2 FROM c WHERE Column1='" & [p].[Column1] & "'","|") AS Child,
Format([S1]=[Child],"Yes/No") AS [Match]
FROM p;
The UDF
Function ConcatList(strSQL As String, strDelim, ParamArray NameList() As Variant)
''Reference: Microsoft DAO x.x Object Library
Dim db As Database
Dim rs As DAO.Recordset
Dim strList As String
Set db = CurrentDb
If strSQL <> "" Then
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF
strList = strList & strDelim & rs.Fields(0)
rs.MoveNext
Loop
strList = Mid(strList, Len(strDelim) + 1)
Else
strList = Join(NameList, strDelim)
End If
ConcatList = strList
End Function
Unfortunately, I do not believe Jet supports Full Outer Join, so a solution using only SQL is likely to be a little tedious, and require more information, such as whether S1 has a fixed number of entries in column2.
ACE/Jet may not support FULL OUTER JOIN directly but the workaround is simple enough i.e. just UNION ALL the LEFT JOIN, INNER JOIN and RIGHT JOIN respectively of the tables.
Incidentally, i was searching for the same solution and have figured it out myself
select * from TableParent where id in
(select temp_parent.id from
(select TableParent.*, count(exact_match.match_count) as exact_count
from TableParent
inner join
(select Column1, count(*) as match_count from TableChild
group by Column1
having match_count = 2
) as exact_match on exact_match.Column1 = TableParent.Column1
inner join
TableChild on TableChild.event_id = exact_match.Column1 where TableChild.Column2 in (P1,P2)
group by TableParent.Column1
having exact_count = 2) as temp_parent)