Oracle Package or function in invalid state - sql

Trying to create a pl/sql cursor based function to return details from an oracle database.
The relevant table, MEETING, has the columns
MEETING_ID: number(10), TIME: timestamp(4), TITLE: varchar(20)
CREATE OR REPLACE FUNCTION GetMeeting
(meetingnumber MEETING.MEETING_ID%TYPE)
RETURN VARCHAR
IS
CURSOR current_meeting(meetingnumber MEETING.MEETING_ID%TYPE)
IS
SELECT TITLE, TIME
FROM MEETING
WHERE MEETING_ID = meetingnumber;
r_meeting current_meeting%ROWTYPE;
BEGIN
OPEN current_meeting(meetingnumber);
FETCH current_meeting INTO r_meeting;
IF current_meeting%NOTFOUND THEN
r_meeting.TITLE := 'UNKNOWN APPOINTMENT';
END IF;
CLOSE current_meeting;
RETURN r_meeting.TITLE;
END;
SELECT GetMeeting (27) name
FROM MEETING;
The function seems to compile okay - but when called throws
ORA-06575: Package or function GETMEETING is in an invalid state

Perhaps this will work better for you:
create or replace function
getmeeting(
meeting_id number)
return
varchar
is
meeting_title meeting.title%Type;
begin
select title
into meeting_title
from meeting
where meeting_id = getmeeting.meeting_id;
return meeting_title;
exception
when NO_DATA_FOUND then
return 'UNKNOWN APPOINTMENT';
end;
/
Not syntax checked.

Error being generated by column identifier 'TIME' which is an SQL keyword; triggering a runtime error when executed.
Code unfortunately returns NULL when 'TIME' is removed

There aren't enough facts to know but I would look into some form of circular dependency.
select *
from user_dependencies
where referenced_name = 'GETMEETING'
and referenced_type = 'FUNCTION';
The best way to avoid circular dependencies is to use packages where references to other packages are made in the body only. Avoid standalone function and procedure objects.

Related

How to check if an sequence is above a certain number and if not change it in Postgres

I have a problem where my SQL sequence has to be above a certain number to fix a unique constraint error. Now I started to write an if-statement that checks for a certain number and if it's below it should be increased to a certain number. The statement is for Postgres.
I got the separate parts running but the connection over if is throwing an error and I don't know why.
First for selecting the current number:
SELECT nextval('mySequence')
Then to update the number:
SELECT setval('mySequence', targetNumber, true)
The full statement looks something like this in my tries:
IF (SELECT nextval('mySequence') < targetNumber)
THEN (SELECT setval('mySequence', targetNumber, true))
END IF;
and the error is
ERROR: syntax error at »IF«
Can someone explain to me what I did wrong there because the error message isn't giving me much to work with? I would appreciate your help.
Try this:
SELECT setval('mySequence', targetNumber, true)
WHERE (SELECT nextval('mySequence') < targetNumber) is true;
You can use postgres functions if you want to use IF statement.
You can try something like this:
CREATE SEQUENCE seq_test_id_seq;
CREATE TABLE seq_test(
id integer NOT NULL DEFAULT nextval('seq_test_id_seq'),
name VARCHAR
);
CREATE OR REPLACE FUNCTION seq_test_function(target_number bigint)
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$
DECLARE
seq_val INTEGER;
BEGIN
SELECT nextval('seq_test_id_seq') INTO seq_val;
RAISE NOTICE 'NEXT SEQUENCE [%]', seq_val;
IF (seq_val < target_number) THEN
SELECT setval('seq_test_id_seq', target_number, true) INTO seq_val;
RAISE NOTICE 'SEQUENCE VALUE MODIFIED [%]', seq_val;
END IF;
END;
$BODY$;
Then call the procedure:
select seq_test_function(10);

Error: ORA-00955: name is already used by an existing object in Oracle Function

I have function which i am trying to compile and getting an error as Error: ORA-00955: name is already used by an existing object. I am really not aware of this error and try to search for this issue but did not find any solution. I dont know is this related to any grant priviledges but i dont have priviledges issue to my schema tables.
create or replace FUNCTION "AK_CHECK"
-- PUBLIC
(ID Number) RETURN Number
IS
TYPE_ID Number := 0;
SUCCESS Number := 0;
S Number := 0;
BEGIN
SELECT ACTIVE(ID) + MANUAL(ID) INTO S FROM DUAL;
CASE S
WHEN 2 THEN
SELECT TYPE INTO TYPE_ID
FROM SALE_SUPPLY KD
WHERE KD.KPI_DEF_ID = ID;
END CASE;
END AK_CHECK;
You probably have another object with the same name (PERFORM_CHECK).
You can find it by quering user_objects:
select *
from user_objects
where object_name = 'PERFORM_CHECK'
Then drop it (replace TYPE_OF_OBJECT by the type of the object from the above query):
drop TYPE_OF_OBJECT perform_check

How to programmatically check if row is deletable?

Say we have a PostgreSQL table like so:
CREATE TABLE master (
id INT PRIMARY KEY,
...
);
and many other tables referencing it with foreign keys:
CREATE TABLE other (
id INT PRIMARY KEY,
id_master INT NOT NULL,
...
CONSTRAINT other_id_master_fkey FOREIGN KEY (id_master)
REFERENCES master (id) ON DELETE RESTRICT
);
Is there a way to check (from within trigger function) if a master row is deletable without actually trying to delete it? The obvious way is to do a SELECT on all referencing tables one by one, but I would like to know if there is an easier way.
The reason I need this is that I have a table with hierarchical data in which any row can have child rows, and only child rows that are lowest in hierarchy can be referenced by other tables. So when a row is about to become a parent row, I need to check whether it is already referenced anywhere. If it is, it cannot become a parent row, and insertion of new child row is denied.
You can try to delete the row and roll back the effects. You wouldn't want to do that in a trigger function because any exception cancels all persisted changes to the database. The manual:
When an error is caught by an EXCEPTION clause, the local variables of
the PL/pgSQL function remain as they were when the error occurred, but
all changes to persistent database state within the block are rolled back.
Bold emphasis mine.
But you can wrap this into a separate block or a separate plpgsql function and catch the exception there to prevent the effect on the main (trigger) function.
CREATE OR REPLACE FUNCTION f_can_del(_id int)
RETURNS boolean AS
$func$
BEGIN
DELETE FROM master WHERE master_id = _id; -- DELETE is always rolled back
IF NOT FOUND THEN
RETURN NULL; -- ID not found, return NULL
END IF;
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
EXCEPTION
WHEN FOREIGN_KEY_VIOLATION THEN
RETURN FALSE;
WHEN SQLSTATE 'MYERR' THEN
RETURN TRUE;
-- other exceptions are propagated as usual
END
$func$ LANGUAGE plpgsql;
This returns TRUE / FALSE / NULL indicating that the row can be deleted / not be deleted / does not exist.
db<>fiddle here
Old sqlfiddle
One could easily make this function dynamic to test any table / column / value.
Since PostgreSQL 9.2 you can also report back which table was blocking.
PostgreSQL 9.3 or later offer more detailed information, yet.
Generic function for arbitrary table, column and type
Why did the attempt on a dynamic function that you posted in the comments fail? This quote from the manual should give a clue:
Note in particular that EXECUTE changes the output of GET DIAGNOSTICS, but does not change FOUND.
It works with GET DIAGNOSTICS:
CREATE OR REPLACE FUNCTION f_can_del(_tbl regclass, _col text, _id int)
RETURNS boolean AS
$func$
DECLARE
_ct int; -- to receive count of deleted rows
BEGIN
EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col)
USING _id; -- exception if other rows depend
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
ELSE
RETURN NULL; -- ID not found, return NULL
END IF;
EXCEPTION
WHEN FOREIGN_KEY_VIOLATION THEN
RETURN FALSE;
WHEN SQLSTATE 'MYERR' THEN
RETURN TRUE;
-- other exceptions are propagated as usual
END
$func$ LANGUAGE plpgsql;
db<>fiddle here
Old sqlfiddle
While being at it, I made it completely dynamic, including the data type of the column (it has to match the given column, of course). I am using the polymorphic type anyelement for that purpose. See:
How to write a function that returns text or integer values?
I also use format() and a parameter of type regclass to safeguard against SQLi. See:
SQL injection in Postgres functions vs prepared queries
You can do that also with Procedure.
CREATE OR REPLACE procedure p_delable(_tbl text, _col text, _id int)
AS $$
DECLARE
_ct bigint;
_exists boolean; -- to receive count of deleted rows
BEGIN
_exists := (SELECT EXISTS ( SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = $1 ));
IF _exists THEN
EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col)
USING _id; -- exception if other rows depend
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
ELSE
RAISE NOTICE 'no records found. no records will be deleted';
END IF;
ELSE
raise notice 'Input text is invalid table name.';
END IF;
EXCEPTION
WHEN undefined_column then
raise notice 'Input text is invalid column name.';
WHEN undefined_table then
raise notice 'Input text is invalid table name.';
WHEN FOREIGN_KEY_VIOLATION THEN
RAISE NOTICE 'foreign key violation, cannot be deleted.';
WHEN SQLSTATE 'MYERR' THEN
RAISE NOTICE 'rows % found and can be deleted.', _ct;
END
$$ LANGUAGE plpgsql;
You can call it, also can validate your input.
call p_delable('parent_tree', 'parent_id',30);
Will get:
NOTICE: no records found. no records will be deleted
Lets try an actual exist row.
call p_delable('parent_tree', 'parent_id',3);
It will return
NOTICE: rows 1 found and can be deleted.
It can also check your input table name exists in public schema or not.
call p_delable('parent_tre', 'parent_id',3);
It will give you notice:
NOTICE: Input text is invalid table name.

Oracle: function only returning null

The following function is supposed to return information relating to a particular meeting, stored in a meeting table:
CREATE TABLE "MEETING"
( "MEETING_ID" NUMBER(10,0) NOT NULL ENABLE,
"TIME" TIMESTAMP (4) NOT NULL ENABLE,
"LOCATION" VARCHAR2(40),
"MAP_HREF" VARCHAR2(140),
"FK_INTEREST_ID" CHAR(4) NOT NULL ENABLE,
"ADDITIONAL_INFO" CLOB,
"PASSED" NUMBER(1,0),
"TITLE" VARCHAR2(20),
CONSTRAINT "MEETING_PK" PRIMARY KEY ("MEETING_ID") ENABLE
) ;
The code compiles just fine, and runs fine as well.
However, if the meeting exists, only null is returned. If the meeting doesn't exist the exception prints 'UNKNOWN APPOINTMENT' correctly.
CREATE OR REPLACE FUNCTION GetMeeting
(meetingnumber MEETING.MEETING_ID%TYPE)
RETURN VARCHAR
IS
CURSOR current_meeting(meetingnumber MEETING.MEETING_ID%TYPE)
IS
SELECT TITLE
FROM MEETING
WHERE MEETING_ID = meetingnumber;
r_meeting current_meeting%ROWTYPE;
BEGIN
OPEN current_meeting(meetingnumber);
FETCH current_meeting INTO r_meeting;
IF current_meeting%NOTFOUND THEN
r_meeting.TITLE := 'UNKNOWN APPOINTMENT';
END IF;
CLOSE current_meeting;
RETURN r_meeting.TITLE;
END;
SELECT GetMeeting (27) appointment
FROM MEETING;
Seems this is an exercise in using cursors? Its much more complicated than it needs to be. Try something like (untested):
create or replace function get_meeting(i_meetingnumber MEETING.MEETING_ID%TYPE)
RETURN VARCHAR2
IS
l_title MEETING.TITLE%TYPE;
BEGIN
select title
into l_title
FROM MEETING
WHERE MEETING_ID = i_meetingnumber;
return l_title;
EXCEPTION
when no_data_found then
return 'UNKNOWN APPOINTMENT';
when others then raise;
END;
This is also a bit unnecessary to put this small logic in a function, I would simply select it as needed (via a join of a larger SQL or individually in a larger pl/sql procedure)
Also, I notice that your original function returns VARCHAR where title is VARCHAR2. Not sure off hand if the conversion is done implicitly by Oracle, but something worth mentioning.
SELECT NVL(TITLE, 'UNKNOWN APPOINTMENT') FROM MEETING WHERE MEETING_ID = meetingnumber;
Is much cleaner.
check below statement:
IF r_meeting%NOTFOUND THEN
r_meeting.TITLE := 'UNKNOWN APPOINTMENT';
END IF;
CLOSE current_meeting;
RETURN r_meeting.TITLE;
END;
PL/SQL Function works fine. It returns the desired result, but your select returns as much datasets, as are present in MEETING. You should select from dual instead.

How can I declare and initialize a package variable by SELECT

Can someone help me understand why I can declare a hardcoded date value in a package but not assign the value from a query? I've seen a number of examples (including the reference book) that show declarations of hardcoded values but I'm unable to locate examples of assigning values to variable through queries.
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := '2012-05-30';
-- ...procedures follow
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := sysdate;
This is not allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := select MAX(date_) from Table_Name;
I've tried a number of ways and I'm ok with it not working - I can use it as needed in the procedures themselves. But I'd like to know why I can't assign a value to tDate this way? The specific error is:
Encountered the symbol 'SELECT' when expecting ....
Adding that I can get variable values assigned through queries in a stored procedure but the same process does not seem to work for package body.
PROCEDURE Proc_Name IS
tDate Date;
BEGIN
SELECT MAX(date_) into tDate from Table_Name;
You need to create a package initialization block in your package body. This is a relatively arcane bit of PL/SQL package lore, but it's there and can be used. Here's an example:
CREATE OR REPLACE PACKAGE TEST_PKG IS
tDate DATE;
END TEST_PKG;
Here we've created a package spec which contains only a single DATE variable. Now we'll create a simple body which initializes that variable using a SELECT statement:
CREATE OR REPLACE PACKAGE BODY TEST_PKG IS
-- package-private variables go first (if any)
(...)
-- then the public functions/procedures
(...)
-- then a final BEGIN-END block which is the package initialization block
BEGIN
SELECT SYSDATE
INTO tDATE
FROM DUAL;
EXCEPTION -- you can have exception handlers in your initialization block
WHEN OTHERS THEN
NULL; -- pointless here, but this is just an example
RAISE;
END TEST_PKG;
Now if you execute the following:
begin
-- Test statements here
DBMS_OUTPUT.PUT_LINE('TEST_PKG.tDate = ' || TEST_PKG.tDate);
end;
it should print the current date.
Share and enjoy.
You need to use INTO when selecting directly into a variable.
select MAX(date_)
INTO tDate
from Table_Name;