Select-statement: different result in direct query and pl/sql - 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

Related

Fill a select with null when join isn't possible

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)

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.

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

Conditional column value, Select

I got 2 tables "Records" and "Char". With 1 -> N relation
I need to make a select, with a subquery/join where the value to present on the join column is a fixed string like "Multiple Chars" or the content Char.char_val
Let me illustrate:
Records:
R_ID | Name Char: C_ID | R_ID | Char_Val
1 A 1 3 c1
2 B 2 1 c2
3 C 3 1 c3
4 2 c3
Expected Result:
R_ID | Name | Char_Val
1 A Multiple Records
2 B c3
3 C c1
I guess my query would be something like:
Select r.R_ID, r.Name, (conditional select) Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
Suggestions for the (conditional select)?
You can use a case statement and aggregation to get a fixed string:
case when count(c.c_id) > 1 then 'Multiple Records' else max(c.char_val) end
and you need to group by r_id and name:
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from records r
join char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
I've also switched to use ANSI joins instead of the old syntax (as #Thorsten suggested).
This is a demo using CTE to generate your data, giving them slightly different names because char is a reserved word:
with t_records (r_id, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
t_char (c_id, r_id, char_val) as (
select 1, 3, 'c1' from dual
union all select 2, 1, 'c2' from dual
union all select 3, 1, 'c3' from dual
union all select 4, 2, 'c3' from dual
)
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from t_records r
join t_char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
R_ID N CHAR_VAL
---------- - ----------------
1 A Multiple Records
2 B c3
3 C c1
Group by r_id. Either MIN = MAX or you want 'Multiple Records':
select r_id, r.name, c.char_vals
from
(
select
r_id,
case when min(char_val) = max(char_val) then min(char_val) else 'Multiple Records' end
as char_vals
from char
group by r_id
) c
join records r using(r_id)
order by r_id;
Following query gives the result (with Char_val separated by comma) you expected:
Select r.R_ID, r.Name, listagg(c.char_val,',') WITHIN GROUP(ORDER BY c.char_val) AS Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
GROUP BY r.R_ID, r.Name

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