How to conditionally disable a join in SQL? - sql

Oracle, specifically. I need to do the following in SQL in a declarative query, without defining a variable or if condition. Consider the following SQL:
SELECT *
FROM table1
JOIN table2 on table2.a = table1.a
WHERE table1.d = 4
AND c = (select c from table3 where b=3);
The catch is, if there is NOT a match in table3, then instead of table3 limiting the rows , the table3 condition should not apply (effectly just removing the last line of the above query)
How can I do that in a single SQL statement without defining a variable first?

You could add a NOT EXISTS clause to effectively remove the c = ... condition:
SELECT *
FROM table1
JOIN table2 on table2.a = table1.a
WHERE table1.d = 4
AND (NOT EXISTS (SELECT c FROM table3 WHERE b=3)
OR c = (SELECT c FROM table3 WHERE b=3)
)

The subquery returns at most one value. Here are two ways that you can handle this without an additional subquery:
c = all (select t3.c from table3 t3 where t3.b = 3)
This works because all matches to an empty list.
c = (select coalesce(max(t3.c), ?.c) from table3 t3 where t3.b = 3)
The ? is for the outer table reference. This works because max() will return null if there are no matches. The coalesce() then replaces the value with a matching value. Note: This assumes that the outer c is not null.

You would want to consider using left join on the table that contains the col c, as follows
SELECT *
FROM table1
JOIN table2
on table2.a = table1.a
LEFT JOIN (select distinct c
from table3
where b=3
)tbl_3
ON table1.c=tbl_3.c /*i assumed that the column c is in table1*/
WHERE table1.d = 4

Related

SQL - Minimum from a JOIN

I am joining several tables. From the joined table I need to select a record with the minimum value in one column. The where clause contains some additional conditions. How can this be achieved without having to list the whole join twice in the select and in the where clause to identify the minimum?
I mean - from the result of the join, I need to select one record that fullfills some conditions and that also includes a minimum in a specific column. It is in Teradata but I am asking about the general principle.
I have something like this. It works, but is ugly as the join is included twice.
SELECT TABLE1.X, TABLE2.Y, TABLE3.Z
FROM TABLE1
INNER JOIN TABLE2
ON TABLE1.A = TABLE2.B
INNER JOIN TABLE3
ON TABLE2.C=TABLE3.D
WHERE TABLE3.M =
(SELECT MIN(TABLE3.M)
FROM TABLE1
INNER JOIN TABLE2
ON TABLE1.A = TABLE2.B
INNER JOIN TABLE3
ON TABLE2.C=TABLE3.D
WHERE TABLE1.K=123 AND TABLE2.L=456
)
Thanks, R.
In a comment you say you only need one row as your output.
In which case, use ORDER BY and LIMIT 1
SELECT TABLE1.X, TABLE2.Y, TABLE3.Z
FROM TABLE1
INNER JOIN TABLE2
ON TABLE1.A = TABLE2.B
INNER JOIN TABLE3
ON TABLE2.C=TABLE3.D
WHERE TABLE1.K=123 AND TABLE2.L=456
ORDER BY TABLE3.M
LIMIT 1
Edit: (To use min() to fulfil unstated requirements...)
SELECT
X, Y, Z
FROM
(
SELECT
TABLE1.X,
TABLE2.Y,
TABLE3.Z,
TABLE3.M,
MIN(TABLE3.M) OVER () AS MIN_M
FROM
TABLE1
INNER JOIN
TABLE2
ON TABLE1.A = TABLE2.B
INNER JOIN
TABLE3
ON TABLE2.C = TABLE3.D
WHERE
TABLE1.K = 123
AND TABLE2.L = 456
)
AS FILTERED
WHERE
MIN_M = M
Even if I was going to use window functions for this, I'd use ROW_NUMBER() OR RANK() rather than using MIN(). Without a clear reason WHY you feel this MUST use it, yet still be DRY, efficient and maintainable, this constraint appears not only pointless, but misguided.
Use min window function as follows:
Select x, y, z from
(SELECT TABLE1.X, TABLE2.Y, TABLE3.Z,
Min(TABLE3.M) over () as mn,
TABLE3.M
FROM TABLE1
INNER JOIN TABLE2
ON TABLE1.A = TABLE2.B
INNER JOIN TABLE3
ON TABLE2.C=TABLE3.D
Where TABLE1.K=123 AND TABLE2.L=456 ) t
Where m = mn
If I am following correctly, you can use qualify:
SELECT TABLE1.X, TABLE2.Y, TABLE3.Z
FROM TABLE1 INNER JOIN
TABLE2
ON TABLE1.A = TABLE2.B INNER JOIN
TABLE3
ON TABLE2.C = TABLE3.D
QUALIFY TABLE3.M = MIN(CASE WHEN TABLE1.K = 123 AND TABLE2.L = 456 THEN TABLE3.M END) OVER ();

specifying count in WHERE clause

select *
from table1 t1,
table2 t2,
table3 t3
where t2.parent_id = t1.row_id
and t2.xyz is not null
and (
select count(*)
from table3
where xyz = t2.row_id
) = 0;
Will it work?
I am using the alias t2 within my subquery.
My requirement is to check is to specify condition in where clause such that there is no record present in table3 where column xyz of table3 is stored as row_id of table2.
You can use NOT EXISTS to assert that there is no row returned from the subquery. Use modern explicit join syntax instead of comma based legacy syntax. No need to join table3 outside (you were making a cross join effectively).
select *
from table1 t1
join table2 t2 on t2.parent_id = t1.row_id
where t2.xyz is not null
and not exists (
select 1
from table3
where xyz = t2.row_id
);

Select if else sql

I have two table say T1 and T2 and one column C is common to both. I need an SQL query in which if C is null in T1 it will select from other table.
I tried writing SELECT statement in THEN clause but not running. Don't know is there any IF ELSE clause in SQL.
Select C, case when c = null Then Select c from T2
from T1
Even better, most RDBMSs support COALESCE, which lets you check multiple values and return the first non-null value.
SELECT COALESCE(T1.C, T2.C) AS C
FROM T1
LEFT OUTER JOIN T2 ON T1.[Primary Key] = T2.[Primary Key]
Is this in TransactSQL?
I like the first answer, however you could also do it this way...
select case t1.C
when null then t2.C
else t1.C
end as [testC]
from t1
inner join t2
on t1.PKID = t2.PKID
What you seem to need is a union
select c from t1
where c is not null
union
select c from t2
where c is not null
Now you get all the columns C from T1and T2 in one result set but only if not null.
Well of course if this has simplified your problem too much you need to work with a join
select coalesce(t1.c,t2.c) as c
from t1
left join t2 on (t2.id = t1.foreign_id)
This assumed that T2.ID is the primary key and related to T1 with T1.FOREIGN_ID
It is important that you do a left join because otherwise you only get T1 rows when the row also exists in T2.

sql, outer join

I have two tables, linked with an outer join. The relationship between the primary and secondary table is a 1 to [0..n]. The secondary table includes a timestamp column indicating when the record was added. I only want to retrieve the most recent record of the secondary table for each row in the primary. I have to use a group by on the primary table due to other tables also part of the SELECT. There's no way to use a 'having' clause though since this secondary table is not part of the group.
How can I do this without doing multiple queries?
For performance, try to touch the table least times
Option 1, OUTER APPLY
SELECT *
FROM
table1 a
OUTER APPY
(SELECT TOP 1 TimeStamp FROM table2 b
WHERE a.somekey = b.somekey ORDER BY TimeStamp DESC) x
Option 2, Aggregate
SELECT *
FROM
table1 a
LEFT JOIN
(SELECT MAX(TimeStamp) AS maxTs, somekey FROM table2
GROUP BY somekey) x ON a.somekey = x.somekey
Note: each table is mentioned once, no correlated subqueries
Something like:
SELECT a.id, b.*
FROM table1 a
INNER JOIN table2 b ON b.parentid = a.id
WHERE b.timestamp = (SELECT MAX(timestamp) FROM table2 c WHERE c.parentid = a.id)
Use LEFT JOIN instead of INNER JOIN if you want to show rows for IDs in table1 without any matches in table2.
select *
from table1 left outer join table2 a on
table1.id = a.table1_id
where
not exists (select 1 from table2 b where a.table1_id = b.table1_id and b.timestamp > a.timestamp)
The quickest way I know of is this:
SELECT
A.*,
B.SomeField
FROM
Table1 A
INNER JOIN (
SELECT
B1.A_ID,
B1.SomeField
FROM
Table2 B1
LEFT JOIN Table2 B2 ON (B1.A_ID=B2.A_ID) AND (B1.TimeStmp < B2.TimeStmp)
WHERE
B2.A_ID IS NULL
) B ON B.A_ID = A.ID

Semantic difference between join queries

I have two queries that I thought meant the same thing, but I keep getting different results and I was hoping someone could explain how these are different:
1.
select *
from table1 a
left join table2 b on a.Id = b.Id and a.val = 0
where b.Id is null
2.
select *
from table1 a
left join table2 b on a.Id = b.Id
where b.Id is null
and a.val = 0
The point of the query is to find the rows that are in table1 and val = 0 that are not in table2.
I'm using sql server 2008 as well, but I doubt that this should matter.
When considering left joins think of them as having 3 conceptual stages.
The join filter is applied
The left rows are added back in
the where clause is applied.
You will then see why you get different results.
That also explains why this returns results
select o.*
from sys.objects o
left join sys.objects o2 on o.object_id=o2.object_id and 1=0
And this doesn't.
select o.*
from sys.objects o
left join sys.objects o2 on o.object_id=o2.object_id
where 1=0
SELECT * from TABLE1 t1
WHERE Val = 0
AND NOT EXISTS(SELEct 1 from Table2 t2 Where t1.Id = t2.Id)
If you remove the WHERE clause entirely, using a LEFT OUTER JOIN means that all the rows from the table on the left hand side will appear, even if they don't satisfy the JOIN criteria. For example, no rows satisfy the expression 1 = 0 however this:
SELECT *
FROM table1 AS a
LEFT OUTER JOIN table2 AS b
ON a.Id = b.Id
AND 1 = 0;
still results in all rows in table1 being returned where the id values match. Simply put, that's the way OUTER JOINs work.
The WHERE clause is applied after the JOIN, therefore this
SELECT *
FROM table1 AS a
LEFT OUTER JOIN table2 AS b
ON a.Id = b.Id
WHERE 1 = 0;
will return no rows.