Passing parameter to PL/SQL procedure using select from database - plsql

Is it possible to define a variable as current_scn:= 'select current_scn from V$database' in the declaration section of a procedure. Using the one below gives me the output. But I would like to define it in the declare section, so each time it gets executed it will get the current scn. Need to execute the procedure based on the input. Is it possible do so?
DECLARE
currnet_scn VARCHAR2(500);
BEGIN
EXECUTE IMMEDIATE 'select current_scn from V$database' INTO currnet_scn;
DBMS_OUTPUT.PUT_LINE(currnet_scn);
END;

No, you can't do it exactly as you wanted (if you could, you'd already do it, I presume).
But, you can create a function which can then be used in declare section. See how:
SQL> create or replace function f_scn
2 return number
3 is
4 retval number;
5 begin
6 select current_scn into retval from V$database;
7 return retval;
8 end;
9 /
Function created.
SQL> set serveroutput on
SQL> declare
2 current_scn number := f_scn; --> function is used here
3 begin
4 dbms_output.put_line('SCN = ' || current_scn);
5 end;
6 /
SCN = 16535194799153
PL/SQL procedure successfully completed.
SQL>
Though, now you don't need to declare anything at all:
SQL> begin
2 dbms_output.put_line('SCN = ' || f_scn);
3 end;
4 /
SCN = 16535194799171
PL/SQL procedure successfully completed.
SQL>
Your example uses dynamic SQL. Why? There's nothing dynamic in it, which means that
select current_Scn into current_scn from v$database;
works just fine.
Finally: don't name variables as column names; it causes confusion. I'd suggest to make it e.g. declare v_current_scn number;

Related

PLSQL Return Sequence.currval When Sequence Name Is Built Using Substitution Strings

I would like to use the same application in different instances so I need to specify the workspace and sequence ID.
Example query
BEGIN
INSERT INTO STEP (STEP_CHART_TITLE)
VALUES ('Action', 'Action');
RETURN '"'||:v_workspace||'"."'||:v_seqid||'".currval';
END;
If I use:
"FREEADMIN"."ISEQ$$_111997".currval;
in the return statement it works fine.
If I use the substitution strings, it will build the string correctly, but won't return the sequence number.
Is there a way to get the sequence number?
Thanks
"PL/SQL" as Oracle's procedural extension to SQL? Asking because I don't quite understand what "workspace" represents (we call it a "user" or a "schema" or even "owner" in Oracle).
If so, then you can't fetch current value unless you first fetch next sequence value (you didn't post whole code you use so I'm not sure whether you did that or not; also, insert statement is wrong - you're inserting 2 values into a single column).
SQL> select seq.currval from dual;
select seq.currval from dual
*
ERROR at line 1:
ORA-08002: sequence SEQ.CURRVAL is not yet defined in this session
SQL> select seq.nextval from dual;
NEXTVAL
----------
6
SQL> select seq.currval from dual;
CURRVAL
----------
6
SQL>
Therefore, the whole code might look like this - you'd use dynamic SQL. As this is SQL*Plus, I'm using substitution variables; you'd use bind variables (i.e. :v_workspace instead of '&v_workspace') (if that's what you're doing). This code just displays the value - you'd return it.
SQL> create table step (id number, step_chart_title varchar2(10));
Table created.
SQL> declare
2 l_str varchar2(200);
3 l_id number;
4 begin
5 l_str := 'select ' || '&v_workspace' ||'.'|| '&v_seqid' ||'.nextval from dual';
6 execute immediate l_str into l_id;
7
8 insert into step (id, step_chart_title) values (l_id, 'Action');
9
10 dbms_output.put_line(l_id); --> L_ID now contains CURRVAL
11 end;
12 /
Enter value for v_workspace: scott
Enter value for v_seqid: seq
8 --> here it is
PL/SQL procedure successfully completed.
SQL>

getting Warning: Function created with compilation errors

I am getting error while executing the following function.I have been banging my head for quite sometime now.I am new to oracle so I am not able to correct it.Can someone please help?
create or replace function rever(x int)
return number
is
y varchar2(30);
c varchar2(30);
v int;
begin
y:=to_char(x);
c:=reverse(y);
v:=to_number(c);
return v;
end rever;
/
the following is showing as error message
LINE/COL ERROR
-------- -----------------------------------------------------------------
9/1 PL/SQL: Statement ignored
9/4 PLS-00201: identifier 'REVERSE' must be declared
Oracle does not provide a function in plsql to do a string reverse.
create or replace function rever(x int)
return number
is
y varchar2(30);
c varchar2(30);
v int;
begin
y:=to_char(x);
-- Loop other each char in a string from the last to the first element
for i in reverse 1.. length(y)
loop
c:= c|| substr(y,i,1);
end loop;
v:=to_number(c);
return v;
end rever;
/
Test:
begin
dbms_output.put_line(rever(1));
dbms_output.put_line(rever(12));
dbms_output.put_line(rever(21));
dbms_output.put_line(rever(123));
end;
/
Result:
dbms_output
1
21
12
321
db<>fiddle here
There is no PL/SQL reverse() function in Oracle. So this does not work:
declare
v varchar2(20);
begin
v:= reverse('krow not does ti');
dbms_output.put_line(v);
end;
/
It throws the PLS-00201 error you got.
However, there is an undocumented SQL function which we can use in PL/SQL, but only by invoking the SQL engine:
declare
v varchar2(20);
begin
select reverse('skrow ti') into v from dual;
dbms_output.put_line(v);
end;
/
Of course, because reverse() is undocumented we're not supposed to use it, at least in production code. Not sure why it's undocumented. I think Oracle uses it for reverse indexes, so maybe there's some limit on reversible string size.
Here is a db<>fiddle demo.
The performace is a bit worse
I think that is the cost of moving from the PL/SQL engine to the SQL engine and back again. So it comes down to use case. If we're writing a function which will only be used in pure PL/SQL then I think your approach is the better one. But if we're writing a function to be used in SQL then I would consider using the Oracle built-in instead, even though it's not supported.
Although to be honest I can't remember the last time I used a reverse() function - Oracle's or hand-rolled - in real life (as opposed to answering questions in forums or similar Code Golf questions :) ).
Just for amusement, without PL/SQL, using a little bit of regular expressions with hierarchical query:
SQL> with test (col) as
2 (select 'Littlefoot' from dual)
3 select listagg(one, '') within group (order by lvl desc) reversed
4 from (select level lvl, regexp_substr(col, '.', 1, level) one
5 from test
6 connect by level <= length(col)
7 );
REVERSED
--------------------------------------------------------------------------
toofelttiL
SQL>
Or, rewritten as a function:
SQL> create or replace function f_reverse (par_col in varchar2)
2 return varchar2
3 is
4 retval varchar2(1000);
5 begin
6 select listagg(one, '') within group (order by lvl desc)
7 into retval
8 from (select level lvl, regexp_substr(par_col, '.', 1, level) one
9 from dual
10 connect by level <= length(par_col)
11 );
12 return retval;
13 end;
14 /
Function created.
SQL> select empno, f_reverse(empno) rev_empno,
2 ename, f_reverse(ename) rev_ename
3 from emp
4 where rownum <= 3;
EMPNO REV_EMPNO ENAME REV_ENAME
---------- ---------- ---------- ----------
7369 9637 SMITH HTIMS
7499 9947 ALLEN NELLA
7521 1257 WARD DRAW
SQL>

PL/SQL procedure successfully completed, but when I want to display Max(DEPARTMENT_ID), can anyone pls suggest?

Max(DEPARTMENT_ID), can anyone Please suggest
<set serveroutput on;
DECLARE
v_max_deptno DEPARTMENTS.DEPARTMENT_ID%TYPE;
BEGIN
SELECT Max(department_id)
INTO v_max_deptno
FROM departments
Where department_id = v_max_deptno;
DBMS_OUTPUT.PUT_LINE('The maximum department_id is: '|| v_max_deptno);
END;/>
This declaration:
v_max_deptno DEPARTMENTS.DEPARTMENT_ID%TYPE;
is for a local variable. Since you provide no default value and it is never assigned any value, it defaults to NULL.
This predicate in the SQL query:
Where department_id = v_max_deptno
means that the query should only return rows where the department_id is equal to the value in that variable. Since the variable is NULL, and since no value can equal NULL, the query returns no rows.
Since your query is based on a simple aggregate (MAX), the query returns without error. Since there were no rows, the MAX function returns NULL back into the same variable again.
For further assistance, you need to explain what the purpose of your code is and what value you expect to get. If you need the maximum department_id currently in the table across all rows, you would simply remove the where clause.
Note: if your purpose in finding the current maximum ID is to set the department_id for a new record, there are some other issues with this approach (to do with concurrent sessions) that you need to consider.
Your query doesn't make much sense, as it is trying to find the MAX(department_id) (and put its value into the V_MAX_DEPTNO variable), while the same variable is used in the WHERE clause.
At the time of declaration, V_MAX_DEPTNO is NULL, it is NULL in the WHERE clause as well, and no department's ID is "equal" to NULL, so that query returns nothing.
If you initialized the V_MAX_DEPTNO (which is, of course, stupid), you'd get something:
SQL> DECLARE
2 v_max_deptno departments.department_id%TYPE := 20;
3 BEGIN
4 SELECT MAX (department_id)
5 INTO v_max_deptno
6 FROM departments
7 WHERE department_id = v_max_deptno;
8
9 DBMS_OUTPUT.put_line ('The maximum department_id is: ' || v_max_deptno);
10 END;
11 /
The maximum department_id is: 20
PL/SQL procedure successfully completed.
SQL>
So the question is: what are you trying to do? In my opinion, the right way to do it is to remove this WHERE clause, i.e.
SQL> DECLARE
2 v_max_deptno departments.department_id%TYPE := 20;
3 BEGIN
4 SELECT MAX (department_id)
5 INTO v_max_deptno
6 FROM departments;
7
8 DBMS_OUTPUT.put_line ('The maximum department_id is: ' || v_max_deptno);
9 END;
10 /
The maximum department_id is: 40
PL/SQL procedure successfully completed.
SQL>

PL/SQL variable scope in nested blocks

I need to run some SQL blocks to test them, is there an online app where I can insert the code and see what outcome it triggers?
Thanks a lot!
More specific question below:
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
DBMS_OUTPUT.PUT_LINE(var);
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
DBMS_OUTPUT.PUT_LINE(block1.var);
END block2;
DBMS_OUTPUT.PUT_LINE(var);
END block1;
Is the output:
3
3
200
or is it:
3
3
3
I read that the variable's value is the value received in the most recent block so is the second answer the good one? I'd love to test these online somewhere if there is a possibility.
Also, is <<block2>> really the correct way to name a block??
Later edit:
I tried this with SQL Fiddle, but I get a "Please build schema" error message:
Thank you very much, Dave! Any idea why this happens?
create table log_table
( message varchar2(200)
)
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
insert into log_table(message) values (var)
select * from log_table
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
insert into log_table(message) values (block1.var || ' 2nd')
select * from log_table
END block2;
insert into log_table(message) values (var || ' 3rd')
select * from log_table
END block1;
In answer to your three questions.
You can use SQL Fiddle with Oracle 11g R2: http://www.sqlfiddle.com/#!4. However, this does not allow you to use dbms_output. You will have to insert into / select from tables to see the results of your PL/SQL scripts.
The answer is 3 3 3. Once the inner block is END-ed the variables no longer exist/have scope. You cannot access them any further.
The block naming is correct, however, you aren't required to name blocks, they can be completely anonymous.
EDIT:
So after playing with SQL Fiddle a bit, it seems like it doesn't actually support named blocks (although I have an actual Oracle database to confirm what I said earlier).
You can, however, basically demonstrate the way variable scope works using stored procedures and inner procedures (which are incidentally two very important PL/SQL features).
Before I get to that, I noticed three issues with you code:
You need to terminate the insert statements with a semi-colon.
You need to commit the the transactions after the third insert.
In PL/SQL you can't simply do a select statement and get a result, you need to select into some variable. This would be a simple change, but because we can't use dbms_output to view the variable it doesn't help us. Instead do the inserts, then commit and afterwards select from the table.
In the left hand pane of SQL Fiddle set the query terminator to '//' then paste in the below and 'build schema':
create table log_table
( message varchar2(200)
)
//
create or replace procedure proc1 as
var NUMBER;
procedure proc2 as
var number;
begin
var := 200;
insert into log_table(message) values (proc1.var || ' 2nd');
end;
begin
var := 3;
insert into log_table(message) values (var || ' 1st');
proc2;
insert into log_table(message) values (var || ' 3rd');
commit;
end;
//
begin
proc1;
end;
//
Then in the right hand panel run this SQL:
select * from log_table
You can see that proc2.var has no scope outside of proc2. Furthermore, if you were to explicitly try to utilize proc2.var outside of proc2 you would raise an exception because it is out-of-scope.

PL/SQL Blocks - seeing output simply ? [A Very simple question I'm sure !]

I'm sure what I want is very simple but I cannot figure out how.
I want :
To declare some variables and initialize them to certain values
To excecute a number of selects (predicated by the above variable values) and see the results as if i had executed the results straight on the sqlplus command line
I believe it's necessary to use the block structure in order that I may declare and make use of variables within the predicates of the queries. Although the examples shown here are quite simple in the real case there are numberous, much more complex SELECT's.
I tried doing this (forgetting about predicates for a moment) ...
DECLARE
EMP_EMPLOYEE_ID_IN VARCHAR2(12);
BEGIN
EXECUTE IMMEDIATE 'SELECT * FROM DEPT WHERE DEPNO';
END;
/
... but when I do that I get to execute the select without seeing the output.
I've also tried this ...
DECLARE
EMP_EMPLOYEE_ID_IN VARCHAR2(12);
BEGIN
SELECT * FROM DEPT;
END;
/
... but then I get ...
PLS-00428: an INTO clause is expected in this SELECT statement
... I really don't want to have to declare a variable for every column which would appear in my output.
Can anyone tell me how I can execute the SELECTs but simply and easily see the output as if I were on the sqlplus command line, ie to see the same output as if I did this
SQL> SELECT * FROM DEPT;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Thanks
I have now tested the answer given by Shannon Severance below and found that it will do what I want.
For the sake of later readers I thought it might be useful to show the complete script here.
set line 32000;
set trimspool on;
var V_CURSOR1 REFCURSOR;
var V_CURSOR2 REFCURSOR;
var V_CURSOR3 REFCURSOR;
DECLARE
DEPT_NUM_IN VARCHAR2(12);
BEGIN
DEPT_NUM_IN := '10';
OPEN :V_CURSOR1 FOR SELECT * FROM DEPT;
OPEN :V_CURSOR2 FOR SELECT * FROM DEPT ORDER BY LOC;
OPEN :V_CURSOR3 FOR SELECT * FROM DEPT WHERE DEPTNO = DEPT_NUM_IN ORDER BY LOC;
END;
/
print V_CURSOR1
print V_CURSOR2
print V_CURSOR3
From sqlplus, other tools may be different.
First declare a sqlplus refcursor variable
SQL> var l_cursor refcursor
Then open that cursor within a PL/SQL block, where you will have access to declared variables and everything:
SQL> edit
Wrote file afiedt.buf
1 declare
2 l_number number;
3 begin
4 open :l_cursor for select table_name from all_tables where rownum < 10;
5* end;
SQL> /
PL/SQL procedure successfully completed.
Notice above that the refcursor variable is prepended with a :, this is because we are binding a sqlplus variable into the PL/SQL anonymous block
Next, use the SQLPLUS print command:
SQL> print l_cursor
TABLE_NAME
------------------------------
ICOL$
CON$
UNDO$
PROXY_ROLE_DATA$
FILE$
UET$
IND$
SEG$
COL$
9 rows selected.
DECLARE
v_result VARCHAR2(400);
BEGIN
SELECT dummy
INTO v_result
FROM dual;
dbms_output.put_line(v_result);
END;

Resources