sql join scenario - sql

I have 2 tables name like A and B. A have columns say X, Y and Z and Table B have coulmns say P, Q and R. here in my case table have blank data for few rows in all the columns.
I need to join these 2 tables such that If A.X<>'' and B.X<>'' then It should join the table. If A.X='' and B.X='' then
it should check the next columns A.Y<>'' and B.Y<>''. If this is also blank it should join the table on next condition A.Z<>'' and B.Z<>''. If all these 3 condition have blanks It should not join for that row.
How can we achieve this using sql join?
Thanks in advance

You can go for conditional JOINS as given below:
SELECT *
FROM A
LEFT OUTER JOIN B as b1
ON A.X = b1.X AND B1.X <> '' -- JOIN only rows WHERE x is not blank
LEFT OUTER JOIN B as b2
ON A.Y = b2.Y AND b2.Y <> '' -- JOIN only rows WHERE y is not blank
LEFT OUTER JOIN B AS b3
ON A.Z = b3.Z AND b3.Z <> '' -- JOIN only rows WHERE z is not blank
WHERE
b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL

Ramu's answer is close (I upvoted it) but it needs to be refined. The important part of the answer that is correct -- the equality conditions in the JOINs make the query easier to optimize.
However, it is better written as:
SELECT a.*,
COALESCE(b1.P, b2.P, b3.P) as P,
COALESCE(b1.Q, b2.Q, b3.Q) as Q,
COALESCE(b1.R, b2.R, b3.R) as R
FROM A LEFT JOIN
B b1
ON A.X = b1.X LEFT JOIN
B b2
ON A.Y = b2.Y AND
b1.X IS NULL LEFT JOIN -- no previous match
B b3
ON A.Z = b3.Z AND
b2.Y IS NULL AND
b1.X IS NULL -- no previous match
WHERE b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL ;
The two key changes are:
The LEFT JOIN conditions check that previous columns did not match.
The SELECT uses COALESCE() to fetch columns.
Also, I don't think the condition on empty strings is needed. There will be no match if there are no empty string values in B for that column. If both tables have empty strings, then you apparently do want a match -- and an empty string matches an empty string in SQL Server.
You can also express this using APPLY:
SELECT a.*, b.*
FROM A CROSS APPLY
(SELECT TOP (1) WITH TIES B.*
FROM B
WHERE A.X = b.X OR
A.Y = b.Y OR
A.Z = b.Z
ORDER BY (CASE WHEN A.X = B.X THEN 1
WHEN A.Y = B.Y THEN 2
WHEN A.Z = B.Z THEN 3
)
) B;
However, I would expect the previous version to have much better performance.

Use a CASE expression in the ON clause:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND B.P <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND B.Q <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND B.R <> '' AND A.Z = B.R THEN 1
END
or:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND A.Z = B.R THEN 1
END
There is no need for further joins.
The CASE expression makes sure that each condition will be checked in the order that you want it to be checked.
So if the 1st condition is satisfied then the rows of the 2 tables will be joined, if not then the 2nd condition will be checked and so on.

Related

Right join without intersection and another condition

I want to do a right join like this:
I want all B without any A
OR
All B with just A.type <> 0 (if they have a single A.type = 0 i don't want them)
For now I have this :
SELECT B.*
FROM A
RIGHT JOIN B ON A.ticket = B.id
WHERE A.id IS NULL
AND B.state NOT IN (3,4,10)
AND B.disable = 0
There is no reason to use RIGHT JOIN. For most people, it is simply more confusing than LEFT JOIN for two reasons. First, the FROM clause is read "left to right". A LEFT JOIN keeps all rows in the first table in the clause. The RIGHT JOIN in the not-yet-seen last table. Second, the FROM clause is parsed left-to-right, which introduces some subtle issues when combining multiple outer joins.
Next, you explicitly say:
i want to exclude B who doesn't have a A.Type = 0
This is equivalent to:
i want to include B who have no A.Type = 0
This is not quite an outer join. I think this is closer to what you want:
In your case:
SELECT B.*
FROM B
WHERE NOT EXISTS (SELECT 1
FROM A
WHERE A.ticket = B.id AND A.Type = 0
) AND
B.state NOT IN (3, 4, 10) AND B.disable = 0;
You want B, where if there is any value in A elated to B, it's type <> 0, so:
SELECT * FROM B
WHERE B.id NOT IN (
SELECT ticket FORM A
WHERE A.type = 0 )
NOT Acceptable
May be you want some thing like this:
SELECT B.* FROM A
RIGHT JOIN B
ON A.ticket = B.id
WHERE
A.type<>0
-- (A.id is null OR A.type<>0)
-- A.id IS NULL
-- AND A.type <> 0 -- added for exclude a.type = 0
AND B.state NOT IN (3,4,10)
AND B.disable = 0
It means JOINing where A.type is not 0 and other conditions that you planned for your query
You can have the desired result with a join instead of a subquery in this way:
select B.id
from A right join B on A.ticket = B.id
where B.state not in (3, 4, 10) and B.disable = 0
group by B.id
having count(case when A.ticket is not null and A.type = 0 then 1 end) = 0;
This way you're only taking into account the rows of B that either don't have any matching row in A or have some but none with A.type = 0.
Wrong pre-edit answer
I'd go with:
select B.*
from A right join B on A.ticket = B.id
where B.state not in (3, 4, 10) and B.disable = 0 and coalesce(A.type, 1) <> 0
This way you only take into account the rows that either don't join or join but don't have A.type = 0.
Note that depending on the database you're using, the coalesce function might have to be replaced with something equivalent.
Using subqueries to achieve the same query and keeping the RIGHT JOIN. This method may also be faster as it tries to join the minimal amount of records from the tables on the two JOIN sides.
SELECT B2.*
FROM
(SELECT ticket, type FROM A WHERE type=0) A2
RIGHT JOIN
(SELECT * FROM B WHERE B.state NOT IN (3,4,10) AND B.disable = 0) B2
ON A2.ticket = B2.id WHERE A2.ticket IS NULL;

Alternative to <> in SQL Developer

I did some searching on this site and couldn't find exactly what I'm looking for, so I hope this isn't a duplicate. I have an issue where a query in a view is taking about 39 seconds to run, which is dragging down a report query that joins to this view multiple times.
To keep this simple I'm going to keep the code simple, but keep the structure exactly as it is on the view. Here is the SELECT statement:
SELECT ....
FROM A a
JOIN B b on a.x = b.x
JOIN C c ON c.s = 'P' AND c.y = b.y
JOIN B AS b2 ON b2.y = c.y AND b2.x <> a.x
JOIN B b3 ON b3.x = b2.x
The x's and y's are the same column names in all join predicates.
The issue I am having comes with the line AND b2.x <> a.x. Without this, it runs in about 1 second, but with it its always taking over 30 seconds. I've tried rewriting this predicate multiple times:
b2.x IN (select b2.x FROM B b2 join A a on b2.x <> a.x)
b2.x NOT IN (select b2.x FROM b b2 JOIN A a on b2.x <> a.x)
NOT b2.x = a.x
OR even removing it and putting in a where clause after the joins, with all of the above varieties and also :
WHERE b2.x NOT IN (SELECT x FROM a)
WHERE b2.x (NOT IN SELECT DISTINCT x FROM a)
Im running out of ideas and need to figure out a way to optimize this. Any suggestions or hints at what else I can look at? Just running
SELECT b2.x from B b2 JOIN A a ON b2.x <> a.x
runs very quickly, so I don't think the underlying tables are the issues.
If the query runs really fast without the condition, but poorly with it, then I might suggest a materialized CTE:
WITH abc as (
SELECT /*+ materialize */...., b2.x as b2x, a.x as ax
FROM A a JOIN
B b
ON a.x = b.x JOIN
C c
ON c.s = 'P' AND c.y = b.y JOIN
B b2
ON b2.y = c.y AND b2.x <> a.x JOIN
B b3
ON b3.x = b2.x
)
SELECT abc.*
FROM ABC
WHERE b2x <> ax;

How to overcome SQLite View limitation

Given that in SQLite, Views:
are read-only
cannot be UPDATEd,
the following is the situation:
There are 4 tables A, B, C and D and a View has to be created with data from all the four tables conditionally. Here's the pseudo-construct:
CREATE VIEW AS E SELECT A.A1, A.A2, A.A3, A.A4, B.B1, C.C1, C.C2, D.D1, D.D2 FROM A, B, C, D
WHERE A.X = 'SOME STRING' AND
A.FK = C.PK AND
A.Y = B.Z AND
D.G = A.PK AND
D.H = 'SOME STRING'
The requirement is that, irrespective of no matches in D, the remaining matches should get populated, (with 0 values in the view E for the columns from D). Needless to say, the above construct works if there are matching D rows, but obviously returns an empty view if there are no D matches.
How can the CASE statement or SELECT sub-queries (or an altogether different construct, like an INSTEAD OF trigger) deliver this requirement?
Greatly appreciate if the database experts could publish the exact construct(s). Many, many thanks in advance!
First, use explicit joins:
SELECT A.A1, A.A2, A.A3, A.A4, B.B1, C.C1, C.C2, D.D1, D.D2
FROM A
JOIN B ON A.Y = B.Z
JOIN C ON A.FK = C.PK
JOIN D ON D.G = A.PK
WHERE A.X = 'SOME STRING'
AND D.H = 'SOME STRING';
Then you can use an outer join when you want to keep rows without a match:
FROM A
JOIN B ON A.Y = B.Z
JOIN C ON A.FK = C.PK
LEFT JOIN D ON D.G = A.PK AND D.H = 'SOME STRING'
WHERE A.X = 'SOME STRING';
(The D.H comparison must be part of the join condition because D.H is NULL for missing rows, and the comparison would fail in the WHERE clause.)

INNER JOIN where **every** row must match the WHERE clause?

Here's a simplified example of what I'm trying to do. I have two tables, A and B.
A B
----- -----
id id
name a_id
value
I want to select only the rows from A where ALL the values of the rows from B match a where clause. Something like:
SELECT * from A INNER JOIN B on B.a_id = A.id WHERE B.value > 2
The problem with the above query is that if ANY row from B has a value > 2 I'll get the corresponding row from A, and I only want the row from A if
1.) ALL the rows in B for B.a_id = A.id match the WHERE, OR
2.) There are no rows in B that reference A
B is basically a table of filters.
SELECT *
FROM a
WHERE NOT EXISTS
(
SELECT NULL
FROM b
WHERE b.a_id = a.a_id
AND (b.value <= 2 OR b.value IS NULL)
)
This should solve your problem:
SELECT *
FROM a
WHERE NOT EXISTS (SELECT *
FROM b
WHERE b.a_id = a.id
AND b.value <= 2)
Here is the way in which this is obtained.
Suppose that we have available a universal quantifier (parallel to EXISTS, the existential quantifier), with a syntax like:
FORALL table WHERE condition1 : condition2
(to be read: FORALL the elements of table that satisfy the condition1, then condition2 is true)
So you could write your query in this way:
SELECT *
FROM a
WHERE FORALL b WHERE b.a_id = a.id : b.value > 2
(Note that forall is true even when no element in b exists with a value of a.id)
Then we can transform the universal quantifier in the existential one, with a double negation, as usual:
SELECT *
FROM a
WHERE NOT EXISTS b WHERE b.a_id = a.id : NOT (b.value > 2)
In plain SQL this can be written as:
SELECT *
FROM a
WHERE NOT EXISTS (SELECT *
FROM b
WHERE b.a_id = a.id
AND (b.value > 2) IS NOT TRUE)
This technique is very handy in case of universal quantification.
Answering this question (which it seems you actually meant to ask):
Return all rows from A, where all rows in B with B.a_id = A.id also pass the test B.value > 2.
Which is equivalent to:
Return all rows from A, where no row in B with B.a_id = A.id fails the test B.value > 2.
SELECT a.* -- "rows from A" (so don't include other columns)
FROM a
LEFT JOIN b ON b.a_id = a.id
AND (b.value > 2) IS NOT TRUE -- safe inversion of logic
WHERE b.a_id IS NULL;
When inverting a WHERE condition carefully consider NULL. IS NOT TRUE is the simple and safe way to perfectly invert a WHERE condition. The alternative would be (b.value <= 2 OR b.value IS NULL) which is longer but may be faster (easier to support with index).
Select rows which are not present in other table
Try this
SELECT * FROM A
LEFT JOIN B ON B.a_id = A.id
WHERE B.value > 2 OR B.a_id IS NULL
SELECT * FROM A LEFT JOIN B ON b.a_id = a.id
WHERE B.a_id IS NULL OR NOT EXIST (
SELECT 1
FROM b
WHERE b.value <= 2)
SELECT a.is, a.name, c.id as B_id, c.value from A
INNER JOIN (Select b.id, b.a_id, b.value from B WHERE B.value > 2) C
on C.a_id = A.id
Note it is a poor practice to use select *. You shoudl only specify fields you need. IN this case, I might possibly remove the b.Id refernces becasue they are probably not needed. If you have a join there is a 100% chance you are wasting resouces sending data you don't need becasue the join fields will be repeated. That is why I did nto include a_id in the final result set.
If you prefer not to use EXISTS, you can use an outer join.
SELECT A.*
FROM
A
LEFT JOIN B ON
B.a_id = A.id
AND B.value <= 2 -- note: condition reversed!!
WHERE B.id IS NULL
This works by searching for the existence of a failing record in B. If it finds one, then the join will match, and the final WHERE clause will exclude that record.

full outer join 3 tables with matching index in Postgre SQL

I have an SQL query
SELECT * FROM A FULL OUTER JOIN B ON A.z = B.z WHERE A.z = 1 OR B.z = 1
where A.z and B.z are primary keys.
The purpose is to do a full outer join on two tables whilst their primary keys match a given value - so that only one row is returned.
But I got confused on how to extend it to 3 or more tables. The restriction that their primary keys match a given index so that only one row is return in total remains. How do you do it?
First, note that in the provided query, the FULL OUTER JOIN that you request could be rewritten as:
SELECT *
FROM (SELECT * FROM A WHERE z = 1) A
FULL OUTER JOIN (SELECT * FROM B WHERE z = 1) B ON A.z = B.z
which makes (IMO) more clear what the data sources are and what the join condition is. For a moment, with your WHERE condition, I had the feeling that you wanted actually an INNER JOIN.
With this you can extend more easily probably:
SELECT *
FROM (SELECT * FROM A WHERE z = 1) A
FULL OUTER JOIN (SELECT * FROM B WHERE z = 1) B ON A.z = B.z
FULL OUTER JOIN (SELECT * FROM C WHERE z = 1) C ON COALESCE(A.z,B.z) = C.z
FULL OUTER JOIN (SELECT * FROM D WHERE z = 1) D ON COALESCE(A.z,B.z,C.z) = D.z