PL/SQL looping 'back' through the records - oracle

I have a problem i hope you can help to resolve.
I have a table named PRODUCT:
Product_ID NOT NULL NUMBER(10)
TARGET_PRODUCT VARCHAR2(10)
SOURCE_PRODUCT VARCHAR2(10)
So each target_product made out of source_product(except the first one - first one just has target_product and source_product is null)
I need to find the fist source_product for given target_product.
I need to go 'back' in a loop until source product is null.
Is there a solution for this scenario?
Thanks in advance

It is not a loop you need, but hierarchical query. Have a look at the following example based on Scott's EMP table.
This is its contents; employees are displayed hierarchically, showing who's whose boss:
SQL> select level,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.mgr is null
5 connect by prior e.empno= e.mgr;
LEVEL NAME
---------- ---------------
1 KING
2 JONES
3 SCOTT
4 ADAMS
3 FORD
4 SMITH
2 BLAKE
3 ALLEN
3 WARD
3 MARTIN
3 TURNER
3 JAMES
2 CLARK
3 MILLER
14 rows selected.
SQL>
As you want to start from the middle of the table (for example, starting from SMITH), you'd "reverse" it:
SQL> select level lvl,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
LVL NAME
---------- ---------------
1 SMITH
2 FORD
3 JONES
4 KING
SQL>
Finally, using that query as a CTE (or a subquery, if your Forms version doesn't support CTEs), fetch the one whose name has the highest level:
SQL> with temp as
2 (select level lvl,
3 lpad(' ', level * 2, ' ') || e.ename name
4 from emp e
5 start with e.ename = 'SMITH'
6 connect by prior e.mgr= e.empno
7 )
8 select trim(t.name) name
9 from temp t
10 where t.lvl = (select max(t1.lvl) from temp t1);
NAME
---------------
KING
SQL>
Or, even better, using connect_by_isleaf:
SQL> select e.ename name
2 from emp e
3 where connect_by_isleaf = 1
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
NAME
---------------
KING
SQL>

You may try this, it will return the base target_product for a given target_product:
select * from (
select PRODUCT_ID, TARGET_PRODUCT, SOURCE_PRODUCT
from PRODUCT
start with target_product = '<your target product>'
connect by prior SOURCE_PRODUCT = TARGET_PRODUCT
)
where SOURCE_PRODUCT is null;

Related

It is possible insert dummy (dash) into first row data use select statement in oracle SQL?

table_A
col_color col_name col_qty
- - - <----- dummy dash
RED APPLE 2
YEL BANANA 1
GRN GREEN_APPLE 3
Hi, it is posible to insert first row of dummy dash for viewing not store into database
use oracle sql plus ?
Anyone help is much apprecited.
One option is to UNION two data sets; one contains dummy dashes, while another contains "real" data. Note that dashes are considered to be strings, which means that you'll have to cast other datatypes to character datatype (see to_char(deptno) in my example):
SQL> with temp as
2 (select 1 rn, '-' deptno , '-' dname, '-' loc from dual
3 union all
4 select 2 rn, to_char(deptno), dname , loc from dept
5 )
6 select deptno, dname, loc
7 from temp
8 order by rn, deptno;
DEPTNO DNAME LOC
---------- -------------- -------------
- - -
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
The rn column is used to correctly sort the output (dashes first, the rest of data next).
If you don't want to use 'with', then how about this?
(
SELECT '-' COL_COLOR
, '-' COL_NAME
, '-' COL_QTY
FROM DUAL
)
UNION ALL
(
SELECT *
FROM table_A
)
I think this way is the best way not using 'with'.

How to use a rowcount in select statement to modify the query to fetch data for 10 days , if rowcount is 0 for 5 days?

I need to modify my script using rowcount to check if the data in table or not?. Here, i write the query to select a data for last 5 days from current system date. But sometimes there is no data in table for 5 days. So i need to fetch for 10 day or more.
Query:
Select ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
from JOB_SUMMARY_EXT e, ob_summary s, enterprise ep
where e.id = s.id and e.name_res_key = 'Model'
and s.job_id in (select id from job_summary where
trunc(start_date) > trunc(sysdate) -10 and service_name ='Model2' )
I don't know how to modify my Query using rowcount. If rowcount is 0 then i want select data for 10 days.Otherwise it should to fetch for 5 days automatically. I want this to be done as single query.
It looks that you want to select the last 5 "days" from that table. So, why would you anchor to SYSDATE if there aren't rows for each of those days? I'd suggest another approach: literally, select last 5 days. Here's how.
As I don't have your tables, I'm using Scott's EMP table which contains information about employees. It is an ancient one so HIREDATE column is set to 1980s, but never mind that. Sorting employees by HIREDATE in descending order shows:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select ename, hiredate from emp order by hiredate desc;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983 1.
SCOTT 09.12.1982 2.
MILLER 23.01.1982 3.
FORD 03.12.1981 4.
JAMES 03.12.1981 4.
KING 17.11.1981 5. --> I want to fetch rows up to KING
MARTIN 28.09.1981
TURNER 08.09.1981
CLARK 09.06.1981
BLAKE 01.05.1981
JONES 02.04.1981
WARD 22.02.1981
ALLEN 20.02.1981
SMITH 17.12.1980
14 rows selected.
SQL>
As you can see, the 4th date is shared by two employees so I want to include them both. DENSE_RANK analytic function helps:
SQL> with last5 as
2 (select ename,
3 job,
4 sal,
5 hiredate,
6 dense_rank() over (order by hiredate desc) rnk
7 from emp
8 )
9 select ename, job, sal, hiredate
10 from last5
11 where rnk <= 5;
ENAME JOB SAL HIREDATE
---------- --------- ---------- ----------
ADAMS CLERK 1100 12.01.1983
SCOTT ANALYST 3000 09.12.1982
MILLER CLERK 1300 23.01.1982
JAMES CLERK 950 03.12.1981
FORD ANALYST 3000 03.12.1981
KING PRESIDENT 5000 17.11.1981
6 rows selected.
SQL>
What does it do? The LAST5 CTE sorts employees (as above), DENSE_RANK ranks them; finally, the last SELECT (which begins at line #9) fetches desired rows.
In your case, that might look like this:
with last5 as
(select id,
dense_rank() over (order by start_date desc) rnk
from job_summary
where service_name = 'Model2'
)
select ep.enterprise_name,
s.id,
s.submission_date,
e.value
from job_summary_ext e
join ob_summary s on e.id = s.id
join last5 t on t.id = s.id
join enterprise ep on <you're missing join condition for this table>
where e.name_res_key = 'Model';
Note that you're missing join condition for the ENTERPRISE table; if that's really so, no problem - you'd use cross join for that table, but I somehow doubt that you want that.
Finally, as you use SQL*Plus, perhaps you don't need to concatenate all columns and separate them by the pipe | sign - set it as a column separator, e.g.
SQL> set colsep '|'
SQL>
SQL> select deptno, dname, loc from dept;
DEPTNO|DNAME |LOC
----------|--------------|-------------
10|ACCOUNTING |NEW YORK
20|RESEARCH |DALLAS
30|SALES |CHICAGO
40|OPERATIONS |BOSTON
SQL>
If you want to
return 10 last days if select count(*) returns 0, or
return 5 last days if select count(*) returns a positive number
then something like this might help (again based on Scott's EMP table):
with
tcnt as
-- count number of rows; use your own requirement, I'm checking
-- whether someone got hired today. In Scott's EMP table, nobody was
-- so CNT = 0
(select count(*) cnt
from emp
where hiredate >= trunc(sysdate)
)
select e.ename, e.job, e.sal, e.hiredate
from emp e cross join tcnt c
where e.hiredate >= case when c.cnt = 0 then trunc(sysdate) - 10
else trunc(sysdate) - 5
end;
Apply it to your tables; I don't know which of those 3 tables' count you want to check.
Tried to add in comments but it was too long for comments and Not clear on count based on but here is case in where clause substitute your count statement with nvl function
SELECT ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
FROM JOB_SUMMARY_EXT e,
ob_summary s,
enterprise ep
WHERE e.id = s.id
AND e.name_res_key = 'Model'
AND s.job_id IN
(SELECT id
FROM job_summary
WHERE service='Model'
AND trunc(start_date) >
CASE WHEN
(WRITE your SELECT COUNT criteria WITH NVL FUNCTION)<=0 THEN
trunc(sysdate) -10
ELSE trunc(sysdate)-5
END )

Getting manager & managers manager number in oracle

I have below table in oracle
EMPNO ENAME MGR
1 A 1
2 B 1
3 C 2
4 D 3
5 F 1
6 G 3
7 H 6
I need to pass a EMPNO and it should give me manger id and if manager has another manager it should give me there id's too in the same field.
For example, If I pass empno = 7 then the output should be 6,3,2,1.
You can use the connect by syntax to create a hierarchical query:
SELECT mgr
FROM emp
START WITH empno = 7
CONNECT BY PRIOR empno = mgr
Here is the query, I used finally for the actual results
SELECT ENAME,
CONNECT_BY_ISCYCLE Cycle,
LEVEL,
SYS_CONNECT_BY_PATH(ENAME, '/') Path
FROM EMP_DEMO
START WITH empno = '7'
CONNECT BY NOCYCLE PRIOR MGR = EMPNO;
You always have to take the max level record.

Is this a right query? If it is what does it mean

I was given a query to explain. Could someone please explain it to me:
select j.ip_num from
jobs j, address a
where j.jobtype='C' and
a.sel_code(+)='H' and
j.ip_num=a.ip_num and
a.ip_num is null order by a.ip_num
That query selects every JOB.IP_NUM which doesn't have a matching ADDRESS record or where the matching ADDRESS record has a SEL_CODE not equal to 'H'.
The (+) is Oracle's old outer join syntax. It is the only OUTER JOIN syntax supported in versions of Oracle before 9i.
In this query we get one row for every row in EMP which matches a department, plus a row for the DEPTNO=40, which has no employees:
SQL> select d.dname
2 , e.ename
3 from dept d
4 , emp e
5 where d.deptno = e.deptno(+)
6 /
DNAME ENAME
-------------- ----------
ACCOUNTING SCHNEIDER
ACCOUNTING BOEHMER
ACCOUNTING KISHORE
RESEARCH ROBERTSON
RESEARCH KULASH
RESEARCH GASPAROTTO
RESEARCH RIGBY
RESEARCH CLARKE
SALES HALL
SALES CAVE
SALES SPENCER
SALES BILLINGTON
SALES PADFIELD
SALES VAN WIJK
SALES KESTELYN
SALES LIRA
OPERATIONS PSMITH
HOUSEKEEPING VERREYNNE
HOUSEKEEPING FEUERSTEIN
HOUSEKEEPING PODER
HOUSEKEEPING TRICHLER
COMMUNICATIONS
22 rows selected.
SQL>
Now, if we put an additional filter on the EMP table like this, we simply get one record for each Department, because only one record in EMP now matches:
SQL> select d.dname
2 , e.ename
3 from dept d
4 , emp e
5 where d.deptno = e.deptno(+)
6 and e.ename(+) = 'CAVE'
7 /
DNAME ENAME
-------------- ----------
ACCOUNTING
RESEARCH
SALES CAVE
OPERATIONS
HOUSEKEEPING
COMMUNICATIONS
6 rows selected.
SQL>
/
To convert this query into the ANSI SQL syntax we have to do this:
SQL> select d.dname
2 , e.ename
3 from dept d
4 left outer join emp e
5 on ( d.deptno = e.deptno
6 and e.ename = 'CAVE' )
7 /
DNAME ENAME
-------------- ----------
ACCOUNTING
RESEARCH
SALES CAVE
OPERATIONS
HOUSEKEEPING
COMMUNICATIONS
6 rows selected.
SQL>
Note that if we don't include the additonal clause in the JOIN but leave it in the WHERE clause we get a different result:
SQL> select d.dname
2 , e.ename
3 from dept d
4 left outer join emp e
5 on ( d.deptno = e.deptno )
6 where e.ename = 'CAVE'
7 /
DNAME ENAME
-------------- ----------
SALES CAVE
SQL>
This is the equivalent of omitting the (+) in the second old skool query.
The query is joining the 2 tables jobs, and address. These tables are joining on the field ip_num but you are looking for the records that exist in the jobs table but do not exist in the address table.
This is a LEFT OUTER JOIN. This query could also be written
SELECT j.ip_num
FROM jobs j
LEFT OUTER JOIN address a
ON j.ip_num=a.ip_num
WHERE j.jobtype='C' AND
a.sel_code(+)='H' AND
a.ip_num is null
ORDER BY a.ip_num
It might be useful to see a visual picture joins http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html

Oracle Hierarchical query: how to include top-level parent

I have a hierarchical query to track a reporting structure. This almost works, except that it's not reporting the very top level node, probably because the top-level people "report" to themselves.
The query is:
select
level,
empid,
parentid
from usertable
connect by nocycle prior parentid= empid
start with empid = 50
This produces:
LEVEL EMPID PARENTID
------ ----- --------
1 50 258
2 258 9555
3 9555 17839
I'm not getting a level 4, since it would look like:
4 17839 17839
Without changing data, is there a way to modify my query so that all 4 levels are returned? The goal is to get the empids, so I can do a check for
id in (hierarchical subquery)
BTW, if I remove the nocycle from the query I get an error.
Chris,
You only get 3 rows because your top level row is not set the way it should to handle hierarchical queries. Typically the top level row, or president KING in Oracle's well known EMP table, has no manager. In your case you should not set the parentid of 17389 to 17389 itself, but to NULL. Either update the table accordingly, or use a view to accomodate for this situation.
An example:
SQL> select empno
2 , mgr
3 from emp
4 where empno in (7876,7788,7566,7839)
5 /
EMPNO MGR
---------- ----------
7566 7839
7788 7566
7839 7839
7876 7788
4 rijen zijn geselecteerd.
This part of the EMP table has four levels with its top level row (7839) set to itself. The same as your empid 17839. And this leads to only three rows using your query:
SQL> select level
2 , empno
3 , mgr
4 from emp
5 connect by nocycle prior mgr = empno
6 start with empno = 7876
7 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
3 rijen zijn geselecteerd.
Either use a (inline) view to set the mgr/parentid column to null for the top level:
SQL> select level
2 , empno
3 , mgr
4 from ( select empno
5 , nullif(mgr,empno) mgr
6 from emp
7 )
8 connect by nocycle prior mgr = empno
9 start with empno = 7876
10 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
4 7839
4 rijen zijn geselecteerd.
Or fix your data with an UPDATE statement:
SQL> update emp
2 set mgr = null
3 where empno = 7839
4 /
1 rij is bijgewerkt.
SQL> select level
2 , empno
3 , mgr
4 from emp
5 connect by nocycle prior mgr = empno
6 start with empno = 7876
7 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
4 7839
4 rijen zijn geselecteerd.
And you can leave out the NOCYCLE keyword as well, after you are done fixing.
Regards,
Rob.
You need to do the hierarchy the other way around, from the root to the leaves.
select
level,
empid,
parentid
from usertable
start with empid = 17839
connect by empid != 17839 and prior empid = parentid
LEVEL EMPID PARENTID
---------------------- ---------------------- ----------------------
1 17839 17839
2 9555 17839
3 258 9555
4 50 258
4 rows selected
You don't have to change your structure.
just use the following query
select
level,
empid,
parentid
from usertable
connect by prior parentid = empid
AND parentid <> empid -- This line prohibits cycling and ALLOWS a row where parentid = empid
start with empid = 50
Van Heddegem Roeland's answer doesn't work for me, I had already tried that, but I have managed to do it without an inline view, in the connect clause, by adding:-
and prior empid <> parentid
The following post explains why that works - if you can get your head round it! Although it does make sound logical sense once you do 'get it'. (It's to do with the order of evaluation of each side of the <> operator.)
Oracle: Connect By Loop in user data
The inline view will work but without research on your particular dataset, I don't know what impact an inline view might have on the query path. Adding the extra clause is probably the 'correct' way to do it in most situations, IMHO.
seems like you have a cycle in data. Without "nocycle" it will not work straight away. If you know that all your data has maximum nesting level 4, then you could add condition "and level <= 4" and remove nocycle. Should work.

Resources