Fill a select with null when join isn't possible - sql

I'm trying to do a select in n tables and show a few columns of each, but sometimes I can't match some columns and instead of getting a line with "null" the entire line is omitted.
For example:
table_a
id
...
1
2
3
table_b
id
name
...
1
a1
...
2
b2
...
3
c3
...
table_c
name
...
a1
...
And then I do the following select:
select
a.id,
c.name
from
table_a a,
table_b b,
table_c
where
( 1 = 1 )
and a.id = b.id
and b.name = c.name
I'm geting:
id
name
...
1
a1
...
I'm looking for:
id
name
...
1
a1
...
2
null
...
3
null
...
How do I do that? I checked a few answers around including this one but I didn't get how to solve it.

You can use an OUTER JOIN:
SELECT a.id,
c.name
FROM table_a a
LEFT OUTER JOIN table_b b
ON (a.id = b.id)
LEFT OUTER JOIN table_c c
ON (b.name = c.name)
or, depending on precedence of the joins:
SELECT a.id,
c.name
FROM table_a a
LEFT OUTER JOIN (
table_b b
INNER JOIN table_c c
ON (b.name = c.name)
)
ON (a.id = b.id)
Which, for the sample data:
CREATE TABLE table_a (id) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL;
CREATE TABLE table_b (id, name) AS
SELECT 1, 'a1' FROM DUAL UNION ALL
SELECT 2, 'b1' FROM DUAL UNION ALL
SELECT 3, 'c1' FROM DUAL;
CREATE TABLE table_c (name) AS
SELECT 'a1' FROM DUAL;
Would both output:
ID
NAME
1
a1
2
null
3
null
fiddle

You should use a left join, not sure on oracle specifically but it would look something like:
select
a.id,
c.name
from
table_a a
LEFT JOIN table_b b ON (a.id = b.id)
LEFT JOIN table_c c ON (b.name = c.name)

Related

Adding values to a column in SQL(Snowflake) from another table

I have two tables A,B
Table A:
uid category
1 a
1 b
1 c
2 b
2 d
Table B:
category
d
e
Table A contains user id and category
Table B contains top 2 most categories selected by the user
How can I add categories from table B to category column in table A but only the distinct value.
Final result
uid category
1 a
1 b
1 c
1 d
1 e
2 b
2 d
2 e
It is possible to generate missing rows by perfroming CROSS JOIN of distinct UID from tableA and categories from tableB:
WITH cte AS (
SELECT A.UID, B.CATEGORY
FROM (SELECT DISTINCT UID FROM tableA) AS A
CROSS JOIN tableB AS B
)
SELECT A.UID, A.CATEGORY
FROM tableA AS A
UNION ALL
SELECT C.UID, C.CATEGORY
FROM cte AS c
WHERE (c.UID, c.category) NOT IN (SELECT A.UID, A.CATEGORY
FROM tableA AS A)
ORDER BY 1,2;
Sample input:
CREATE OR REPLACE TABLE tableA(uid INT, category TEXT)
AS
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 1,'c' UNION ALL
SELECT 2,'b' UNION ALL
SELECT 2,'d';
CREATE OR REPLACE TABLE tableB(category TEXT)
AS
SELECT 'd' UNION ALL SELECT 'e';
Output:
Let union take care of duplicates
select uid, category
from t1
union
select uid, category
from (select distinct uid from t1) t1 cross join t2
order by uid, category

Multiple Unioned Self-joins in BigQuery

I have a table with id, name and parent_id where parent_id is a parent hierarchy relating to id, see below.
id
name
parent_id
0
A
null
1
B
0
2
C
1
3
D
1
4
E
2
I'm trying to create a nicer looking table with each id and its parent_id, including multiple levels up in the hierarchy. I use UNION and self-join to accomplish this, but I have a feeling there should be a nicer way of querying it with BigQuery's Standard SQL.
In the query below I go two levels, but you can imagine I want to go 5-6 levels.
WITH T1 as (
select 0 as id, 'A' as name, null as parent_id union all
select 1 as id, 'B' as name, 0 as parent_id union all
select 2 as id, 'C' as name, 1 as parent_id union all
select 3 as id, 'D' as name, 1 as parent_id union all
select 4 as id, 'E' as name, 2 as parent_id
)
SELECT
a.id as id,
a.name as req_name,
FROM T1 as a
UNION ALL
SELECT
a.id as id,
b.name as req_name,
FROM T1 as a
JOIN T1 as b ON a.parent_id = b.id
UNION ALL
SELECT
a.id as id,
c.name as req_name,
FROM T1 as a
JOIN T1 as b on a.parent_id = b.id
JOIN T1 as c on b.parent_id = c.id
resulting in the table
id
req_name
0
A
1
B
2
C
3
D
4
E
2
A
3
A
4
B
1
A
2
B
3
B
4
C
I would be thankful for any insights!
BigQuery does not (yet) support recursive or hierarchical queries. So your approach is actually fine. You can condense it, if you like, using left joins:
with t as (
select 0 as id, 'A' as name, null as parent_id union all
select 1 as id, 'B' as name, 0 as parent_id union all
select 2 as id, 'C' as name, 1 as parent_id union all
select 3 as id, 'D' as name, 1 as parent_id union all
select 4 as id, 'E' as name, 2 as parent_id
)
select distinct id, t1.name
from t t1 left join
t t2
on t2.parent_id = t1.id left join
t t3
on t3.parent_id = t2.id cross join
unnest(array[t1.id, t2.id, t3.id]) id
where id is not null;
You still need explicit joins to the maximum depth of the data.
The other alternative is to use a looping construct, which is available in the scripting language.

SQL inner join if not found omit condition

SQL inner join if not found omit condition
Suppose I have a SQL query like:
SELECT A.* FROM Table_A A
INNER JOIN Table_B B ON A.Field1 = B.Field1 AND A.Field2 = B.Field2
WHERE A.Field3 > 10
What I'd like to achieve is, we first inner join by Field1/Field2, if no data found only join by Field1.
What's the proper way to this?
Table A:
Id Field1 Field2 Field3
1 a b 12
2 a c 13
3 e f 14
2 d c 15
Table B:
Id Field1 Field2
1 a b
2 e g
Result should be:
Id Field1 Field2 Field3
1 a b 12
3 e f 14
First part of the union is your original. Second part joins only on the first set of fields in cases where there is no match for both sets of fields.
SELECT * FROM Table_A A
INNER JOIN Table_B B ON A.Field1 = B.Field1 AND A.Field2 = B.Field2
WHERE A.Field3 > 10
UNION
SELECT * FROM Table_A A
INNER JOIN Table_B B ON A.Field1 = B.Field1
WHERE A.Field3 > 10
AND NOT EXISTS
(SELECT 1 FROM TABLE_B B2
WHERE A.Field1 = B2.Field1 AND A.Field2 = B2.Field2);
You can join on the same table twice, one with inner join and one with left join and put the conditional logic on the left join.
select distinct t1.*
from table1 t1
left join table2 t2 on t1.f2 = t2.f2
inner join table2 t3 on t1.f1 = t3.f1
You can use CASE in INNER JOIN. Just check this :
declare #a table (col1 int, col2 int, col3 int)
declare #b table (col1 int, col2 int, col3 int)
Insert into #a values(1,1,11),(2,2,15),(3,4,20),(4,5,9)
Insert into #b values(1,1,5),(2,2,2),(3,3,20),(4,5,20)
select A.* from #a A inner join #b B on
(CASE
WHEN A.col2 = B.col2 then 1
WHEN A.col2 <> B.col2 then 1
ELSE 1
END) = 1 and A.col1 = B.col1
where A.col3 > 10
You could try the following query:
SELECT ta.*
FROM #Table_A ta
INNER JOIN #Table_B tb ON ta.Field1 = tb.Field1
WHERE ta.Field3 > 10
AND
(
ta.Field2 = tb.Field2
OR
NOT EXISTS (
SELECT 1 FROM #Table_A ta2
INNER JOIN #Table_B tb2 ON ta2.Field1 = tb2.Field1 AND ta2.Field2 = tb2.Field2
WHERE ta2.Field1 = ta.Field1
)
)
Demo link: http://rextester.com/DKRG30176
;With Tab1(Id,Field1, Field2, Field3)
AS
(
SELECT 1,'a','b',12 Union all
SELECT 2,'a','c',13 Union all
SELECT 3,'e','f',14 Union all
SELECT 2,'d','c',15
)
,Tab2 (Id, Field1, Field2)
AS
(
SELECT 1,'a','b' Union ALL
SELECT 2,'e','g'
)
SELECT Id,Field1, Field2, Field3 From Tab1
EXCEPT
SELECT Id
,Field1
,Field2
,Field3
FROM (
SELECT I.Id
,i.Field1
,i.Field2
,i.Field3
,COUNT(i.Id) OVER (
PARTITION BY i.Id ,i.Field1, i.Field2 ORDER BY i.Id
) Seq
FROM Tab1 i
LEFT JOIN Tab2 o ON i.Id = o.Id
WHERE i.Field1 !=o.Field1 AND i.Field2 !=o.Field2
) Dt
WHERE Dt.Seq = 1 AND Dt.Field3 > 10
OutPut
Id Field1 Field2 Field3
---------------------------
1 a b 12
3 e f 14

Select-statement: different result in direct query and pl/sql

I don't get why this is not working.
There are two tables:
a) id | value b) id | value
---------- ------------
1 | 1 1 | Hello
2 | 2 2 | Bye
3 | 1
I am doing this query containing a left join:
select b.value
from a
left join b on a.value = b.id
where a.id = 2
The result is: 'Bye'. Which is correct.
But if I am using the same statement in a package with pl/sql it gets the wrong result:
select b.value into word
from a
left join b on a.value = b.id
where a.id = 2 and rownum <= 1
The result is: word = 'Hello' which is incorrect.
You get an exception without the ROWNUM clause inside your PL/SQL function, whereas you get only one result when running the query directly. That's a clear indicator that your PL/SQL procedure is not using the same tables as your adhoc query.
Please check:
do you run your adhoc query as the same user that owns the PL/SQL package?
do you use schema prefixes for your table names in your PL/SQL package?
is your package using invoker rights (i.e. does it contain AUTHID CURRENT_USER)? If yes, why?
First of all, remove "rownum <= 1" condition.
If you really need it, try this:
select value
into word
from (select b.value
from a
left join b on a.value = b.id
where a.id = 2)
where rownum <= 1;
To understand better what is going on, try to execute this:
with a as (select 1 id, 1 value from dual union all
select 2, 2 from dual union all
select 3, 1 from dual),
b as (select 1 id, 'Hello' value from dual union all
select 2, 'Bye' from dual)
select a.id aid, a.value avalue, b.id bid, b.value bvalue, rownum
from a left join b on a.value = b.id;
this:
with a as (select 1 id, 1 value from dual union all
select 2, 2 from dual union all
select 3, 1 from dual),
b as (select 1 id, 'Hello' value from dual union all
select 2, 'Bye' from dual)
select a.id aid, a.value avalue, b.id bid, b.value bvalue, rownum
from a left join b on a.value = b.id
where a.id = 2;
this:
with a as (select 1 id, 1 value from dual union all
select 2, 2 from dual union all
select 3, 1 from dual),
b as (select 1 id, 'Hello' value from dual union all
select 2, 'Bye' from dual)
select a.id aid, a.value avalue, b.id bid, b.value bvalue, rownum
from a left join b on a.value = b.id
where rownum = 1;
and this:
with a as (select 1 id, 1 value from dual union all
select 2, 2 from dual union all
select 3, 1 from dual),
b as (select 1 id, 'Hello' value from dual union all
select 2, 'Bye' from dual)
select a.id aid, a.value avalue, b.id bid, b.value bvalue, rownum
from a left join b on a.value = b.id
where a.id = 2 and rownum = 1;
And compare results.
Your problem is not in the difference between SQL and PL/SQL, but in behavior of rownum.
Why are you using for binding value=id? its bit of disturbing, nevermind. In addition you have a rownum "constraint" between the condition. So think it over, you are saying to oracle to do a left join on value and id, and in the condition you saying you only need the first row, which means the set will only containing the Hello "related" element

Oracle result set combinations

I have a query which returns the result set as follows:
Col1
A
B
C
D
Is it possible to get the following result set? That is associating a value to remaining 3 row values?
col1 col2
A B
A C
A D
B A
B C
B D
C A
C B
C D
D A
D B
D C
I am using Oracle 10g
You can get this with a self join as follows:
SELECT a.col1, b.col1 as col2
FROM <YOUR_TABLE> a,
<YOUR_TABLE> b
WHERE a.col1 <> b.col1
Working example:
WITH DAT AS
(
SELECT 'A' NAME FROM DUAL
UNION
SELECT 'B' NAME FROM DUAL
UNION
SELECT 'C' NAME FROM DUAL
UNION
SELECT 'D' NAME FROM DUAL
)
SELECT *
FROM DAT A, DAT B
WHERE a.Name <> b.Name