Related
I'm trying to write a PL/SQL script that searches the entire database for a string and report the tables and columns it finds it in. It looks like this:
DECLARE
ncount NUMBER;
vwhere VARCHAR2(1000) := '';
vsearchstr VARCHAR2(1000) := 'search string here';
vresult VARCHAR2(10000) := 'result: ';
vtab VARCHAR2(1000) := '';
vcol VARCHAR2(1000) := '';
BEGIN
FOR k IN (SELECT a.table_name, a.column_name FROM all_tab_columns a WHERE a.data_type LIKE '%VARCHAR%')
LOOP
vtab := k.table_name;
vcol := k.column_name;
vwhere := ' where ' || vcolumnname || ' = :vsearchstr ';
EXECUTE IMMEDIATE 'select count(1) from ' || vtab || vwhere INTO ncount USING vsearctstr;
IF (ncount > 0)
THEN
vresult := CONCAT(vresult, vcol || ' ' || vtab || ', ');
END IF;
END LOOP;
IF (LENGTH(vresult) > 1)
THEN
dbms_output.put_line(vresult);
ELSE
dbms_output.put_line('not found');
END IF;
END;
When I run it, I get the following error:
It is essentially saying it doesn't recognize the table vtab in the line EXECUTE IMMEDIATE 'select count(1) from ' || vtab || vwhere INTO ncount USING vsearctstr.
So in order to see which table it's complaining about, I added the following exception block to the end of the script:
EXCEPTION
WHEN OTHERS
THEN
BEGIN
dbms_output.put_line('exception: ' || vtab);
END;
It tells me the table name is IND$.
I'm not sure what this table (or view) is and it doesn't look relevant anyway.
So my question is two fold: 1) If it is fetching IND$ in the FOR loop from k.table_name (which in turn is from all_tab_columns), why does it say it doesn't exist in the select query? 2) I'm not sure what IND$ is but I'm pretty sure I don't need to search it; So is there a way to limit my search to only relevant tables (not views)? By 'relevant', I mean tables that we created to store data for our application (as opposed to system tables or user tables, etc.).
Thanks very much.
all_tab_columns lists columns in all tables that your current schema has access to. In general, if you're going to use ALL_... views to build dynamic SQL, you should incorporate the OWNER column as well since it may return rows for tables in other schemas.
If you really only want to consider tables in your current schema, use user_tab_columns instead.
If you want to get more, or less, particular than that about which tables are 'relevant', then you'll probably need to hardcode specific rules into your query.
FYI: IND$ is part of the Oracle data dictionary and is in the SYS schema. (Although, it's always possible someone has created a table with that name in some other schema.) It's unusual that your application schema would have direct access to this.
If you know how to skip tables you don't want, do it - you might filter by OWNER, maybe table name, etc.
If you don't want to bother, include inner begin-exception-end block into the loop so that it skips errors (either silently, or display errors, or store them into some table). Here's one option:
DECLARE
ncount NUMBER;
vwhere VARCHAR2(1000) := '';
vsearchstr VARCHAR2(1000) := 'search string here';
vresult VARCHAR2(10000) := 'result: ';
vtab VARCHAR2(1000) := '';
vcol VARCHAR2(1000) := '';
BEGIN
FOR k IN (SELECT a.table_name, a.column_name FROM all_tab_columns a WHERE a.data_type LIKE '%VARCHAR%')
LOOP
BEGIN
vtab := k.table_name;
vcol := k.column_name;
vwhere := ' where ' || vcolumnname || ' = :vsearchstr ';
EXECUTE IMMEDIATE 'select count(1) from ' || vtab || vwhere INTO ncount USING vsearctstr;
IF (ncount > 0)
THEN
vresult := CONCAT(vresult, vcol || ' ' || vtab || ', ');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(vtab ||': '|| sqlerrm);
END;
END LOOP;
IF (LENGTH(vresult) > 1)
THEN
dbms_output.put_line(vresult);
ELSE
dbms_output.put_line('not found');
END IF;
END;
You need to:
Also get the owner from all_tab_columns
Use quoted identifiers around the owner, table_name and column_name so that the query does not fail for identifiers in different cases or with special characters.
Handle errors (i.e. when you do not have the permissions to SELECT from a table).
To make it faster, you can:
Filter the columns to only those that are long enough to contain the search string.
You can do that using:
DECLARE
ncount NUMBER;
vsearchstr VARCHAR2(1000) := 'search string here';
vresult VARCHAR2(10000);
BEGIN
FOR k IN (SELECT owner,
table_name,
column_name
FROM all_tab_columns
WHERE data_type LIKE '%VARCHAR%'
AND data_length >= LENGTH(vsearchstr))
LOOP
BEGIN
EXECUTE IMMEDIATE
'select count(1)
from "' || k.owner || '"."' || k.table_name || '"
where "' || k.column_name || '" = :1'
INTO ncount
USING vsearchstr;
IF (ncount > 0)
THEN
vresult := vresult || ','
|| k.owner || '.' || k.table_name || '.' || k.column_name;
END IF;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
END LOOP;
IF (LENGTH(vresult) > 1)
THEN
dbms_output.put_line(vresult);
ELSE
dbms_output.put_line('not found');
END IF;
END;
/
db<>fiddle here
Here is the final query (which works):
DECLARE
ncount NUMBER;
vwhere VARCHAR2(1000) := '';
vsearchstr VARCHAR2(1000) := '%your string here%';
vresult VARCHAR2(10000) := '';
vtab VARCHAR2(1000) := '';
vcol VARCHAR2(1000) := '';
BEGIN
FOR k IN (SELECT table_name, column_name
FROM all_tab_columns
WHERE data_type LIKE '%VARCHAR%'
AND data_length >= LENGTH(vsearchstr)
AND table_name NOT IN (SELECT view_name FROM all_views))
LOOP
BEGIN
vtab := k.table_name;
vcol := k.column_name;
vwhere := ' where ' || vcol || ' like :vsearchstr ';
EXECUTE IMMEDIATE 'select count(1) from OWNER.' || vtab || vwhere INTO ncount USING vsearchstr;
IF (ncount > 0)
THEN
vresult := CONCAT(vresult, 'table: ' || vtab || ', column: ' || vcol || chr(13) || chr(10));
END IF;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END LOOP;
IF (LENGTH(vresult) > 0)
THEN
dbms_output.put_line(vresult);
ELSE
dbms_output.put_line('string not found');
END IF;
END;
I took Littlefoot's advice and added an exception block to catch the exception when the table is not found and just continue with the loop. I also took MT0's advice and checked the length of the column in the select query. I also included the owner in the select query.
This works. Thanks to everyone who gave their advice.
The below code is used to create the stored procedure and this is how the stored procedure is called. Stored procedure is throwing ORA-20000: ORA-00913: too many values
BEGIN EDW_ODS_DELETE_RECORDS_PKG.MAIN('EDWFIN', 'PSP_LABOR_ADJUSTMENTS_F'); END;
create or replace
PACKAGE BODY "EDW_ODS_DELETE_RECORDS_PKG" AS
CON_EDW_IUD_FLAG CONSTANT CHAR := 'D';
CON_CURRENT_FLAG CONSTANT CHAR := 'N';
CON_DELETED_FLAG CONSTANT CHAR := 'Y';
PROCEDURE main (table_schema_in IN VARCHAR2, target_table_alias_in IN VARCHAR2) AS
v_source_schema VARCHAR2(30);
v_source_table_name VARCHAR2(30);
v_target_schema VARCHAR2(30);
v_target_table_name VARCHAR2(30);
v_source_table_key VARCHAR2(1000);
v_target_table_key VARCHAR2(1000);
v_table_type VARCHAR2(20);
v_last_deleted_date DATE;
v_delete_control_id NUMBER := 0;
v_target_table_alias VARCHAR2(30);
v_arc_target_table_name VARCHAR2(30);
BEGIN
SELECT SOURCE_TABLE_SCHEMA, SOURCE_TABLE_NAME, SOURCE_TABLE_KEY,
TARGET_TABLE_SCHEMA, TARGET_TABLE_NAME, TARGET_TABLE_KEY,
TABLE_TYPE, LAST_DELETED_DATE, DELETE_CONTROL_ID,
TARGET_TABLE_ALIAS,
ARC_TARGET_TABLE_NAME
INTO
v_source_schema, v_source_table_name, v_source_table_key,
v_target_schema, v_target_table_name, v_target_table_key,
v_table_type, v_last_deleted_date, v_delete_control_id,
v_target_table_alias,
v_arc_target_table_name
FROM EDW_ODS_DWH_DELETES_CONTROL
WHERE target_table_schema = table_schema_in AND target_table_alias = target_table_alias_in;
DBMS_OUTPUT.PUT_LINE ('Values : ' || v_last_deleted_date);
v_source_table_key := REPLACE(v_source_table_key, '~', ' || ''~'' || ');
v_target_table_key := REPLACE(v_target_table_key, '~', ' || ''~'' || ');
IF v_table_type = 'FACT' THEN
UPDATE_FACT_TABLES (v_source_schema, v_source_table_name, v_source_table_key,
v_target_schema, v_target_table_name, v_target_table_key,
v_last_deleted_date, v_delete_control_id,v_arc_target_table_name);
ELSE
UPDATE_DIMENSION_TABLES (v_source_schema, v_source_table_name, v_source_table_key,
v_target_schema, v_target_table_name, v_target_table_key,
v_last_deleted_date, v_delete_control_id, v_table_type);
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
ERROR_LOG(v_delete_control_id, table_schema_in, target_table_alias_in,
SQLERRM, DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
RAISE;
END main;
PROCEDURE UPDATE_FACT_TABLES (source_schema_in IN VARCHAR2, source_table_name_in IN VARCHAR2, source_table_key_in IN VARCHAR2,
target_schema_in IN VARCHAR2, target_table_name_in IN VARCHAR2, target_table_key_in IN VARCHAR2,
last_deleted_date_in IN DATE, delete_control_id_in IN NUMBER, arc_target_table_name IN VARCHAR2) AS
v_source_stage_table VARCHAR2(70);
v_target_fact_table VARCHAR2(70);
v_target_arc_table VARCHAR2(70);
query_fetch_deleted_recs CLOB;
query_insert_into_arc CLOB;
query_delete_from_fact CLOB;
v_delete_count NUMBER;
v_insert_count NUMBER;
v_last_deleted_date VARCHAR2(100) := TO_CHAR(last_deleted_date_in,'MM/DD/YYYY HH24:MI:SS');
BEGIN
v_source_stage_table := source_schema_in || '.' || source_table_name_in;
v_target_fact_table := target_schema_in || '.' || target_table_name_in;
v_target_arc_table := target_schema_in || '.' || arc_target_table_name;
query_fetch_deleted_recs := ' SELECT * FROM ' || v_source_stage_table ||
' STAGING_TABLE WHERE STAGING_TABLE.' || source_table_key_in || ' = FACT_TABLE.' || target_table_key_in ||
' AND NVL(STAGING_TABLE.EDW_IUD_FLAG, ''X'') = '''|| CON_EDW_IUD_FLAG ||
''' AND STAGING_TABLE.EDW_UPDATE_DATE_TIME >= TO_DATE('''|| v_last_deleted_date || ''', ''MM/DD/YYYY HH24:MI:SS'')';
DBMS_OUTPUT.PUT_LINE ('Query : ' || query_fetch_deleted_recs);
query_insert_into_arc := 'INSERT INTO ' || v_target_arc_table || ' SELECT * FROM ' || v_target_fact_table ||
' FACT_TABLE WHERE EXISTS (' || query_fetch_deleted_recs || ')';
query_delete_from_fact := 'DELETE FROM ' || v_target_fact_table ||
' FACT_TABLE WHERE EXISTS (' || query_fetch_deleted_recs || ')';
DBMS_OUTPUT.PUT_LINE ('Insert Query : ' || query_insert_into_arc);
EXECUTE IMMEDIATE query_insert_into_arc;
v_insert_count := SQL%ROWCOUNT;
EXECUTE IMMEDIATE query_delete_from_fact;
v_delete_count := SQL%ROWCOUNT;
COMMIT;
DBMS_OUTPUT.PUT_LINE ('Results : ' || v_insert_count ||v_delete_count);
--IF v_insert_count > 0 AND v_delete_count > 0 THEN
UPDATE_LAST_DELETED_DATE(v_source_stage_table, delete_control_id_in);
--END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END UPDATE_FACT_TABLES;
PROCEDURE UPDATE_DIMENSION_TABLES (source_schema_in IN VARCHAR2, source_table_name_in IN VARCHAR2, source_table_key_in IN VARCHAR2,
target_schema_in IN VARCHAR2, target_table_name_in IN VARCHAR2, target_table_key_in IN VARCHAR2,
last_deleted_date_in IN DATE, delete_control_id_in IN NUMBER, table_type_in IN VARCHAR2) AS
v_source_stage_table VARCHAR2(70);
v_target_dim_table VARCHAR2(70);
query_fetch_deleted_recs CLOB;
query_update_dim1 CLOB;
query_update_dim2 CLOB;
query_update_dim6 CLOB;
v_update_count NUMBER;
v_last_deleted_date VARCHAR2(100) := TO_CHAR(last_deleted_date_in,'MM/DD/YYYY HH24:MI:SS');
BEGIN
v_source_stage_table := source_schema_in || '.' || source_table_name_in;
v_target_dim_table := target_schema_in || '.' || target_table_name_in;
query_fetch_deleted_recs := ' SELECT * FROM ' || v_source_stage_table ||
' STAGING_TABLE WHERE STAGING_TABLE.' || source_table_key_in || ' = DIM_TABLE.' || target_table_key_in ||
' AND NVL(STAGING_TABLE.EDW_IUD_FLAG, ''X'') = '''|| CON_EDW_IUD_FLAG ||
''' AND STAGING_TABLE.EDW_UPDATE_DATE_TIME >= TO_DATE('''|| v_last_deleted_date || ''', ''MM/DD/YYYY HH24:MI:SS'')';
IF table_type_in = 'DIMTYP1' THEN
query_update_dim1 := 'UPDATE ' || v_target_dim_table || ' DIM_TABLE' ||
' SET DIM_TABLE.DELETE_FLAG = ''' || CON_DELETED_FLAG || ''', DIM_TABLE.EDW_UPDATE_DATE_TIME = SYSDATE, DIM_TABLE.EDW_UPDATED_BY = ''ETL'''||
' WHERE (DIM_TABLE.DELETE_FLAG = ''N'' OR DIM_TABLE.DELETE_FLAG IS NULL)' ||
' AND EXISTS (' || query_fetch_deleted_recs || ')';
DBMS_OUTPUT.PUT_LINE ('Query : ' || query_update_dim1);
EXECUTE IMMEDIATE query_update_dim1;
v_update_count := SQL%ROWCOUNT;
ELSIF table_type_in = 'DIMTYP2' THEN
query_update_dim2 := 'UPDATE ' || v_target_dim_table || ' DIM_TABLE' ||
' SET DIM_TABLE.CURRENT_FLAG = ''' || CON_CURRENT_FLAG || ''', DIM_TABLE.EDW_END_DATE = TRUNC(SYSDATE), ' ||
' EDW_UPDATE_DATE_TIME = SYSDATE, EDW_UPDATED_BY = ''ETL'''||
' WHERE DIM_TABLE.CURRENT_FLAG = ''Y'' AND EXISTS (' || query_fetch_deleted_recs || ')';
EXECUTE IMMEDIATE query_update_dim2;
v_update_count := SQL%ROWCOUNT;
ELSIF table_type_in = 'DIMTYP6' THEN
query_update_dim6 := 'UPDATE ' || v_target_dim_table || ' DIM_TABLE' ||
' SET DIM_TABLE.EDW_END_DATE = TRUNC(SYSDATE) , ' ||
' DIM_TABLE.DELETE_FLAG = ''' || CON_DELETED_FLAG || ''', DIM_TABLE.EDW_UPDATE_DATE_TIME = SYSDATE, DIM_TABLE.EDW_UPDATED_BY = ''ETL'''||
' WHERE DIM_TABLE.EDW_END_DATE = TO_DATE(''12/31/2099'',''MM/DD/YYYY'') AND EXISTS (' || query_fetch_deleted_recs || ')';
EXECUTE IMMEDIATE query_update_dim6;
v_update_count := SQL%ROWCOUNT;
END IF;
COMMIT;
DBMS_OUTPUT.PUT_LINE ('Results : ' || v_update_count);
--IF v_update_count > 0 THEN
UPDATE_LAST_DELETED_DATE(v_source_stage_table, delete_control_id_in);
--END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END UPDATE_DIMENSION_TABLES;
PROCEDURE UPDATE_LAST_DELETED_DATE (source_stage_table_in IN VARCHAR2, delete_control_id_in IN NUMBER) AS
query_max_updated_query CLOB;
v_max_updated_date DATE;
BEGIN
query_max_updated_query := 'SELECT MAX(STAGE_TABLE.EDW_UPDATE_DATE_TIME) FROM ' || source_stage_table_in || ' STAGE_TABLE ';
EXECUTE IMMEDIATE query_max_updated_query INTO v_max_updated_date;
UPDATE EDW_ODS_DWH_DELETES_CONTROL
SET EDW_UPDATE_DATE_TIME = SYSDATE, EDW_UPDATED_BY = 'ETL', LAST_DELETED_DATE = v_max_updated_date
WHERE DELETE_CONTROL_ID = delete_control_id_in;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END UPDATE_LAST_DELETED_DATE;
PROCEDURE ERROR_LOG (delete_record_key_in IN NUMBER, table_schema_in IN VARCHAR2, target_table_in IN VARCHAR2,
error_message_in IN VARCHAR2, error_backtrace_in IN VARCHAR2 := NULL) AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO edw_ods_dwh_deletes_log VALUES (delete_record_key_in, target_table_in, table_schema_in, SYSDATE, 'ETL',
error_message_in, error_backtrace_in);
COMMIT;
RAISE_APPLICATION_ERROR(-20000, error_message_in);
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ERROR_LOG;
END EDW_ODS_DELETE_RECORDS_PKG;
Your error is, as the ORA-00913 error message states, that in one of your statements you are passing too many values.
Cause:
As an example, if you have the table:
CREATE TABLE table_name (a NUMBER, b NUMBER);
And you do either of:
INSERT INTO table_name VALUES (1,2,3);
INSERT INTO table_name (a,b) VALUES (1,2,3);
Then you get the error:
ORA-00913: too many values
As you are trying to insert three values into two columns.
If you name all the columns:
INSERT INTO table_name (a,b,c) VALUES (1,2,3);
Then you will get a more expressive error message:
ORA-00904: "C": invalid identifier
Solution:
Go through every statement and explicitly name the columns you are inserting and make sure you are selecting the same number of columns.
For example, in the UPDATE_FACT_TABLES procedure you have:
query_insert_into_arc := 'INSERT INTO ' || v_target_arc_table || ' SELECT * FROM ' || v_target_fact_table ||
' FACT_TABLE WHERE EXISTS (' || query_fetch_deleted_recs || ')';
It would be better to be:
query_insert_into_arc := 'INSERT INTO ' || v_target_arc_table || ' (col1, col2, col3, col4)
|| ' SELECT other_col1, other_col2, other_col3, other_col3'
|| ' FROM ' || v_target_fact_table || ' FACT_TABLE'
|| ' WHERE EXISTS (' || query_fetch_deleted_recs || ')';
Then you know which columns are being inserted where and you will get a more detailed error message if you are trying to insert into a column that does not exist.
You also do not need to use:
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
As the default behaviour is to rollback the transaction when an exception is raised.
Additionally, you should not raise an exception in your autonomous transaction as you are already raising the exception in the non-autonomous procedure.
PROCEDURE ERROR_LOG (
delete_record_key_in IN NUMBER,
table_schema_in IN VARCHAR2,
target_table_in IN VARCHAR2,
error_message_in IN VARCHAR2,
error_backtrace_in IN VARCHAR2 := NULL
)
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO edw_ods_dwh_deletes_log (
delete_record_key, -- Give the columns their proper names
target_table,
table_schema,
change_date,
col1,
error_message,
error_backtrace
) VALUES (
delete_record_key_in,
target_table_in,
table_schema_in,
SYSDATE,
'ETL',
error_message_in,
error_backtrace_in
);
COMMIT;
-- Remove raising the exception.
END ERROR_LOG;
I've been trying to identify what's wrong with the Insert Statement in the Execute Immediate for few hours without luck. Made sure that I am not missing any commas or entering any incorrect character.
I have gone through all the answers on SO and other websites trying to figure out what could I be doing wrong but no luck.
Running this function results in the following error (error line starts with "-->" Please ignore it as its just for highlighting purpose):
ORA-00936: missing expression
ORA-06512: at "BDW_AMPS.COUNT_RECORDS", line 62
and here's the PL/SQL Code for the function:
CREATE OR REPLACE FUNCTION count_records (
p_test_case_id IN NUMBER,
p_table_name IN VARCHAR2
) RETURN VARCHAR2 IS
v_amt_recs INT;
v_test_result VARCHAR2(10);
v_threshold_val VARCHAR2(10);
v_test_suite_table VARCHAR2(100);
v_test_result_id NUMBER;
v_batch_id NUMBER;
v_report_id NUMBER;
v_test_seq_no NUMBER;
v_session_name VARCHAR2(100);
v_error_description VARCHAR2(100);
v_process_by VARCHAR2(100);
BEGIN
v_test_suite_table := 'bdw_amps.spares_bdw_test_suite';
v_process_by := 'INFORMATICA';
EXECUTE IMMEDIATE 'SELECT THRESHHOLD_VALUE FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
INTO v_threshold_val;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || p_table_name
INTO v_amt_recs;
EXECUTE IMMEDIATE 'SELECT BDW_AMPS.SPARES_TEST_SEQ_ID_SEQ.NEXTVAL FROM DUAL'
INTO v_test_result_id;
EXECUTE IMMEDIATE 'SELECT MAX(BATCH_ID) FROM BDW_AMPS.spares_bdw_session_audit
WHERE SESSION_NAME=(SELECT SESSION_NAME FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
|| ')'
INTO v_batch_id;
EXECUTE IMMEDIATE 'SELECT REPORT_ID FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
INTO v_report_id;
EXECUTE IMMEDIATE 'SELECT TEST_SEQ FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
INTO v_test_seq_no;
EXECUTE IMMEDIATE 'SELECT SESSION_NAME FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
INTO v_session_name;
IF
v_amt_recs > v_threshold_val
THEN
v_test_result := 'PASS';
--> EXECUTE IMMEDIATE 'INSERT INTO BDW_AMPS.spares_bdw_test_results(
TEST_RESULT_ID,
BATCH_ID,
REPORT_ID,
TEST_CASE_ID,
TEST_SEQ_NO,
TABLE_NAME,
SESSION_NAME,
TEST_RESULT,
PROCESS_DATE,
PROCESS_BY
)
VALUES
('|| v_test_result_id || ',
' || v_batch_id || ',
' || v_report_id || ',
' || p_test_case_id || ',
' || v_test_seq_no || ',
' || p_table_name || ',
' || v_session_name || ',
' || v_test_result || ',
SYSDATE,
' || v_process_by || '
)';
EXECUTE IMMEDIATE 'commit';
ELSE
v_test_result := 'FAIL';
v_error_description := 'Count: ' || v_amt_recs || ' is greater than threshold value: ' || v_threshold_val;
EXECUTE IMMEDIATE 'INSERT INTO BDW_AMPS.spares_bdw_test_results(
TEST_RESULT_ID,
BATCH_ID,
REPORT_ID,
TEST_CASE_ID,
TEST_SEQ_NO,
TABLE_NAME,
SESSION_NAME,
TEST_RESULT,
ERROR_DESCRIPTION,
PROCESS_DATE,
PROCESS_BY
)
VALUES (
'|| v_test_result_id || ',
' || v_batch_id || ',
' || v_report_id || ',
' || p_test_case_id || ',
' || v_test_seq_no || ',
' || p_table_name || ',
' || v_session_name || ',
' || v_test_result || ',
' || v_error_description || ',
SYSDATE,
' || v_process_by || '
)';
EXECUTE IMMEDIATE 'commit';
END IF;
RETURN v_test_result;
END;
Assuming that this is a simplified example of something that really does need to be dynamic, one issue is that the string values are not quoted. (If you'd had date values they would need special handling too.)
For example:
create table demo (numcol number, stringcol varchar2(20));
declare
l_num number := 123;
l_string varchar2(20) := 'Kittens';
l_sql long := 'insert into demo(numcol, stringcol) values ('||l_num||', '||l_string||')';
begin
dbms_output.put_line(l_sql);
execute immediate l_sql;
end;
/
Generated code:
insert into demo(numcol, stringcol) values (123, Kittens)
Fails with:
ORA-00984: column not allowed here
The error will vary depending on the contents of the string. For example, if it contains spaces:
declare
l_num number := 123;
l_string varchar2(20) := 'Kittens are cute';
l_sql long := 'insert into demo(numcol, stringcol) values ('||l_num||', '||l_string||')';
begin
dbms_output.put_line(l_sql);
execute immediate l_sql;
end;
/
Generated code:
insert into demo(numcol, stringcol) values (123, Kittens are cute)
ORA-00917: missing comma
or commas:
declare
l_num number := 123;
l_string varchar2(20) := 'Kittens, Puppies';
l_sql long := 'insert into demo(numcol, stringcol) values ('||l_num||', '||l_string||')';
begin
dbms_output.put_line(l_sql);
execute immediate l_sql;
end;
/
insert into demo(numcol, stringcol) values (123, Kittens, Puppies)
ORA-00913: too many values
You need to build the quoting:
declare
l_num number := 123;
l_string varchar2(20) := 'Kittens, Puppies';
l_sql long := 'insert into demo(numcol, stringcol) values ('||l_num||', '''||l_string||''')';
begin
dbms_output.put_line(l_sql);
execute immediate l_sql;
end;
/
so that you generate
insert into demo(numcol, stringcol) values (123, 'Kittens, Puppies')
(If the string could contain quote characters, that would need more work.)
It's worth always building the dynamic SQL as a variable and printing or logging it on failure, as it's usually pretty clear what the issue is when you can see the code.
Another point is that concatenating values like this is resource-intensive, as Oracle tries to cache SQL statements for reuse, so they will be individually parsed and optimised and take space in the cache, but they will never be reused. If this is going to be frequently run with different values, you should consider using bind variables via the using clause of execute immediate.
Your DML(INSERT) statements do not need EXECUTE IMMEDIATE statements. So, remove them after line 61 :
CREATE OR REPLACE FUNCTION count_records (
p_test_case_id IN NUMBER,
p_table_name IN VARCHAR2
) RETURN VARCHAR2 IS
v_amt_recs INT;
v_test_result VARCHAR2(10);
v_threshold_val VARCHAR2(10);
v_test_suite_table VARCHAR2(100);
v_test_result_id NUMBER;
v_batch_id NUMBER;
v_report_id NUMBER;
v_test_seq_no NUMBER;
v_session_name VARCHAR2(100);
v_error_description VARCHAR2(100);
v_process_by VARCHAR2(100);
BEGIN
v_test_suite_table := 'bdw_amps.spares_bdw_test_suite';
v_process_by := 'INFORMATICA';
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || p_table_name
INTO v_amt_recs;
v_test_result_id := BDW_AMPS.SPARES_TEST_SEQ_ID_SEQ.NEXTVAL;
EXECUTE IMMEDIATE 'SELECT MAX(BATCH_ID) FROM BDW_AMPS.spares_bdw_session_audit
WHERE SESSION_NAME=(SELECT SESSION_NAME FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = '
|| p_test_case_id
|| ')'
INTO v_batch_id;
EXECUTE IMMEDIATE 'SELECT THRESHHOLD_VALUE, REPORT_ID, TEST_SEQ,SESSION_NAME FROM '
|| v_test_suite_table
|| ' WHERE TEST_CASE_ID = :caseId'
INTO v_threshold_val,v_report_id,v_test_seq_no,v_session_name
USING p_test_case_id;
IF
v_amt_recs > v_threshold_val
THEN
v_test_result := 'PASS';
INSERT INTO BDW_AMPS.spares_bdw_test_results(
TEST_RESULT_ID,
BATCH_ID,
REPORT_ID,
TEST_CASE_ID,
TEST_SEQ_NO,
TABLE_NAME,
SESSION_NAME,
TEST_RESULT,
PROCESS_DATE,
PROCESS_BY
)
VALUES
( v_test_result_id ,
v_batch_id ,
v_report_id ,
p_test_case_id ,
v_test_seq_no ,
p_table_name ,
v_session_name ,
v_test_result ,
SYSDATE,
v_process_by
);
commit;
ELSE
v_test_result := 'FAIL';
v_error_description := 'Count: ' || v_amt_recs || ' is greater than threshold value: ' || v_threshold_val;
INSERT INTO BDW_AMPS.spares_bdw_test_results(
TEST_RESULT_ID,
BATCH_ID,
REPORT_ID,
TEST_CASE_ID,
TEST_SEQ_NO,
TABLE_NAME,
SESSION_NAME,
TEST_RESULT,
ERROR_DESCRIPTION,
PROCESS_DATE,
PROCESS_BY
)
VALUES (
v_test_result_id ,
v_batch_id ,
v_report_id ,
p_test_case_id ,
v_test_seq_no ,
p_table_name ,
v_session_name ,
v_test_result ,
v_error_description ,
SYSDATE,
v_process_by
);
commit;
END IF;
RETURN v_test_result;
END;
while usage of them are right for SELECT statements because of dynamic table names.
I'm still a relatively newbe when it comes to PL/SQL.
Using Oracle 12c on Linux RHEL 6.8, the following shell script will attempt to activate all RI constraints in a collection of tables, and if they fail with parent key failures, it will dump the first 100 rows (or less) of the offending data. Or at least that is the goal. Since the script deals mostly with system tables on 12c (with only a small user table list that is unique to my installation), I'm including the whole thing exactly from my environment.
The main work occurs in the exception handling where the system tables are queried for the constraint, and user queries are formed from those data.
As a extra goal, the output is rather messy and I want to clean it up, but first it has to work :)
The output / error I get for my tables is the following:
ERROR Handling here for table NRNG_MTC_VST Constraint Name:
SYS_C0011790 Final SQL = SELECT DISTINCT NRNG_MTC_VST.LOG_CRT_DT ,
NRNG_MTC_VST.NRRNG_MTC_LG_ID FROM ODB_PRIMARY.NRNG_MTC_VST WHERE NOT
EXISTS (SELECT 1 FROM ODB_PRIMARY.NRNG_MTC_LOG WHERE
NRNG_MTC_VST.LOG_CRT_DT = NRNG_MTC_LOG.LOG_CRT_DT AND
NRNG_MTC_VST.NRRNG_MTC_LG_ID = NRNG_MTC_LOG.NRRNG_MTC_LG_ID) FETCH
FIRST 100 rows only
---xxx End SQL DECLARE
* ERROR at line 1: ORA-01001: invalid cursor ORA-06512: at line 111 ORA-02298: cannot validate (ODB_PRIMARY.SYS_C0011790) - parent keys
not found
The output SQL from my print_line is correct, and would work if pasted directly into a SQLDeveloper session. There is just something silly about how the cursor is defined I don't understand.
The full text of the script. BYW, if you see other bonehead changes that should be made unrelated to the error, please suggest them as well.
cd $OGGHOME/scripts
export ORACLE_SID=odbod07 $ORACLE_HOME/bin/sqlplus <<-EOF / as sysdba
alter session set container=p01_odbod07;
set echo on set feedback on
set heading off
set serveroutput on size 10000
DECLARE finalsql varchar2(2048);
part1sql varchar2(1024) ;
part2sql varchar2(1024) := ' ';
cownername varchar2(1024);
ctablename varchar2(1024);
pownername varchar2(1024);
ptablename varchar2(1024);
cnt number := 0;
-- Weak cursor defs
my_cursor sys_refcursor;
BEGIN FOR i in (
select owner, table_name, constraint_name
from dba_constraints
where constraint_type = 'R'
and status = 'DISABLED'
and owner = 'ODB_PRIMARY'
and TABLE_NAME in
-- enter user tables with RI constraints here
('RRNG_MTC_STN_CPLY',
'NRNG_MTC_VST_MTRL_USG',
'NRNG_MTC_VST',
'CAR_CORE',
'NRNG_MTC_LOG'))
-- end user table definitions, rest of code should rely only on system tables
LOOP BEGIN
dbms_output.put_line('alter table '||i.owner|| '.' ||
i.table_name || ' enable constraint '||i.constraint_name);
execute immediate 'alter table '||i.owner|| '.' ||
i.table_name || ' enable constraint '||i.constraint_name;
EXCEPTION
-- exception handling - dump offending data
WHEN OTHERS THEN -- take all exceptions for now
dbms_output.put_line ('ERROR Handling here for table ' ||
i.table_name || ' Constraint Name: ' ||i.constraint_name);
finalsql := 'SELECT DISTINCT ';
part1sql := '';
part2sql := ' ';
cnt := 0;
for constraint in (
SELECT ucc1.OWNER as childowner,
ucc1.TABLE_NAME as childtable,
ucc1.column_name as childcolumn,
ucc2.OWNER as parentowner,
ucc2.TABLE_NAME as parenttable,
ucc2.column_name as parentcolumn,
utc1.data_type as childdatatype,
utc1.data_length as childdatalen
FROM all_constraints uc ,
all_cons_columns ucc1 ,
all_cons_columns ucc2,
all_tab_columns utc1
WHERE
uc.constraint_name = ucc1.constraint_name
AND uc.r_constraint_name = ucc2.constraint_name
AND ucc1.POSITION = ucc2.POSITION
AND ucc1.table_name = utc1.table_name
AND ucc1.column_name = utc1.column_name
AND uc.constraint_type = 'R'
AND uc.constraint_name = i.constraint_name
ORDER BY ucc1.TABLE_NAME , uc.constraint_name)
loop
cownername := constraint.childowner;
ctablename := constraint.childtable;
pownername := constraint.parentowner;
ptablename := constraint.parenttable;
if cnt > 0 then
part1sql := part1sql || ' , ';
part2sql := part2sql || ' AND ';
end if;
part1sql := part1sql || constraint.childtable ||
'.'||constraint.childcolumn || ' ';
part2sql := part2sql || constraint.childtable || '.'
|| constraint.childcolumn || ' = '
|| constraint.parenttable || '.' ||
constraint.parentcolumn;
cnt := cnt + 1;
end loop;
finalsql := finalsql || part1sql ||
' FROM ' || ' ' || cownername ||
'.' || ctablename ||
' WHERE NOT EXISTS (SELECT 1 FROM ' ||
pownername || '.' || ptablename ||
' WHERE ' || part2sql || ') FETCH FIRST 100 rows only';
dbms_output.put_line ('Final SQL = ' || finalsql);
dbms_output.put_line ('---xxx End SQL');
open my_cursor for finalsql;
dbms_sql.return_result(my_cursor);
close my_cursor;
-- EXECUTE IMMEDIATE finalsql;
END;
end loop; end;
/
EOF
Many thanks for any help provided.
Brian
Just to narrow this down to a simple test case, I think this is the error you are seeing:
declare
my_cursor sys_refcursor;
begin
open my_cursor for 'select ''Hello, world'' as message from dual';
dbms_sql.return_result(my_cursor);
close my_cursor; -- << Remove this line
end;
/
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 6
This is because you attempted to close the cursor when you have already passed it to dbms_sql for processing. Remove the line with close my_cursor.
declare
my_cursor sys_refcursor;
begin
open my_cursor for 'select ''Hello, world'' as message from dual';
dbms_sql.return_result(my_cursor);
end;
/
PL/SQL procedure successfully completed.
ResultSet #1
MESSAGE
------------
Hello, world
1 row selected.
I had same kind of issue when i tried to print Ref_cursor directly. Then i created a Record type variable and then fetched field values in that variable and then i used DBMS_OUTPUT for that record type variable.
Please see if below code and scenario can help you-
set serveroutput on;
declare
v_sql varchar2(1000);
v_cursor sys_refcursor;
type myrec is record(col1 varchar2(100),col2 varchar2(1000));
rec myrec;
begin
v_sql:='select name,status from t_employee where user_id in (''C001117'',''C001122'')';
open v_cursor for v_sql;
loop
fetch v_cursor
into rec;
exit when v_cursor%notfound;
dbms_output.put_line( rec.col1||':status '||rec.col2 );
end loop;
end;
/
The following is my semi-complete script. Given a table list, it will attempt to activate the RI Constraints, and if they fail it will print out the FK data records in the child table that prevent it from being applied.
The hardest part of this project was the fact that the FKs can be any number of columns and of any type, so the print the results of the select in this case was very tricky (IMO).
Thanks for the help people provided.
cd $OGGHOME/scripts
. ./functions.sh
$ORACLE_HOME/bin/sqlplus ${ORACLE_USERID}/${ORACLE_PASSWORD}#${ORACLE_SID} << EOF
set echo on
set feedback on
set heading off
set serveroutput on size unlimit
DECLARE
finalsql varchar2(2048);
part1sql varchar2(1024) ;
part2sql varchar2(1024) := ' ';
cownername varchar2(1024);
ctablename varchar2(1024);
pownername varchar2(1024);
ptablename varchar2(1024);
cnt number := 0;
desc_tab dbms_sql.desc_tab;
col_count INTEGER;
cursor_name INTEGER;
-- Weak cursor defs
my_cursor sys_refcursor;
col1 varchar2(50);
d number;
j number;
lineout varchar2(2048);
plineout varchar2(2048);
rows number;
eCount number := 0;
BEGIN
FOR i in (
select owner, table_name, constraint_name
from dba_constraints
where constraint_type = 'R'
and status = 'DISABLED'
and owner = '$DBSCHEMA'
and TABLE_NAME in (
'RRNG_MTC_STN_CPLY',
'NRNG_MTC_VST_MTRL_USG',
'NRNG_MTC_VST',
'MTC_TSK_HRHY'))
LOOP
BEGIN
dbms_output.put_line ('.');
dbms_output.put_line ('=====================================');
dbms_output.put('alter table '||i.owner|| '.' || i.table_name || ' enable constraint '||i.constraint_name);
execute immediate 'alter table '||i.owner|| '.' || i.table_name || ' enable constraint '||i.constraint_name;
dbms_output.put_line (' ... SUCCESS');
EXCEPTION -- exception handling - dump offending data
WHEN OTHERS THEN
eCount := eCount + 1;
dbms_output.put_line (' ... FAILED. Constraint Name: ' || i.constraint_name);
finalsql := 'SELECT DISTINCT ';
part1sql := '';
part2sql := ' ';
cnt := 0;
for constraint in (
SELECT ucc1.OWNER as childowner,
ucc1.TABLE_NAME as childtable,
ucc1.column_name as childcolumn,
ucc2.OWNER as parentowner,
ucc2.TABLE_NAME as parenttable,
ucc2.column_name as parentcolumn,
utc1.data_type as childdatatype,
utc1.data_length as childdatalen
FROM all_constraints uc ,
all_cons_columns ucc1 ,
all_cons_columns ucc2,
all_tab_columns utc1
WHERE
uc.constraint_name = ucc1.constraint_name
AND uc.r_constraint_name = ucc2.constraint_name
AND ucc1.POSITION = ucc2.POSITION
AND ucc1.table_name = utc1.table_name
AND ucc1.column_name = utc1.column_name
AND uc.constraint_type = 'R'
AND uc.constraint_name = i.constraint_name
ORDER BY ucc1.TABLE_NAME ,
uc.constraint_name)
loop
cownername := constraint.childowner;
ctablename := constraint.childtable;
pownername := constraint.parentowner;
ptablename := constraint.parenttable;
if cnt > 0 then
part1sql := part1sql || ' , ';
part2sql := part2sql || ' AND ';
end if;
part1sql := part1sql || constraint.childtable || '.' || constraint.childcolumn || ' ';
part2sql := part2sql || constraint.childtable || '.' || constraint.childcolumn || ' = '
|| constraint.parenttable || '.' || constraint.parentcolumn;
cnt := cnt + 1;
end loop;
finalsql := finalsql || part1sql || ' FROM ' || ' ' || cownername || '.' || ctablename || ' WHERE NOT EXISTS (SELECT 1 FROM ' ||
pownername || '.' || ptablename || ' WHERE ' || part2sql || ') FETCH FIRST 100 rows only';
dbms_output.put_line ('Final SQL = (' || finalsql || ')');
-- dbms_output.put_line ('---xxx End SQL');
lineout := 'Child Table: ' || ctablename || '(';
plineout := 'Parent Table: ' || ptablename;
cursor_name := dbms_sql.open_cursor;
dbms_sql.PARSE (cursor_name, finalsql, DBMS_SQL.NATIVE);
d := dbms_sql.execute (cursor_name);
dbms_sql.describe_columns (cursor_name, col_count, desc_tab);
for j in 1..col_count
LOOP
DBMS_SQL.DEFINE_COLUMN (cursor_name, j, col1, 30);
lineout := lineout || desc_tab(j).col_name || ' , ';
-- plineout := plineout || constraint.parentcolumn || ' ';
-- dbms_output.put_line ('Column 1: ' || j || ' is ' || desc_tab(j).col_name || ' type '
-- || desc_tab(j).col_type);
END LOOP j;
lineout := lineout || ')';
-- plineout := plineout || ')';
dbms_output.put_line (lineout);
dbms_output.put_line (plineout);
lineout := NULL;
for j in 1..col_count
LOOP
if j > 1 then
lineout := lineout || ' ';
end if;
lineout := lineout || desc_tab(j).col_name;
END LOOP;
dbms_output.put_line (lineout);
dbms_output.put_line ('----------------------------------------');
LOOP
rows := dbms_sql.fetch_rows (cursor_name);
EXIT WHEN rows = 0;
lineout := NULL;
for j in 1..col_count
LOOP
dbms_sql.column_value (cursor_name, j, col1);
if j > 1 then
lineout := ltrim(lineout || ' ' || col1);
else
lineout := col1;
END IF;
END LOOP;
dbms_output.put_line (lineout);
END LOOP;
dbms_sql.close_cursor (cursor_name);
END;
end loop;
end;
/
EOF
your FETCH FIRST 100 rows only would seem to be out of place.
This is part of the BULK COLLECT clause in a SELECT statement in PL/SQL; as far as I know, it is not part of a SQL statement you can pass into a cursor like this
This is resulting in the cursor statement being invalid
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to export the result into different tabs of Excel in Toad for Data Analyst?
I have a pl/sql code where I have two procedures in it (independent of each other) and on executing the procedure I want the output to be written in one excel file but with two worksheets in it.
Ex:
for one procedure the output should be in sheet 1
for next procedure the ouput should be in sheet 2.
can any one help? i am using utl_file
If you really want to tackle this here's some code I picked up somewhere. Warning: for my purposes I abandoned this approach as I found that loading the XML data into Excel was too slow. Loading a comma-separated values file is much faster although you lose the pretty formatting (not important in my case).
To use:
Call WORKBOOK_OPEN to initialize the CLOB that represents the workbook.
Call WORKSHEET_OPEN to create a worksheet in the book.
Use ROW_OPEN to create a row.
Make a series of calls to CREATE_CELL to create and populate cells in the row.
Continue making calls to ROW_OPEN and CREATE_CELL to create rows and cells.
When you're done with a worksheet call WORKSHEET_CLOSE.
If you need another worksheet call WORKSHEET_OPEN again to open another sheet, then use ROW_OPEN and CREATE_CELL to populate it.
When you're done, call WORKBOOK_CLOSE to close the workbook.
Use EXPORT_WORKBOOK_TO_FILE to write the workbook to a file.
Note that EXPORT_WORKBOOK_TO_FILE uses UTL_FILE to write its output, satisfying your requirement to use UTL_FILE.
Share and enjoy.
PROCEDURE WORKBOOK_OPEN(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := '<?xml version="1.0" encoding="ISO-8859-9"?>' || chr(10) ||
'<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"' || chr(10) ||
'xmlns:o="urn:schemas-microsoft-com:office:office"' || chr(10) ||
'xmlns:x="urn:schemas-microsoft-com:office:excel"' || chr(10) ||
'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"' || chr(10) ||
'xmlns:html="http://www.w3.org/TR/REC-html40">' || chr(10) ||
'<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">' || chr(10) ||
'<WindowHeight>8580</WindowHeight>' || chr(10) ||
'<WindowWidth>15180</WindowWidth>' || chr(10) ||
'<WindowTopX>120</WindowTopX>' || chr(10) ||
'<WindowTopY>45</WindowTopY>' || chr(10) ||
'<ProtectStructure>False</ProtectStructure>' || chr(10) ||
'<ProtectWindows>False</ProtectWindows>' || chr(10) ||
'</ExcelWorkbook>' || chr(10) ||
'<Styles>' || chr(10) ||
'<Style ss:ID="Default" ss:Name="Normal">' || chr(10) ||
'<Alignment ss:Vertical="Bottom"/>' || chr(10) ||
'<Borders/>' || chr(10) ||
'<Font/>' || chr(10) ||
'<Interior/>' || chr(10) ||
'<NumberFormat/>' || chr(10) ||
'<Protection/>' || chr(10) ||
'</Style>' || chr(10) ||
'<Style ss:ID="s22">' || chr(10) ||
'<Font x:Family="Swiss" ss:Bold="1" ss:Underline="Single"/>' || chr(10) ||
'</Style>' || chr(10) ||
'</Styles>';
END WORKBOOK_OPEN;
PROCEDURE WORKBOOK_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Workbook>';
END WORKBOOK_CLOSE;
PROCEDURE WORKSHEET_OPEN(pWorkbook IN OUT NOCOPY CLOB,
pstrWorksheet_name IN VARCHAR2) IS
BEGIN
--
-- Create the worksheet
--
pWorkbook := pWorkbook || '<Worksheet ss:Name="' || pstrWorksheet_name || '"><Table>';
END WORKSHEET_OPEN;
PROCEDURE WORKSHEET_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Table></Worksheet>';
END WORKSHEET_CLOSE;
PROCEDURE ROW_OPEN(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '<Row>';
END ROW_OPEN;
PROCEDURE ROW_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Row>' || chr(10);
END row_close;
PROCEDURE CREATE_CELL(pWorkbook IN OUT NOCOPY CLOB,
pstrCell_contents IN VARCHAR2) IS
BEGIN
pWorkbook := pWorkbook || '<Cell><Data ss:Type="String"> ' ||
pstrCell_contents || ' </Data></Cell>';
END CREATE_CELL;
PROCEDURE EXPORT_WORKBOOK_TO_FILE(pstrDirectory_name IN VARCHAR2,
pstrFilename IN VARCHAR2,
pWorkbook IN CLOB)
IS
nChunk_size CONSTANT BINARY_INTEGER := 32767;
strChunk VARCHAR2(32767);
nPos_chr10 NUMBER;
nWorkbook_len NUMBER;
fHandle UTL_FILE.FILE_TYPE;
nPos NUMBER := 1;
BEGIN
nWorkbook_len := DBMS_LOB.GETLENGTH(pWorkbook);
fHandle := UTL_FILE.FOPEN(pstrDirectory_name, pstrFilename, 'W', nChunk_size);
WHILE nPos < nWorkbook_len LOOP
strChunk := dbms_lob.substr(pWorkbook, nChunk_size, nPos);
EXIT WHEN strChunk IS NULL;
nPos_chr10 := INSTR(strChunk, CHR(10), -1);
IF nPos_chr10 != 0 THEN
strChunk := SUBSTR(strChunk, 1, nPos_chr10 - 1);
END IF;
UTL_FILE.PUT_LINE(fHandle, strChunk, TRUE);
nPos := nPos + LEAST(LENGTH(strChunk)+1, nChunk_size);
END LOOP;
UTL_FILE.FCLOSE(fHandle);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fHandle) THEN
UTL_FILE.FCLOSE(fHandle);
END IF;
RAISE;
END EXPORT_WORKBOOK_TO_FILE;
The common approach is to create the file with the Microsoft XML Spreadsheet (XMLSS), appending the content to a VARCHAR or CLOB.
For instance, to write a cell you would do something like this:
xmlBody := xmlBody || '<Cell><Data ss:Type="String">' || stringContent || '</Data></Cell>';
Now, to open a new worksheet you would do something like:
xmlBody := xmlBody || '<Worksheet ss:Name="' || yourWorksheetName || '"><Table>';
To close it (after appending all your content):
xmlBody := xmlBody || '</Table></Worksheet>';
There are a few utility packages that allow you to do this or you could write your own like I did. You would just have to write functions to open and close elements, for instance, openRow, closeRow, openWorkbook, closeWorkbook, openWorksheet, etc.
Refer to Oracle PL/SQL Utility Library, codename "Alexandria" for more information and examples.