Needs fixing a procedure for inserting values to the table dynamically - oracle

I am trying to use dynamic SQL to insert values into my table. But I am struggling with it! This is my table
CREATE TABLE CARS
(
ID INTEGER PRIMARY KEY,
Manufacturer VARCHAR2(1000),
Model VARCHAR2(1000),
Year INTEGER NOT NULL,
Category VARCHAR2(1000) NOT NULL,
Mileage NUMBER,
FuelType VARCHAR2(1000),
EngineVolume NUMBER,
DriveWheels VARCHAR2(1000),
GearBox VARCHAR2(1000),
Doors VARCHAR2(1000),
Wheel VARCHAR2(1000),
Color VARCHAR2(1000),
InteriorColor VARCHAR2(1000),
VIN VARCHAR2(1000),
LeatherInterior VARCHAR2(1000) NOT NULL,
Price VARCHAR2(1000) NOT NULL,
Clearence VARCHAR2(1000) NOT NULL
)
And I have created a trigger that will increment the id column automatically.
CREATE SEQUENCE cars_seq START WITH 93100;
CREATE OR REPLACE TRIGGER cars_id_inc
BEFORE INSERT ON cars FOR EACH ROW
BEGIN
:NEW.ID := CARS_SEQ.nextval;
END;
Then I have created a procedure that will insert values into the cars table.
CREATE OR REPLACE PROCEDURE insert_all_cars (p_values VARCHAR2) IS
v_stmt VARCHAR2(10000);
BEGIN
v_stmt := 'INSERT INTO CARS ' || ' VALUES ' || p_values;
EXECUTE IMMEDIATE v_stmt;
END;
When I am trying to insert values to the cars table using a procedure like this:
DECLARE
p_values VARCHAR2 := '(''new_manufacturer'', ''new_model'', ' || '2000' || ' ,''new_category'', ' || '2000' ||' ,''new_fueltype'', ' || '3.0' ||
' ,''new_drivewheels'',''new_gearbox'',''new_doors'',''new_wheel'',''new_color'',
''new_interior_color'',''new_vin'',''new_leather_interior'',''new_price'',''new_clearence'')';
BEGIN
insert_all_cars(p_values);
END;
I am getting this kind of error:
Error starting at line : 60 in command -
DECLARE
p_values VARCHAR2 := '(''new_manufacturer'', ''new_model'', ' || '2000' || ' ,''new_category'', ' || '2000' ||' ,''new_fueltype'', ' || '3.0' ||
' ,''new_drivewheels'',''new_gearbox'',''new_doors'',''new_wheel'',''new_color'',
''new_interior_color'',''new_vin'',''new_leather_interior'',''new_price'',''new_clearence'')';
BEGIN
insert_all_cars(p_values);
END;
Error report -
ORA-06550: line 2, column 14:
PLS-00215: String length constraints must be in range (1 .. 32767)
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Also tried to put numbers without quotes got the same kind error.
How I can fix it?

You didn't define the length of p_values in your anonymous pl/sql block. But why use dynamic sql? This is a really poor use case for it. Why not this?
create or replace procedure insert_all_cars (
p_manufacturer VARCHAR2,
p_model VARCHAR2,
p_year INTEGER,
p_category VARCHAR2,
p_mileage NUMBER,
p_fueltype VARCHAR2,
p_enginevolume NUMBER,
p_drivewheels VARCHAR2,
p_gearbox VARCHAR2,
p_doors VARCHAR2,
p_wheel VARCHAR2,
p_color VARCHAR2,
p_interiorcolor VARCHAR2,
p_vin VARCHAR2,
p_leatherinterior VARCHAR2,
p_price VARCHAR2,
p_clearence VARCHAR2) is
begin
insert into cars (
Manufacturer,
Model,
Year,
Category,
Mileage,
FuelType,
EngineVolume,
DriveWheels,
GearBox,
Doors,
Wheel,
Color,
InteriorColor,
VIN,
LeatherInterior,
Price,
Clearence )
values (
p_manufacturer,
p_model,
p_year,
p_category,
p_mileage,
p_fueltype,
p_enginevolume,
p_drivewheels,
p_gearbox,
p_doors,
p_wheel,
p_color,
p_interiorcolor,
p_vin,
p_leatherinterior,
p_price,
p_clearence );
end;
/
And then this:
begin
insert_all_cars (
p_manufacturer => 'new_manufacturer',
p_model => 'new_model',
p_year => 2000,
p_category => 'new_category',
p_mileage => 2000,
p_fueltype => 'new_fueltype',
p_enginevolume => 3.0,
p_drivewheels => 'new_drivewheels',
p_gearbox => 'new_gearbox',
p_doors => 'new_doors',
p_wheel => 'new_wheel',
p_color => 'new_color',
p_interiorcolor => 'new_interior_color',
p_vin => 'new_vin',
p_leatherinterior => 'new_leather_interior',
p_price => 'new_price',
p_clearence => 'new_clearence'
);
commit;
end;
/

Related

CLOB - ORA-06502: PL/SQL: numeric or value error: character string buffer too small

I am using Toad For Oracle and I have a procedure in a package where I'm getting the CLOB value for result. I'll be using dummy variables only but I'll be putting the whole process here.
My goal is to a rest web service where my output will be a JSON CLOB.
START OF THE BODY PACKAGE
TYPE in_emp_type IS RECORD ( emp_id NUMBER,
emp_fname VARCHAR2(50), emp_lname VARCHAR2(50),
city VARCHAR2(40), country(50) )
in_emp_rec in_emp_type ;
TYPE out_emp_type IS RECORD (emp_addr_1 VARCHAR2(100), emp_addr_2 VARCHAR2(100);
TYPE out_emp_tab_type IS TABLE OF out_emp_type;
query_tab out_emp_tab_type := out_emp_tab_type ();
PROCEDURE get_employee_details(api_key VARCHAR2, emp_id NUMBER,
emp_fname VARCHAR2, emp_lname VARCHAR2, out_result_json OUT CLOB ) IS
CURSOR get_all_emp IS
select city, country from emp_table
where emp_id = emp_id
and fname = emp_fname
and lname = emp_lname ;
CURSOR get_emp_addr (emp_id NUMBER, city VARCHAR2) IS
select addr_1, addr_2 from emp_addr
where emp_id = emp_id and city = city;
v_city VARCHAR2;
v_country VARCHAR2;
v_json_input CLOB;
v_result_json CLOB;
BEGIN
open get_all_emp;
fetch get_all_emp into v_city , v_country ;
close get_all_emp;
v_json_input := '{"EmployeeDetails":
{"EmployeeID": || emp_id || ' ",
"EmployeeFirstName": || emp_fname || '",
"EmployeeLastName": || emp_lname || '",
"EmployeeCity": || v_city || '",
"EmployeeCountry": || v_country || '"}'
convert_input_json(v_json_input,in_emp_rec);
open get_emp_addr (emp_id, city);
fetch get_emp_addr bulk collect into query_tab ;
v_result_json := convert_result_json(query_tab);
out_result_json := v_result_json;
END;
And this is my sample of the convert_input_json procedures & convert_result_json function
PROCEDURE convert_input_json(input_json IN CLOB, output_json OUT in_emp_type) IS
CURSOR get_details IS
select emp_id, emp_fname, emp_lname, city, country
from json_table(input_json, '$' COLUMNS (
emp_id NUMBER PATH '$.EmployeeDetails.EmployeeID',
emp_fname VARCHAR2(50) PATH '$.EmployeeDetails.EmployeeFirstName',
emp_lname VARCHAR2(50) PATH '$.EmployeeDetails.EmployeeLastName', ... includeother fields here)
BEGIN
output_json := in_emp_type( emp_id => NULL, emp_fname => NULL....)
open get_details;
fetch get_details into output_json.emp_id , output_json.emp_fname ....);
close get_details;
END;
FUNCTION convert_result_json (in_result out_emp_tab_type) RETURN CLOB IS
v_clob CLOB
BEGIN
FOR i in 1...query_tab.count LOOP
v_clob := '{ '||chr(13)||'"customerResults": {'
v_clob := v_clob||chr(13)||chr(9)||chr(9)||'"addr1": "'||query_tab(i).emp_addr_1 ||'",';
v_clob := v_clob||chr(13)||chr(9)||chr(9)||'"addr2": "'||query_tab(i).emp_addr_2 ||'",';
END LOOP;
RETURN (v_clob)
END;
So when I try to run this, I'm getting the ORA-06502: PL/SQL: numeric or value error: character string buffer too small
And I'm not really sure why am I getting this error. Can someone please help me.
There should be an error stack trace showing the line number that raised the exception. Looking at your code, there are two obvious places where this can happen:
The JSON_TABLE COLUMNS mapping - ensure that emp_fname/emp_lname never overflows the allotted 50 characters.
In the convert_result_json function, you are using || to append new addresses to an existing CLOB value. || will convert the operands to varchar2(32767). If the size of the CLOB + the new string being appended > 32KB, it will fail due to the varchar2 datatype limit. If this is your issue, use dbms_lob to append to your LOBs, not ||.

change column alias and its display format using stored procedure in Oracle PL/SQL

This is my procedure:
CREATE OR REPLACE PROCEDURE sp_SalHireDate_18127057
(
salAlias VARCHAR,
hireDateAlias VARCHAR,
salDisplayFormat VARCHAR,
hireDateFormat VARCHAR
)
AS
sysRefCursor SYS_REFCURSOR;
BEGIN
OPEN sysRefCursor FOR
SELECT SAL TO_CHAR(salAlias), HIREDATE TO_CHAR(hireDateAlias)
FROM EMP_18127057;
DBMS_SQL.RETURN_RESULT(sysRefCursor);
END;
What i want to do is, when passing the alias and its display format(Ex: salary: 1000 to 1000.00; date: dd/mm/yyyy to mm/dd/yyyy), the procedure will display the two columns with alias as their names and show their values in new format. I still don't figure out the code.Can anyone help me please? Thank you very much.
You need to use dynamic SQL to include the aliases in the output:
CREATE OR REPLACE PROCEDURE sp_SalHireDate_18127057
(
salAlias VARCHAR2,
hireDateAlias VARCHAR2,
salDisplayFormat VARCHAR2,
hireDateFormat VARCHAR2
)
AS
sysRefCursor SYS_REFCURSOR;
BEGIN
OPEN sysRefCursor
FOR 'SELECT TO_CHAR( sal, :s ) AS ' || salAlias
|| ', TO_CHAR( hiredate, :h ) AS ' || hireDateAlias
|| ' FROM EMP_18127057' USING salDisplayFormat, hireDateFormat;
DBMS_SQL.RETURN_RESULT(sysRefCursor);
END;
/
However, don't do this as you will introduce SQL injection attacks and can make queries such as:
sp_SalHireDate_18127057(
'salary',
'hd, ( SELECT password_hash FROM secret_table WHERE username = ''ADMIN'' ) AS pwd',
'99999999.00',
'YYYY-MM-DD"T"HH24:MI:SS'
);
Which will return an extra column and data from a table you were not expecting the user to have access to.
Update
If you want to also assert that the aliases are simple SQL identifiers then you can wrap then in a call to DBMS_ASSERT.SIMPLE_SQL_NAME:
CREATE OR REPLACE PROCEDURE sp_SalHireDate_18127057
(
salAlias VARCHAR2,
hireDateAlias VARCHAR2,
salDisplayFormat VARCHAR2,
hireDateFormat VARCHAR2
)
AS
sysRefCursor SYS_REFCURSOR;
BEGIN
OPEN sysRefCursor
FOR 'SELECT TO_CHAR( sal, :s ) AS ' || DBMS_ASSERT.SIMPLE_SQL_NAME( salAlias )
|| ', TO_CHAR( hiredate, :h ) AS ' || DBMS_ASSERT.SIMPLE_SQL_NAME( hireDateAlias )
|| ' FROM EMP_18127057' USING salDisplayFormat, hireDateFormat;
DBMS_SQL.RETURN_RESULT(sysRefCursor);
END;
/
Then:
BEGIN
sp_SalHireDate_18127057( 'salary', 'hd, ( SELECT password_hash FROM secret_table WHERE username = ''ADMIN'' ) AS pwd', '99999999.00', 'YYYY-MM-DD"T"HH24:MI:SS' );
END;
/
Would output:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 215
ORA-06512: at "SCHEMA_NAME.SP_SALHIREDATE_18127057", line 11
ORA-06512: at line 2
db<>fiddle here
Use this select:
SELECT TO_CHAR(salAlias, salDisplayFormat) SAL, TO_CHAR(hireDateAlias, hireDateFormat) HIREDATE

Accessing record fields by their name in Oracle

I want to know if there exists something like Reflection API in PL/SQL or not.
I have a table like
create table my_table (
id number,
value1 number,
value2 number,
value3 number,
value4 number,
value5 number);
And I have a variable as
rec as my_table%rowtype
... fill rec
insert into my_table values rec;
is there any way I can populate rec field dynamically by its name.
I mean I know the index (in this case something between 1 and 5), so I want to set 'value'||index to something.
As in my real case the last index is much more than 5, using a set of if/elsif is not proper. By the way number of fields is increased in long term (for example value6, value7 ma be added next year and so on, so I wan to write some kind of code not to be changed on every new column).
You can access variables in a program using dynamic SQL only if they are available globally. If you declare you record in the spec you can build utility functions that will use EXECUTE IMMEDIATE to build a small PL/SQL block to set the value. Here is a simple example of what you are looking for. Notice you can overload the set procedure to keep your datatypes intact.
CREATE TABLE my_table (
value1 NUMBER,
value2 VARCHAR2(100),
value3 DATE);
CREATE OR REPLACE PACKAGE pkg_my_table IS
my_table_rec my_table%ROWTYPE;
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE);
PROCEDURE insert_a_row;
END pkg_my_table;
/
CREATE OR REPLACE PACKAGE BODY pkg_my_table IS
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN 'BEGIN ' || lower($$PLSQL_UNIT) || '.' || i_record || '.' || i_field || ' := :x; END;';
END build_statement;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE insert_a_row IS
BEGIN
my_table_rec := NULL;
set_value(i_record => 'my_table_rec',
i_field => 'value1',
i_value => 42);
set_value(i_record => 'my_table_rec',
i_field => 'value2',
i_value => 'forty-two');
set_value(i_record => 'my_table_rec',
i_field => 'value3',
i_value => to_date('1/1/1942',
'mm/dd/yyyy'));
INSERT INTO my_table
VALUES my_table_rec;
END insert_a_row;
END pkg_my_table;
/
BEGIN
pkg_my_table.insert_a_row;
END;
/
SELECT *
FROM my_table;
-- 42 forty-two 1/1/1942
Beware globals: you need to reset them correctly before using them again or you might get data carried over from previous invocations. Setting an entire record type variable to NULL will reset all the underlying fields (handy).
You will also be prone to ORA-04068: existing state of packages has been discarded errors should you compile PL/SQL with globals where there are active sessions referencing your code. Generally this is not a problem, but it is a behavioral difference.

Evaluating dynamic PLSQL statment

The purpose of this proc is to combine columns of a record type into a line of text.
Example: SALARY= 80000, POSITION=ENG, ID=8
How can I evaluate l_stmt dynamically.
Please note I do not want to do it the traditional way by concatenation.
Open to other suggestions.
Thank you
CREATE TABLE Employee
(
ID INTEGER,
POSITION VARCHAR2 (32),
SALARY NUMBER
);
INSERT INTO EMPLOYEE (ID, POSITION, SALARY)
VALUES (1, 'ENG', '100000');
INSERT INTO EMPLOYEE (ID, POSITION, SALARY)
VALUES (2, 'PROGRAMMER', '80000');
COMMIT;
CREATE OR REPLACE PROCEDURE column_to_text (rec employee%ROWTYPE)
AS
TYPE tb IS TABLE OF VARCHAR2 (30)
INDEX BY BINARY_INTEGER;
l_colnames tb;
l_stmt VARCHAR2 (2500);
BEGIN
SELECT column_name
BULK COLLECT INTO l_colnames
FROM sys.all_tab_cols
WHERE table_name = 'EMPLOYEE';
FOR i IN 1 .. l_colnames.COUNT
LOOP
l_stmt :=
l_stmt || l_colnames (i) || '=' || 'REC.' || l_colnames (i) || ', ';
END LOOP;
DBMS_OUTPUT.PUT_LINE ( l_stmt );
--SALARY=REC.SALARY, POSITION=REC.POSITION, ID=REC.ID,
END;

Modify multiple oracle triggers

I have a task that involves updating many triggers which are exactly the same query but applied to several different tables. Is there a way to update all these TRIGGERS using a FOR or similar statement? Actually what I need to do is modify the WHEN clause for all this triggers.
you can use dbms_metadat for this.
for example:
declare
type arr_tab is table of varchar2(30);
v_arr arr_tab;
v_trig clob;
begin
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'SQLTERMINATOR', FALSE );
v_arr := arr_tab('TEST_TRIG', 'TEST2_TRIG'); -- change these triggers.
for idx in 1..v_arr.count
loop
v_trig := dbms_metadata.get_ddl('TRIGGER',v_arr(idx), user);
execute immediate regexp_replace(regexp_replace(v_trig, 'ALTER TRIGGER.*', ''), 'WHEN ([^\)]*\))', 'WHEN (1=1)', 1, 1, 'mi');
end loop;
end;
/
the 'WHEN ([^\)]*\))', 'WHEN (1=1)' part replaces the WHEN clause with (in my case) WHEN (1=1).
You can use dba_triggers to extract the text of the trigger into CREATE or replace statements. But due to some of the columns being LONG datatype you will have trouble extracting them as VARCHAR2. This can be resolved by using Tom Kytes package which is lost somewhere on the oracle site. I include my own version which you may have to modify to meet your needs.
Run the select, insert your when clause and then run the create or replace statements.
This won't work due to the trigger_body being a long datatype
select 'CREATE OR REPLACE TRIGGER '|| description
||trigger_body
from dba_triggers
where owner = 'Your schema'
but this should work if your triggers are not more than 4000 characters
select 'CREATE OR REPLACE TRIGGER '|| description
|| ADMIN.LONG_HELP.SUBSTR_OF('select trigger_body from dba_triggers where trigger_name = :0',
1,4000,'0',dt.trigger_name)
from dba_triggers dt
where owner = 'YourSchema';
CREATE OR REPLACE PACKAGE ADMIN.LONG_HELP
/******************************************************************************
NAME: LONG_HELP
PURPOSE: Read fields of type long. (commonly found in data dictionary)
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.0 10/27/2011 1. Created this package. based on Tom Kyte's column here
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:839298816582
note that it only retrieves the first 4000 characters of any LONG column
USAGE in a WHERE
INSTR(
ADMIN.LONG_HELP.SUBSTR_OF('SELECT text from all_views where view_name =:o ',
1,4000,'o',m2.obj_name),m1.FK_ID) > 0
******************************************************************************/
--AUTHID CURRENT_USER
--removed to get around ORA-29470: Effective userid or roles are not the same as when cursor was parsed
--restrict usage to admin schema for Oracle 11g
AS
FUNCTION substr_of (p_query IN VARCHAR2,
p_from IN NUMBER,
p_for IN NUMBER,
p_name1 IN VARCHAR2 DEFAULT NULL ,
p_bind1 IN VARCHAR2 DEFAULT NULL ,
p_name2 IN VARCHAR2 DEFAULT NULL ,
p_bind2 IN VARCHAR2 DEFAULT NULL ,
p_name3 IN VARCHAR2 DEFAULT NULL ,
p_bind3 IN VARCHAR2 DEFAULT NULL ,
p_name4 IN VARCHAR2 DEFAULT NULL ,
p_bind4 IN VARCHAR2 DEFAULT NULL )
RETURN VARCHAR2;
END LONG_HELP;
/
CREATE OR REPLACE PACKAGE BODY ADMIN.LONG_HELP
AS
g_cursor NUMBER := DBMS_SQL.open_cursor;
g_query VARCHAR2 (32765);
PROCEDURE bind_variable (p_name IN VARCHAR2, p_value IN VARCHAR2)
IS
BEGIN
IF (p_name IS NOT NULL)
THEN
DBMS_SQL.bind_variable (g_cursor, p_name, p_value);
END IF;
END BIND_VARIABLE;
FUNCTION substr_of (p_query IN VARCHAR2,
p_from IN NUMBER,
p_for IN NUMBER,
p_name1 IN VARCHAR2 DEFAULT NULL ,
p_bind1 IN VARCHAR2 DEFAULT NULL ,
p_name2 IN VARCHAR2 DEFAULT NULL ,
p_bind2 IN VARCHAR2 DEFAULT NULL ,
p_name3 IN VARCHAR2 DEFAULT NULL ,
p_bind3 IN VARCHAR2 DEFAULT NULL ,
p_name4 IN VARCHAR2 DEFAULT NULL ,
p_bind4 IN VARCHAR2 DEFAULT NULL )
RETURN VARCHAR2
AS
/******************************************************************************
NAME: LONG_HELP.SUBSTR_OF
PURPOSE: CONVERT long data fields into VARCHAR2
WHOSE DATA IS CHANGED: none
WHAT USES THIS:
WHERE ARE THE RESOURCES NEEDED:
******************************************************************************/
l_buffer VARCHAR2 (4000);
l_buffer_len NUMBER;
BEGIN
IF (NVL (p_from, 0) <= 0)
THEN
raise_application_error (-20002,
'From must be >= 1 (positive numbers)');
END IF;
IF (NVL (p_for, 0) NOT BETWEEN 1 AND 4000)
THEN
raise_application_error (-20003, 'For must be between 1 and 4000');
END IF;
IF (p_query <> g_query OR g_query IS NULL)
THEN
IF (UPPER (TRIM (NVL (p_query, 'x'))) NOT LIKE 'SELECT%')
THEN
raise_application_error (-20001, 'This must be a select only');
END IF;
DBMS_SQL.parse (g_cursor, p_query, DBMS_SQL.native);
g_query := p_query;
END IF;
bind_variable (p_name1, p_bind1);
bind_variable (p_name2, p_bind2);
bind_variable (p_name3, p_bind3);
bind_variable (p_name4, p_bind4);
DBMS_SQL.define_column_long (g_cursor, 1);
IF (DBMS_SQL.execute_and_fetch (g_cursor) > 0)
THEN
DBMS_SQL.column_value_long (g_cursor,
1,
p_for,
p_from - 1,
l_buffer,
l_buffer_len);
END IF;
RETURN l_buffer;
END substr_of;
END LONG_HELP;
/

Resources