PL/SQL Procedure - Ticket system - oracle

I'm trying to make a Procedure in PL/SQL for a ticket system for my website.
I want a simple procedure that saves some data into the database.
It will have name of the user (random VARCHAR2) and a number and the date of today as input.
When this procedure is called it will automatically higher a number in the database (Maybe primary key) and add the input into the database. But I have no idea how to make something like this with the option to have more people access it at once.
Can anyone help please?
For the people that want to know what I have already.
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS VoegBoeteToe
DECLARE
v_nextNum NUMBER;
BEGIN
SELECT BETALINGSNR
INTO v_nextNum
FROM BOETES
ORDER BY BETALINGSNR DESC;
v_nextNum := v_nextNum + 1;
INSERT INTO BOETES VALUES(v_nextNum,v_speler,v_date,v_boete);
end;
/

The fourth line of your procedure should be IS instead of IS VoegBoeteToe. In addition, remove the fifth line (DECLARE), which is not needed in a procedure. And use a sequence to derive the sequential number safely.
So, when all's said and done, your procedure should look something like:
CREATE SEQUENCE BOETES_SEQ;
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS
BEGIN
INSERT INTO BOETES VALUES(BOETES_SEQ.NEXTVAL,v_speler,v_date,v_boete);
end;
It would also be a very good idea to include the list of field names in the BOETES table into which you're inserting values. This will make life better for anyone who has to maintain your code, and may save you from making a few errors.
Share and enjoy.

You need to create SEQUENCE
CREATE SEQUENCE DUMMY_SEQ
/
Then in Your code You can use something like that:
select DUMMY_SEQ.NEXTVAL into myVar from dual;

First, create a sequence. A sequence generates an ever incrementing number whenever it is invoked. See this for some nice examples. The documentation could be useful too.
CREATE SEQUENCE user_sequence
START WITH 1000 -- your sequence will start with 1000
INCREMENT BY 1; -- each subsequent number will increment by 1
Then,
SELECT user_sequence.NEXTVAL INTO v_nextNum FROM DUAL;

Related

oracle: populate a table from another schema using PL/sql procedure

hi i'm newb in pl/sql :), this is for educational pruposes only.
the schama Dispatching including a table named Employes.and PRF schema that include a table named ZONE.
Dispatching : Employes(num_emp number,name nvarchar2,design_unit varchar2,design_zone varchar2)
and PRF: ZONE(num_zone number,design_zone varchar2,number_of_units number).
the problema is writing a pl/sql procedure to populate ZONE table from Employes table. this is my procedure :
create or replace procedure zoneD as
cursor cur is select design_zone,design_unit from dispatching.employes group by design_zone,design_unit;
varzone cur%rowtype;
begin
open cur;
fetch cur into varzone;loop
exit when cur%notfound;
insert into zone(num_zone,design_zone,nbr_of_unit) values (num_zone.nextval,varzone.design_zone,0);
update zone set nbr_of_unit =( select count(design_unit) from dispatching.employes);
end loop;
close cur;
end zoneD;
the unit is a town , each zone contains many units. in a simple way the prob the procedure does not insert the data i dont know if it is the right way to do that. (sorry about my english :)).
It seems that you are connected as PRF and want to fetch values that belong to DISPATCHING user. In order to do that, DISPATCHING has to grant (at least) SELECT on its EMPLOYEES table to PRF:
-- connect as DISPATCHING
grant select on employees to prf;
A procedure (as you're practicing PL/SQL) should utilize a cursor FOR loop as it is much easier to maintain than a loop which uses explicitly declared cursor (as you don't need to declare it as well as variable(s) you need to store its values into), open it, worry when to exit the loop and - finally - close it. Cursor FOR loop does all of that for you (OK, except writing a SELECT statement which is just the same as the one you'd use while declaring an explicit cursor).
-- connect as PRF
create or replace procedure zoned as
begin
-- cursor FOR loop - you can select both DESIGN_ZONE and count number of
-- units so that you wouldn't have to update that value separately
for cur_r in (select e.design_zone, count(*) number_of_units
from dispatching.employees e -- naming the owner which granted SELECT on its table to PRF user
group by e.design_zone
)
loop
insert into zone (num_zone, design_zone, number_of_units)
values (num_zone.nextval, cur_r.design_zone, cur_r.number_of_units);
end loop;
end;
/
That should do it (unless I made a typo).
Finally, a suggestion, if I may: do format your code properly. The one you posted is a mess difficult to read - no indentation, too long lines (break them!), and it contains only several lines. Imagine what happens when you have thousands of lines of code - who do you expect to debug it? Just a month or two after you've done with that code, you'll forget what you did and why (so comment it), and - if it is unformatted - you'll get a headache. Today's GUI tools offer automatic formatting, so - use it. Otherwise, there are free online formatters, such as Instant SQL Formatter.

Get oracle sequenve next value without change it

I have a big problem. I need to know the next value of an oracle sequence without changing it.
If I use sequence.NEXTVAL , I get what I need. The problem is that the value changed, so the next record will get this value + 1 and it's not good for me.
I know I can use sequence.CURRVAL and it's great because it does not change the value, but in case of no records, it's not working and in this case the sequence value can be any number (and not only 1 cause the sequence value steel exist).
Please help me to find a solution.
Thank you vary much !!!!!
You should definitely not rely on never missing a value in a sequence, as they optimise for concurrency over sequential numbering. There are quite a few situations in which a number can be "lost".
Furthermore, the value visible in the dba_sequences may not be the actual next value, as the numbers are assigned from an in-memory cache. The underlying sequence metadata table has no data on the usage of that cache. You should also bear in mind that in a RAC system each instance has its own cache of sequence numbers.
You might describe the problem you are trying to solve, as it could be that sequences are not an appropriate mechanism for you.
In multi-user environment there is no way. The value is either known - when you call .NEXTVAL or unknown. Sequences can not be locked.
Well the only suggestion that I can give you is to get the nextval and then revert back. But CAUTION: Perform these operations only while you sequence is not begin used from any other session. Otherwise if sequence is fetched from another session during this operation, it is possible that you could never revert back to the original value. So be cautious before checking:
In my database, current value of seq_test is 6
--Get the next value
SQL> SELECT seq_test.NEXTVAL FROM dual;
NEXTVAL
----------
7
-- Ok, so this is the next value, now Restore to Original
SQL> ALTER SEQUENCE seq_test INCREMENT BY -1;
Sequence altered.
SQL> SELECT seq_test.NEXTVAL FROM dual;
NEXTVAL
----------
6
-- Restore original increment by clause
SQL> ALTER SEQUENCE seq_test INCREMENT BY 1;
Sequence altered.
--Check
SQL> SELECT seq_test.CURRVAL FROM dual;
CURRVAL
----------
6
I again warn you that this could be fatal ;-)
I can't see any reason why you want to know the next value of a sequence. The only importance for the value is when you have used it for a new record. So you either create a new record and return the ID value immediately:
declare
l_id number;
begin
Insert into table (id)
values (sequence.nextval)
return id into l_id;
-- and then do something more with the ID value
end;
or first get a new ID and use it afterwards
declare
l_id number;
begin
select sequence.nextval
into l_id
from dual;
-- then do something with the ID value
end;
Look at the "last_number" column from
select * from USER_SEQUENCES
Edit:
This works only if the sequence is created with the nocache option.
So if the sequence does has a cache setting, drop it and recreate it with nocache, and then you can use user_sequences.last_number +1.
Note that if the sequence is heavily used than this does impact performance. If on the other hand, the sequence is not used so much, it will be fine.
Short of writing the application (which is the best solution), this is only solution.

PL/SQL: Error when creating sequence

I'm quite new to PL/SQL, and am using Oracle SQL Developer to write a procedure which uses a sequence to generate a primary key for some existing data, to write into another DB.
The code in question is under NDA.. Essentially I have the following:
create or replace
PROCEDURE Generate_Data
(
output IN VARCHAR2
)
AS
-- Variables here --
CURSOR myCursor IS
SELECT data1, data2
FROM table;
CREATE SEQUENCE mySequence <-- error on this line
START WITH 0
INCREMENT BY 1;
BEGIN
LOOP
-- snip --
It raises the error PLS-00103, saying it encountered the symbol CREATE when expecting on of the following: begin, function, package, pragma, procedure, ...
I've been following the example at:
http://www.techonthenet.com/oracle/sequences.php
The reason you're getting this error is that you're trying to perform DDL, in this case creating a sequence, within PL/SQL. It is possible to do this, but you must use execute immediate.
As Alex says, you also wouldn't be able to do this in the declare section. It would look something like this:
begin
execute immediate 'CREATE SEQUENCE mySequence
START WITH 0
INCREMENT BY 1';
end;
However, as Padmarag also says, it's highly unlikely that you want to do this within PL/SQL. It would be more normal to create a sequence outside and then reference this later. More generally speaking, performing DDL inside a PL/SQL block is a bad idea; there should be no need for you to do it.
You don't mention what version of Oracle you're using. From 11g the ways in which you could access sequences got extended. If you're using 11g then you can access the sequence by creating a variable and assigning the next value in the sequence, .nextval, to this variable:
declare
l_seq number;
begin
loop
-- For each loop l_seq will be incremented.
l_seq := mysequence.nextval;
-- snip
end;
If you're before 11g you must (outside of DML) use a select statement in order to get the next value:
declare
l_seq number;
begin
loop
-- For each loop l_seq will be incremented.
select mysequence.nextval into l_seq from dual;
-- snip
end;
Please bear in mind that a sequence is meant to be a persistent object in the database. There is no need to drop and re-create it each time you want to use it. If you were to run your script, then re-run it the sequence would happily keep increasing the returned value.
Further Reading
About sequences
Using sequences
You can't create sequence in the DECLARE block of procedure. Move it after BEGIN. It's arguable if it makes sense, though. You probably need to create it outside your procedure in the first place.
Update
Actually, if you truly want it inside BEGIN/END use following:
EXECUTE IMMEDIATE 'CREATE SEQUENCE mySequence START WITH 0 INCREMENT BY 1';
You'd need to create the sequence before using it.
And in the PL/SQL code use
-- Variables here --1
v_seq_val number;
BEGIN
Select mySequence.nextval from dual into v_seq_val
In general SQL is for DDL(Data Definition Language) and PL/SQL is for DML(Data Manipulation Language) and logic.
If you wanted you could do Create from PL/SQL, but I think that's not what you want over here.

Finding the datatype of a cursor or table column in a block

Is is possible to find out the datatype of a column of a cursor or variable within block without using system tables? While I understand that I can use the system tables to find out this information it would be a lot slower.
Something like,
declare
my_column_data_type varchar2(30);
begin
my_column_data_type := all_tables.table_name%type;
dbms_output.put_line(my_column_data_type);
end;
I can't find any way of doing it without resorting to dbms_sql, which would be overkill for my eventual purpose.
But, Oracle already has all the information to hand. If I were to try to assign a varchar2 to a number then it would complain instantly so it knows what the datatypes are.
And, yes I know the number of versions of Oracle are ridiculous but that's the amount we've got at the moment... 9i is dying shortly in favour of 11 but this code'll run on 9i immediately if I can find an answer! But I included 11 as I can wait for a better solution if needs be,
Use the dump function and compare the result with this code.
DUMP returns a VARCHAR2 value containing the datatype code, length in bytes, and internal representation of expr.
It sounds as if you want a self describing object. Meaning programmatically find the type of a variable without selecting from some metadata view. Just ask the object, what are you?
It seems unnecessary for most situations as in most cases we already know the type (strongly typed). For example, a procedures parameters will typically specify the type (number, varchar2, whatever). Local variables will typically specify the type or tie themselves to a database object type via %type notation.
There are some situations where weakly typed objects are needed or useful, such as a weakly typed cursor variable that can be used for any query. An overly simplistic example:
create or replace procedure get_data(o_cur OUT SYS_REFCURSOR) as
begin
OPEN o_cur FOR
-- without changing parameter, this could select from any table
select * from emp;
end;
Now the problem is that you may have errors (at runtime) if someone codes the cursor to be used with another table (I chose a terrible procedure name on purpose). Something like:
declare
l_cur sys_refcursor;
l_row dept%rowtype;
begin
get_data(l_cur);
-- oops, I thought this was dept data when I coded it, Oracle didn't complain at compile time
LOOP
fetch l_cur
into l_row;
exit when l_cur%notfound;
-- do something here
END LOOP;
close l_cur;
end;
This is also why I prefer strongly typed cursors and avoid this situation.
Anyway, in the case of a self-describing object, you can use SYS.ANYDATA built in type (similarly, SYS.ANYDATASET for generic collection types). This was introduced with 9i I believe. For example, this procedure takes some data and branches logic based on the type:
CREATE OR REPLACE procedure doStuffBasedOnType(i_data in sys.anydata) is
l_type SYS.ANYTYPE;
l_typecode PLS_INTEGER;
begin
-- test type
l_typecode := i_data.GetType (l_type);
CASE l_typecode
when Dbms_Types.Typecode_NUMBER then
-- do something with number
dbms_output.put_line('You gave me a number');
when Dbms_Types.TYPECODE_DATE then
-- do something with date
dbms_output.put_line('You gave me a date');
when Dbms_Types.TYPECODE_VARCHAR2 then
-- do something with varchar2
dbms_output.put_line('You gave me a varchar2');
else
-- didn't code for this type...
dbms_output.put_line('wtf?');
end case;
end;
Here you have your programatic branching based on the type. And to use it:
declare
l_data sys.anydata;
begin
l_data := sys.anydata.convertvarchar2('Heres a string');
doStuffBasedOnType(l_data);
end;
-- output: "You gave me a varchar2"
Hope that wasn't too long winded a response ;)

how do oracle stored procedures (w/ cursors) work?

I have a following oracle stored procedure
CREATE OR REPLACE
PROCEDURE getRejectedReasons
(
p_cursor IN OUT SYS_REFCURSOR)
AS
BEGIN
OPEN p_cursor FOR SELECT * FROM reasons_for_rejection;
END;
However, when I run this stored procedure in sql-developer then I dont see anything. I just see something like this:
Connecting to the database oracleLocal.
Process exited.
Disconnecting from the database oracleLocal.
I'm coming from MS sql server and am used to seeing actual results when running a stored procedure like this. Is this stored procedure not returning results because I am using a cursor??
The stored procedure is returning something it's just you aren't doing anything with the results.
You can do this simply by running the following script in SQLDeveloper:
VARIABLE csr REFCURSOR;
EXEC getRejectedReasons(:csr); -- the colon identifies the parameter as a variable
PRINT csr;
Another method is to fetch each row and do some sort of processing:
DECLARE
-- sys_refcursor is weakly typed
refcsr SYS_REFCURSOR;
-- define a record so we can reference the fields
rej_rec Reasons_for_Rejection%ROWTYPE;
BEGIN
getRejectedReasons(refcsr);
-- loop through the results
LOOP
-- gets one row at a time
FETCH refcsr INTO rej_rec;
-- if the fetch doesn't find any more rows exit the loop
EXIT WHEN refcsr%NOTFOUND;
-- Do something here.
-- For example : DBMS_OUTPUT.PUT_LINE(rej_rec.reason_desc);
END LOOP;
END;
You opened the cursor. You didn't select anything from it, update it, or advance it.
All open does, effectively, to select the matching rows into temporary memory, so you can advance the cursor row by row. Which you didn't do.
One of the differences between Oracle and SQL Server is that the latter returns result sets naturally. I'd use a function, by the way.
In Oracle, functions typically return a single element. Cursors came later.
There's some documentation online that will help you understand the use of refcursor bind variables. Here's one such for SQL*Plus:
http://download.oracle.com/docs/cd/B19306_01/server.102/b14357/ch5.htm#sthref1122
I think in SQL Developer you can do the same thing with autoprint on, although I haven't tested that.
Found a blog that also discusses something similar:
http://vadimtropashko.wordpress.com/cursors/
ETA: Ok. Ignore what I wrote. Listen to someone else. Apparently it's wrong, as I got down voted.
What tpdi said is correct. You have to do something with the cursor after you declare it.
Here's an example using two cursors in nested loops
PROCEDURE update_insert_tree (exid_in IN NUMBER, outvar_out OUT VARCHAR2)
IS
nxtid NUMBER;
phaseid NUMBER;
rowcounter1 NUMBER;
BEGIN
rowcounter1 := 0;
outvar_out := 0;
FOR acur IN (SELECT dept_exercise_id, phase
FROM ep_dept_exercise
WHERE exercise_id = exid_in)
LOOP
<<dept_loop>>
FOR thecur IN (SELECT document_name, thelevel, sortnum, type_flag,
ex_save_id
FROM ep_exercise_save
WHERE exercise_id = exid_in)
LOOP
phaseid := acur.phase;
IF phaseid = 0
THEN
phaseid := 10;
UPDATE ep_dept_exercise
SET phase = 10
WHERE dept_exercise_id = acur.dept_exercise_id;
END IF;
<<doc_loop>>

Resources