I have problem with my select in cursor. Select works normal in other windows but I got error in procedure with cursor why ?
I change name of Tabele and Database but Select is OK outside this procedure
My error:
PL/SQL: ORA-04052:
ORA-00604:
ORA-03106:
CREATE OR REPLACE PROCEDURE ChangeDismissDate
IS
BEGIN
DECLARE
v_id VARCHAR2(40);
v_dateABC DATE;
v_dateDismiss DATE;
CURSOR cur IS
select emp.EMP_NO, abc.date, emp.DISMISS_DATE
from bazaabc.EmployeesDetails#BAZA_ABC abc,
employee_tab emp,
person_info_tab pin,
oracle_account oa
where bhd.emp_no = emp.EMP_NO
and pin.PERSON_ID = emp.EMP_NO
and oa.USERNAME = pin.USER_ID
and emp.EMP_NO in (
select pi.PERSON_ID from tab_person pi
where fu.active = 'TRUE'
and fu.IDENTITY = pi.USER_ID) AND emp.EMP_NO like '%L%'
and nvl(abc.date,to_date('20491231','yyyymmdd')) <> nvl(emp.DISMISS_DATE,to_date('20491231','yyyymmdd'));
BEGIN
OPEN cur;
LOOP
FETCH cur INTO v_id,v_dateABC,v_dateDismiss;
DBMS_OUTPUT.PUT_LINE('v_id: ' || v_id);
DBMS_OUTPUT.PUT_LINE('v_dateABC: ' || v_dateABC);
DBMS_OUTPUT.PUT_LINE('v_dateDismiss: ' ||v_dateDismiss );
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
END;
END;
/
Can you try with what errors you are getting by adding exception block
CREATE OR REPLACE PROCEDURE ChangeDismissDate
IS
BEGIN
DECLARE
v_id VARCHAR2(40);
v_dateABC DATE;
v_dateDismiss DATE;
CURSOR cur IS
select emp.EMP_NO, abc.date, emp.DISMISS_DATE
from bazaabc.EmployeesDetails#BAZA_ABC abc,
employee_tab emp,
person_info_tab pin,
oracle_account oa
where bhd.emp_no = emp.EMP_NO
and pin.PERSON_ID = emp.EMP_NO
and oa.USERNAME = pin.USER_ID
and emp.EMP_NO in (
select pi.PERSON_ID from tab_person pi
where fu.active = 'TRUE'
and fu.IDENTITY = pi.USER_ID)
AND emp.EMP_NO like '%L%'
and nvl(abc.date,to_date('20491231','yyyymmdd')) <>
nvl(emp.DISMISS_DATE,to_date('20491231','yyyymmdd'));
BEGIN
OPEN cur;
LOOP
FETCH cur INTO v_id,v_dateABC,v_dateDismiss;
DBMS_OUTPUT.PUT_LINE('v_id: ' || v_id);
DBMS_OUTPUT.PUT_LINE('v_dateABC: ' || v_dateABC);
DBMS_OUTPUT.PUT_LINE('v_dateDismiss: ' ||v_dateDismiss );
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
END;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
/
Related
I am trying to call this function
CREATE or replace FUNCTION get_column_names(param text)
RETURNS text AS $get_column_names$
DECLARE
return_value text;
x record;
y int;
begin
return_value := '';
y := 0;
for x in SELECT *
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = param
ORDER BY ordinal_position
loop
if (y = 0)
then
return_value = x.column_name;
else
return_value := return_value ||','|| x.column_name;
end if;
Y = Y+1;
end loop;
return return_value;
END;
$get_column_names$ LANGUAGE plpgsql;
Which this function works here
select get_column_names('users');
Results
first_name,middle_name,last_name,gender,locale,auth_user_id,identity_id,active,date_created,provisioned,user_id,timezone,last_seen
However when I use it in another function I get an error.
Here is the other function I am calling it from.
CREATE OR REPLACE FUNCTION process_users_audit() RETURNS TRIGGER AS $users_audit$
BEGIN
--
-- Create a row in users_audit to reflect the operation performed on users
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'D', user, OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'U', user, NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'I', user, NEW.*);
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$users_audit$ LANGUAGE plpgsql;
The error I am receiving is this (same as title).
SQL Error [42601]: ERROR: syntax error at or near "("
Position: 406
UPDATE
#Ed Brook's answer worked except for had to change how NEW was being used. Will remove this update once he has updated his answer but here is what worked.
CREATE OR REPLACE FUNCTION process_audit_table() RETURNS TRIGGER AS $audit_table$
BEGIN
--
-- Create a row in the requesting table's audit to reflect the operation performed on users,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''D'', user, $1.*);'
USING OLD;
ELSIF (TG_OP = 'UPDATE') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''U'', user, $1.*);'
USING NEW;
ELSIF (TG_OP = 'INSERT') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''I'', user, $1.*);'
USING NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$audit_table$ LANGUAGE plpgsql;
Also added some extra bits in there like dynamically getting the audit table name and then get_primary_key_name below is that function.
CREATE OR REPLACE FUNCTION get_primary_key_name(table_name text)
RETURNS text AS $primary_key_name$
DECLARE
return_value text;
BEGIN
SELECT pg_attribute.attname INTO return_value
FROM pg_index, pg_class, pg_attribute, pg_namespace
WHERE pg_class.oid = table_name::regclass
AND indrelid = pg_class.oid
AND nspname = 'public'
AND pg_class.relnamespace = pg_namespace.oid
AND pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary;
RETURN return_value;
END
$primary_key_name$ LANGUAGE plpgsql;
You may need to use EXECUTE, as I don't think you can have function calls in the column list?
ref: https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
So, something like this?
EXECUTE 'INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''U'', user, NEW.*)';
I am trying to create a Stored Procedure in Postgresql that allow me to delete foreign keys from other tables without using the DELETE CASCADE when creating tables but gives me an error that one id is still referenced in other table. This is the code i have tried:
CREATE OR REPLACE PROCEDURE sp_delete_restaurante_msg
( _id_restaurante integer,
_id_local integer,
_nome_restaurante varchar(100),
INOUT _msg varchar(100),
INOUT _msg1 varchar(100) )
LANGUAGE plpgsql
AS $$
DECLARE
loc_cursor CURSOR FOR SELECT local.id_local FROM local WHERE local.id_local = _id_local FOR UPDATE;
ementa_cursor CURSOR FOR SELECT ementa.id_restaurante FROM ementa WHERE ementa.id_restaurante = _id_restaurante FOR UPDATE;
func_cursor CURSOR FOR SELECT funcionario.id_restaurante FROM funcionario WHERE funcionario.id_restaurante = _id_restaurante FOR UPDATE;
apont_loc RECORD;
apont_em RECORD;
apont_func RECORD;
BEGIN
IF EXISTS(SELECT l.id_local, rest.id_restaurante FROM local l JOIN restaurante rest ON l.id_local = rest.id_local WHERE rest.id_restaurante = _id_restaurante AND
l.id_local = _id_local) THEN
DELETE FROM restaurante WHERE restaurante.id_restaurante = _id_restaurante;
COMMIT;
ELSE
IF (SELECT COUNT(*) FROM local WHERE local.id_local = _id_local) > 0 THEN
OPEN loc_cursor;
LOOP
FETCH NEXT FROM loc_cursor INTO apont_loc;
EXIT WHEN NOT FOUND;
IF apont_loc.id_local = _id_local THEN
DELETE FROM local WHERE CURRENT OF loc_cursor;
COMMIT;
END IF;
END LOOP;
CLOSE loc_cursor;
COMMIT;
ELSIF (SELECT COUNT(*) FROM ementa WHERE ementa.id_restaurante = _id_restaurante) > 0 THEN
OPEN ementa_cursor;
LOOP
FETCH NEXT FROM ementa_cursor INTO apont_em;
EXIT WHEN NOT FOUND;
IF apont_em.id_restaurante = _id_restaurante THEN
DELETE FROM ementa WHERE CURRENT OF ementa_cursor;
COMMIT;
END IF;
END LOOP;
CLOSE ementa_cursor;
COMMIT;
ELSIF (SELECT COUNT(*) FROM funcionario WHERE funcionario.id_restaurante = _id_restaurante) > 0 THEN
OPEN func_cursor;
LOOP
FETCH NEXT FROM func_cursor INTO apont_func;
EXIT WHEN NOT FOUND;
IF apont_func.id_restaurante = _id_restaurante THEN
DELETE FROM funcionario WHERE CURRENT OF func_cursor;
COMMIT;
END IF;
END LOOP;
CLOSE func_cursor;
ELSE
RAISE NOTICE 'Nao existe mais tabelas ligadas';
END IF;
END IF;
IF(SELECT COUNT(*) FROM restaurante WHERE restaurante.id_restaurante = _id_restaurante) = 0 THEN
RAISE EXCEPTION '%', _msg;
ROLLBACK;
END IF;
IF _nome_restaurante IS NULL OR _nome_restaurante = '' THEN
RAISE EXCEPTION '%', _msg1;
ROLLBACK;
END IF;
END;
$$
Created this Postgres Function which is working fine, but the actual requirement is to pass the input parameter in the function to the Cursor which uses the dynamic SQL as follows,
The below is the Function
CREATE OR REPLACE FUNCTION ssp2_pcat.find_shift_dates (date_to_find date)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR FOR
SELECT TABLE_NAME, 'SELECT COUNT(*) FROM ' || TABLE_NAME || ' WHERE ' ||
COLUMN_NAME || ' = '||
'CASE WHEN ' || COLUMN_NAME || ' LIKE ' || '''%START%'''||' THEN
date_to_find ELSE date_to_find-1 END;' SQL_TEXT
FROM (
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN (SELECT TABLE_NAME FROM RESET_DATES WHERE RESET_IT =
'Y') AND
UPPER(DATA_TYPE) = 'DATE'
AND (COLUMN_NAME LIKE '%START%' OR COLUMN_NAME LIKE '%END%')
AND (COLUMN_NAME NOT LIKE '%TEST%'
AND COLUMN_NAME NOT LIKE '%PCAT%'
AND COLUMN_NAME NOT LIKE '%ORDER%'
AND COLUMN_NAME NOT LIKE '%SEASON%'
AND COLUMN_NAME NOT LIKE '%_AT')
ORDER BY 1, 2) A;
END_COUNT INTEGER := 0;
START_COUNT INTEGER := 0;
TABLENAME VARCHAR(32) := 'ALFU';
l_start TIMESTAMP;
l_end TIMESTAMP;
Time_Taken VARCHAR(20);
BEGIN
l_start := clock_timestamp();
DELETE FROM SHIFT_DATES_COUNT;
FOR I IN C1 LOOP
IF I.TABLE_NAME <> TABLENAME THEN
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT,
END_COUNT, current_timestamp::timestamp(0));
TABLENAME := I.TABLE_NAME;
END_COUNT := 0;
START_COUNT := 0;
END IF;
IF STRPOS(I.SQL_TEXT, 'END') > 0 THEN
EXECUTE I.SQL_TEXT INTO END_COUNT;
RAISE NOTICE '% ', ('END: ' || I.SQL_TEXT);
ELSE
EXECUTE I.SQL_TEXT INTO START_COUNT;
RAISE NOTICE '% ', ('START: ' || I.SQL_TEXT);
END IF;
END LOOP;
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT, END_COUNT,
current_timestamp::timestamp(0));
RAISE NOTICE '% ', ('INSERT INTO SHIFT_DATES_COUNT Done...');
l_end := clock_timestamp();
Time_Taken := (l_end-l_start);
RAISE NOTICE '% ', ('FIND_SHIFT_DATES Took: ' || Time_Taken );
END;
$BODY$;
Please let me know how can I use the date_to_find input parameter in the Dynamic SQL in the Cursor in the above Function.
You can use unbound cursor, clause fetch to get data from cursor, and exit when not found to finish, like:
CREATE OR REPLACE FUNCTION example (p_name text) RETURNS void LANGUAGE 'plpgsql' AS $$
DECLARE
C1 refcursor;
res record;
BEGIN
OPEN c1 FOR EXECUTE 'SELECT * FROM pg_database WHERE datname like ''%'||p_name||'%''';
LOOP
FETCH c1 INTO res;
EXIT WHEN not found;
raise notice 'value datname: %',res.datname;
END LOOP;
CLOSE c1;
RETURN;
END; $$;
--in my case
select example ('test')
NOTICE: value datname: test
NOTICE: value datname: test_msmov
NOTICE: value datname: test_resources
NOTICE: value datname: test_load_table
NOTICE: value datname: test_resources2
Total query runtime: 63 msec
1 row retrieved.
You can use EXECUTE clause for open cursor, see the documentation of PostgreSQL
https://www.postgresql.org/docs/10/plpgsql-cursors.html#PLPGSQL-CURSOR-OPENING
Example:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;
I want to use a variable as part of the statement, but it says that "tableref" doesn't exist.
CREATE OR REPLACE FUNCTION ff(tipo_acta integer, hasta date)
RETURNS void AS
$BODY$
DECLARE
tableref varchar;
r record;
BEGIN
if tipo_acta = 1 then
tableref = 't1';
elsif tipo_acta = 2 then tableref = 't2';
else tableref = 't3';
end if;
for r select id from tableref where somedate >= hasta loop
--
end loop;
I tried to use EXECUTE 'select id from ' || tableref || ' where....' but doesn't work either
I thought to get the record first with select id into r from t1 where .. and then use it in the loop, but there seems to be no way to use a record in a loop like that:
FOR r LOOP
....
END LOOP;
You need to use dynamic sql for that. You need to use execute command to do that in PLPG/SQL.
In your code it should be something like:
EXECUTE 'SELECT id FROM ' || tableref || ' WHERE somedate >= $1'
INTO c
USING hasta;
Can any one of you tell me how to approach this:
CREATE OR REPLACE FUNCTION name()
RETURNS ????? AS
$func$
BEGIN
SELECT * FROM tbl_a a;
SELECT * FROM tbl_b b;
END
$func$ LANGUAGE plpgsql;
Both tables have different structures.
You can use cursors but I can hardly imagine why you need such a function.
CREATE OR REPLACE FUNCTION my_multiselect(refcursor, refcursor) RETURNS VOID AS
$func$
BEGIN
OPEN $1 FOR SELECT * FROM information_schema.routines;
OPEN $2 FOR SELECT * FROM information_schema.sequences;
END
$func$ LANGUAGE plpgsql;
BEGIN;
SELECT my_multiselect('first_cursor_to_routines', 'second_cursor_to_sequences');
FETCH ALL IN first_cursor_to_routines;
FETCH ALL IN second_cursor_to_sequences;
COMMIT;
I'm not really sure what you're doing with this, but it sounds like you just want to return a union of these distinct result sets. You can do this with a dynamic query. I'm using Postgres 9.4.
CREATE OR REPLACE FUNCTION make_query(IN p_tables text[])
RETURNS void AS
$BODY$
DECLARE
v_qry text;
v_cols text;
v_types text;
v_as text;
BEGIN
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
column_name || '' '' || data_type AS def
FROM
sub
)
SELECT
string_agg(def, '','')
FROM
sub2;
',
p_tables
) INTO v_types;
v_qry := '
CREATE OR REPLACE FUNCTION name()
RETURNS TABLE(' || v_types || ') AS
$func$';
FOR i IN 1..array_upper(p_tables, 1)
LOOP
v_as := 'tbl' || i;
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
CASE WHEN table_name = ''%I''
THEN %L || ''.'' || column_name
ELSE ''NULL::'' || data_type
END AS cols
FROM
sub
)
SELECT
string_agg(cols, '','')
FROM
sub2;
',
p_tables,
p_tables[i],
v_as
) INTO v_cols;
IF i > 1 THEN
v_qry := v_qry || '
UNION ALL';
END IF;
v_qry := v_qry || '
SELECT ' || v_cols || ' FROM ' || p_tables[i] || ' AS ' || v_as;
IF i = array_upper(p_tables, 1) THEN
v_qry := v_qry || ';';
END IF;
END LOOP;
v_qry := v_qry || '
$func$ LANGUAGE sql;
';
EXECUTE v_qry;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Sorry it looks ugly here, but this formatting helps the final product look nicer. If you're shy about executing a dynamic query like this off the bat, just replace EXECUTE v_qry; with RAISE INFO 'v_qry: %', v_qry; and it will simply print the dynamic query out in a message without executing it, so you can review what it will do once executed.
Then execute make_query() with a list of tables you want to display like this:
SELECT make_query(ARRAY['tbl_a', 'tbl_b']);
The result is that you will now have a function called name() which you can call in order to see the results of both tables at the same time, with all the union details already sorted out:
SELECT * FROM name();