Changing a Procedure to a View in Oracle - oracle

Not sure if this is possible, but I'm trying to see if I can convert this procedure to become a view because we've been having trouble with drives not populating the table when the procedure is run.
I'm trying to understand someone else's code and because of the cursors, I'm not even sure we can change this procedure to a view.
----------------------------------------------------------------------
--This Procedure will interface drive information on a nightly basis--
----------------------------------------------------------------------
Procedure HEMA_DRIVE_AUTO IS
v_start_date DATE := trunc(sysdate) -30;
v_end_date DATE := trunc(sysdate);
v_delete_stats_dt DATE := trunc(sysdate)-120;
v_total_registration_count NUMBER;
v_total_performed_count NUMBER;
v_total_collected_count NUMBER;
v_total_deferred_count NUMBER;
v_total_qns_count NUMBER;
v_existing_drive NUMBER;
v_existing_performed NUMBER;
v_maph_drive NUMBER;
--This Cursor will collect the initial data
cursor c_drive_info is
select dr.drive_id, dr.Start_time, dr.vehicle_id
from drives dr
--where dr.drive_id in(1605606);
where trunc(dr.start_time) between v_start_date and v_end_date;
--This Cursor will be used to decode the Donation Types
cursor c_procedure_codes is
select * from hema_donation_type_map hdt
where hdt.mobiles = 1 order by procedure_code_id;
--This Cursor will define the intentions but exclude theraputics inthe mapping
cursor c_intention is
select rsa_motivation_id,hema_intent_id from hema_intent_map
where rsa_motivation_id <> 4 order by rsa_motivation_id;
BEGIN
-- delete records older then 4 months
delete from hema_nightly h where trunc(h.drive_date) < v_delete_stats_dt;
commit;
FOR cur_drive IN c_drive_info LOOP
delete from hema_nightly where drive_id = cur_drive.drive_id;
commit;
-- Loop by motivation/intention
FOR cur_intent in c_intention LOOP
-- Loop to get the procedure code data
FOR cur_proc_code IN c_procedure_codes LOOP
v_total_registration_count := 0;
v_total_performed_count := 0;
v_total_collected_count := 0;
v_total_deferred_count := 0;
v_total_qns_count := 0;
v_maph_drive := 0;
-- get the count for all other procedures
select count(1)
into v_total_registration_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
--get the deferral count
select count(unique(r.registration_id))
into v_total_deferred_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id
and r.step_completed < 12
and exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99);
-- QNS count
select count(unique(r.registration_id))
into v_total_qns_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.step_completed < 12
and not exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99)
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
-- performed count is the difference between total registrations and total deferrals.
v_total_performed_count := v_total_registration_count -
(v_total_deferred_count +
v_total_qns_count);
-- not calulatind yield so keep count the same
v_total_collected_count := v_total_performed_count;
-- does this drive exist
select count(drive_id)
into v_existing_drive
from hema_nightly
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
-- Is this an aph vehicle?
select count(vehicle_id)
into v_maph_drive
from vehicles
where veh_drive_type_uid = 2
and vehicle_id = cur_drive.vehicle_id;
if v_existing_drive > 0 then
update hema_nightly
set performed = performed + v_total_performed_count,
collected = collected + v_total_collected_count,
registered = registered + v_total_registration_count,
deferrals = deferrals + v_total_deferred_count,
qns = qns + v_total_qns_count,
drive_date = cur_drive.start_time,
mod_date = sysdate,
intent = cur_intent.hema_intent_id,
aph = v_maph_drive
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
commit;
elsif v_existing_drive = 0 and v_total_registration_count > 0 then
insert into hema_nightly
(drive_id,
procedure_id,
performed,
collected,
registered,
deferrals,
qns,
drive_date,
mod_date,
intent,
aph)
values
(cur_drive.drive_id,
cur_proc_code.procedure_code_id,
v_total_performed_count,
v_total_collected_count,
v_total_registration_count,
v_total_deferred_count,
v_total_qns_count,
trunc(cur_drive.start_time),
sysdate,
cur_intent.hema_intent_id,
v_maph_drive);
commit;
end if;
v_existing_drive := 0;
end loop;
end loop;
end loop;
end hema_drive_auto;

Views don't perform DML (insert, update, delete) and they don't manage transactions with COMMIT and ROLLBACK; they only select and retrieve data.

Related

06533. 00000 - "Subscript beyond count"

I have this plsql block that populates a table with information of two other tables and I have to use a variable array:
DECLARE nombre_grupo VARCHAR2(15);
direccion_tipo direccion;
persona_tipo persona;
personas_array personas := personas();
CURSOR departamento IS
SELECT * FROM departamentos;
CURSOR empleado IS
SELECT * FROM empleados, departamentos
WHERE empleados.dept_no = departamentos.dept_no;
i INTEGER;
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
nombre_grupo := departamento.dnombre;
i := 1;
personas_array := personas();
FOR empleado IN (SELECT * FROM empleados WHERE dept_no = departamento.dept_no) LOOP
direccion_tipo := DIRECCION(departamento.loc, 'NULL', empleado.dir);
personas_array(i) := PERSONA(empleado.emp_no, empleado.apellido,
direccion_tipo, empleado.fecha_alt);
i := i + 1;
END LOOP;
INSERT INTO grupos VALUES (nombre_grupo, personas_array);
END LOOP;
END;
Here's the type personas:
CREATE OR REPLACE TYPE personas AS VARRAY(5) OF PERSONA
So when I execute that block and it reaches the personas_array(i) bit, it exits the execution with "subscript beyond count" error, no matter what value of i. What am I missing?
I've already deleted and created the type personas again, I've also tried creating the type inside the procedure, but it can't insert into the table
A few tips for a SQL beginner:
Don't learn 30 years old Oracle join syntax. Use modern ANSI join syntax, i.e.
SELECT *
FROM empleados
JOIN departamentos ON empleados.dept_no = departamentos.dept_no;
Your cursors are redundant. Either use
DECLARE
CURSOR cur_departamento IS
SELECT *
FROM departamentos;
BEGIN
FOR departamento IN cur_departamento LOOP
...
END LOOP;
END;
or
DECLARE
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
...
END LOOP;
END;
You can also use this:
DECLARE
CURSOR cur_empleados(d IN EMPLEADOS.DEPT_NO%TYPE) IS
SELECT *
FROM EMPLEADOS
WHERE dept_no = d;
/*
-- Do not use this!
CURSOR cur_empleados(dept_no IN EMPLEADOS.DEPT_NO%TYPE) IS
SELECT *
FROM EMPLEADOS
WHERE EMPLEADOS.dept_no = dept_no; -> will return all rows
*/
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
FOR empleado IN cur_empleados(departamento.dept_no) LOOP
...
END LOOP;
END LOOP;
END;
According to my feelings, VARRAYs are often part of student material but hardly used in real life.
Using string 'NULL' is most likely not want you want. Use literal NULL, i.e.
DIRECCION(departamento.loc, NULL, empleado.dir)
Type VARRAY(5) OF PERSONA defines a varray with maximum size of 5 elements. When you initialize it with personas_array := personas(); then the actual size is 0. You need to extend the varray.
You code may look like this:
DECLARE
nombre_grupo VARCHAR2(15);
direccion_tipo direccion;
persona_tipo persona;
personas_array personas;
i INTEGER;
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
nombre_grupo := departamento.dnombre;
i := 1;
personas_array := personas();
FOR empleado IN (SELECT * FROM empleados WHERE dept_no = departamento.dept_no AND ROWNUM <= 5) LOOP
direccion_tipo := DIRECCION(departamento.loc, NULL, empleado.dir);
personas_array.extend();
personas_array(i) := PERSONA(empleado.emp_no, empleado.apellido, direccion_tipo, empleado.fecha_alt);
i := i + 1;
END LOOP;
INSERT INTO grupos VALUES (nombre_grupo, personas_array);
END LOOP;
END;
Just a note, such procedure would have rather low performance. The professional way of doing it would be a Nested Table and then insert the data with a single command:
CREATE OR REPLACE TYPE personas_NT AS TABLE OF PERSONA;
INSERT INTO grupos VALUES (nombre_grupo, personas_array)
SELECT dnombre,
CAST(MULTISET(
SELECT
emp_no,
apellido,
DIRECCION(dept.loc, NULL, dir),
fecha_alt
FROM EMPLEADOS
WHERE dept_no = dept.dept_no
) AS personas_NT) AS personas_array
FROM DEPARTAMENTOS dept;
But maybe, that would be a chapter in the "advanced" SQL course.

If the capacity (cap) is 0, this trigger should delete the newly inserted tuple. what did i do wrong here? here is the code

i was making an application on oracle apex. now, If the capacity (cap) is 0, this trigger should delete the newly inserted tuple because that passenger_id will not be valid. what did i do wrong here?
Here is the code
create or replace trigger Ticket_katbo
before insert on passenger
for each row
declare
newcap number;
cap number;
flightpk number;
begin
flightpk := :new.flight_fid;
select flight_capacity into cap from flight where fid = flightpk;
if (cap>0) THEN
newcap := cap-1;
update flight set flight_capacity = newcap where fid = flightpk ;
ELSIF (cap=0 or cap = NULL) THEN
Delete from passenger where passenger_id = :new.passenger_id;
END IF;
end;
You are doing this before the insert. So raise an error if the capacity is not there:
create or replace trigger Ticket_katbo
before insert on passenger
for each row
declare
v_capacity number;
begin
select flight_capacity into v_capacity
from flight
where fid = :new.flight_fid;
if (cap <= 0) then
raise_application_error( -20001,
'No capacity for another passenger');
end if;
update flight
set flight_capacity = flight_capacity - 1
where fid = :new.flight_fid;
end;

ORA-01555 error when updating 200 million rows with BULK COLLECT

I have the following PL/SQL code, which updates one column of each row in a table with about 200 million rows. I use BULK COLLECT to repeatedly fetch 150,000 rows from the table and update the rows. I do a commit after 50,000 updates.
DECLARE
CURSOR jobs_cursor IS
SELECT e.ID, e.PTI, e.CAT, e.JOBNAME, e.JOBDATE, e.WORK_DESCRIPTION
FROM JOB e
WHERE length(e.WORK_DESCRIPTION) > 1000;
TYPE JOBS_TYPE IS TABLE OF jobs_cursor%ROWTYPE;
v_jobs JOBS_TYPE;
fetch_jobs_limit PLS_INTEGER := 150000;
trimmed_work_description VARCHAR2(2000 CHAR);
sub_string_work_description_left VARCHAR2(1000 CHAR);
sub_string_work_description_right VARCHAR2(1000 CHAR);
update_counter NUMBER := 0;
commit_counter NUMBER := 50000;
BEGIN
OPEN jobs_cursor;
LOOP
FETCH jobs_cursor BULK COLLECT INTO v_jobs LIMIT fetch_jobs_limit;
EXIT WHEN v_jobs.COUNT = 0;
FOR idx IN 1..v_jobs.COUNT
LOOP
trimmed_work_description := ' ';
IF v_jobs(idx).WORK_DESCRIPTION IS NOT NULL THEN
trimmed_work_description := TRIM(TRAILING ' ' FROM v_jobs(idx).WORK_DESCRIPTION);
END IF;
IF length(trimmed_work_description) <= 1000 THEN
UPDATE JOBS j SET j.WORK_DESCRIPTION = trimmed_work_description WHERE j.ID = v_jobs(idx).ID;
update_counter := update_counter + 1;
IF mod(update_counter, commit_counter) = 0 THEN
COMMIT;
update_counter := 0;
END IF;
CONTINUE;
ELSIF length(trimmed_work_description) > 1000 THEN
sub_string_work_description_left := SUBSTR(trimmed_work_description, 1, 1000);
sub_string_work_description_right := SUBSTR(trimmed_work_description, 1001, 2000);
END IF;
UPDATE JOBS j SET j.WORK_DESCRIPTION = sub_string_work_description_left WHERE j.ID = v_jobs(idx).ID;
INSERT INTO JOBS j VALUES ("SEQUENCE_JOBS".NEXTVAL, j.PTI, j.CAT, j.JOBNAME, j.JOBDATE, sub_string_work_description_right);
update_counter := update_counter + 1;
IF mod(update_counter, commit_counter) = 0 THEN
COMMIT;
update_counter := 0;
END IF;
END LOOP;
END LOOP;
COMMIT;
CLOSE jobs_cursor;
END;
The code runs for several hours, but then Oracle raises an ORA-01555 - Snapshot too old - Rollback segment number 14 with name xxxx too small.
Could you please tell me what is wrong with my PL/SQL? I already did the Google research and found some threads saying that this error could be avoided by expanding the UNDO table space, however this is not an option in my case. Thus, I need to modify the PL/SQL code.
On first view I don't see any reason why you make the update in a loop, it should be possible with single statements. Would be similar to this (not verified/tested)
update JOBS j SET
WORK_DESCRIPTION = SUBSTR(TRIM(TRAILING ' ' FROM WORK_DESCRIPTION), 1, 1000)
WHERE length(WORK_DESCRIPTION) > 1000;
INSERT INTO JOBS
SELECT SEQUENCE_JOBS.NEXTVAL, j.PTI, j.CAT, j.JOBNAME, j.JOBDATE,
SUBSTR(WORK_DESCRIPTION, 1001, 2000)
FROM JOBS j
WHERE length(TRIM(TRAILING ' ' FROM WORK_DESCRIPTION)) > 1000;

create and RETURN a list of numbers in a PL/SQL procedure/function

I have a function where i am summing the value of all sale_lines attached to a sale_head by sale_number. The problem is, the function will only return one sum, whereas I need it to return all sale_head sums in a specific time frame...
create or replace function sale_line_accounts
return number is
total_value_all number(38, 2);
value_sales number(12, 2);
last_month date;
this_month date;
sumPrice number(12,2);
-- type array_s is varray(200) of varchar2(30);
-- sums array_s := array_s();
begin
--------------------------------------
last_month := to_date('01/08/16');
this_month := to_date('03/08/16');
--------------------------------------
FOR headers IN
(
select sale_num, sale_date, status
from sale_head
where status = 'S' AND sale_date BETWEEN last_month AND this_month
) LOOP
-------------------------------------------
--sums.extend();--extend array
sumPrice:= 0;
FOR lines IN
(
select sale_num, quantity, actual_price
from sale_line
where sale_num = headers.sale_num
)LOOP
sumPrice := sumPrice + (lines.quantity * lines.actual_price);
dbms_output.put_line(sumPrice);
-- sums(sumPrice) := lines.sale_num;
END LOOP;
------------------------------------------
END LOOP;
-------------------------------------------
return sumPrice;
END;
/
the commented code was meant to create a collection or something? Please help!!
You can probably get your answer by just using a single SQL query:
select headers.sale_num
,sum(lines.quantity * lines.actual_price) price
from sale_head headers
join sale_line lines
on lines.sale_num = headers.sale_num
where headers.status = 'S'
and headers.sale_date between to_date('01/08/2016', 'DD/MM/YYYY') and to_date('03/08/2016', 'DD/MM/YYYY')
group by headers.sale_num

ORA-01007 "variable not in select list" from dbms_sql.column_value call

I am trying to use dynamic SQL to sample all the data in a schema with a pattern:
DECLARE
xsql varchar2(5000);
c NUMBER;
d NUMBER;
col_cnt INTEGER;
f BOOLEAN;
rec_tab DBMS_SQL.DESC_TAB;
col_num NUMBER;
varvar varchar2(500);
PROCEDURE print_rec(rec in DBMS_SQL.DESC_REC) IS
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('col_type = '
|| rec.col_type);
DBMS_OUTPUT.PUT_LINE('col_maxlen = '
|| rec.col_max_len);
DBMS_OUTPUT.PUT_LINE('col_name = '
|| rec.col_name);
DBMS_OUTPUT.PUT_LINE('col_name_len = '
|| rec.col_name_len);
DBMS_OUTPUT.PUT_LINE('col_schema_name = '
|| rec.col_schema_name);
DBMS_OUTPUT.PUT_LINE('col_schema_name_len = '
|| rec.col_schema_name_len);
DBMS_OUTPUT.PUT_LINE('col_precision = '
|| rec.col_precision);
DBMS_OUTPUT.PUT_LINE('col_scale = '
|| rec.col_scale);
DBMS_OUTPUT.PUT('col_null_ok = ');
IF (rec.col_null_ok) THEN
DBMS_OUTPUT.PUT_LINE('true');
ELSE
DBMS_OUTPUT.PUT_LINE('false');
END IF;
END;
BEGIN
c := DBMS_SQL.OPEN_CURSOR;
xsql:='
WITH got_r_num AS
(
SELECT e.* -- or whatever columns you want
, ROW_NUMBER () OVER (ORDER BY dbms_random.value) AS r_num
FROM dba_tab_columns e
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num <= 10';
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
NULL;
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
--dbms_output.put_line('varvar=');
--DBMS_SQL.COLUMN_VALUE(source_cursor, 2, name_var);
--DBMS_SQL.COLUMN_VALUE(source_cursor, 3, birthdate_var);
-- Bind the row into the cursor that inserts into the destination table. You
-- could alter this example to require the use of dynamic SQL by inserting an
-- if condition before the bind.
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':id_bind', id_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':name_bind', name_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':birthdate_bind',
--birthdate_var);
--ignore := DBMS_SQL.EXECUTE(destination_cursor);
--ELSE
-- No more rows to copy:
--EXIT;
END IF;
END LOOP;
--EXIT WHEN d != 10;
--END LOOP;
col_num := rec_tab.first;
IF (col_num IS NOT NULL) THEN
LOOP
print_rec(rec_tab(col_num));
col_num := rec_tab.next(col_num);
EXIT WHEN (col_num IS NULL);
END LOOP;
END IF;
DBMS_SQL.CLOSE_CURSOR(c);
END;
/
When I run that it gives me this error from the line with the dbms_sql.column_value call:
ORA-01007: variable not in select list
If I comment out that dbms_sql.column_value call it still errors but now with:
ORA-01002: fetch out of sequence
What am I doing wrong?
You have two problems in the code you posted. Firstly you have skipped part of the execution flow because you haven't called the DEFINE_COLUMN procedure. That is what is causing the ORA-01007 error, as the dynamic SQL processing hasn't been told about the select list columns via that call. For your current code you only need to define column 2, but assuming you will actually want to refer to the others you can define them in a loop. To treat them all as string for display you could do:
...
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
FOR i IN 1..col_cnt
LOOP
-- dbms_output.put_line('col_name is ' || rec_tab(i).col_name);
DBMS_SQL.DEFINE_COLUMN(c, i, varvar, 500);
END LOOP;
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
...
If you want to do anything that needs to treat the variables as the right types you could have a local variable of each type and use the data type from the rec_tab information you already have from describe_columns to use the appropriately typed variable for each column.
The second problem, which you were hitting when you commented the column_value call, is still there once that definbe issue has been fixed. Your loop doesn't ever exit, so after you fetch the last row from the cursor you do a further invalid fetch, which throws ORA-01002. You have the code to avoid that already but it's commented out:
...
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
...
ELSE
-- No more rows to copy:
EXIT;
END IF;
END LOOP;
...
With those two changes your code runs, and dumps the view structure:
PL/SQL procedure successfully completed.
col_type = 1
col_maxlen = 30
col_name = OWNER
col_name_len = 5
col_schema_name =
col_schema_name_len = 0
col_precision = 0
col_scale = 0
col_null_ok = false
col_type = 1
col_maxlen = 30
col_name = TABLE_NAME
...
To those who find this question when accessing Oracle through ODP.NET, as I did:
We started getting this error whenever we would add column to an existing table in our application. I'm not sure what all the conditions were to make it fail, but ours were:
Run a SELECT * FROM "table".
Include a ROWNUM restriction in the WHERE clause (WHERE ROWNUM < 10).
Run that through the ODP.NET dataReader.GetSchemaTable() call.
Running unrestricted queries or running queries directly on Oracle SQL Developer did not seem to cause the error.
I've hit some pretty weird stuff in the past with Oracle connection pooling, so I eventually thought that could be the problem. The solution was to restart the web service to force all the connections to be fully dropped and recreated.
The theory is that the ODP.NET connection from the connection pool still had no idea the column existed on the table, but the column was returned by the database.

Resources