Oracle, Connect By rownum - sql

I tried to find some informations about connect by "engine".
I found this post: Confusion with Oracle CONNECT BY
User krokodilko answered and says:
The analyze of the last query:
select level from dual connect by rownum<10;
I leave to you as a homework assignment.
So i tried to do exactly as described to query
Select rownum from dual connect by rownum < 3
And here's my "work":
CREATE TABLE step1 AS
SELECT 1 "LEVEL" FROM dual;
SELECT * FROM step1;
create table step2 as
SELECT 2 "LEVEL" from dual
JOIN step1 "PRIOR" on rownum <=3;
SELECT * FROM step2;
create table step3 as
select 3 "LEVEL" from dual
join step2 "PRIOR" on rownum <=3;
SELECT * FROM step3;
create table step4 as
select 4 "LEVEL" from dual
join step3 "PRIOR" on rownum <=3;
SELECT * FROM step4;
But last SELECT still returns rows. Am I misunderstood something? Every time i Select LEVEL + 1 "LEVEL" it has rownum = 1 so it's always true. So am i failed steps?

The explanation in Krokodilko's answer is simply wrong. You may disregard the "Correct Answer" mark and the numerous upvotes, it's still wrong. It is interesting that he left as an exercise exactly the case that proves the explanation is wrong.
A CONNECT BY query doesn't work "as if" new tables (or new output rowsets of SELECT statements, anyway) are generated at each step. This is the mistake in the argument.
Rather, there is only one rowset generated overall (across all steps). It is true that new rows are added based on the rows generated at the previous step; but the rowset itself is one, and growing, not separate rowsets.
This is particularly relevant with regard to ROWNUM. ROWNUM is assigned to rows in a single "result" rowset, starting with 1. In a CONNECT BY query, there is only one rowset, and ROWNUM goes from 1 to n in an increasing sequence.
If Krokodilko's answer were correct, then ROWNUM would restart at 1 at each step. This is clearly not the case: let's try it on a "standard" hierarchical query.
select empno, ename, mgr, level, rownum
from scott.emp
start with mgr is null
connect by prior empno = mgr
;
EMPNO ENAME MGR LEVEL ROWNUM
---------- ---------- ---------- ---------- ----------
7839 KING 1 1
7566 JONES 7839 2 2
7788 SCOTT 7566 3 3
7876 ADAMS 7788 4 4
7902 FORD 7566 3 5
7369 SMITH 7902 4 6
7698 BLAKE 7839 2 7
7499 ALLEN 7698 3 8
7521 WARD 7698 3 9
7654 MARTIN 7698 3 10
7844 TURNER 7698 3 11
7900 JAMES 7698 3 12
7782 CLARK 7839 2 13
7934 MILLER 7782 3 14

Related

Ignore column in the WHERE clause if the parameter is null, else use IN clause to filter the column in WHERE clause

I need to write a query where I should Ignore any filter on that column if the respective parameter is Null, but should filter with IN clause if the said parameter is not null. I am trying to use the below query but I am not able to make it work. It is the HR DB and Employees table in Oracle 11 XE and I am trying to pass Job ID as a param and this param could be null or it could contain multiple values.
What I have done so far -
SELECT * FROM HR.EMPLOYEES
WHERE
CASE WHEN NVL(:PARAM_JOB_ID,'NONE')= 'NONE' THEN 'NONE' ELSE JOB_ID END IN NVL(:PARAM_JOB_ID,'NONE');
Please guide.
I would use IS NULL logic here:
SELECT *
FROM HR.EMPLOYEES
WHERE JOB_ID IN (:PARAM_JOB_ID) OR :PARAM_JOB_ID IS NULL;
You'll have to split values in :PARAM_JOB_ID into rows. Something like this (Scott's sample schema and its EMP table):
select job, ename
from emp
where ( job in (select trim(regexp_substr(:param_job_id, '[^,]+', 1, level))
from dual
connect by level <= regexp_count(:param_job_id, ',') + 1
)
or :param_job_id is null
)
order by job, ename;
Demonstration in SQL*Plus:
SQL> select job, ename
2 from emp
3 where ( job in (select trim(regexp_substr('&&param_job_id', '[^,]+', 1, level))
4 from dual
5 connect by level <= regexp_count('&&param_job_id', ',') + 1
6 )
7 or '&&param_job_id' is null
8 )
9 order by job, ename;
Enter value for param_job_id: --> empty parameter returns all rows
JOB ENAME
--------- ----------
ANALYST FORD
ANALYST SCOTT
CLERK ADAMS
CLERK JAMES
CLERK MILLER
CLERK SMITH
MANAGER BLAKE
MANAGER CLARK
MANAGER JONES
PRESIDENT KING
SALESMAN ALLEN
SALESMAN MARTIN
SALESMAN TURNER
SALESMAN WARD
14 rows selected.
SQL> undefine param_job_id
SQL> /
Enter value for param_job_id: CLERK, PRESIDENT
JOB ENAME
--------- ----------
CLERK ADAMS
CLERK JAMES
CLERK MILLER
CLERK SMITH
PRESIDENT KING
SQL>

Use substitution variable within Oracle IN clause

I'm trying to write a simple query where the requirement is to use a substitution variable that can be used to enter mutiple possible values for a column that's used for filtering the query.
The reqiurement is to produce the following query
select
CONTRACT,
ORDER_NO,
CUSTOMER_NO
from CUSTOMER_ORDER
where state='Picked'
and contract in ('ABC','DEF')
but the contract values will need to be entered during runtime by means of a substitution variable. I'm working with the limitation of only being able to write a static SQL Query ("select ..from..where..") and no dynamic code can be written inside pl sql blocks.
So, what I tried was the following,
select
CONTRACT,
ORDER_NO,
CUSTOMER_NO
from CUSTOMER_ORDER_JOIN
where contract in (select '''' || REPLACE('&CONTRACT',';',''',''') || '''' from dual)
When the prompt appears for the substitution, I enter ABC;DEF
But this doesn't seem to work. Although when I run the following separately,
select '''' || REPLACE('&CONTRACT',';',''',''') || '''' from dual
I get 'ABC','DEF' as the result.
Why is this not working? Is there a way to achieve my desired result ?
Thanks
One option is to split those values into rows and use them as a subquery.
Example based on Scott's EMP table:
SQL> select ename, job, sal from emp;
ENAME JOB SAL
---------- --------- ----------
SMITH CLERK 920
ALLEN SALESMAN 1600
WARD SALESMAN 1250
JONES MANAGER 2975
MARTIN SALESMAN 1250
BLAKE MANAGER 2850
CLARK MANAGER 2450
SCOTT ANALYST 3000
KING PRESIDENT 10000
TURNER SALESMAN 1500
ADAMS CLERK 1100
JAMES CLERK 950
FORD ANALYST 3000
MILLER CLERK 1300
14 rows selected.
SQL> select ename, job, sal
2 from emp
3 where ename in (select regexp_substr(replace(q'[&&par_ename]', chr(39), ''), '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count(q'[&&par_ename]', ',') + 1
6 );
Enter value for par_ename: 'SMITH','ALLEN'
ENAME JOB SAL
---------- --------- ----------
SMITH CLERK 920
ALLEN SALESMAN 1600
SQL>
Alternatively, use sys.odcivarchar2list:
SQL> select ename, job, sal
2 from emp
3 where ename in (select column_value
4 from table(sys.odcivarchar2list(&par_ename))
5 );
Enter value for par_ename: 'SMITH','ALLEN'
ENAME JOB SAL
---------- --------- ----------
SMITH CLERK 920
ALLEN SALESMAN 1600
SQL>
It works without single quotes as well:
SQL> Select ename, job, sal
2 from emp
3 where ename in (select regexp_substr(replace(q'[&&par_ename]', chr(39), ''), '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count(q'[&&par_ename]', ',') + 1
6 );
Enter value for par_ename: SMITH,ALLEN,KING
ENAME JOB SAL
---------- --------- ----------
SMITH CLERK 920
ALLEN SALESMAN 1600
KING PRESIDENT 10000
SQL>

Oracle- sql query to print odd number of rows when we do not have number data type columns

I was trying to print odd numbers of rows from my table without taking taking help of my numeric cloumns
when I try to execute this query I was getting only first row.
select * from emp3 where mod(rownum,2)=1;
emp3 is my table name.
and when I use my one of the numeric columns in place of rownum I was getting desired output.
select * from emp3 where mod(eid,2)=1 order by eid;
where eid is a numeric column in the table.
But what if do not have a numeric column and I want to print only odd number of rows from the table?
Help me!
Try to execute the below query
select * from (select rownum rn ,column from column_name) where mod(rn,2) <> 0
and please refer to this link for better understanding the concept of rownum https://www.youtube.com/watch?v=QMyw1jumGyQ
If the EID column isn't numeric, then use something that is. For example, ROW_NUMBER gives such an information:
SQL> with temp as
2 (select empno, ename, job sal,
3 row_number() over (order by null) rn
4 from emp
5 )
6 select *
7 from temp
8 where mod(rn, 2) = 1;
EMPNO ENAME SAL RN
---------- ---------- --------- ----------
7369 SMITH CLERK 1
7521 WARD SALESMAN 3
7654 MARTIN SALESMAN 5
7782 CLARK MANAGER 7
7839 KING PRESIDENT 9
7876 ADAMS CLERK 11
7902 FORD ANALYST 13
7 rows selected.
SQL>
Or even ROWNUM you already tried to use:
SQL> with temp as
2 (select empno, ename, job sal,
3 rownum rn
4 from emp
5 )
6 select *
7 from temp
8 where mod(rn, 2) = 1;
EMPNO ENAME SAL RN
---------- ---------- --------- ----------
7369 SMITH CLERK 1
7521 WARD SALESMAN 3
7654 MARTIN SALESMAN 5
7782 CLARK MANAGER 7
7839 KING PRESIDENT 9
7876 ADAMS CLERK 11
7902 FORD ANALYST 13
7 rows selected.
SQL>

Deleting duplicate records with ROWNUM?

I know how to delete duplicate records with ROWID.
Please guide me to delete duplicate records with ROWNUM in Oracle.
That just won't work. From documentation:
For each row returned by a query, the ROWNUM pseudocolumn returns a number indicating the order in which Oracle selects the row from a table or set of joined rows. The first row selected has a ROWNUM of 1, the second has 2, and so on.
Its value is set at the moment you run the query and can be changed, depending on how you fetch data (different ORDER BY will produce different ROWNUM value for the same row). As it is sequential, you can't set "groups" of ROWNUM values (for example, so that it goes from 1, 2, 3 for one set, then 1, 2, 3, 4, 5 for another - you'll always get 1, 2, 3, 4, 5, 6, 7, 8).
If you planned to do something like this:
SQL> create table test as
2 select e.empno, e.deptno, d.dname, e.ename
3 from emp e join dept d on e.deptno = d.deptno;
Table created.
SQL> select * from test order by deptno;
EMPNO DEPTNO DNAME ENAME
---------- ---------- -------------- ----------
7782 10 ACCOUNTING CLARK
7839 10 ACCOUNTING KING
7934 10 ACCOUNTING MILLER
7369 20 RESEARCH SMITH
7902 20 RESEARCH FORD
7566 20 RESEARCH JONES
7900 30 SALES JAMES
7844 30 SALES TURNER
7654 30 SALES MARTIN
7521 30 SALES WARD
7499 30 SALES ALLEN
7698 30 SALES BLAKE
12 rows selected.
SQL> delete from test t
2 where t.empno in (select a.empno
3 from (select t1.empno, t1.deptno, t1.dname, rownum rn
4 from test t1
5 ) a
6 where a.rn > 1
7 );
11 rows deleted.
As you can see, all rows (but one) are duplicates. Here's why:
SQL> rollback;
Rollback complete.
SQL> select a.deptno, a.empno, a.rn, a.rn1
2 from (select t1.empno, t1.deptno, t1.dname, rownum rn,
3 row_number() over (partition by t1.deptno order by null) rn1
4 from test t1
5 ) a;
DEPTNO EMPNO RN RN1
---------- ---------- ---------- ----------
10 7782 2 1
10 7839 1 2
10 7934 3 3
20 7369 5 1
20 7902 4 2
20 7566 6 3
30 7900 7 1
30 7844 8 2
30 7654 9 3
30 7521 10 4
30 7499 11 5
30 7698 12 6
12 rows selected.
See? RN (ROWNUM) has all values from 1, 2, ..., 12. RN1 (ROW_NUMBER, which allows us to set partitions) does the job correctly. So, if you used RN1 instead of RN, it would work:
SQL> delete from test t
2 where t.empno in (select a.empno
3 from (select t1.empno, t1.deptno, t1.dname, rownum rn,
4 row_number() over (partition by t1.deptno order by null) rn1
5 from test t1
6 ) a
7 where a.rn1 > 1
8 );
9 rows deleted.
SQL> select * From test;
EMPNO DEPTNO DNAME ENAME
---------- ---------- -------------- ----------
7782 10 ACCOUNTING CLARK
7369 20 RESEARCH SMITH
7900 30 SALES JAMES
SQL>
[EDIT: deleting duplicates #2]
Here's another example which shows how/what to do if you want to delete duplicates. It is based on the "ROWID technique" (although there are another ones too).
Back to the table we've been working with. Suppose that we want to keep only one distinct job per department:
SQL> select deptno, job, dname, empno, ename
2 from test
3 order by deptno, job;
DEPTNO JOB DNAME EMPNO ENAME
---------- --------- -------------- ---------- ----------
10 CLERK ACCOUNTING 7934 MILLER
10 MANAGER ACCOUNTING 7782 CLARK
10 PRESIDENT ACCOUNTING 7839 KING
20 ANALYST RESEARCH 7902 FORD
20 CLERK RESEARCH 7369 SMITH
20 MANAGER RESEARCH 7566 JONES
30 CLERK SALES 7900 JAMES
30 MANAGER SALES 7698 BLAKE
30 SALESMAN SALES 7844 TURNER -- leave
30 SALESMAN SALES 7654 MARTIN -- only
30 SALESMAN SALES 7521 WARD -- one
30 SALESMAN SALES 7499 ALLEN -- salesman
12 rows selected.
in department 10, there are no duplicates - 3 employees, each of them doing their own job
the same goes for department 20
however, in department 30, there are 4 SALESMEN and we want to keep only one - another ones are duplicates
It means that you have to take both columns - DEPTNO and JOB - into account when deleting rows. Let's do that:
SQL> delete from test a
2 where rowid > (select min(rowid)
3 from test b
4 where a.deptno = b.deptno --> take both DEPTNO ...
5 and a.job = b.job --> and JOB into account
6 );
3 rows deleted.
The result: departments 10 and 20 didn't change, but in department 30 now we have only one salesman, just as we wanted:
SQL> select deptno, job, dname, empno, ename
2 from test
3 order by deptno, job;
DEPTNO JOB DNAME EMPNO ENAME
---------- --------- -------------- ---------- ----------
10 CLERK ACCOUNTING 7934 MILLER
10 MANAGER ACCOUNTING 7782 CLARK
10 PRESIDENT ACCOUNTING 7839 KING
20 ANALYST RESEARCH 7902 FORD
20 CLERK RESEARCH 7369 SMITH
20 MANAGER RESEARCH 7566 JONES
30 CLERK SALES 7900 JAMES
30 MANAGER SALES 7698 BLAKE
30 SALESMAN SALES 7844 TURNER
9 rows selected.
SQL>

select rows between two values in Oracle 11g

This is a common question I saw in many places, but don't know yet it possible or not. I'm trying to select rows between 2 and 5 in following way using oracle sql developer tool.
As of result this query, this should select 3rd and 4th query according to below query
SELECT * FROM MyTable
WHERE ROWNUM > 2 AND ROWNUM < 5
but it's not selecting the 3rd and 4th rows,
Then I tried the following query
SELECT * FROM MyTable
WHERE RN BETWEEN 2 AND 5
This also syntactically and progrmatically correct, but not selecting the exact columns.
Use a subquery:
SELECT t.*
FROM (SELECT t.*, ROWNUM as rn
FROM MyTable t
) t
WHERE rn > 2 AND rn < 5;
Note that tables represent unordered sets. There is no such thing as a first or second row. You should have an ORDER BY clause to specify the ordering.
The reason that your version doesn't work is that rownum starts at 1 when the first row is put into the result set. If no row is put in, the value never increments. So, it never hits 2 or 3.
I should also note that between in SQL is inclusive. So >= and <= are more appropriate.
EDIT:
I should note that Oracle 12+ supports FETCH/OFFSET:
select t.*
from mytable t
offset 2 -- start on the third row
fetch first 2 rows only -- fetch two rows in total
An order by is still recommended in this case.
A little bit of analytics.
Salaries in the EMP table, sorted by $$$, look like this:
SQL> select ename, sal
2 from emp
3 order by sal;
ENAME SAL
---------- ----------
SMITH 800
JAMES 950 2 you want to return James ...
WARD 1250 3
MARTIN 1250 4
MILLER 1300 5 ... to Miller
TURNER 1500
ALLEN 1600
CLARK 2450
BLAKE 2850
JONES 2975
FORD 3000
KING 5000
12 rows selected.
SQL>
If you do it as follows, you'd get what you wanted:
SQL> select ename, sal, rn
2 from (select ename, sal, row_number() over (order by sal) rn
3 from emp
4 )
5 where rn between 2 and 5;
ENAME SAL RN
---------- ---------- ----------
JAMES 950 2
WARD 1250 3
MARTIN 1250 4
MILLER 1300 5
SQL>
However, as you can see, Ward and Martin earn the same $1250. So, should we count them as having the same salary and include Turner into the list, or not? Yet two another analytic functions might help you decide: RANK and DENSE_RANK:
SQL> select ename, sal,
2 row_number() over (order by sal) rn,
3 rank() over (order by sal) rnk,
4 dense_rank() over (order by sal) drnk
5 from emp
6 order by sal;
ENAME SAL RN RNK DRNK
---------- ---------- ---------- ---------- ----------
SMITH 800 1 1 1
JAMES 950 2 2 2 2nd isn't questionable, but ...
WARD 1250 3 3 3
MARTIN 1250 4 3 3
MILLER 1300 5 5 4 ... which one is 5th? Miller (RN and RNK), ...
TURNER 1500 6 6 5 ... or Turner (DRNK column)?
ALLEN 1600 7 7 6
CLARK 2450 8 8 7
BLAKE 2850 9 9 8
JONES 2975 10 10 9
FORD 3000 11 11 10
KING 5000 12 12 11
12 rows selected.
SQL>
To be fair, DENSE_RANK is probably the best option in such cases:
SQL> select ename, sal, drnk
2 from (select ename, sal, dense_rank() over (order by sal) drnk
3 from emp
4 )
5 where drnk between 2 and 5;
ENAME SAL DRNK
---------- ---------- ----------
JAMES 950 2
WARD 1250 3
MARTIN 1250 3
MILLER 1300 4
TURNER 1500 5
SQL>
Now you have several options; pick the one that suits you best.