Right join without intersection and another condition - sql

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;

Related

Case statement in JOIN slows the performance of the query

Case statement in JOIN slows the performance of the query, is there any way to improve to make the query performance faster
SELECT A.TestColumn
FROM A with(nolock)
INNER JOIN B with(nolock)
ON A.ID = B.ID
and A.InvoiceNo = CASE WHEN B.InvoiceType=2
THEN B.MainInvoiceNo
ELSE B.InvoiceNo END
Try :
1) with the valued CASE
SELECT A.TestColumn
FROM A
INNER JOIN B
ON A.ID = B.ID and
A.InvoiceNo = CASE B.InvoiceType
WHEN 2 THEN B.MainInvoiceNo
ELSE B.InvoiceNo
END
2) with a UNION ALL
SELECT A.TestColumn
FROM A
INNER JOIN B
ON A.ID = B.ID and
A.InvoiceNo = B.MainInvoiceNo
WHERE B.InvoiceType=2
UNION ALL
SELECT A.TestColumn
FROM A
INNER JOIN B
ON A.ID = B.ID and
A.InvoiceNo = B.InvoiceNo
WHERE B.InvoiceType<>2
probably using union will be the best performant :
SELECT A.TestColumn
FROM A
INNER JOIN B
ON A.ID = B.ID
and B.InvoiceType=2
and A.InvoiceNo = B.MainInvoiceNo
union all
SELECT A.TestColumn
FROM A with(nolock)
INNER JOIN B with(nolock)
ON A.ID = B.ID
and B.InvoiceType <> 2
and A.InvoiceNo = B.InvoiceNo
but also you can try using OR :
SELECT A.TestColumn
FROM A with(nolock)
INNER JOIN B with(nolock)
ON A.ID = B.ID
and
(
(B.InvoiceType= 2 and A.InvoiceNo = B.MainInvoiceNo)
or
(B.InvoiceType<> 2 and A.InvoiceNo = B.InvoiceNo)
)
I would suggest two LEFT JOINs and a WHERE
SELECT A.TestColumn
FROM A LEFT JOIN
B B1
ON A.ID = B1.ID AND
B1.InvoiceType = 2
A.InvoiceNo = B.MainInvoiceNo LEFT JOIN
B B2
ON A.ID = B2.ID AND
A.InvoiceNo = B2.InvoiceNo
WHERE B1.ID IS NOT NULL OR B2.ID IS NOT NULL;
SQL Server should find it much easier to implement an efficient execution plan. I would recommend indexes on B(ID, MainInvoiceNo, InvoiceType) and B(ID, InvoiceNo).
Using a case expression as part of the join criteria will be forcing SQL server to scan your index (probably a table scan if id is a clustered index).
Depending on the cardinality of your data and expected rows for each join condition, using two separate queries with the results unioned can yield better performance.
This is because for each individual query the optimiser can likely utilise an index seek resulting in far few logical reads overall.
Also since you are only returning data from A you could try using exists
Select A.TestColumn
from A
where exists (
select * from B where B.Id = A.Id and B.InvoiceType=2 and A.InvoiceNo=B.MainInvoiceNo
)
or exists (
select * from B where B.Id=A.Id and B.InvoiceNo = A.InvoiceNo
)

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.

How to find if LEFT JOIN joined an actual row, or placeholder NULL values?

Suppose I issue a query like this:
SELECT a.x, b.y FROM a LEFT JOIN b ON b.id = a.id
I also want to know if a row from b is actually joined or there are just placeholder NULL values supplied by LEFTJOIN. I guess I can determine it comparing values of a.id and b.id in the result, but is there a way to do this in the query itself?
I.e. I'd want something like
SELECT a.x, b.y, b_is_actually_joined FROM a LEFT JOIN b ON b.id = a.id
where values in the column b_is_actually_joined are 1 or 0 (for example).
Just check for NULL b.id:
SELECT a.x, b.y, b.id IS NOT NULL AS b_is_actually_joined
FROM a
LEFT JOIN b ON b.id = a.id
For Oracle SQL you can use NVL2 function:
SELECT a.id, b.*, NVL2(b.id, 1, 0) AS b_is_actually_joined
FROM a
LEFT JOIN b ON b.id = a.id
SQL Fiddle
This should work in Ms Sql Server:
select CAST((coalesce(b.id, 0)) as bit) as b_is_actually_joined FROM a
LEFT JOIN b ON b.id = a.id
I am unaware of a standard SQL solution for this

Simulate a left join without using "left join"

I need to simulate the left join effect without using the "left join" key.
I have two tables, A and B, both with id and name columns. I would like to select all the dbids on both tables, where the name in A equals the name in B.
I use this to make a synchronization, so at the beginning B is empty (so I will have couples with id from A with a value and id from B is null). Later I will have a mix of couples with value - value and value - null.
Normally it would be:
SELECT A.id, B.id
FROM A left join B
ON A.name = B.name
The problem is that I can't use the left join and wanted to know if/how it is possible to do the same thing.
you can use this approach, but you must be sure that the inner select only returns one row.
SELECT A.id,
(select B.id from B where A.name = B.name) as B_ID
FROM A
Just reverse the tables and use a right join instead.
SELECT A.id,
B.id
FROM B
RIGHT JOIN A
ON A.name = B.name
I'm not familiar with java/jpa. Using pure SQL, here's one approach:
SELECT A.id AS A_id, B.id AS B_id
FROM A INNER JOIN B
ON A.name = B.name
UNION
SELECT id AS A_id, NULL AS B_id
FROM A
WHERE name NOT IN ( SELECT name FROM B );
In SQL Server, for example, You can use the *= operator to make a left join:
select A.id, B.id
from A, B
where A.name *= B.name
Other databases might have a slightly different syntax, if such an operator exists at all.
This is the old syntax, used before the join keyword was introduced. You should of course use the join keyword instead if possible. The old syntax might not even work in newer versions of the database.
I can only think of two ways that haven't been given so far. My last three ideas have already been given (boohoo) but I put them here for posterity. I DID think of them without cheating. :-p
Calculate whether B has a match, then provide an extra UNIONed row for the B set to supply the NULL when there is no match.
SELECT A.Id, A.Something, B.Id, B.Whatever, B.SomethingElse
FROM
(
SELECT
A.*,
CASE
WHEN EXISTS (SELECT * FROM B WHERE A.Id = B.Id) THEN 1
ELSE 0
END Which
FROM A
) A
INNER JOIN (
SELECT 1 Which, B.* FROM B
UNION ALL SELECT 0, B* FROM B WHERE 1 = 0
) B ON A.Which = B.Which
AND (
A.Which = 0
OR (
A.Which = 1
AND A.Id = b.Id
)
)
A slightly different take on that same query:
SELECT A.Id, B.Id
FROM
(
SELECT
A.*,
CASE
WHEN EXISTS (SELECT * FROM B WHERE A.Id = B.Id) THEN A.Id
ELSE -1 // a value that does not exist in B
END PseudoId
FROM A
) A
INNER JOIN (
SELECT B.Id PseudoId, B.Id FROM B
UNION ALL SELECT -1, NULL
) B ON A.Which = B.Which
AND A.PseudoId = B.PseudoId
Only for SQL Server specifically. I know, it's really a left join, but it doesn't SAY LEFT in there!
SELECT A.Id, B.Id
FROM
A
OUTER APPLY (
SELECT *
FROM B
WHERE A.Id = B.Id
) B
Get the inner join then UNION the outer join:
SELECT A.Id, B.Id
FROM
A
INNER JOIN B ON A.name = B.name
UNION ALL
SELECT A.Id, NULL
FROM A
WHERE NOT EXISTS (
SELECT *
FROM B
WHERE A.Id = B.Id
)
Use RIGHT JOIN. That's not a LEFT JOIN!
SELECT A.Id, B.Id
FROM
B
RIGHT JOIN A ON B.name = A.name
Just select the B value in a subquery expression (let's hope there's only one B per A). Multiple columns from B can be their own expressions (YUCKO!):
SELECT A.Id, (SELECT TOP 1 B.Id FROM B WHERE A.Id = B.Id) Bid
FROM A
Anyone using Oracle may need some FROM DUAL clauses in any SELECTs that have no FROM.
You could use subqueries, something like:
select a.id
, nvl((select b.id from b where b.name = a.name), "") as bId
from a
you can use oracle + operator for left join :-
SELECT A.id, B.id
FROM A , B
ON A.name = B.name (+)
Find link :-
Oracle "(+)" Operator
SELECT A.id, B.id
FROM A full outer join B
ON A.name = B.name
where A.name is not null
I'm not sure if you just can't use a LEFT JOIN or if you're restricted from using any JOINS at all. But as far as I understand your requirements, an INNER JOIN should work:
SELECT A.id, B.id
FROM A
INNER JOIN B ON A.name = B.name
Simulating left join using pure simple sql:
SELECT A.name
FROM A
where (select count(B.name) from B where A.id = B.id)<1;
In left join there are no lines in B referring A so 0 names in B will refer to the lines in A that dont have a match
+ or A.id = B.id in where clause to simulate the inner join

select row from table and substitute a field with one from another column if it exists

I'm trying construct a PostgreSQL query that does the following but so far my efforts have been in vain.
Problem:
There are two tables: A and B. I'd like to select all columns from table A (having columns: id, name, description) and substitute the "A.name" column with the value of the column "B.title" from table B (having columns: id, table_A_id title, langcode) where B.table_A_id is 5 and B.langcode is "nl" (if there are any rows).
My attempts:
SELECT A.name,
case when exists(select title from B where table_A_id = 5 and langcode= 'nl')
then B.title
else A.name
END
FROM A, B
WHERE A.id = 5 and B.table_A_id = 5 and B.langcode = 'nl'
-- second try:
SELECT COALESCE(B.title, A.name) as name
from A, B
where A.id = 5 and B.table_A_id = 5 and exists(select title from B where table_A_id = 5 and langcode= 'nl')
I've tried using a CASE and COALESCE() but failed due to my inexperience with both concepts.
Thanks in advance.
araqnid's is the answer you are looking for, I bet.
But if you want to enforce that no more than one row is returned for each original matching A row, you might prefer to do a subselect instead of a LEFT JOIN. For example:
SELECT A.id, COALESCE(
( SELECT max(B.title) FROM B WHERE
langcode = 'nl' AND B.table_a_id = A.id), A.name ) as name
FROM A
WHERE A.id = 5
I use "max" here to select an arbitrary value, in the event there is more than one. You can use "min" or whatever you consider appropiate in your case.
Perhaps this is more easy to understand than the LEFT JOIN, but (apart from the two being not exactly equivalent) a JOIN will perform better than N subselects (much better is N is large).
Anyway, from a learning point of view, it's good to understand both.
select A.id, coalesce(B.title, A.name)
from TableA AS A
left join (select table_a_id, title from TableB where langcode = 'nl') AS B
on B.table_a_id = A.id
WHERE A.id = 5
Ok, I'm not sure how your tables have to be joined, but something like this should do the job:
SELECT yourcolumnlist,
CASE WHEN A.name IS NULL THEN B.title ELSE A.name END
FROM TableA AS A
INNER JOIN TableB AS B
ON A.id = B.table_A_id
WHERE B.table_A_id = 5
AND B.langcode = 'nl'
Another way to do it would be to use the COALESCE() function:
SELECT yourcolumnlist,
COALESCE(A.name, B.title)
FROM TableA AS A
INNER JOIN TableB AS B
ON A.id = B.table_A_id
WHERE B.table_A_id = 5
AND B.langcode = 'nl'
Try this
select A.id,B.title,A.description from TableA as A inner join tableB as B
on A.id=B.id where B.table_A_id = 5 and B.langcode ='nl'