Is a recursive query possible? - oracle11g

An Oracle SQL question that seems simple to me, but I just can't do it (without manually making several successive queries)... I would like to use a SQL "recursive" approach or any other method that would avoid me to manually use "n" times the same query.
Here is a very simplified example of my data:
row
ID
Value
Result
1
135
AAA
AAA
2
246
BBB
BBB
3
357
135
AAA
4
468
357
AAA
5
578
EEE
EEE
If a value refers to an ID, then we need to fetch the value of that ID. If the value of an ID is another ID, then you have to keep looking for an "true" value. If the value is not an ID, then we can take it directly.
The column "Result" gives the expected result:
For the row 3, you have to look for the value of the ID 135.
For the line 4, you have to get the value of the ID 357, which corresponds to the value of the ID 135.
Here, what I don't want to do:
with my_data as(
select '135' as id, 'AAA' as value from dual union all
select '246', 'BBB' from dual union all
select '357', '135' from dual union all
select '468', '357' from dual union all
select '578', 'EEE' from dual
)
,step as(
select d1.*
,nvl((select d2.value from my_data d2 where d1.value=d2.id),d1.value) as step_1
from my_data d1
)
-- steps to do again and again...
select step.*
,nvl((select my_data.value from my_data where step.step_1=my_data.id),step.step_1) as step_n
from step
order by 1
;
Thank you for your help.
In addition to Carlos S anwser:
It does exactly what I want in my "very simplified" example. However, with a sub-set of real data, there is something wrong (a little too much data). On the other hand, if I remove the results that refer to IDs, it gives exactly what I want. I can't find the problem.
Note: as shown in my initial example, IDs will always start with a number and a "real" value will always start with a letter. So the regex strategy is "ok".
Here is a real example:
with my_data as(
select '116554226_2' as id, '116554226_1' as value from dual union all
select '119675285_2' as id, '119675285_1' as value from dual union all
select '119675285_3' as id, '119675285_2' as value from dual union all
select '13656777_1' as id, '119675471_1' as value from dual union all
select '13656777_5001' as id, '119675471_1' as value from dual union all
select '13656777_2' as id, '13656777_1' as value from dual union all
select '13656155_1' as id, '13657581_1' as value from dual union all
select '13657581_2' as id, '13657581_1' as value from dual union all
select '13657015_1' as id, '13657759_1' as value from dual union all
select '13657759_2' as id, '13657759_1' as value from dual union all
select '116554226_1' as id, '471502681_1' as value from dual union all
select '462721769_1' as id, 'O7X5J' as value from dual union all
select '471502681_1' as id, 'T3L8L' as value from dual union all
select '119675471_1' as id, 'T8Q0G' as value from dual union all
select '119675471_5001' as id, 'T8Q0G' as value from dual union all
select '116555133_1' as id, 'T9J2Q' as value from dual union all
select '13657581_1' as id, 'U5H5Z' as value from dual union all
select '119674049_1' as id, 'Y5G7V' as value from dual union all
select '13657759_1' as id, 'Z0Y9C' as value from dual union all
select '119675285_1' as id, 'Z7E0D' as value from dual
)
SELECT my_data.*, CONNECT_BY_ROOT value result
FROM my_data
CONNECT BY PRIOR id = value
START WITH REGEXP_LIKE(value,'[^0-9]')
;
The boxed data is unwanted, otherwise everything below it is ok.

I think this query may help you:
with my_data as(
select '135' as id, 'AAA' as value from dual union all
select '246', 'BBB' from dual union all
select '357', '135' from dual union all
select '468', '357' from dual union all
select '578', 'EEE' from dual
)
SELECT rownum, id, value, CONNECT_BY_ROOT value result
FROM my_data
CONNECT BY PRIOR id = value
START WITH REGEXP_LIKE(value,'[^0-9]')
ROWNUM
ID
VALUE
RESULT
1
135
AAA
AAA
2
357
135
AAA
3
468
357
AAA
4
246
BBB
BBB
5
578
EEE
EEE

Related

Always return all row, even when query has no result

I have a "tt_Results" table which contains exactly three row / ID like:
ID | ResultType
---------------
1 | first
1 | second
1 | third
My query looks like:
select t.resultType
from tt_Results
where ID = 1;
Normally, this query should return all the three row. My problem is if one or more row not exist I must union all the three "type".
select res.* from
(
select resultType, '1' as exists
from tt_Results
where ID = 1
union all
select 'first' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'first' and ID = 1)
union all
select 'second' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'second' and ID = 1)
union all
select 'third' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'third' and ID = 1)
) res
The final query result looks good but it is very slow. Anyone have a better PL/SQL solution for it? Thanks for the answers!
Left join to your table from a table of all types, using a case to calculate exists based on whether a join was made or not:
select
type,
case when resultType is null then '0' else '1' end as exists
from (select 'first' type from dual union
select 'second' from dual union
select 'third' from dual) t
left join tt_Results on resultType = type
and ID = 1
Note that the condition ID = 1 is part of the join condition, not in a where clause.
I recommend creating a 3-row table for the types, then the query would become simply:
select
type,
case when resultType is null then '0' else '1' end as exists
from types
left join tt_Results on resultType = type
and ID = 1
You can do this by creating a subquery to hold the result types you're interested in, and then using a partitioned outer join, like so:
with tt_results as (select 1 id, 'first' resulttype from dual union all
select 1 id, 'second' resulttype from dual union all
select 1 id, 'third' resulttype from dual union all
select 2 id, 'second' resulttype from dual union all
select 2 id, 'third' resulttype from dual union all
select 3 id, 'first' resulttype from dual),
dummy as (select 1 position, 'first' resulttype from dual union all
select 2 position, 'second' resulttype from dual union all
select 3 position, 'third' resulttype from dual)
select res.id,
d.resulttype,
case when res.resulttype is not null then 1 else 0 end res_exists
from dummy d
left outer join tt_results res partition by (res.id) on d.resulttype = res.resulttype
order by res.id,
d.position;
ID RESULTTYPE RES_EXISTS
---------- ---------- ----------
1 first 1
1 second 1
1 third 1
2 first 0
2 second 1
2 third 1
3 first 1
3 second 0
3 third 0
Adrian Billington has produced an excellent article on partititioned outer joins if you want to learn more about them.
If your tt_results table could contain more resulttypes than you're interested in, you might want/need to add a predicate to only grab the rows from the dummy subquery, otherwise you could get ids where all 3 resulttypes don't exist (although that may be what you want to see).
ETA: This will work if you need to select multiple ids in one go.

case statement inside nested case

I will make this question as more precise
I have this data
id product count
1 a 10
1 b 20
1 c 10
1 d 30
I want the result like
Since both product A and B has values i want to count them as one so the result should count(distinct A,C,D) that is 3
If any of product that is (A has values but B does not or vice versa ) then also the result has to be 3
in case both product A and B does not have value then the result should be 2
how to achieve this by using a case statement in oracle plsql
I'm not sure how you define either count of a or either count of b not both, but if you defined it explicitly then you can try this one:
with t as (
select 1 as id, 'a' as product from dual
union all
select 1 as id, 'b' as product from dual
union all
select 1 as id, 'c' as product from dual
union all
select 1 as id, 'd' as product from dual
) select id,
product,
count( case when product in ('c', 'd', 'a') then 1 end ) --change 'a' to 'b' to get the the result for 'b'
from t
group by id, product;

plsql count by date and sum up to the date

I'd like to have a plsql request that count the number of row by date and the sum of row up to the date.
The source will be somethings like this (hundreds of dates):
2019.05.01
2019.05.01
2019.05.02
2019.05.03
2019.05.03
2019.05.03
...
and the result:
date nb sum
-------------------
2019.05.01 2 2
2019.05.02 1 3
2019.05.03 3 6
. . .
The key here is to use an Aggregate Function as an Analytical function which is what the SUM(COUNT(dt)) OVER (ORDER BY dt) in the query below is doing.
WITH dates AS
(
SELECT to_date('2019.05.01', 'YYYY.MM.DD') AS dt FROM dual UNION ALL
SELECT to_date('2019.05.01', 'YYYY.MM.DD') AS dt FROM dual UNION ALL
SELECT to_date('2019.05.02', 'YYYY.MM.DD') AS dt FROM dual UNION ALL
SELECT to_date('2019.05.03', 'YYYY.MM.DD') AS dt FROM dual UNION ALL
SELECT to_date('2019.05.03', 'YYYY.MM.DD') AS dt FROM dual UNION ALL
SELECT to_date('2019.05.03', 'YYYY.MM.DD') AS dt FROM dual
)
SELECT dt, COUNT(dt) AS nb, SUM(COUNT(dt)) OVER (ORDER BY dt) AS sum
FROM dates
GROUP BY dt
;
First half is relatively easy. Something like
SELECT date, count(*) from table GROUP BY date ORDER BY date;
But I'm not sure about the cumulative sum. If it's a manual process then I'd probably just import into Excel and do it there to be honest.

how to join all the tables without using any condition

all the below results are not related to each other wheras we cannot use any condition.
ID
----------
1
2
3
4
5
6
7
7 rows selected.
NAME
-----------------
SRUJAN
DEERAJ
VINEETH
CHANIKYA
LAVANYA
KAVITHA
BUNNY
7 rows selected.
AGE
----------
23
24
26
25
29
28
24
7 rows selected.
ADDRESS
-------------
NAGARAM
BANDLAGUDA
UPPAL
KUKATPALLY
HB COLONY
MOULALI
BOUDHA NAGAR
7 rows selected.
SALARY
----------
12000
13000
14000
15000
16000
17000
18000
7 rows selected.
I USED
SQL>select id,name,age,address,salary from table1,table2,table3,table4,table5;
but it showing 16807 rows selected
i want to get only one table.
please suggest a query.
The only possible join between 2 tables having only 1 columns and both having no relation is cross join. You cannot avoid it. And the same you are getting when you tried to join. The best way for you is to create a sequence as ID and then call it in your select statement of table2.
CREATE SEQUENCE TEST_SEQ
START WITH 1
MAXVALUE 9999999999999999999999999999
MINVALUE 1
NOCYCLE;
select TEST_SEQ.nextval ID,col1 NAME
from table2;
Here is one way, using inner joins. This solution matches the lowest id with the first name in alphabetical order, the lowest age, etc.
If instead of this ordered matching you need random matching, that is easy to do as well: in the over... clause of the prep tables, change the order by clause to order by dbms_random.value() (in all places).
In the solution below I use only the first three tables, but the same works for any number of input tables.
with
tbl_id ( id ) as (
select 1 from dual union all
select 2 from dual union all
select 3 from dual union all
select 4 from dual union all
select 5 from dual union all
select 6 from dual union all
select 7 from dual
),
tbl_name ( name ) as (
select 'SRUJAN' from dual union all
select 'DEERAJ' from dual union all
select 'VINEETH' from dual union all
select 'CHANIKYA' from dual union all
select 'LAVANYA' from dual union all
select 'KAVITHA' from dual union all
select 'BUNNY' from dual
),
tbl_age ( age ) as (
select 23 from dual union all
select 24 from dual union all
select 26 from dual union all
select 25 from dual union all
select 29 from dual union all
select 28 from dual union all
select 24 from dual
),
prep_id ( id, rn ) as (
select id, row_number() over (order by id) from tbl_id
),
prep_name ( name, rn ) as (
select name, row_number() over (order by name) from tbl_name
),
prep_age ( age , rn ) as (
select age, row_number() over (order by age) from tbl_age
)
select i.id, n.name, a.age
from prep_id i inner join prep_name n on i.rn = n.rn
inner join prep_age a on i.rn = a.rn
;
Output:
ID NAME AGE
---------- -------- ----------
1 BUNNY 23
2 CHANIKYA 24
3 DEERAJ 24
4 KAVITHA 25
5 LAVANYA 26
6 SRUJAN 28
7 VINEETH 29
7 rows selected

An SQL query to display the count of distinct values of a single column into a single row

There is a table "OBJECTS" with one column "OBJECT_NAME" having three values: OBJ1, OBJ2, OBJ3.
Snapshot of the table OBJECTS is:
OBJECT_NAME
----------
OBJ3
OBJ1
OBJ2
OBJ1
OBJ1
OBJ2
Need to write a most efficient SQL query to output the count of the distinct object in the format as shown below. Make sure that performances are optimised and explain what was done for the optimization.
OBJ1_Count OBJ2_COUNT OBJ3_COUNT
---------------------------------
3 2 1
Your homework assignment marks should be given to me once i illustrate the example for your query :P. Hope it helps.
SELECT *
FROM
(SELECT 'OBJ1' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ1' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ2' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ2' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ3' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ2' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ3' AS NM FROM DUAL
UNION ALL
SELECT 'OBJ3' AS NM FROM DUAL
) PIVOT ( COUNT (NM) FOR NM IN ('OBJ1','OBJ2','OBJ3') );
---------------------------------OUTPUT--------------------------------------
'OBJ1' 'OBJ2' 'OBJ3'
2 3 3
---------------------------------OUTPUT--------------------------------------

Resources