Reading rows from an Oracle Object that is a table of other objects - sql

Forgive me, this is my first attempt at an Oracle Package, so I am hopefully missing something simple.
EDIT I sorted it...
Need to reference the actual declaring sub-type, as such:
FOR j in outvar(i).tbl_ORDER_TENDERS.first..outvar(i).tbl_ORDER_TENDERS.last LOOP
DBMS_OUTPUT.PUT_LINE('tender record : '
|| to_char(outvar(i).tbl_ORDER_TENDERS(j).TENDER_AMT) || ' '
I created several a couple new Oracle Types, that will hold row data from my DB, as such:
create or replace
TYPE ORDERS_TABLE
IS TABLE OF ORDER_HEADER;
And that refers to my other type:
create or replace
TYPE ORDER_HEADER FORCE
AS OBJECT (
TRANSACTION_NUMBER VARCHAR2(20),
LOCATION_NUMBER VARCHAR2(10),
TERMINAL_NAME VARCHAR2(25),
START_DATETIME TIMESTAMP(6),
GROSS_SALES_AMOUNT NUMBER(20,0),
NET_SALES_AMOUNT NUMBER(20,0),
SAVINGS_AMOUNT NUMBER(20,0),
SAVINGS_PRECISION NUMBER(6,0),
TOTAL_TAX NUMBER(20,0),
CUSTOMER_IDENTIFIER VARCHAR2(50),
tbl_ORDER_LINES ORDER_LINES,
tbl_ORDER_TENDERS ORDER_TENDERS,
TBL_ORDER_REBATES ORDER_REBATES
);
And for example, take the ORDER_TENDERS type:
create or replace
TYPE ORDER_TENDERS FORCE
AS TABLE OF ORDER_TENDER;
Which contains the rows of tender data:
create or replace
TYPE ORDER_TENDER AS OBJECT
(
TENDER_LINE_ID NUMBER(20,0),
TENDER_CODE VARCHAR2(10),
TENDER_AMT NUMBER(20,0),
UNENCODED_ACCOUNT_NUMBER VARCHAR2(25)
);
So my package I think is working, where I can fill these objects (verified the HEADER stuff working at least.) But I'm not sure how to test/debug/view the results when I call this package to see if I'm getting the ORDER_TENDER/ORDER_TENDERS data...
For example, this works fine:
declare
invar varchar2(5);
outvar ORDERS_TABLE;
O_ORDER_TENDERS ORDER_TENDERS;
O_ORDER_TENDER ORDER_TENDER;
begin
sales_trickler.GetSales(invar, outvar);
FOR i in 1..outvar.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(' record : '
|| to_char(outvar(i).TRANSACTION_NUMBER) || ' '
|| to_char(outvar(i).LOCATION_NUMBER) || ' '
|| to_char(outvar(i).TERMINAL_NAME) || ' '
|| to_char(outvar(i).CUSTOMER_IDENTIFIER));
END LOOP;
end;
But how do I see the ORDER_TENDERS/ORDER_TENDER data?
I tried putting this extra FOR LOOP in, but it doesn't like how I'm referring to ORDER_TENDER(S)...
declare
invar varchar2(5);
outvar ORDERS_TABLE;
O_ORDER_TENDERS ORDER_TENDERS;
O_ORDER_TENDER ORDER_TENDER;
begin
sales_trickler.GetSales(invar, outvar);
FOR i in 1..outvar.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(' record : '
|| to_char(outvar(i).TRANSACTION_NUMBER) || ' '
|| to_char(outvar(i).LOCATION_NUMBER) || ' '
|| to_char(outvar(i).TERMINAL_NAME) || ' '
|| to_char(outvar(i).CUSTOMER_IDENTIFIER));
FOR j in 1..outvar(i).O_ORDER_TENDERS.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('tender record : '
|| to_char(O_ORDER_TENDERS(j).TENDER_AMT) || ' '
);
END LOOP;
END LOOP;
end;
I tried relating to outvar(i).ORDER_TENDERS but that didn't work either... any ideas?

The problem was in the calling code, I shouldn't refer to new instances of the sub-objects, I should refer to them 'as they live' in the original parameter being passed, as such:
tbl_ORDER_LINES ORDER_LINES,
tbl_ORDER_TENDERS ORDER_TENDERS,
TBL_ORDER_REBATES ORDER_REBATES
Here's the actual statement:
declare
invar varchar2(5);
outvar ORDERS_TABLE;
begin
sales_trickler.GetSales(invar, outvar);
FOR i in 1..outvar.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(' record : '
|| to_char(outvar(i).TRANSACTION_NUMBER) || ' '
|| to_char(outvar(i).LOCATION_NUMBER) || ' '
|| to_char(outvar(i).TERMINAL_NAME) || ' '
|| to_char(outvar(i).CUSTOMER_IDENTIFIER));
FOR j in outvar(i).tbl_ORDER_TENDERS.first..outvar(i).tbl_ORDER_TENDERS.last LOOP
DBMS_OUTPUT.PUT_LINE('tender record : '
|| to_char(outvar(i).tbl_ORDER_TENDERS(j).TENDER_AMT) || ' '
);
END LOOP;
--lines
FOR j in outvar(i).tbl_ORDER_LINES.first..outvar(i).tbl_ORDER_LINES.last LOOP
DBMS_OUTPUT.PUT_LINE('line record : '
|| to_char(outvar(i).tbl_ORDER_LINES(j).SKU) || ' '
);
END LOOP;
END LOOP;
end;

Related

Problem with PL/SQL anonymous block in an exam

I am working with a code in SQLDeveloper for an exam and I'm having problems with the code. The error shown is
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 7
00000 - "PL/SQL: numeric or value error%s"
*Cause: An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2).
*Action: Change the data, how it is manipulated, or how it is declared so
that values do not violate constraints.
The code I'm using is this one:
VAR RUT_CLIENTE VARCHAR2(15);
EXEC :RUT_CLIENTE:= '12487147-9';
DECLARE
V_NOMBRE VARCHAR2(75);
V_RUN VARCHAR2(50);
V_RENTA VARCHAR2(12);
V_EST_CIVIL VARCHAR2(40);
BEGIN
SELECT
CLI.NOMBRE_CLI || ' ' || CLI.APPATERNO_CLI || ' ' || CLI.APMATERNO_CLI,
TO_CHAR(CLI.NUMRUT_CLI || '-' || CLI.DVRUT_CLI),
TO_CHAR(CLI.RENTA_CLI, '$999G999G999'),
EST.DESC_ESTCIVIL
INTO V_NOMBRE, V_RUN, V_RENTA, V_EST_CIVIL
FROM CLIENTE CLI JOIN ESTADO_CIVIL EST
ON CLI.ID_ESTCIVIL = EST.ID_ESTCIVIL
WHERE CLI.NUMRUT_CLI || '-' || CLI.DVRUT_CLI = :RUT_CLIENTE;
DBMS_OUTPUT.PUT_LINE('DATOS DEL CLIENTE');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('----------------');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Nombre: ' || V_NOMBRE);
DBMS_OUTPUT.PUT_LINE('RUN: ' || V_RUN);
DBMS_OUTPUT.PUT_LINE('Estado Civil: ' || V_EST_CIVIL);
DBMS_OUTPUT.PUT_LINE('Renta: ' || V_RENTA);
END;
What am I doing wrong? Also, I have to make this block run three times, each time having to enter a different RUT_CLIENTE (the equivalent of the Social Security number in Chile) to show different results, so should I use a loop for that?
You can avoid such errors if you will use define your variables using the types from your cursor:
DECLARE
cursor cur(p_RUT_CLIENTE) is
SELECT
CLI.NOMBRE_CLI || ' ' || CLI.APPATERNO_CLI || ' ' || CLI.APMATERNO_CLI as col_nombre,
TO_CHAR(CLI.NUMRUT_CLI || '-' || CLI.DVRUT_CLI) as col_run,
TO_CHAR(CLI.RENTA_CLI, '$999G999G999') as col_renta,
EST.DESC_ESTCIVIL as col_est_civil
FROM CLIENTE CLI JOIN ESTADO_CIVIL EST
ON CLI.ID_ESTCIVIL = EST.ID_ESTCIVIL
WHERE CLI.NUMRUT_CLI || '-' || CLI.DVRUT_CLI = p_RUT_CLIENTE;
V_NOMBRE cur.col_nombre%type;
V_RUN cur.col_run%type;
V_RENTA cur.col_renta%type;
V_EST_CIVIL cur.est_civil%type;
BEGIN
open cur(:RUT_CLIENTE)
fetch cur into INTO V_NOMBRE, V_RUN, V_RENTA, V_EST_CIVIL;
close cur;
DBMS_OUTPUT.PUT_LINE('DATOS DEL CLIENTE');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('----------------');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Nombre: ' || V_NOMBRE);
DBMS_OUTPUT.PUT_LINE('RUN: ' || V_RUN);
DBMS_OUTPUT.PUT_LINE('Estado Civil: ' || V_EST_CIVIL);
DBMS_OUTPUT.PUT_LINE('Renta: ' || V_RENTA);
END;

How can i turn this pl/sql into a procedure

I had to write this query for an assignement. So we have a database and we are pulling information from it, this is going to work with some back end c# eventually. Is there anything i can do , knowing im going to reuse this, in order to make it better and more adaptable when the day comes when i have to connect it all.
set serveroutput on
DECLARE
LV_DATE HVK_RESERVATION.RESERVATION_START_DATE%TYPE;
LV_SERV VARCHAR(100);
CURSOR LCUR_RES IS
SELECT *
FROM HVK_RESERVATION R
INNER JOIN HVK_PET_RESERVATION PR
ON R.RESERVATION_NUMBER = PR.RES_RESERVATION_NUMBER
INNER JOIN HVK_PET P
ON P.PET_NUMBER = PR.PET_PET_NUMBER
INNER JOIN HVK_OWNER OW
ON OW.OWNER_NUMBER = P.OWN_OWNER_NUMBER
WHERE R.RESERVATION_START_DATE < LV_DATE
AND R.RESERVATION_END_DATE > LV_DATE;
CURSOR LCUR_SERVICE(PET_RES_NUM NUMBER) IS
SELECT *
FROM HVK_SERVICE S
INNER JOIN HVK_PET_RESERVATION_SERVICE PRS
ON PRS.SERV_SERVICE_NUMBER = S.SERVICE_NUMBER
AND PRS.PR_PET_RES_NUMBER = PET_RES_NUM;
BEGIN
LV_DATE := TO_DATE('&logdate', 'yy-mm-dd');
DBMS_OUTPUT.PUT_LINE('Kennel log for ' || '' || LV_DATE);
DBMS_OUTPUT.PUT_LINE('-------------------------------');
FOR I IN LCUR_RES LOOP
DBMS_OUTPUT.PUT_LINE('Run:' || '' || I.RUN_RUN_NUMBER || ' ' ||
'Pet: ' || '' || I.PET_NAME || ' ' ||
I.OWNER_LAST_NAME || ' Pet Reservation: ' || '' ||
I.PET_RES_NUMBER);
DBMS_OUTPUT.PUT_LINE('Reservation start/end ' || ' ' ||
I.RESERVATION_START_DATE || ' ' ||
I.RESERVATION_END_DATE);
DBMS_OUTPUT.PUT('Services : ');
FOR X IN LCUR_SERVICE(I.PET_RES_NUMBER) LOOP
DBMS_OUTPUT.PUT(X.SERVICE_DESCRIPTION || ' ');
END LOOP;
DBMS_OUTPUT.PUT_LINE('');
FOR LREC_LOG IN (SELECT *
FROM HVK_KENNEL_LOG KL
WHERE KL.PR_PET_RES_NUMBER = I.PET_RES_NUMBER
) LOOP
DBMS_OUTPUT.PUT_LINE('Notes: ' || '' ||
LREC_LOG.KENNEL_LOG_SEQUENCE_NUMBER || ' ' ||
'Log Note: ' || '' || LREC_LOG.KENNEL_LOG_NOTES);
END LOOP;
DBMS_OUTPUT.PUT_LINE(' ');
END LOOP;
END;
It it supposed to output the run number , reservation number , pet name , and any relate notes.
you can replace DECLARE with CREATE OR REPLACE PROCEDURE my_proc(in_logdate in date) IS.
in that case my_proc will be the name of your procedure.
you should also use a parameter instead of &logdate
so e.g. parameter name in_logdate of type date
...
LV_DATE := in_logdate;
...

PL/SQL - Create tables based on cursor using execute immediate?

I've written the following code that selects some student test data and using a cursor, inserts it into a table.
What id like to be able to do is create one table for each student and insert their relative data. This could be one row or multiple rows.
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE run_student_scores
IS
CURSOR c_pass_fail_cursor
IS
SELECT students.firstname,
test_history.score,
test_id.test_name,
test_id.passing_grade
FROM students
INNER JOIN test_history
ON students.student_id = test_history.student_id
INNER JOIN test_id
ON test_id.test_id = test_history.test_id
WHERE test_history.start_time BETWEEN to_timestamp(sysdate) + INTERVAL '8' HOUR
AND to_timestamp(sysdate) + INTERVAL '21' HOUR;
v_name students.firstname%TYPE;
v_score test_history.score%TYPE;
v_test varchar2(40);
v_passing test_id.passing_grade%TYPE;
v_result varchar2(4);
BEGIN
EXECUTE IMMEDIATE ('create table student_tests_' || (to_char(sysdate, 'yyyymmdd')) || '(student_name VARCHAR2(20), test_name varchar2(40), test_score NUMBER(3), pass_rate NUMBER(3), pass_fail VARCHAR2(4))');
OPEN c_pass_fail_cursor;
LOOP
FETCH c_pass_fail_cursor INTO v_name, v_score, v_test, v_passing;
EXIT WHEN c_pass_fail_cursor%NOTFOUND;
If v_score < v_passing
THEN v_result := 'Fail';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
ELSE
v_result := 'Pass';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
END IF;
EXECUTE IMMEDIATE 'INSERT INTO student_tests_' || (to_char(sysdate, 'yyyymmdd')) || ' ' || ' values(:1, :2, :3, :4, :5)' using v_name, v_test, v_score, v_passing, v_result;
END LOOP;
CLOSE c_pass_fail_cursor;
END;
/
I've played around with it for the last couple of days and cant get it to work. The closest i can get is to create the tables and insert the first row only, generating an error when the loop tries to create a table that already exists.
Any help would be awesome
Thanks folks
Ben
Outcome: 1 table for each student (unique by name).
The create table ... is now inside the loop surrounded by an exception block. If the table already exists, the exception (ORA-00955) is handled.
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE run_student_scores
IS
CURSOR c_pass_fail_cursor
IS
SELECT students.firstname,
test_history.score,
test_id.test_name,
test_id.passing_grade
FROM students
INNER JOIN test_history
ON students.student_id = test_history.student_id
INNER JOIN test_id
ON test_id.test_id = test_history.test_id
WHERE test_history.start_time BETWEEN to_timestamp(sysdate) + INTERVAL '8' HOUR
AND to_timestamp(sysdate) + INTERVAL '21' HOUR;
v_name students.firstname%TYPE;
v_score test_history.score%TYPE;
v_test varchar2(40);
v_passing test_id.passing_grade%TYPE;
v_result varchar2(4);
--New variables
table_already_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(table_already_exists, -955);
BEGIN
OPEN c_pass_fail_cursor;
LOOP
FETCH c_pass_fail_cursor INTO v_name, v_score, v_test, v_passing;
EXIT WHEN c_pass_fail_cursor%NOTFOUND;
begin
EXECUTE IMMEDIATE ('create table student_tests_' || v_name || ' (student_name VARCHAR2(20), test_name varchar2(40), test_score NUMBER(3), pass_rate NUMBER(3), pass_fail VARCHAR2(4))');
exception when table_already_exists then
null;
end;
If v_score < v_passing
THEN v_result := 'Fail';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
ELSE
v_result := 'Pass';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
END IF;
EXECUTE IMMEDIATE 'INSERT INTO student_tests_' || v_name || ' ' || ' values(:1, :2, :3, :4, :5)' using v_name, v_test, v_score, v_passing, v_result;
END LOOP;
CLOSE c_pass_fail_cursor;
END;
/
Grafros answer looks good, but i strongly recommend to take a look at DBMS_ASSERT with DBMS_ASSERT.ENQUOTE_NAME or DBMS_ASSERT.ENQUOTE_LITERAL whenever you have take abitrary strings and use them in execute immidiate.

Generate change scripts from SQL

I want to change database development from creating change scripts to automatically generating change scripts from the declarative defitinitions. How would I go about doing that for PL/SQL on an Oracle DB?
You can find a short introduction at section "Version Datamodel" here on stackoverflow. I've worked on this interesting area for many years, starting in 1994 on Oracle7 to allow automatic overnight going in production. Not all nights I got my sleep...
For Invantive Producer, we have a boot-package that runs on Oracle 11 and up and takes care of automatic upgrades, but it is over 8.000 lines. The boot package is called by PL/SQL when bootstrapping and later on takes it instructions from a repository with software definitions. The repository is described at Technical Reference Manual, also a Diagram is available and might give some inspiration.
I can give you a sample of such as procedure, please let me know what types of objects you would like to automatically generate DDL for, then I can add things when necessary.
procedure verify_index
( p_table_name varchar2
, p_index_name varchar2
, p_index_unique boolean default null
, p_column_list varchar2
)
;
and
procedure verify_index
( p_mode pls_integer
, p_table_name varchar2
, p_index_name varchar2
, p_index_unique boolean default null
, p_column_list varchar2
)
is
begin
verify_index
( get_table_name(p_mode, p_table_name)
, get_index_name(p_mode, p_index_name)
, case
when p_mode = g_history_mode
then false
else p_index_unique
end
, p_column_list
|| case
when p_mode = g_history_mode
then ',h_date_starts,h_date_ends'
else ''
end
)
;
end;
--
-- Verify existence of an index with the indicated column list.
--
procedure verify_index
( p_table_name varchar2
, p_index_name varchar2
, p_index_unique boolean default null
, p_column_list varchar2
)
is
l_index_exists_anywhere boolean;
l_index_exists_on_table boolean;
l_create_index boolean;
l_ist_column_list varchar2(4000);
l_soll_column_list varchar2(4000);
l_story varchar2(4000);
l_stmt varchar2(4000);
begin
l_story := 'Reason: ';
--
-- Determine whether to recreate the index.
-- In the process, drop the current index when not correct.
--
l_index_exists_anywhere := index_exists(p_index_name, null, null);
l_index_exists_on_table := index_exists(p_index_name, null, p_table_name);
if l_index_exists_on_table
then
l_ist_column_list := index_column_list(p_index_name, true);
l_soll_column_list := lower(p_column_list);
if identical_character_sequence(l_ist_column_list, l_soll_column_list)
then
-- Column lists are equal. Just check uniqueness.
if index_exists(p_index_name, p_index_unique, p_table_name)
then
null; -- Do nothing.
l_create_index := false;
l_story := l_story || ' index ' || p_index_name || ' already exists on the correct table with the correct column list ' || l_soll_column_list || '. Do nothing.';
else
drop_index_existing(p_index_name);
l_create_index := true;
l_story := l_story || ' index ' || p_index_name || ' has the correct column list ' || l_soll_column_list || ' but on a different table. Recreate index.';
end if;
else
drop_index_existing(p_index_name);
l_create_index := true;
l_story := l_story || ' index ' || p_index_name || ' has the incorrect column list ' || l_ist_column_list || ' but should have ' || l_soll_column_list || '. Recreate index.';
end if;
elsif l_index_exists_anywhere
then
drop_index_existing(p_index_name);
l_create_index := true;
l_story := l_story || ' index ' || p_index_name || ' exists on the wrong table. Recreate index.';
else
l_ist_column_list := null;
l_create_index := true;
l_story := l_story || ' index ' || p_index_name || ' does not yet exist. Create index.';
end if;
--
-- Now l_create_index describes whether to create the index or not.
--
if l_create_index
then
/* Only uncomment this when itgen_log is available. During bootstrapping it is not possible
to use itgen_log. */
-- itgen_log.debug('Create index ' || p_index_name || '. ' || l_story);
l_stmt := 'create'
|| case
when p_index_unique
then ' unique'
else ''
end
|| ' index '
|| p_index_name
|| ' on '
|| p_table_name
|| '('
|| p_column_list
|| ')'
;
execute_dynamic_sql(l_stmt);
end if;
end
;

Finding specific data in Oracle Tables

I needed to find a value for a column in my oracle database but i don't know which
table or column it's stored in
How can I search for a specific or like %% data as I do in
select * from SYS.dba_source
is there a table like that
Column Name ID Data Type Null? Comments
OWNER 1 VARCHAR2 (30 Byte) Y
NAME 2 VARCHAR2 (30 Byte) Y Name of the object
TYPE 3 VARCHAR2 (12 Byte) Y
Type of the object:
"TYPE", "TYPE BODY", "PROCEDURE", "FUNCTION",
"PACKAGE", "PACKAGE BODY" or "JAVA SOURCE"
LINE 4 NUMBER Y Line number of this line of source
TEXT 5 VARCHAR2 (4000 Byte) Y Source text
LINK: pl/sq to find any data in a schema
Imagine, there are a few tables in your schema and you want to find a specific value in all columns within these tables. Ideally, there would be an sql function like
select * from * where any(column) = 'value';
Unfortunately, there is no such function.
However, a PL/SQL function can be written that does that. The following function iterates over all character columns in all tables of the current schema and tries to find val in them.
create or replace function find_in_schema(val varchar2)
return varchar2 is
v_old_table user_tab_columns.table_name%type;
v_where Varchar2(4000);
v_first_col boolean := true;
type rc is ref cursor;
c rc;
v_rowid varchar2(20);
begin
for r in (
select
t.*
from
user_tab_cols t, user_all_tables a
where t.table_name = a.table_name
and t.data_type like '%CHAR%'
order by t.table_name) loop
if v_old_table is null then
v_old_table := r.table_name;
end if;
if v_old_table <> r.table_name then
v_first_col := true;
-- dbms_output.put_line('searching ' || v_old_table);
open c for 'select rowid from "' || v_old_table || '" ' || v_where;
fetch c into v_rowid;
loop
exit when c%notfound;
dbms_output.put_line(' rowid: ' || v_rowid || ' in ' || v_old_table);
fetch c into v_rowid;
end loop;
v_old_table := r.table_name;
end if;
if v_first_col then
v_where := ' where ' || r.column_name || ' like ''%' || val || '%''';
v_first_col := false;
else
v_where := v_where || ' or ' || r.column_name || ' like ''%' || val || '%''';
end if;
end loop;
return 'Success';
end;