EXEC USP_USER_INSERT (P_ID = '2', P_FIRST_NAME = 'A', P_LAST_NAME = 'BALAJI', P_SALARY = '50000', P_CITY = 'CHENNAI');
EXEC USP_USER_INSERT #P_ID = '2', #P_FIRST_NAME = 'A', #P_LAST_NAME = 'BALAJI', #P_SALARY = '50000', #P_CITY = 'CHENNAI';
Both are showing error, how to call this SP.
create or replace procedure USP_USER_INSERT
(
P_FIRST_NAME in varchar default null,
P_LAST_NAME in varchar default null,
P_SALARY in number default null,
P_CITY in varchar default null,
P_EMAIL_ID in varchar default null,
P_EMP_ID in number default null,
P_ADDED_BY in number default null,
P_WINDOWS_ID in varchar2 default null,
P_USER_TYPE in number default null,
P_PASSWORD in varchar2 default null,
P_ROLE in varchar2 default null,
RES OUT SYS_REFCURSOR
)
as
P_ID NUMBER;
begin
select NVL(max(ID),0)+1 into P_ID from USER_MASTER ;
insert into USER_MASTER
(
ID,FIRST_NAME,
LAST_NAME,
SALARY,
CITY,
EMAIL_ID,
EMP_ID,
ADDED_ON,
ADDED_BY,
WINDOWS_ID,
IS_ACTIVE,
USER_TYPE,
PASSWORD,
role
)
values (
P_ID,P_FIRST_NAME,
P_LAST_NAME,
P_SALARY,
P_CITY,
P_EMAIL_ID,
P_EMP_ID,
Systimestamp,
P_ADDED_BY,
P_WINDOWS_ID,
1,
P_USER_TYPE,
P_PASSWORD,
P_ROLE
);
open RES for
SELECT P_ID "P_ID" FROM DUAL;
END;
Define a cursor bind variable and then wrap the procedure call in an anonymous PL/SQL block:
VARIABLE cur REFCURSOR;
BEGIN
USP_USER_INSERT (
P_EMP_ID => '2', -- P_ID is not an argument to the procedure
P_FIRST_NAME => 'A',
P_LAST_NAME => 'BALAJI',
P_SALARY => '50000',
P_CITY => 'CHENNAI',
RES => :cur
);
END;
/
PRINT cur;
try this
Procedure has not return value, if you need you can use function
exec USP_USER_INSERT ( par1,par2,....)
if you are running it on command line, you should have / at the end of sql code.
try this use single quotes if 2 is of varchar else leave as it is,
EXEC USP_USER_INSERT '2','A','BALAJI','50000','CHENNAI'
Related
I would like to archive some situation-based type conversions in PLSQL. I guess, it is just not possible but, let's see: Can I do something like the below:
function my_type( p_type in variant) return varchar2
as
begin
if is_date(p_type) and p_type = trunc(p_type ,'dd')
then
return to_string(p_type,'some_setting');
end if;
-- and so on for numbers and other date-type cases
end;
I am also happy about other ideas. Oracle would convert the data into varchar2 anyways. I am just not happy about how it does it. Are there maybe some flexible data settings that you can recommend?
Best, Peter
ANYDATA
Example:
create table t_anydata (
nsq number(19,0) primary key,
anyd anydata
);
insert into t_anydata(nsq, anyd) values (1, sys.anyData.convertNumber(5) ) ;
insert into t_anydata(nsq, anyd) values (2, sys.anyData.convertDate(to_date('01-10-2019', 'dd-mm-yyyy')) ) ;
insert into t_anydata(nsq, anyd) values (3, sys.anyData.convertVarchar2('test varchar') ) ;
insert into t_anydata(nsq, anyd) values (4, sys.anyData.convertChar('c') ) ;
insert into t_anydata(nsq, anyd) values (5, sys.anyData.convertBDouble(3.14159) ) ;
insert into t_anydata(nsq, anyd) values (6, sys.anyData.ConvertTimestamp(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (7, sys.anyData.ConvertTimestampTZ(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (8, sys.anyData.ConvertTimestampLTZ(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (9, sys.anyData.ConvertCollection( sys.odcivarchar2list( 'abcd', 'efgh' ) ) ) ;
insert into t_anydata(nsq, anyd) values (10, sys.anyData.ConvertCollection( sys.odcinumberlist( 1.1, 2.2, 3.3 ) ) ) ;
commit ;
SELECT nsq,
CASE sys.anyData.gettypename(anyd)
WHEN 'SYS.NUMBER' THEN
TO_CHAR(SYS.ANYDATA.accessNumber(anyd))
WHEN 'SYS.VARCHAR2' THEN
SYS.ANYDATA.accessVarchar2(anyd)
WHEN 'SYS.CHAR' THEN
SYS.ANYDATA.accessChar(anyd)
WHEN 'SYS.DATE' THEN
TO_CHAR(SYS.ANYDATA.accessDate(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP' THEN
TO_CHAR(SYS.ANYDATA.accessTimestamp(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP_WITH_TIMEZONE' THEN
TO_CHAR(SYS.ANYDATA.accessTimestampTZ(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP_WITH_LTZ' THEN
TO_CHAR(SYS.ANYDATA.accessTimestampLTZ(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.ODCIVARCHAR2LIST' THEN
'COLLECTION'
END as value,
sys.anyData.gettypename(anyd), vsize(anyd)
FROM t_anydata
;
There are two approaches:
Use if ... then ... else ... statements and perform processing in the branches. It makes the code less readable and maintainable, but it doesn't require extra objects. Then you may use a unified container of the data (for example, anydata).
Use overloading and divide responsibility: perform datatype-dependent processing and serialization in each individual instance of the datatype and then bring the results together. It's possible in Oracle as long as it allows function overloading in packages.
Note that it may cause unexpected results for varchar2 parameter, because it may cause implicit conversion for supported datatypes for which there's no overloaded function exist.
create package pkg_my_to_varchar
as
function f_prepare(p_number in number) return varchar2;
function f_prepare(p_date in date) return varchar2;
function f_prepare(p_timestamp_tz in timestamp with time zone) return varchar2;
function f_prepare(p_varchar in varchar2) return varchar2;
function f_all_together(
p1 varchar2,
p2 varchar2 default null,
p3 varchar2 default null,
p4 varchar2 default null,
p5 varchar2 default null,
p6 varchar2 default null,
p7 varchar2 default null,
p8 varchar2 default null,
p9 varchar2 default null
) return varchar2;
end pkg_my_to_varchar;/
create package body pkg_my_to_varchar
as
function f_prepare(
p_number in number
) return varchar2
as
begin
return 'I''ve processed the number: ' || to_char(p_number, 'RN');
end;
function f_prepare(
p_date in date
) return varchar2
as
begin
return 'I''ve processed the date: ' || to_char(p_date, 'YYYY-MM-DD HH24:MI:SS "and some stuff"');
end;
function f_prepare(
p_timestamp_tz in timestamp with time zone
) return varchar2
as
begin
return 'I''ve processed the timestamp with TZ: ' || to_char(p_timestamp_tz, 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
end;
function f_prepare(
p_varchar in varchar2
) return varchar2
as
begin
return 'I''ve processed the string: ' || p_varchar;
end;
function f_all_together(
p1 varchar2,
p2 varchar2 default null,
p3 varchar2 default null,
p4 varchar2 default null,
p5 varchar2 default null,
p6 varchar2 default null,
p7 varchar2 default null,
p8 varchar2 default null,
p9 varchar2 default null
) return varchar2
as
begin
return p1 || chr(10) || p2 || chr(10) || p3 || chr(10) || p4 || chr(10) || p5 || chr(10) || p6 || chr(10) || p7 || chr(10) || p8 || chr(10) || p9;
end;
end pkg_my_to_varchar;/
with a(n, d, ts, vc) as (
select
123.456,
sysdate,
systimestamp at time zone '+07:30',
'qwerty'
from dual
)
select
a.*
, pkg_my_to_varchar.f_all_together(
pkg_my_to_varchar.f_prepare(n),
pkg_my_to_varchar.f_prepare(d),
pkg_my_to_varchar.f_prepare(ts),
pkg_my_to_varchar.f_prepare(vc)
) as concat_all
from a
N
D
TS
VC
CONCAT_ALL
123.456
16-JAN-23
16-JAN-23 23.13.00.685956 +07:30
qwerty
I've processed the number: CXXIIII've processed the date: 2023-01-16 15:43:00 and some stuffI've processed the timestamp with TZ: 2023-01-16 23:13:00.685956000 +07:30I've processed the string: qwerty
fiddle
If you are trying to pass different data types to a function then it is not possible to have many different versions of a function with different data types; however, you can pass the ANYDATA data type:
CREATE FUNCTION ANYDATA_TO_STRING(
p_anydata IN ANYDATA
) RETURN VARCHAR2
IS
v_typeid PLS_INTEGER;
v_anytype ANYTYPE;
v_result_code PLS_INTEGER;
BEGIN
v_typeid := p_anydata.GetType( typ => v_anytype );
CASE v_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
DECLARE
v_value NUMBER;
BEGIN
v_result_code := p_anydata.GetNumber( v_value );
RETURN TO_CHAR( v_value );
END;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DECLARE
v_value VARCHAR2(4000);
BEGIN
v_result_code := p_anydata.GetVarchar2( v_value );
RETURN v_value;
END;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
DECLARE
v_value DATE;
BEGIN
v_result_code := p_anydata.GetDate( v_value );
RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
END;
END CASE;
RETURN NULL;
END;
/
Then:
WITH table_name (value) AS (
SELECT ANYDATA.ConvertDate(SYSDATE) FROM DUAL UNION ALL
SELECT ANYDATA.ConvertNumber(42) FROM DUAL UNION ALL
SELECT ANYDATA.ConvertVarchar2('Hello') FROM DUAL
)
SELECT ANYDATA_TO_STRING(value)
FROM table_name;
Outputs:
ANYDATA_TO_STRING(VALUE)
2023-01-16 14:48:37
42
Hello
fiddle
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;
/
I want to implement as below :
CREATE OR REPLACE PROCEDURE INPUT_XML (v_sne_id varchar2(10) -- input can be single or multiple values,
v_card_name varchar2(10) DEFAULT NULL,
v_port_no varchar2(10) DEFAULT NULL,
v_refcur OUT SYS_REFCURSOR)
AS
BEGIN
IF (card_name is null and port_no is null )
then
open v_refcur for
select * from table where sne_id in (all the input sneId(s) );
end if;
END INPUT_XML:
You may create and use a User-defined collection TYPE with TABLE function.
Type
CREATE OR REPLACE TYPE sne_id_type IS TABLE OF varchar2(10);
Procedure
CREATE OR REPLACE PROCEDURE input_xml (
v_sne_id sne_id_type, --use the parameter of the collection type
v_card_name VARCHAR2 DEFAULT NULL,
v_port_no VARCHAR2 DEFAULT NULL,
v_refcur OUT SYS_REFCURSOR
) AS
BEGIN
IF ( v_card_name IS NULL AND v_port_no IS NULL ) THEN
OPEN v_refcur FOR SELECT *
FROM tablename
WHERE sne_id IN ( SELECT column_value
FROM TABLE ( v_sne_id )
);
END IF;
END input_xml;
/
To pass multiple values while executing, you may do
BEGIN
INPUT_XML(sne_id_type('ID1','ID2','ID3'), 'CARD', null,null);
END;
/
Here is my procedure:
CREATE OR REPLACE PACKAGE shop_query_pkg IS
PROCEDURE shop_info_pp
(p_id IN bb_shopper.idshopper%TYPE,
p_firstname OUT bb_shopper.firstname%TYPE,
p_city OUT bb_shopper.city%TYPE,
p_state OUT bb_shopper.state%TYPE,
p_phone OUT bb_shopper.phone%TYPE,
p_email OUT bb_shopper.email%TYPE);
PROCEDURE shop_info_pp
(p_id IN bb_shopper.lastname%TYPE,
p_firstname OUT bb_shopper.firstname%TYPE,
p_city OUT bb_shopper.city%TYPE,
p_state OUT bb_shopper.state%TYPE,
p_phone OUT bb_shopper.phone%TYPE,
p_email OUT bb_shopper.email%TYPE);
END;
/
CREATE OR REPLACE PACKAGE BODY shop_query_pkg IS
PROCEDURE shop_info_pp
(p_id IN bb_shopper.idshopper%TYPE,
p_firstname OUT bb_shopper.firstname%TYPE,
p_city OUT bb_shopper.city%TYPE,
p_state OUT bb_shopper.state%TYPE,
p_phone OUT bb_shopper.phone%TYPE,
p_email OUT bb_shopper.email%TYPE)
IS
BEGIN
SELECT firstname, city, state, phone, email
INTO p_firstname, p_city, p_state, p_phone, p_email
FROM bb_shopper
WHERE idshopper = p_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ID does not exist');
END;
PROCEDURE shop_info_pp
(p_id IN bb_shopper.lastname%TYPE,
p_firstname OUT bb_shopper.firstname%TYPE,
p_city OUT bb_shopper.city%TYPE,
p_state OUT bb_shopper.state%TYPE,
p_phone OUT bb_shopper.phone%TYPE,
p_email OUT bb_shopper.email%TYPE)
IS
BEGIN
SELECT firstname, city, state, phone, email
INTO p_firstname, p_city, p_state, p_phone, p_email
FROM bb_shopper
WHERE lastname = p_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Last name does not exist');
END;
END;
However when I try to execute my procedure using this code:
VARIABLE g_fname VARCHAR2
VARIABLE g_city VARCHAR2
VARIABLE g_state VARCHAR2
VARIABLE g_phone VARCHAR2
VARIABLE g_email VARCHAR2
EXECUTE shop_query_pkg.shop_info_pp(23, :g_fname, :g_city, :g_state, :g_phone, :g_email);
PRINT :g_fname
PRINT :g_city
PRINT :g_state
PRINT :g_phone
PRINT :g_email
I get this error message:
BEGIN shop_query_pkg.shop_info_pp(23, :g_fname, :g_city, :g_state, :g_phone, :g_email); END;
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "S36.SHOP_QUERY_PKG", line 11
ORA-06512: at line 1
I think it something wrong with my select statement, but I keep checking it and I can't figure out what's wrong with it.
Here is the DESC bb_shopper:
Name Null? Type
IDSHOPPER NOT NULL NUMBER(4)
FIRSTNAME VARCHAR2(15)
LASTNAME VARCHAR2(20)
ADDRESS VARCHAR2(40)
CITY VARCHAR2(20)
STATE CHAR(2)
ZIPCODE VARCHAR2(15)
PHONE VARCHAR2(10)
FAX VARCHAR2(10)
EMAIL VARCHAR2(25)
USERNAME VARCHAR2(8)
PASSWORD VARCHAR2(8)
COOKIE NUMBER(4)
DTENTERED DATE
PROVINCE VARCHAR2(15)
COUNTRY VARCHAR2(15)
PROMO CHAR(1)
Yes I've tried changing state to CHAR and I get the same message.
You must fully specify the VARIABLE types and add the maximum varchar2 sizes.
If you use the following block, it works as expected:
VARIABLE g_fname VARCHAR2(60)
VARIABLE g_city VARCHAR2(60)
VARIABLE g_state VARCHAR2(60)
VARIABLE g_phone VARCHAR2(60)
VARIABLE g_email VARCHAR2(60)
EXECUTE shop_query_pkg.shop_info_pp(23, :g_fname, :g_city, :g_state, :g_phone, :g_email);
PRINT :g_fname
PRINT :g_city
PRINT :g_state
PRINT :g_phone
PRINT :g_email
I'm writing a function for batch-importing data into our org-chart. It seems to work fine for retrieving entries that already exists, but when an entry does not already exist, and it's supposed to insert, commit, and return the result of a re-attempt (so as to get the auto-generated ID), it always returns NULL.
I'm sure I'm doing something wrong here, but what? Help appreciated.
Note: there's a before-insert trigger that fills in DEPT_ID if it's not specified. Works fine if the insert statement is executed by hand.
CREATE TABLE DEPTS
(
"DEPT_ID" VARCHAR2(10 BYTE),
"HEADER_ID" VARCHAR2(10 BYTE),
"COMMENTS" VARCHAR2(100 BYTE),
"CATEGORY" VARCHAR2(2 BYTE)
);
CREATE OR REPLACE FUNCTION get_or_make_unit(
, in_cat VARCHAR2
, in_cmt VARCHAR2
, in_hdr VARCHAR2 DEFAULT NULL
) RETURN VARCHAR2 AS
unit_id VARCHAR2(10);
BEGIN
unit_id := NULL;
IF in_hdr IS NULL THEN
SELECT dept_id
INTO unit_id
FROM depts unit
WHERE unit.category = in_cat
AND unit.comments = in_cmt
AND unit.header_id IS NULL;
ELSE
SELECT dept_id
INTO unit_id
FROM depts unit
WHERE unit.category = in_cat
AND unit.comments = in_cmt
AND unit.header_id = in_hdr;
END IF;
IF unit_id IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Inserting!');
INSERT INTO depts (
header_id
, comments
, category
) VALUES (
in_hdr
, in_cmt
, in_cat);
COMMIT;
unit_id := get_or_make_unit(in_cat, in_cmt, in_hdr);
RETURN unit_id;
ELSE
DBMS_OUTPUT.PUT_LINE('Not inserting!');
RETURN unit_id;
END IF;
END get_or_make_unit;
And the trigger:
CREATE OR REPLACE TRIGGER HRD.DEPTS_BIR
BEFORE INSERT
ON HRD.DEPTS
FOR EACH ROW
DECLARE
JML NUMBER;
BEGIN
SELECT SEQ_DEPT_ID.NEXTVAL INTO JML FROM DUAL;
:NEW.DEPT_ID:='D'||to_char(JML);
END DEPTS_BIR;
Examples
This works:
INSERT INTO depts (
header_id
, comments
, category
) VALUES (
'D532'
, 'ACCOUNTING'
, '2');
COMMIT;
SELECT get_or_make_unit('2', 'ACCOUNTING', 'D532') FROM DUAL;
=> 'D533'
This does not:
SELECT get_or_make_unit('2', 'NEW DEPT', 'D532') FROM DUAL;
=> NULL
Instead of:
INSERT INTO depts (
header_id
, comments
, category
) VALUES (
in_hdr
, in_cmt
, in_cat);
COMMIT;
unit_id := get_or_make_unit(in_cat, in_cmt, in_hdr);
use RETURNING INTO:
INSERT INTO depts (
header_id
, comments
, category
) VALUES (
in_hdr
, in_cmt
, in_cat) RETURNING dept_id INTO unit_id;
COMMIT;
I think recursive call in not the best approach, but if you are strict of using it, then please post definition of mentioned before-insert trigger.
UPDATE: You cannot call functions containing DML operations from SQL statement. Please see this answer for details. Example of correct call:
DECLARE
unit_id varchar2(32);
BEGIN
unit_id := get_or_make_unit('2', 'NEW DEPT', 'D532');
dbms_output.put_line(unit_id);
END;
UPDATE2: Also you need to catch NO_DATA_FOUND exception that is raised when you call your function with not existent combination. Below is example:
CREATE OR REPLACE FUNCTION get_or_make_unit(in_cat VARCHAR2,
in_cmt VARCHAR2,
in_hdr VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2 AS
unit_id VARCHAR2(10);
BEGIN
unit_id := NULL;
IF in_hdr IS NULL THEN
SELECT dept_id
INTO unit_id
FROM depts unit
WHERE unit.category = in_cat
AND unit.comments = in_cmt
AND unit.header_id IS NULL;
ELSE
SELECT dept_id
INTO unit_id
FROM depts unit
WHERE unit.category = in_cat
AND unit.comments = in_cmt
AND unit.header_id = in_hdr;
END IF;
DBMS_OUTPUT.PUT_LINE('Not inserting!');
RETURN unit_id;
exception
when NO_DATA_FOUND then
DBMS_OUTPUT.PUT_LINE('Inserting!');
INSERT INTO depts
(header_id, comments, category)
VALUES
(in_hdr, in_cmt, in_cat)
returning dept_id into unit_id;
COMMIT;
RETURN unit_id;
END get_or_make_unit;
Your call to get_or_make_unit appears to be missing the first parameter.
INSERT INTO depts (
header_id
, comments
, category
) VALUES (
in_hdr
, in_cmt
, in_cat);
This code is not inserting anything into dept_id of depts table. Thats the reason you are getting null