Conditional Left Join SQL - sql

table A
----------------------------
NAME | CODE | BRANCH
----------------------------
bob | PL | B
david | AA | B
susan | PL | C
joe | AB | C
alfred | PL | B
table B
----------------------------
CODE | DESCRIPTION
----------------------------
PL | code 1
PB | code 2
PC | code 3
table C
----------------------------
CODE | DESCRIPTION
----------------------------
AA | code 4
AB | code 5
AC | code 6
Is there any way to join table A, B and C. without join all the table?
select A.*, COALESCE(B.DESCRIPTION, C.DESCRIPTION) AS DESCRIPTION from A
left join B on A.CODE = B.CODE
left join C on A.CODE = C.CODE
In my real case there will be more than 10 to join with the same column.
So I need conditional left join, something like this
SELECT A* , DESCRIPTION
FROM A LEFT JOIN (
CASE
WHEN A.CODE = 'B' THEN SELECT * FROM B
WHEN A.CODE = 'C' THEN SELECT * FROM C
END
) BC ON A.CODE = BC.CODE

You cannot use CASE to implement flow control. In SQL CASE is an expression that returns a single value.
You can instead use the following query:
select A.*,
CASE A.BRANCH
WHEN 'B' THEN B.DESCRIPTION
WHEN 'C' THEN C.DESCRIPTION
END AS DESCRIPTION
from A
left join B on A.CODE = B.CODE AND A.BRANCH = 'B'
left join C on A.CODE = C.CODE AND A.BRANCH = 'C'

You could use this to generate queries. Then you write a PL/SQL block to loop through all these queries and execute dynamically to give you separate results.
SELECT 'SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN '
|| CASE WHEN A.BRANCH = 'B' THEN 'TABLEB B' END
|| CASE WHEN A.BRANCH = 'C' THEN 'TABLEC C' END
|| ' ON '
|| 'A.CODE = '
|| CASE WHEN A.BRANCH = 'B' THEN 'B.CODE' END
|| CASE WHEN A.BRANCH = 'C' THEN 'C.CODE' END
v_query
FROM TableA A;
Output
V_QUERY
--------------------------------------------------------------------------------
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE

Related

How to manage COUNT, GROUP BY and HAVING?

I'm not experimented in SQL and I try maybe to do something which is impossible. SQL give this sensation it is possible to do it on only 1 request, but maybe not...
I have 4 joined tables A -> S -> T -> F.
A simple request give me these data :
select a.id, s.id, t.type, f.name
from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C';
a.id | s.id | t.type | f.name
-----------------------------
1 | 1 | E | C
1 | 2 | R | C
2 | 3 | E | C
3 | 4 | R | C
I would like to find ALL A ids which have multiple S rows associated.
And I would like to find ALL a ids which have only one S row of T type = R.
For the first one, I made this SQL query :
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) > 1;
But now, for the second query I don't understand how to filter on t.type and count.
I try this request but the response is not good (a.id = 1 is returned)
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) = 1 and t.type = 'R';
Any idea ?
Thank you
I would like to find ALL a ids which have only one S row of T type = R.
Does this do what you want?
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(*) = 1 and min(t.type) = 'R'
This gives you groups that have only one row, whose type is "R".

Select count of rows in two other tables

I have 3 tables. The main one in which I want to retrieve some information and two others for row count only.
I used a request like this :
SELECT A.*,
COUNT(B.id) AS b_count
FROM A
LEFT JOIN B on B.a_id = A.id
WHERE A.id > 50 AND B.ID < 100
GROUP BY A.id
from Gerry Shaw's comment here. It works perfectly but only for one table.
Now I need to add the row count for the third (C) table. I tried
SELECT A.*,
COUNT(B.id) AS b_count
COUNT(C.id) AS c_count
FROM A
LEFT JOIN B on B.a_id = A.id
LEFT JOIN C on C.a_id = A.id
GROUP BY A.id
but, because of the two left joins, my b_count and my c_count are false and equal to each other. In fact my actual b_count and c_count are equal to real_b_count*real_c_count. Any idea of how I could fix this without adding a lot of complexity/subqueries ?
Data sample as requested:
Table A (primary key : id)
id | data1 | data2
------+-------+-------
1 | 0,45 | 0,79
----------------------
2 | -2,24 | -0,25
----------------------
3 | 1,69 | 1,23
Table B (primary key : (a_id,fruit))
a_id | fruit
------+-------
1 | apple
------+-------
1 | banana
--------------
2 | apple
Table C (primary key : (a_id,color))
a_id | color
------+-------
2 | blue
------+-------
2 | purple
--------------
3 | blue
expected result:
id | data1 | data2 | b_count | c_count
------+-------+-------+---------+--------
1 | 0,45 | 0,79 | 2 | 0
----------------------+---------+--------
2 | -2,24 | -0,25 | 1 | 2
----------------------+---------+--------
3 | 1,69 | 1,23 | 0 | 1
There are two possible solutions. One is using subqueries behind SELECT
SELECT A.*,
(
SELECT COUNT(B.id) FROM B WHERE B.a_id = A.id AND B.ID < 100
) AS b_count,
(
SELECT COUNT(C.id) FROM C WHERE C.a_id = A.id
) AS c_count
FROM A
WHERE A.id > 50
the second are two SQL queries joined together
SELECT t1.*, t2.c_count
FROM
(
SELECT A.*,
COUNT(B.id) AS b_count
FROM A
LEFT JOIN B on B.a_id = A.id
WHERE A.id > 50 AND B.ID < 100
GROUP BY A.id
) t1
JOIN
(
SELECT A.*,
COUNT(C.id) AS c_count
FROM A
LEFT JOIN C on C.a_id = A.id
WHERE A.id > 50
GROUP BY A.id
) t2 ON t1.id = t2.id
I prefer the second syntax since it clearly shows the optimizer that you are interested in GROUP BY, however, the query plans are usually the same.
If tables B & C also have their own key fields, then you can use COUNT DISTINCT on the primary key rather than foreign key. That gets around the multi-line problem you see on link to several tables. If you can post the table structures then we can advise further.
Try something like this
SELECT A.*,
(SELECT COUNT(B.id) FROM B WHERE B.a_id = A.id) AS b_count,
(SELECT COUNT(C.id) FROM C WHERE C.a_id = A.id) AS c_count
FROM A
That is the easier way I can think:
Create table #a (id int, data1 float, data2 float)
Create table #b (id int, fruit varchar(50))
Create table #c (id int, color varchar(50))
Insert into #a
SELECT 1, 0.45, 0.79
UNION ALL SELECT 2, -2.24, -0.25
UNION ALL SELECT 3, 1.69, 1.23
Insert into #b
SELECT 1, 'apple'
UNION ALL SELECT 1, 'banana'
UNION ALL SELECT 2, 'orange'
Insert into #c
SELECT 2, 'blue'
UNION ALL SELECT 2, 'purple'
UNION ALL SELECT 3, 'orange'
SELECT #a.*,
(SELECT COUNT(#b.id) FROM #b where #b.id = #a.id) AS b_count,
(SELECT COUNT(#c.id) FROM #c where #c.id = #a.id) AS b_count
FROM #a
ORDER BY #a.id
Result:
id data1 data2 b_count b_count
1 0,45 0,79 2 0
2 -2,24 -0,25 1 2
3 1,69 1,23 0 1
If table b and c have unique id, you can try this:
SELECT A.*,
COUNT(distinct B.fruit) AS b_count,
COUNT(distinct C.color) AS c_count
FROM A
LEFT JOIN B on B.a_id = A.id
LEFT JOIN C on C.a_id = A.id
GROUP BY A.id
See SQLFiddle MySQL demo.

Sum query with reference to different coloumn for each row?

Is it possible to carry out a sum query where the row for each part of the sum is determine from a join?
For example if I have tables
table A
id | value
1 | 10
2 | 15
3 | 10
And
table b
id | b | c
1 | 2 | 3
2 | 1 | 2
Is it possible to do a SUM(tableA.value * tableB.<specific_column>) Where either the SUM is carried out directly as a join or the join table is prequired from a specification, for sake of argument, a string "bcb"?
Edit:
The end result I'm hoping to achieve would be equivalent to this:
SUM(SELECT value * b FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1,
SELECT value * c FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 2,
SELECT value * b FROM tableA a JOIN tableB b ON b.id = 2 WHERE a.id = 3);
I guess there's two parts to this: A simple join of A and selected values from B such that B is reduced to a single selectValue column.
Thanks.
As asked in comment it should be better to show us what output you really wants, but as I understand you wants to do something like :
SELECT id, SUM(a.value * b.b)
FROM a JOIN b USING(id)
GROUP BY id;
It's what you want ? I do not really understand you "bcb" point ...
Not because in your comment you said SUM(value, value, value) and I think you want to add those values so, I'll do something like this :
WITH
sum1 AS (SELECT value * b AS res
FROM tableA a
JOIN tableB b ON b.id = 1
WHERE a.id = 1),
sum2 AS (SELECT value * c AS res
FROM tableA a
JOIN tableB b ON b.id = 1
WHERE a.id = 2),
sum3 AS (SELECT value * b AS res
FROM tableA a
JOIN tableB b ON b.id = 2
WHERE a.id = 3)
SELECT SUM(sum1.res + sum2.res + sum3.res)
FROM sum1, sum2, sum3;
I've tested #Hervé Piedvache's code and it returns NULL, because SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1 has two rows. A work around would be:
SELECT SUM(val) FROM
(SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1
UNION
SELECT value * c AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 2
UNION
SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 2 WHERE a.id = 3) data;

Find rows where one column value match and other does not

I have two tables A and B
Table A
CODE TYPE
A 1
A 2
A 3
B 1
C 1
C 2
Table B
CODE TYPE
A 1
A 2
A 4
B 2
C 1
C 3
I want to return rows where CODE is in both tables but TYPE is not and also CODE has more than one TYPE in both tables so my result would be
CODE TYPE SOURCE
A 3 Table A
A 4 Table B
C 2 Table A
C 3 Table B
Any help with this?
I think this covers both of your conditions.
select code, coalesce(typeA, typeB) as type, src
from
(
select
coalesce(a.code, b.code) as code,
a.type as typeA,
b.type as typeB,
case when b.type is null then 'A' when a.type is null then 'B' end as src,
count(a.code) over (partition by coalesce(a.code, b.code)) as countA,
count(b.code) over (partition by coalesce(a.code, b.code)) as countB
from
A a full outer join B b
on b.code = a.code and b.type = a.type
) T
where
countA >= 2 and countB >= 2
and (typeA is null or typeB is null)
You can use a full join to see if the code matches and check if the type is null on either of the tables.
select coalesce(a.code,b.code) code, coalesce(a.type,b.type) type,
case when b.type is null then 'A' when a.type is null then 'B' end src
from a
full join b on a.code = b.code and a.type = b.type
where a.type is null or b.type is null
To limit the results to codes which have more than one type, use
select x.code, coalesce(a.type,b.type) type,
case when b.type is null then 'Table A' when a.type is null then 'Table B' end src
from a
full join b on a.code = b.code and a.type = b.type
join (select a.code from a join b on a.code = b.code
group by a.code having count(*) > 1) x on x.code = a.code or x.code = b.code
where a.type is null or b.type is null
order by 1
Using union
with tu as (
select CODE, TYPE, src='Table A'
from TableA
union all
select CODE, TYPE, src='Table B'
from TableB
)
select CODE, TYPE, max(src)
from tu t1
where exists (select 1 from tu t2 where t2.CODE=t1.CODE and t2.src=t1.src and t1.TYPE <> t2.TYPE)
group by CODE, TYPE
having count(*)=1
order by CODE, TYPE

SQL Server, selecting from 2 columns from different tables

I have these columns from 2 tables
Table1 Table2
Code ID Code ID
A 1 A 1
B 1 B 1
C 1 C 1
D 1
E 1
My query:
Select
a.id, a.code, b.code
from
Table1 a, Table2 b
where
a.id = '1' and a.id = b.id
What I expected
ID code code
1 A A
1 B B
1 C C
1 D NULL
1 E NULL
What I got
ID code code
1 A A
1 B A
1 C A
1 D A
1 E A
1 A B
1 B B
1 C B
....
Any ideas? distinct didn't help
Thanks
Well, all the ID's in both tables are 1, so by joining on ID you'll get the cartesian product of both tables.
Instead, you'll need to do a left outer join based on Table1.Code:
Select a.id, a.code, b.code
from Table1 a LEFT OUTER JOIN Table2 b
on a.code = b.code
where a.id = '1';
You need to do a LEFT OUTER JOIN instead of a Cartesian Product
SELECT a.Id, a.Code, b.Code FROM Table1 a
LEFT OUTER JOIN Table2 b ON a.Code = b.Code
WHERE a.Id = '1'
A LEFT OUTER JOIN returns all rows from the left-hand side of the join (in this case Table 1) regardless of whether there is a matching record in the table on the right-hand side of the join (in this case Table 2). Where there is no match a NULL is returned for b.Code as per your requirements.
Reference OUTER JOINS