Error in Unix shell script by using oracle cursor - oracle

I keep on getting error as said below:
line 10: warning: here-document at line 2 delimited by end-of-file (wanted `')L_QUERY
My shell script code is as follows:
#!/bin/bash
sqlplus -silent USERNAME/PASSWORD#DBS <<SQL_QUERY
begin
for cur_r in (select Branch, Account_Type, Title, FirstName, LastName, Birthday, WorkPhone, HomePhone, Address, State, Zip, Email from accountrequest_temp)
loop
insert into accountrequest (Branch, Account_Type, Title, FirstName, LastName, Birthday, WorkPhone, HomePhone, Address, State, Zip, Email)
values (cur_r.branch, cur_r.account_type, cur_r.title, cur_r.firstname, cur_r.lastname, to_date(cur_r.birthday, 'DD/MM/YYYY), cur_r.workphone, cur_r.homephone, cur_r.address, cur_r.state, cur_r.zip, cur_r.email);
end loop;
end;
SQL_QUERY
Please help.

As far as I can see you have two errors:
When you use a BEGIN/END block, you need to finish with /. If not, it is interpreted as a comment and it won't be executed.
You are missing the end quote in the TO_DATE function
Besides the point that you don't need a cursor for this, it would be
#!/bin/bash
sqlplus -silent USERNAME/PASSWORD#DBS <<SQL_QUERY
begin
for cur_r in (select Branch, Account_Type, Title, FirstName, LastName, Birthday, WorkPhone, HomePhone, Address, State, Zip, Email from accountrequest_temp)
loop
insert into accountrequest (Branch, Account_Type, Title, FirstName, LastName, Birthday, WorkPhone, HomePhone, Address, State, Zip, Email)
values (cur_r.branch, cur_r.account_type, cur_r.title, cur_r.firstname, cur_r.lastname, to_date(cur_r.birthday, 'DD/MM/YYYY'), cur_r.workphone, cur_r.homephone, cur_r.address, cur_r.state, cur_r.zip, cur_r.email);
end loop;
commit;
end;
/
SQL_QUERY
Although I would do this instead
#!/bin/bash
sqlplus -S USERNAME/PASSWORD#DBS <<SQL_QUERY
set echo off
insert into accountrequest (Branch, Account_Type, Title, FirstName, LastName, Birthday, WorkPhone, HomePhone, Address, State, Zip, Email)
select Branch, Account_Type, Title, FirstName, LastName, to_date(birthday, 'DD/MM/YYYY'), WorkPhone, HomePhone, Address, State, Zip, Email from accountrequest_temp;
commit;
SQL_QUERY

Related

ORA-00904: invalid identifier from PL/SQL code while the SQL part within works

When I defined a PL/SQL function in SQL developer and tried to run it, it returned "ORA-00904: "SYS"."FUNC1": invalid identifier;00904. 00000 - "%s: invalid identifier"", which isn't very helpful. I don't know what has gone wrong. The SQL part alone can run though.
the PL/SQL that return error
CREATE OR REPLACE FUNCTION func1 (
emp_id IN NUMBER
) RETURN NUMBER AS
emp_fname VARCHAR2(50);
BEGIN
SELECT
firstname
INTO emp_fname
FROM
employees
WHERE
employeeid = emp_id;
RETURN emp_fname;
END func1;
/
select sys.func1(9) from dual;
the SQL that run
SELECT
firstname
FROM
employees
WHERE
employeeid = 9;
Definition of Table Employees
Name Null? Type
--------------- -------- -------------
EMPLOYEEID NOT NULL NUMBER
LASTNAME NOT NULL VARCHAR2(20)
FIRSTNAME NOT NULL VARCHAR2(10)
TITLE VARCHAR2(30)
TITLEOFCOURTESY VARCHAR2(25)
BIRTHDATE DATE
HIREDATE DATE
ADDRESS VARCHAR2(60)
CITY VARCHAR2(15)
REGION VARCHAR2(15)
POSTALCODE VARCHAR2(10)
COUNTRY VARCHAR2(15)
HOMEPHONE VARCHAR2(24)
EXTENSION VARCHAR2(4)
PHOTO LONG RAW
NOTES VARCHAR2(600)
REPORTSTO NUMBER
PHOTOPATH VARCHAR2(255)
Please help!
Test environment:
OS: Oracle Linux 7.9
Oracle DB 21c Express Edition for Linux
SQL Developer for Linux 21.2.1.204 build 204.1703
Don't use SYS as a work area. If you mess with any of the system tables then you may make your database unusable.
Use the SYS user to create a new user and then work in that user's schema.
Your function compiles successfully; however, it will fail at runtime (except for those rare people who are known by numbers and not names) as the signature is RETURN NUMBER but it returns the firstname which is a string and you would get the exception:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "FIDDLE_RAHCBFZAHWTUCSZNOWGB.FUNC1", line 14
You can fix it by making the return type the same as the firstname column and the simplest method is to use %TYPE. You also ought to handle a NO_DATA_FOUND exception:
CREATE OR REPLACE FUNCTION func1 (
emp_id IN EMPLOYEES.EMPLOYEEID%TYPE
) RETURN EMPLOYEES.FIRSTNAME%TYPE
AS
emp_fname VARCHAR2(50);
BEGIN
SELECT firstname
INTO emp_fname
FROM employees
WHERE employeeid = emp_id;
RETURN emp_fname;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END func1;
/
If you have the sample data:
INSERT INTO employees (employeeid, lastname, firstname)
VALUES (9, 'Abbots', 'Alice');
Then:
select func1(9) from dual;
Outputs:
FUNC1(9)
Alice
db<>fiddle here

Issue with BULK COLLECT of million rows - out of process memory

I have below code in my pl/sql procedure, which is trying to collect 17 millions rows, and it fails with error ORA-04030: out of process memory when trying to allocate 16328 bytes (koh-kghu call ,pmuccst: adt/record)
TYPE rc_test IS RECORD(
ROWID VARCHAR2(200),
contact_id VARCHAR2(200),
last_name VARCHAR2(200),
first_name VARCHAR2(200),
phone VARCHAR2(200),
email VARCHAR2(200),
birth_day date,
address_id VARCHAR2(200),
seq NUMBER,
NEWID NUMBER);
TYPE rctype
IS TABLE OF RC_TEST;
rcrecords RCTYPE;
BEGIN
SELECT ROWID,
contact_id,
last_name,
first_name,
phone,
email,
birth_day,
address_id,
seq,
NEWID
bulk collect INTO rcrecords
FROM HR.TMP_TBL_SEQ a
order by last_name, first_name, seq;
FOR i IN 1..rcrecords.count LOOP
-- <<>>
END LOOP;
It runs fine with sample data of hundreds or thousand of rows but doesn't work with millions of rows.
I have read troubleshooting for this error but most of them revolve around getting this done through DBA who will perform some tunings or will advise us to increase RAM that might resolve the issue but due to certain limitations at first place I really want to change the logic of the code itself.
Is there a better way to improve the logic in the above type of code to get rid of the out of memory error?
In place of table record used in my code can anyone provide me alternate code?
Many Thanks.
You can use BULK COLLECT with LIMIT clause (http://www.oracle.com/technetwork/issue-archive/2008/08-mar/o28plsql-095155.html). In your case it will look similar to :
CURSOR cur1 IS
SELECT ROWID, contact_id, last_name, first_name,
phone, email, birth_day, address_id, seq,
NEWID FROM HR.TMP_TBL_SEQ a order by last_name, first_name, seq;
TYPE T_TEST IS TABLE OF cur1 %ROWTYPE INDEX BY PLS_INTEGER;;
test_data T_TEST;
BEGIN
OPEN cur1;
LOOP
FETCH cur1
BULK COLLECT INTO test_data LIMIT 10000;
FOR indx IN 1 .. test_data.COUNT
LOOP
NULL;
--- process data
END LOOP;
EXIT WHEN test_data.COUNT < 10000;
END LOOP;
CLOSE cur1;
Avoid this sort of "unlimited" use of BULK COLLECT by using the LIMIT clause.
Move the SELECT statement into an explicit cursor declaration and then use a simple loop to fetch many but not all the rows from the table with each execution of the loop body using the optional LIMIT clause.
For an examples and explanations see this oracle tech-issue, see http://www.oracle.com/technetwork/issue-archive/2008/08-mar/o28plsql-095155.html
Replace the BULK COLLECT with a cursor for-loop:
begin
for rcrecords in
(
SELECT ROWID,
contact_id,
last_name,
first_name,
phone,
email,
birth_day,
address_id,
seq,
NEWID
bulk collect INTO rcrecords
FROM HR.TMP_TBL_SEQ a
order by last_name, first_name, seq
) loop
--Process data.
null;
end loop;
end;
/
Oracle will automatically take care of bulk collect, limit, and declaring the the data types.

Compile Error for Oracle Procedure

I have the following PL/SQL code in a script and am trying to run it in SQL*Plus:
create or replace procedure add_employee
(fname IN varchar2(20), lname IN varchar2(25), email IN varchar2(25), job IN varchar2(25))
AS
eid INTEGER := 300;
BEGIN
insert into Employees (employee_id, first_name, last_name, email, hire_date, job_id)
values (eid, fname, lname, email, job);
END add_employee;
/
I get the following error:
Errors for PROCEDURE ADD_EMPLOYEE:
LINE/COL
---------------------------------------------------------------------------
ERROR
---------------------------------------------------------------------------
1/42
PLS-00103: Encountered the symbol "(" when expecting one of the following:
:= . ) , # % default character
The symbol ":=" was substituted for "(" to continue.
I don't understand what the issue is. I have written other procedures where the opening parenthesis follows the procedure name without any problem.
You can't specify the size of strings in the procedure declaration, so it should be:
create or replace procedure add_employee
(fname IN varchar2, lname IN varchar2, email IN varchar2, job IN varchar2)
It's not a good idea to have the argument names match any table columns (email and job in this case); prefixing the parameters is common, e.g. using p_email and p_job; but should be done consistently. It often also makes it easier to follow the code even when the names don't clash - you know where the variable comes from and what its scope is.

Call to the function failing with : ORA-00904: "TEMP_DATA"."P_LNAME": invalid identifier

The following function compiles fine. But when a call is made to the function in an anonymous PL/SQL code, the call fails.
Can anyone please suggest why ? I am trying to insert the un-matched (new) data and update the matching (existing) data.
Function Fn_Insert_Data_Using_Merge (p_Id in Number,
p_fname in varchar2,
p_mname in varchar2,
p_lname in varchar2,
p_birth_date in date) Return Boolean
Is
Begin
Dbms_Output.put_line ('Inside the function Fn_Insert_Data_Using_Merge ...');
Merge Into test_employee te
using (select distinct p_Id, p_fname, p_mname, p_lname, p_birth_date
from test_employee) temp_data
on (te.first_name = temp_data.p_fname and
te.middle_name = temp_data.p_mname and
te.last_name = temp_data.p_lname)
when matched then
update
set first_name = p_fname,
middle_name = p_mname,
last_name = p_lname,
run_status = 'Updated'
when not matched then
insert (id, first_name, middle_name, last_name, birth_date, run_status)
values
(p_id, p_fname, p_mname, p_lname, p_birth_date, 'Inserted');
Dbms_Output.put_line ('Returning successfully from the function Fn_Insert_Data_Using_Merge ...');
Return True;
Exception
When Dup_Val_On_Index Then
Dbms_Output.put_line ('The name already exists...Cannot insert again ..');
Return False;
When Others Then
Dbms_Output.put_line ('Facing some critical error : ' || SQLERRM);
Return False;
End Fn_Insert_Data_Using_Merge;
The error is trying to tell you that there is no column in your TEMP_DATA extract named P_LNAME. There is a parameter to the function named 'p_lname' but in the extract you've assigned no column aliases so there are no named fields. In addition (although not a cause of the error), doing a DISTINCT from a table with potentially many rows is a slow way to get the function parameters into the temp table in the USING clause; you'd be better off using a SELECT...FROM DUAL. Try replacing your MERGE statement with the following and let us know how it goes:
Merge Into test_employee te
using (select p_Id as p_Id,
p_fname as p_fname,
p_mname as p_mname,
p_lname as p_lname,
p_birth_date as p_birth_date
from dual) temp_data
on (te.first_name = temp_data.p_fname and
te.middle_name = temp_data.p_mname and
te.last_name = temp_data.p_lname)
when matched then
update
set first_name = temp_data.p_fname,
middle_name = temp_data.p_mname,
last_name = temp_data.p_lname,
run_status = 'Updated'
when not matched then
insert (id, first_name, middle_name,
last_name, birth_date, run_status)
values
(temp_data.p_id, temp_data.p_fname, temp_data.p_mname,
temp_data.p_lname, temp_data.p_birth_date, 'Inserted');
Share and enjoy.

Oracle trigger to create an autonumber

I have never created a trigger in Oracle before so I am looking for some direction.
I would like to create a trigger that increments an ID by one if the ID isnt in the insert statement.
The ID should start at 10000, and when a record is inserted the next ID should be 10001. If the insert statement contains a ID, it should override the auto increment.
ie
insert into t1 (firstname, lastname) values ('Michael','Jordan'),('Larry','Bird')
should look like:
firstname lastname id
Micahel Jordan 10000
Larry Bird 10001
insert into t1 (firstname, lastname, id) values ('Scottie','Pippen',50000)
should look like:
firstname lastname id
Micahel Jordan 10000
Larry Bird 10001
Scottie Pippen 50000
Something like this will work on 11g
CREATE SEQUENCE t1_id_seq
start with 10000
increment by 1;
CREATE TRIGGER trigger_name
BEFORE INSERT ON t1
FOR EACH ROW
DECLARE
BEGIN
IF( :new.id IS NULL )
THEN
:new.id := t1_id_seq.nextval;
END IF;
END;
If you're on an earlier version, you'll need to do a SELECT INTO to get the next value from the sequence
CREATE TRIGGER trigger_name
BEFORE INSERT ON t1
FOR EACH ROW
DECLARE
BEGIN
IF( :new.id IS NULL )
THEN
SELECT t1_id_seq.nextval
INTO :new.id
FROM dual;
END IF;
END;
Be aware that Oracle sequences are not gap-free. So it is entirely possible that particular values will be skipped for a variety of reasons. Your first insert may have an ID of 10000 and the second may have an ID of 10020 if it's done minutes, hours, or days later.
Additionally, be aware that Oracle does not support specifying multiple rows in the VALUES clause as MySQL does. So rather than
insert into t1 (firstname, lastname) values ('Michael','Jordan'),('Larry','Bird')
you'd need two separate INSERT statements
insert into t1 (firstname, lastname) values ('Michael','Jordan');
insert into t1 (firstname, lastname) values ('Larry','Bird');
I would recommend to code this trigger with a condition on the trigger itself, not in the sql block.
CREATE OR REPLACE TRIGGER your_trigger
BEFORE INSERT ON your_table
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
WHEN (new.id IS NULL)
BEGIN
SELECT your_sequence.nextval
INTO :new.id
FROM dual;
END;
/
With this solution the trigger is only executed if the condition matches (id is null).
Otherwise the trigger is always executed and the block checks if id is null. The DB must execute the SQL block which does nothing on not null values.

Resources