Subqueries vs Multi Table Join - sql

I've 3 tables A, B, C. I want to list the intersection count.
Way 1:-
select count(id) from A a join B b on a.id = b.id join C c on B.id = C.id;
Result Count - X
Way 2:-
SELECT count(id) FROM A WHERE id IN (SELECT id FROM B WHERE id IN (SELECT id FROM C));
Result Count - Y
The result count in each of the query is different. What exactly is wrong?

A JOIN can multiply the number of rows as well as filtering out rows.
In this case, the second count should be the correct one because nothing is double counted -- assuming id is unique in a. If not, it needs count(distinct a.id).
The equivalent using JOIN would use COUNT(DISTINCT):
select count(distinct a.id)
from A a join
B b
on a.id = b.id join
C c
on B.id = C.id;
I mention this for completeness but do not recommend this approach. Multiplying the number of rows just to remove them using distinct is inefficient.
In many databases, the most efficient method might be:
select count(*)
from a
where exists (select 1 from b where b.id = a.id) and
exists (select 1 from c where c.id = a.id);
Note: This assumes there are indexes on the id columns and that id is unique in a.

Related

Query left join without all the right rows from B table

I have 2 tables, A and B.
I need all columns from A + 1 column from B in my select.
Unfortunately, B has multiples rows(all identicals) for 1 row in A
on the join condition.
I tried but I can't isolate one row in A for one row in B with left join for example while keeping my select.
How can I do this query ? Query in ORACLE SQL
Thanks in advance.
This is a good use for outer apply. The structure of the query looks like this:
select a.*, b.col
from a outer apply
(select top 1 b.col
from b
where b.? = a.?
) b;
Normally, you would only use top 1 with order by. In this case, it doesn't seem to make a difference which row you choose.
You can group by on all columns from A, and then use an aggregate (like max or min) to pick any of the identical B values:
select a.*
, b.min_col1
from TableA a
left join
(
select a_id
, min(col1) as min_col1
from TableB
group by
a_id
) b
on b.a_id = a.id

Run a query on Parent to show records of only one child table

I've 3 tables, viz A, B & c.
B & C has forign key of A.
Now I want to run a query on A, in such a way, that only records of B are returned.
That is, I want to exclude all the results of C and show only results of B, when a query is executed on all the records of A.
Hope, I've the question makes sense.
If you want to return all records of A and any matching records from B then something a left outer join is appropriate:
SELECT a.*, b.*
FROM a, b
WHERE a.id = b.id
This will return each record from A and populate values from B where there is a match. This will also return multiple rows for records that occur in A if there are multiple rows in B that match.
Just because there is a foreign key in C that references something in table A, it won't be returned unless you use it in your query.
If you just want to return all records from B when the foreign key appears in A then maybe you want:
SELECT *
FROM B
WHERE B.id in (SELECT id FROM A)
AND B.id not in (SELECT id FROM C)
or
SELECT *
FROM B
WHERE EXISTS (SELECT 1 FROM A JOIN B on A.id = B.id)
AND NOT EXISTS (SELECT 1 FROM C JOIN B on C.id = B.id)
All these assume that id is the key which is common.
Is this what you want?
select a.*
from a
where exists (select 1 from b where b.aid = a.aid) and
not exists (select 1 from c where c.aid = c.aid);

Compare table one value with two different tables in postgresql

There are three tables, A,B and C having common columns(name and number)
Table A have 10 records(say x) which can be only from table B(say, y) and table C(say, z) (like, x = y+z).
In table A, there are some records whose value is 0 (zero)
I need to compare those zero value based records using column = name, with other two tables.
And check the column "number" for the same "name" is also zero (0) in table B and table C?
I tried to write the below sample query to test on my small set of 3 tables data- but for some reasons I am not able to get all the 10 records as a result?
SELECT a.name,a.number as A_number, b.number as B_number, c.number as C_number
from A a, B b, C c
WHERE a.name = b.name
The above query gives me data as follows in the sqlfiddle-
http://sqlfiddle.com/#!2/57f86/1
In the above data- theres no record name="hello"
Can anyone please correct me where I am going wrong? and how to get the exact result? I need all the records from Table A. I know if I use left join it will populate all the left table data even if no match.
Possibilities: Table A having records, some may be present in table B
and some in table C, but not on both.
I think this is what you want:
SELECT a.*, b.number as bnumber, c.number as cnumber
from a left outer join
b
on a.name = b.name left outer join
c
on a.name = c.name
where a.number = 0;
By the way, here is a Postgres SQL Fiddle.
It's been over 20 years since the JOIN keyword was added to SQL. Use it:
select
a.name,
a.number as A_number,
b.number as B_number,
c.number as C_number
from A a
left join B b on a.name = b.name
left join C c on a.name = c.name
where a.number = 0
The key here is the use of left join, which allows all rows in table A to be returned, even if there are no matching rows in the other tables.
If you want to just display true/false if the number is zero in the other tables, do this:
select
a.name,
a.number as A_number,
(b.number = 0 and c.number = 0) as zero_elsewhere
from A a
left join B b on a.name = b.name
left join C c on a.name = c.name
where a.number = 0
When you wrote WHERE a.name = b.name, that restricted the records returned from table A to only those that also exist in table B. This is not equivalent to a left join. If you used only a WHERE statement you would need to do:
WHERE ((a.name = b.name) OR (b.name is NULL))
AND
((a.name = c.name) OR (c.name is NULL))
In the comments and other answers, they have been using LEFT JOIN which is easier to write and read. I suggest you adopt that style as it is widely accepted.

Is there alternative way to write this query?

I have tables A, B, C, where A represents items which can have zero or more sub-items stored in C. B table only has 2 foreign keys to connect A and C.
I have this sql query:
select * from A
where not exists (select * from B natural join C where B.id = A.id and C.value > 10);
Which says: "Give me every item from table A where all sub-items have value less than 10.
Is there a way to optimize this? And is there a way to write this not using exists operator?
There are three commonly used ways to test if a value is in one table but not another:
NOT EXISTS
NOT IN
LEFT JOIN ... WHERE ... IS NULL
You have already shown code for the first. Here is the second:
SELECT *
FROM A
WHERE id NOT IN (
SELECT b.id
FROM B
NATURAL JOIN C
WHERE C.value > 10
)
And with a left join:
SELECT *
FROM A
LEFT JOIN (
SELECT b.id
FROM B
NATURAL JOIN C
WHERE C.value > 10
) BC
ON A.id = BC.id
WHERE BC.id IS NULL
Depending on the database type and version, the three different methods can result in different query plans with different performance characteristics.

filter duplicates in SQL join

When using a SQL join, is it possible to keep only rows that have a single row for the left table?
For example:
select * from A, B where A.id = B.a_id;
a1 b1
a2 b1
a2 b2
In this case, I want to remove all except the first row, where a single row from A matched exactly 1 row from B.
I'm using MySQL.
This should work in MySQL:
select * from A, B where A.id = B.a_id GROUP BY A.id HAVING COUNT(*) = 1;
For those of you not using MySQL, you will need to use aggregate functions (like min() or max()) on all the columns (except A.id) so your database engine doesn't complain.
It helps if you specify the keys of your tables when asking a question such as this. It isn't obvious from your example what the key of B might be (assuming it has one).
Here's a possible solution assuming that ID is a candidate key of table B.
SELECT *
FROM A, B
WHERE B.id =
(SELECT MIN(B.id)
FROM B
WHERE A.id = B.a_id);
First, I would recommend using the JOIN syntax instead of the outdated syntax of separating tables by commas. Second, if A.id is the primary key of the table A, then you need only inspect table B for duplicates:
Select ...
From A
Join B
On B.a_id = A.id
Where Exists (
Select 1
From B B2
Where B2.a_id = A.id
Having Count(*) = 1
)
This avoids the cost of counting matching rows, which can be expensive for large tables.
As usual, when comparing various possible solutions, benchmarking / comparing the execution plans is suggested.
select
*
from
A
join B on A.id = B.a_id
where
not exists (
select
1
from
B B2
where
A.id = b2.a_id
and b2.id != b.id
)