Oracle How to make SELECT INSIDE A SELECT work? - oracle

Just wondering why the following select isn't working:
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
ERROR at line 2: ORA-00904: "A"."CUST": invalid identifier
Important to mention:
TABLE1 has as fields FIELD1, CUST.
TABLE2 has as fields PCN, PRIORITY, CUST.
Thanks in advance.

Your query shouldn't give you that error message, on when you remove the outer qiery this would happen
CREATE tABLE TABLE1 (FIELD1 int, CUST int)
INSERT INTO TABLE1 VALUES(1,1)
1 rows affected
CREATE TABLE TABLE2 (PCN int, PRIORITY int, CUST int)
INSERT INTO TABLE2 VALUES (1,1,1)
1 rows affected
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
FIELD1
PCN
1
1
fiddle

You can't nest inline selects (more than one level) without losing the ability of the inner nested selects being able to reference the parent block. So your query on TABLE2 cannot see the columns from TABLE1 because of this nesting.
Try this:
SELECT a.field1,
pcn.pcn
FROM table1 a,
(SELECT b.cust,
b.priority,
b.pcn,
ROW_NUMBER() OVER (PARTITION BY b.cust ORDER BY b.priority DESC) seq
FROM table2 b) pcn
WHERE a.cust = pcn.cust(+)
AND pcn.seq(+) = 1
That will work well for report queries. If you end up adding a filter on a specific customer, then you would be better off using OUTER APPLY if you have a recent-enough version of Oracle that supports that.

You could try this:
SELECT
A.FIELD1
, (SELECT B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY
FETCH FIRST 1 ROWS ONLY) AS PCN
FROM TABLE1 A;
FETCH FIRST 1 ROWS ONLY gets you the first ordered record. Works on 12c and up and supports nesting, and no 2nd subquery needed.

Yet another option might be a CTE.
Sample data:
SQL> with
2 table1 (field1, cust) as
3 (select 1, 100 from dual union all
4 select 2, 200 from dual
5 ),
6 table2 (pcn, priority, cust) as
7 (select 10, 1, 100 from dual union all
8 select 20, 2, 100 from dual union all
9 select 30, 1, 200 from dual
10 ),
Query begins here. Rank rows by priority, and then fetch the ones that rank as the highest (line #20):
11 temp as
12 (select a.field1,
13 b.pcn,
14 rank() over (partition by a.field1 order by b.priority desc) rnk
15 from table1 a join table2 b on a.cust = b.cust
16 )
17 select field1,
18 pcn
19 from temp
20 where rnk = 1;
FIELD1 PCN
---------- ----------
1 20
2 30
SQL>

You may use first aggregate function to achieve the same (assuming that you have completely deterministic order by) functionality without nested subquery:
select
a.field1
, (
select max(b.pcn) keep(dense_rank first order by b.priority)
from table2 b
where b.cust = a.cust
) as pcn
from table1 a
which for this sample data
insert into table1 values(1,1);
insert into table1 values(2,2);
insert into table2 values(1,1,1);
insert into table2 values(2,2,1)
returns
FIELD1
PCN
1
1
2
(null)
SQL fiddle

Related

How can I count the amount of values in different columns in oracle plsql

For example, I have a table with these values:
ID
Date
Col1
Col2
Col3
Col4
1
01/11/2021
A
A
B
2
01/11/2021
B
B
The A and B values are dynamic, they can be other characters as well.
Now I need somehow to get to the result that id 1 has 2 occurences of A and one of B. Id 2 has 0 occurences of A and 2 occurences of B.
I'm using dynamic SQL to do this:
for v_record in table_cursor
loop
for i in 1 .. 4
loop
v_query := 'select col'||i||' from table where id = '||v_record.id;
execute immediate v_query into v_char;
if v_char = "any letter I'm checking" then
amount := amount + 1;
end if;
end loop;
-- do somehting with the amount
end loop;
But there has to be a better much more efficient way to do this.
I don't have that much knowledge of plsql and I really don't know how to formulate this question in google. I've looked into pivot, but I don't think that will help me out in this case.
I'd appreciate it if someone could help me out.
Assuming the number of columns would be fixed at four, you could use a union aggregation approach here:
WITH cte AS (
SELECT ID, Col1 AS val FROM yourTable UNION ALL
SELECT ID, Col2 FROM yourTable UNION ALL
SELECT ID, Col3 FROM yourTable UNION ALL
SELECT ID, Col4 FROM yourTable
)
SELECT
t1.ID,
t2.val,
COUNT(c.ID) AS cnt
FROM (SELECT DISTINCT ID FROM yourTable) t1
CROSS JOIN (SELECT DISTINCT val FROM cte) t2
LEFT JOIN cte c
ON c.ID = t1.ID AND
c.val = t2.val
WHERE
t2.val IS NOT NULL
GROUP BY
t1.ID,
t2.val;
This produces:
Demo

ORACLE SQL REPEAT SAME QUERY

It seems that I haven't been clear enough.
The query that seems to work is:
Select ((Select count (table1.id) from table1 where table1.code=2 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=2 and table1.name=7) as ‘name7’)
From table.1;
union
Select ((Select count (table1.id) from table1 where table1.code=5 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=5 and table1.name=7) as ‘name7’)
From table.1;
union
Select ((Select count (table1.id) from table1 where table1.code=15 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=15 and table1.name=7) as ‘name7’)
From table.1;
….
Which gets an outcome like this:
name5 name7
52 47
42 84
61 11
My problem is that the table1.code has a thousand and more values other than 2,5 and 15 and I can not repeat a union statement for so many times.
Well it seems like you actually just want to group by the values in the code column, and you can use IN or EXISTS
select count(table1.id) as theCount, table1.code as theCode
from table1 where table1.code in ('code a','code b', 'etc...')
group by table1.code;
the output would be
theCount||theCode
code a || 8074
code b || 34
etc... || 9575
or something like that but with non notional numbers for counts
HTH
You could try to list all values in a nested select listing integers from a to your value, e.g. 100, like that:
select count table1.id from table1 one where table1.code in (
select rownum from all_objects where rownum < 100
);
or if you don't want to start at "1":
select count table1.id from table1 one where table1.code in (
select rownum n from dual connect by level 10 where n>3
);

How to handle singe-row subquery returns more than one row error with case statement

I am building an Oracle query which has a case statement involved.
SELECT
CASE WHEN
((SELECT agent_or_group_id from trans_slot where slot_id =
(SELECT slot_id from trans_slot where slot_alias = 'PP' and measure_expiration > sysdate)) > 0)
/*The below subquery returns 1 row*/
THEN (SELECT agent_or_group_id from trans_slot where slot_id =
(SELECT slot_id from trans_slot where slot_alias = 'PP' and measure_expiration > sysdate))
ELSE
/* The below subquery returns 2 rows*/
(SELECT child_agent_id FROM agent_object_group_member WHERE parent_agent_id IN
(SELECT agent_or_group_id FROM trans_slot WHERE slot_id IN
(SELECT slot_id FROM trans_slot WHERE slot_alias = 'PP' AND measure_expiration > sysdate)
)
)
END
"Agent_ID" from DUAL;
When the run the subqueries independent they run fine. But running the whole query returns
ORA-01427: single-row subquery returns more than one row
01427. 00000 - "single-row subquery returns more than one row"
*Cause:
*Action:
If I understand what you're after, you can't do it like that. You'll need to use a left outer join and then chose the column value you want to display.
Here's a simplified example based on the SQL you provided:
WITH t1 AS (SELECT 1 ID FROM dual UNION ALL
SELECT 0 ID FROM dual UNION ALL
SELECT 2 ID FROM dual),
t2 AS (SELECT 10 child_id, 0 parent_id FROM dual UNION ALL
SELECT 20 child_id, 0 parent_id FROM dual UNION ALL
SELECT 30 child_id, 1 parent_id FROM dual UNION ALL
SELECT 40 child_id, 2 parent_id FROM dual)
---- end of mimicking two tables with the sample data in them. See the query below:
SELECT COALESCE(t2.child_id, t1.id) ID
FROM t1
LEFT OUTER JOIN t2 ON (t1.id = t2.parent_id AND t1.id = 0);
ID
----------
10
20
2
1
Here, I have used the t1 and t2 subqueries to mimic the output you'd get from your main subqueries in your original query.
Then we outer join t2 to t1 only where the t1.id = 0. By doing this, you can then simply choose the t2.child_id value if it exists, otherwise use the t1.id value.
(I realise that in your example, the t1 equivalent subquery would only generate 1 row, based on what you said, but I've included 3 rows so that you can see what the results would be based on the different ids.)
ETA:
In your case, the t1 subquery in my example above would be:
SELECT agent_or_group_id
from trans_slot
where slot_id = (SELECT slot_id
from trans_slot
where slot_alias = 'PP'
and measure_expiration > sysdate)
and the t2 subquery would be:
SELECT child_agent_id
FROM agent_object_group_member
WHERE parent_agent_id IN (SELECT agent_or_group_id
FROM trans_slot
WHERE slot_id IN (SELECT slot_id
FROM trans_slot
WHERE slot_alias = 'PP'
AND measure_expiration > sysdate))

return null if no rows found oracle query with IN clause

I have a table with three columns.
I query that table with IN clause.
select column1 from table1 where column1 in (1,2,3) order by column2, column3
The table1 contains only values 1 and 2 in column1. I want to return the not available value also in my result, and that should be sorted in the bottom.
example data
column1 column 2 column 3
1 100 11
2 101 50
output, the not available values should be in the last.
column1 column 2 column 3
1 100 11
2 101 50
3 null null
I tried with subquery with NVL, like select nvl((select.. in(1,2,3)),null) from dual, due to IN Clause, I am getting single row subquery returns more than one row issue, which is expected.
Also tried with the union but nothing works. Great if any help. Thanks
I think you can do it with a union all:
select column1 from table1 where column1 in (1,2,3) order by column2, column3
union all
select null from table1 where column1 not in (1,2,3) order by column2, column3
If you can't take 1,2,3 values from another table you can try with:
with t1 as (
select col1,col2,col3
from tab1
where cod_flusso in ('1','2','3')),
t2 as (
select '1' as col1,null,null
from dual
union
select '2',null,null
from dual
union
select '3',null,null
from dual)
select t2.col1,col2,col3
from t2
left outer join t1
on t1.col1= t2.col1
It's better if you can store 1,2,3 values in a second table, then use left outer join.

Oracle select random rows matching a join condition

My objective is simple, I have to create a temporary table with some random values from a employee table whenever the department is in some particular department (say 2). For the rest of departments I don't care the value, it can be NULL.
Currently I have the following :
create table test
as
select s.DEPTNAME,
cast (
(case when s.DEPTID in (2) then
(SELECT a.ENAME FROM
(SELECT b.ENAME, b.DEPTID FROM EMPLOYEE b
WHERE b.DEPTID IS NOT NULL
ORDER BY DBMS_RANDOM.VALUE) a
WHERE a.DEPTID = s.DEPTID AND ROWNUM = 1
)
END)
AS VARCHAR2(30)) "ENAME" from DEPARTMENT s;
But the main issue here is related to performance. For every department value in 2 we do a sort of EMPLOYEE table to get a single random ENAME.
Is there a better way to do this ? I know sample might work but I want to achieve more randomness.
First idea - join randomly numbered enames:
with
e as (select ename, deptid, row_number() over (order by dbms_random.value) rn
from employee where deptid = 2),
c as (select count(1) cnt from e),
d as (select deptname, deptid, round(dbms_random.value(1, c.cnt)) rn from department, c)
select d.deptname, e.ename from d left join e using (rn, deptid)
SQLFiddle demo
Second possible solution, which worked for me, is to create function returning random ename from table employee
and use it in your query, but it would be probably slower.
Edit - according to comment:
If, for some reason, the first part of your statement is "fixed", then you could use this syntax:
create table test as
select deptname, ename from (
with
e as (select ename, deptid, row_number() over (order by dbms_random.value) rn
from employee where deptid = 2),
c as (select count(1) cnt from e),
d as (select deptname, deptid, round(dbms_random.value(1, c.cnt)) rn
from department cross join c)
select d.deptname, e.ename from d left join e using (rn, deptid));

Resources