Multiple Unioned Self-joins in BigQuery - sql

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.

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

How to write SQL join to find description of id using Oracle?

I have 2 input tables, and I need output in string format.
I tried following query, but it does not work. How can I get the above output?
with
cte1 as --table 1
(select 1 as id , 'A' as abc from dual
union
select 2 as id , 'B' as abc from dual
union
select 3 as id , 'C' as abc from dual
union
select 4 as id , 'D' as abc from dual
union
select 5 as id , 'E' as abc from dual
union
select 6 as id , 'F' as abc from dual
),
cte2 as --table2
(select 1 as id, 3 as name from dual
union
select 1 as id, 5 as name from dual
union
select 1 as id, 4 as name from dual
union
select 2 as id, 3 as name from dual
union
select 2 as id, 6 as name from dual
)
SELECT e.id, e.abc, m.id as mgr, e.abc, c.*
FROM
cte1 e, cte2 m, cte2 c
WHERE e.id = m.id
and
e.id=c.name;
You are trying to join each row in table 1 to two rows in table 2, and the conditions can never both be true.
You want to join each row in table 2 to two rows in table 1:
SELECT e.abc, m.abc
FROM cte2 c, cte1 e, cte1 m
WHERE e.id = c.id
AND m.id = c.name
ORDER BY c.id, c.name;
A A
- -
A C
A D
A E
B C
B F
or with 'modern' join syntax, which you should really be using:
SELECT e.abc, m.abc
FROM cte2 c
JOIN cte1 e ON e.id = c.id
JOIN cte1 m ON m.id = c.name
ORDER BY c.id, c.name;
A A
- -
A C
A D
A E
B C
B F

Oracle sql - display if record exists else display parent

I want to categorize data into two types based on:
If a value exists in T2, display "A", else display "B" as "Type". Is there a way to implement this in case when or decode?
T1 is the parent of T2.
T1
1
2
3
4
5
T2
1
1
3
3
3
4
Ideally my output would be
Type
A
B
A
A
B
edit: I want to add that A and B are text values I want to display based on my above condition, this is not coming from the db. Also, T2 will not have a corresponding record at all for 2 & 5. so I cannot really check for null.
Try this:
SELECT T1.Col,
CASE WHEN T2.Col IS NOT NULL THEN 'A' ELSE 'B' END AS Type
FROM T1
LEFT JOIN (
SELECT DISTINCT Col FROM T2
) AS T2 ON T1.Col = T2.Col
Oracle Setup:
CREATE TABLE T1 ( column_name ) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 6;
CREATE TABLE t2 ( column_name ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 1 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL;
Query:
SELECT NVL2( T2.column_name, 'A', 'B' ) AS Type
FROM T1
LEFT OUTER JOIN
( SELECT DISTINCT column_name FROM T2 ) T2
ON T1.column_name = T2.column_name
ORDER BY T1.column_name;
Output:
TYPE
----
A
B
A
A
B

How to write a query by joining two tables?

I have two tables:
Table 1:
ID NAME
1 ID1
2 ID2
3 ID3
4 ID4
5 ID5
6 ID6
7 ID7
Table 2:
Parent_ID Child_ID
1 2
2 5
2 3
3 6
How do I write a query to get below output if I assign Parent_Id = 1 in where condition?
P_ID NAME Is_Group Selected
1 ID1 Yes No
2 ID2 Yes Yes
3 ID3 Yes Yes
4 ID4 No No
5 ID5 No Yes
6 ID6 No Yes
7 ID7 No No
So, output mainly contains records from table one but also it need to have two additional columns.
Value in Is_Group column should be "Yes" if ID from Table 1 exists in Parent_ID column in Table 2. Value in Selected column should be "yes" if ID from Table 1 exists in Child_ID column in Table 2 and Parent_ID = 1 (like a cross reference).
In additional, I need to check if a Child_ID has any cross reference. For example In Table 2 Child_ID is 2 for Parent_Id 1, 2 also has 5 and 3 as child_Id so I need to have Selected column values as "Yes" for Id's 3 and 5 and so on.
Thanks in advance for your reply. Sorry for my English.
This should give you the output you need.
It uses a recursive cte to get the hierarchy.
Then outer joins to the cte twice to determine if the the ID is a Group, or Selected by checking for null values
WITH cte AS
(
SELECT Parent_ID,
Child_ID
FROM Table2
WHERE Parent_ID = 1
UNION ALL
SELECT t2.Parent_ID,
t2.Child_ID
FROM Table2 t2
INNER JOIN cte ON t2.Parent_ID = cte.Child_ID
)
SELECT DISTINCT
t1.*,
(CASE WHEN grp.Parent_ID IS NULL THEN 'No'
ELSE 'Yes'
END) AS Is_Group,
(CASE WHEN sel.Parent_ID IS NULL THEN 'No'
ELSE 'Yes'
END) AS Selected
FROM Table1 t1
LEFT JOIN cte grp ON t1.ID = grp.Parent_ID
LEFT JOIN cte sel ON t1.ID = sel.Child_ID
The fact that you're selecting everything from Table1 regardless of the whether it's in the selected hierarchy or not would give you No for Is_Group for any ID's that are Parent_IDs, but not actually in the hierachy cte. To always determine if an ID is a Group or not, just left join to Table2 as grp instead of the cte.. like.
;WITH cte AS
(
SELECT Parent_ID,
Child_ID
FROM Table2
WHERE Parent_ID = 1
UNION ALL
SELECT t2.Parent_ID,
t2.Child_ID
FROM Table2 t2
INNER JOIN cte ON t2.Parent_ID = cte.Child_ID
)
SELECT DISTINCT
t1.*,
(CASE WHEN grp.Parent_ID IS NULL THEN 'No'
ELSE 'Yes'
END) AS Is_Group,
(CASE WHEN sel.Parent_ID IS NULL THEN 'No'
ELSE 'Yes'
END) AS Selected
FROM Table1 t1
LEFT JOIN Table2 grp ON t1.ID = grp.Parent_ID
LEFT JOIN cte sel ON t1.ID = sel.Child_ID
try this,
select distinct id, t.NAME,
case when t1.Parent_ID is not null then 'Yes' else 'No' end Is_Group
,case when b.Child_ID is null then 'No' else 'Yes' end Selected
from Table1 t left join Table2 t1 on t.ID =t1.Parent_ID
outer apply (select Child_ID from Table2 a where a.Child_ID=t.ID ) b

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