PL/SQL block and LOOP exercise - sql

I have to create a PL/SQL block to insert 10 to 100 multiples of 10 in a table called TEN_MULTIPLES that I have to create... (SCHEMA -> TEN_MULTIPLES(numbervalue)). I will have to insert inside the table only 10,20,30,...,100 but exluding 50 and 90. So far I have done this... is it correct?
DECLARE
CREATE TABLE ten_multiples
(numbervalue NUMBER (3));
BEGIN
FOR i IN 9..101 LOOP
IF (i = 50 OR i = 90) THEN
ELSIF (i%10 = 0) THEN
INSERT INTO ten_multiples
VALUE (i);
END IF;
END LOOP;
END;
When I use 10..100 are 10 and 100 included and evaluated as 'i' in the loop?
I need also to find the MAXIMUM number from that table using a cursor, so in this case 100, store it in a variable 'num' declared in the DECLARE part and print it out...
DECLAR
CURSOR my_cursor IS
SELECT MAX(v_number) FROM ten_multiples;
num NUMBER;
BEGIN
OPEN my_cursor;
FETCH my_cursor INTO (num);
DBMS_OUTPUT.PUT_LINE(‘Maximum number is ‘ | num);
CLOSE my_cursor;
END;
Is this right?
I really thank you in advance :)

Why is so much PL/SQL coursework consists of exercises in how not to use PL/SQL?
insert into ten_multiples
with data as ( select level*10 as mult
from dual
connect by level <=10)
select * from data
where mult not in (50,90)
/

First part:
You cannot execute the CREATE TABLE statement directly in a PL/SQL
context. You must use the DBMS_DDL package or dynamic SQL via the
EXECUTE IMMEDIATE command, if you must execute within PL/SQL context.
Yes number literals in the for loop are included.
Second part:
Use DECLARE not DECLAR.
Your SELECT member must be a column of the table, not v_number.
Your single quote character is incorrect, use ', not ‘.
Use double pipe for concatenation, not single.
Finally:
Actually run these commands through SQL*Plus and listen to the tool.
Trying is your friend.

Related

'Cursor is already open' error when I try to execute my procedure in oracle sql

I wrote a function in oracle sql to fetch the name of an employee whose id in the table matches the input given to the function .
create or replace function id_search
(
x in number
)
return number
is
cursor e is select ename from employee where emp_id = x;
begin
open e;
for i in e
loop
dbms_output.put_line('name is :'||i.ename);
end loop;
close e;
return 0;
end;
/
After this , I called this function in a procedure I had written :
SQL> create or replace procedure id_searchP
2 (
3 y in number
4 )
5 is
6 t number:= 1;
7 begin
8 t := id_search(y);
9 end;
10 /
However , when I try to execute my procedure , I am encountering the error :
SQL> exec id_searchP(2);
BEGIN id_searchP(2); END;
*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "tom.ID_SEARCH", line 7
ORA-06512: at "tom.ID_SEARCH", line 10
ORA-06512: at "tom.ID_SEARCHP", line 8
ORA-06512: at line 1
why Am I getting this error ?
You are opening and closing the cursor two times. You do not need to open e; and then again for i in e. Do i like this in your function:
create or replace function id_search(x in number)
return number
is
cursor e
is
select ename
from employee
where emp_id = x;
begin
for i in e loop
dbms_output.put_line('name is :'||i.ename);
end loop;
return 0;
end;
Then the procedure is ok:
create or replace procedure id_searchP(y in number)
is
t number:= 1;
begin
t := id_search(y);
end;
And then only one call will be enough:
BEGIN
id_searchP(2);
END;
Here is the DEMO
There are very few times when you need to use an explicitly-declared cursor. Get in the habit of using cursor FOR-loops:
create or replace function id_search(pinEmployee_ID in number)
return number
is
begin
for rowEmployee in (select ename
from employee
where emp_id = pinEmployee_ID)
loop
dbms_output.put_line('name is :' || rowEmployee.ename);
end loop;
return 0;
end id_search;
There - shorter, simpler, less error-prone, and easier to understand.
Please use meaningful names. I don't care if it's a school assignment or whatever - use meaningful names. Don't make people dig into your code to figure out what your parameters mean or are used for.
Further - please take the time to format your code so it's readable. Indent consistently, make sure that code levels are apparent to the naked eye, put whitespace in so your code can be read easily, spell things properly, and spell things out. I don't want to see ifor a parameter name - if what's being passed in is (supposed to be) an employee ID, then make sure that the name of the parameter makes that apparent. Computer programming is primarily an exercise in communication - between you, the computer, and the poor dumb SOB who in ten years will have to read your code and figure out what it does. Don't be the guy that everyone cusses at when they have to maintain your code. Develop good habits now.
Programming is a craft. If this is something you really want to do, work on doing it well. Every assignment, every script, every day is an opportunity to improve. Or at the very least to show what you know. Make sure that what you show is your best, always.
You are encountering this issues because you are explicitly opening cursor e, and then attempting to start a cursor for loop on cursor e. However, the cursor for loop implicitly attempts to open cursor e, which you already explicitly opened.
If you want to use a cursor for loop you will need to remove your explicit open and close statements.

How can I workaround looping a cursor in Oracle PL/SQL 8i which uses a subscalar query?

I am trying to create a PL/SQL procedure which needs to do a for loop through a cursor. I have read in the oracle forums that PL/SQL 8i doesn't support subscalar queries.
This is what I have so far :
DECLARE
CURSOR C1
IS
SELECT texto,id_evento,clave_evento FROM gegf.eventos_omega_rima WHERE id_evento IN
(select max(eo.id_evento) from gegf.eventos_omega_rima eo, correctivo_rima.equipos b
where eo.fecha_ins_tab > sysdate - 25/24 and eo.fecha_ins_tab < sysdate - 1/24 and upper(eo.ORIGEN) = upper(b.nodo) and upper(b.red) = 'RIMA' group by eo.clave_evento);
r_emp C1%ROWTYPE;
BEGIN
OPEN C1;
LOOP
FETCH c1 INTO r_emp;
EXIT WHEN C1%NOTFOUND;
INSERT INTO CORRECTIVO_RIMA.T_CLOB VALUES (r_emp.TEXTO);
END LOOP;
CLOSE c1;
END;
/
How could I workaround the fact that I can't use subscalar queries in the PL/SQL version I am using?
The PLS-00103 is telling you where the problem is; line 6 column 49. In this part of your query:
where eo.fecha_ins_tab > sysdate - and
... something is missing after the minus sign; presumably you're trying to subtract some number of days from today, but you haven't supplied that number.
I don't have an 8i database lying around any more (perhaps not surprisingly) but I don't recall ever needing to quote a cursor query; and if you do I'm pretty sure the semicolon would need to be outside the closing quote. But that was also what was causing the earlier line 4, column 5 error, which was pointing at that opening quote.
You will also try to insert the last value twice; you need to test C1%NOTFOUND before the INSERT, immediately after the FETCH (unless you are using bulk collect). Of course you're inserting a dummy value, but you'll get one too many rows; with your real CLOB you'd process the last fetch value twice.

Dynamic Cursors

I use a cursor for the statement:
SELECT NAME FROM STUDENT WHERE ROLL = 1;
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL = roll;
--roll is a variable I receive via a procedure, and the procedure works fine for the received parameter.
Upon executing this, I am able to retrieve all records with roll = 1.
Now, I need to retrieve the records of a group (possibly via a cursor), just like:
SELECT NAME FROM STUDENT WHERE ROLL IN (2, 4, 6);
But the values in the IN clause are known only at run time. How should I do this? That is, is there any way I could assign parameters to the WHERE clause of the cursor?
I tried using an array in the declaration of the cursor, but an error pops up telling something like: standard types cannot be used.
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
--rolls is an array initialized with the required roll numbers.
First, I assume that the parameter to your procedure doesn't actually match the name of a column in the STUDENT table. If you actually coded the statement you posted, roll would be resolved as the name of the column, not the parameter or local variable so this statement would return every row in the STUDENT table where the ROLL column was NOT NULL.
CURSOR C
IS SELECT NAME
FROM STUDENT
WHERE ROLL = roll;
Second, while it is possible to use dynamic SQL as #Gaurav Soni suggests, doing so generates a bunch of non-sharable SQL statements. That's going to flood the shared pool, probably aging other statements out of cache, and use a lot of CPU hard-parsing the statement every time. Oracle is built on the premise that you are going to parse a SQL statement once, generally using bind variables, and then execute the statement many times with different values for the bind variables. Oracle can go through the process of parsing the query, generating the query plan, placing the query in the shared pool, etc. only once and then reuse all that when you execute the query again. If you generate a bunch of SQL statements that will never be used again because you're using dynamic SQL without bind variables, Oracle is going to end up spending a lot of time caching SQL statements that will never be executed again, pushing useful cached statements that will be used again out of the shared pool meaning that you're going to have to re-parse those queries the next time they're encountered.
Additionally, you've opened yourself up to SQL injection attacks. An attacker can exploit the procedure to read any data from any table or execute any function that the owner of the stored procedure has access to. That is going to be a major security hole even if your application isn't particularly security conscious.
You would be better off using a collection. That prevents SQL injection attacks and it generates a single sharable SQL statement so you don't have to do constant hard parses.
SQL> create type empno_tbl is table of number;
2 /
Type created.
SQL> create or replace procedure get_emps( p_empno_arr in empno_tbl )
2 is
3 begin
4 for e in (select *
5 from emp
6 where empno in (select column_value
7 from table( p_empno_arr )))
8 loop
9 dbms_output.put_line( e.ename );
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serveroutput on;
SQL> begin
2 get_emps( empno_tbl( 7369,7499,7934 ));
3 end;
4 /
SMITH
ALLEN
MILLER
PL/SQL procedure successfully completed.
create or replace procedure dynamic_cur(p_empno VARCHAR2) IS
cur sys_refcursor;
v_ename emp.ename%type;
begin
open cur for 'select ename from emp where empno in (' || p_empno || ')';
loop
fetch cur into v_ename;
exit when cur%notfound;
dbms_output.put_line(v_ename);
end loop;
close cur;
end dynamic_cur;
Procedure created
Run the procedure dynamic_cur
declare
v_empno varchar2(200) := '7499,7521,7566';
begin
dynamic_cur(v_empno);
end;
Output
ALLEN
WARD
JONES
Note:As mentioned by XQbert,dynamic cursor leads to SQL injection ,but if you're not working on any critical requirement ,where security is not involved then you can use this .
Maybe you can pass rolls as a set of quoted comma separated values.
e.g. '1', '2' etc
If this value is passes into the procedure in a varchar input variable, the it can be used to get multiple rows as per the table match.
Hence the cursor
SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
will be evaluated as
SELECT NAME FROM STUDENT WHERE ROLL IN ('1','2');
Hope it helps

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.

Declaring & Setting Variables in a Select Statement

I'm attempting to write a simple query where I declare some variables and then use them in a select statement in Oracle. I've been able to do this before in SQL Server with the following:
DECLARE #date1 DATETIME
SET #date1 = '03-AUG-2010'
SELECT U.VisualID
FROM Usage u WITH(NOLOCK)
WHERE U.UseTime > #Date1
From the searching I've done it appears you can not declare and set variables like this in Select statements. Is this right or am I mssing something?
From the searching I've done it appears you can not declare and set variables like this in Select statements. Is this right or am I missing something?
Within Oracle PL/SQL and SQL are two separate languages with two separate engines. You can embed SQL DML within PL/SQL, and that will get you variables. Such as the following anonymous PL/SQL block. Note the / at the end is not part of PL/SQL, but tells SQL*Plus to send the preceding block.
declare
v_Date1 date := to_date('03-AUG-2010', 'DD-Mon-YYYY');
v_Count number;
begin
select count(*) into v_Count
from Usage
where UseTime > v_Date1;
dbms_output.put_line(v_Count);
end;
/
The problem is that a block that is equivalent to your T-SQL code will not work:
SQL> declare
2 v_Date1 date := to_date('03-AUG-2010', 'DD-Mon-YYYY');
3 begin
4 select VisualId
5 from Usage
6 where UseTime > v_Date1;
7 end;
8 /
select VisualId
*
ERROR at line 4:
ORA-06550: line 4, column 5:
PLS-00428: an INTO clause is expected in this SELECT statement
To pass the results of a query out of an PL/SQL, either an anonymous block, stored procedure or stored function, a cursor must be declared, opened and then returned to the calling program. (Beyond the scope of answering this question. EDIT: see Get resultset from oracle stored procedure)
The client tool that connects to the database may have it's own bind variables. In SQL*Plus:
SQL> -- SQL*Plus does not all date type in this context
SQL> -- So using varchar2 to hold text
SQL> variable v_Date1 varchar2(20)
SQL>
SQL> -- use PL/SQL to set the value of the bind variable
SQL> exec :v_Date1 := '02-Aug-2010';
PL/SQL procedure successfully completed.
SQL> -- Converting to a date, since the variable is not yet a date.
SQL> -- Note the use of colon, this tells SQL*Plus that v_Date1
SQL> -- is a bind variable.
SQL> select VisualId
2 from Usage
3 where UseTime > to_char(:v_Date1, 'DD-Mon-YYYY');
no rows selected
Note the above is in SQLPlus, may not (probably won't) work in Toad PL/SQL developer, etc. The lines starting with variable and exec are SQLPlus commands. They are not SQL or PL/SQL commands. No rows selected because the table is empty.
I have tried this and it worked:
define PROPp_START_DT = TO_DATE('01-SEP-1999')
select * from proposal where prop_start_dt = &PROPp_START_DT
The SET command is TSQL specific - here's the PLSQL equivalent to what you posted:
v_date1 DATE := TO_DATE('03-AUG-2010', 'DD-MON-YYYY');
SELECT u.visualid
FROM USAGE u
WHERE u.usetime > v_date1;
There's also no need for prefixing variables with "#"; I tend to prefix variables with "v_" to distinguish between variables & columns/etc.
See this thread about the Oracle equivalent of NOLOCK...
Try the to_date function.
Coming from SQL Server as well, and this really bugged me. For those using Toad Data Point or Toad for Oracle, it's extremely simple. Just putting a colon in front of your variable name will prompt Toad to open a dialog where you enter the value on execute.
SELECT * FROM some_table WHERE some_column = :var_name;