Postgresql Syntax Error for Trigger - postgresql

here is my Trigger and when i try to execute this I am getting error
Trigger:
CREATE OR REPLACE FUNCTION vqm_minoutline_cp_trg()
RETURNS trigger AS
$BODY$ DECLARE
--TYPE RECORD IS REFCURSOR;
Cur_Defaults RECORD;
v_M_Product_ID VARCHAR(32); --OBTG:varchar2--
v_VQM_Parameter_ID VARCHAR(32); --OBTG:varchar2--
vqm_minoutline_parameter_id VARCHAR(32);
v_count NUMERIC;
BEGIN
IF AD_isTriggerEnabled()='N' THEN IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END IF;
-- Default Quality Parameter for Product
IF (TG_OP = 'INSERT') THEN
FOR Cur_Defaults IN
(
SELECT VQM_Parameter_ID, Description, Criteria, MinValue, MaxValue, TextValue
FROM VQM_Product_Parameter VPP
WHERE VPP.M_PRODUCT_ID=new.M_PRODUCT_ID
)
LOOP
/*
Creating quality lines for Purchase Order Line
*/
SELECT * INTO vqm_minoutline_parameter_id FROM Ad_Sequence_Next('vqm_minoutline_parameter', Cur_Defaults.VQM_Parameter_ID);
INSERT INTO vqm_minoutline_parameter //here line 65 and so on
(vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description)
VALUES
(vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description);
END LOOP;
ELSIF (TG_OP = 'UPDATE') THEN
IF new.M_PRODUCT_ID != old.M_PRODUCT_ID THEN
DELETE FROM vqm_minoutline_parameter WHERE m_inoutline_id = new.m_inoutline_id;
FOR Cur_Defaults IN
(
SELECT VQM_Parameter_ID, Description, Criteria, MinValue, MaxValue, TextValue
FROM VQM_Product_Parameter VPP
WHERE VPP.M_PRODUCT_ID=new.M_PRODUCT_ID
)
LOOP
/*
Creating quality lines for Purchase Order Line
*/
SELECT * INTO vqm_minoutline_parameter_id FROM Ad_Sequence_Next('vqm_minoutline_parameter', Cur_Defaults.VQM_Parameter_ID);
INSERT INTO vqm_minoutline_parameter
(
vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description
)
VALUES
(
vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description
);
END LOOP;
END IF;
END IF;
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END
; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION vqm_minoutline_cp_trg()
OWNER TO saksham27;
This is error:
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO vqm_minoutline_parameter ( $1 , m_inoutline_id, ...
^
QUERY: INSERT INTO vqm_minoutline_parameter ( $1 , m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID, IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue, TextValue, Description) VALUES ( $1 , $2 , $3 , $4 , $5 , 'Y', TO_DATE(NOW()), $6 , TO_DATE(NOW()), $7 , $8 , $9 , $10 , $11 , $12 )
CONTEXT: SQL statement in PL/PgSQL function "vqm_minoutline_cp_trg" near line 66
********** Error **********
ERROR: syntax error at or near "$1"
SQL state: 42601
Context: SQL statement in PL/PgSQL function "vqm_minoutline_cp_trg" near line 66

In this query:
INSERT INTO vqm_minoutline_parameter //here line 65 and so on
(vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description)
VALUES
(vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description);
You're using vqm_minoutline_parameter_id both as a column name and a plpgsql variable name. As a result, plpgsql replaces the column name with $1 which leads to an incorrect query.
The workaround is to change the variable name to something that does not clash with any column name.
The documentation mentions the problem in Variable Substitution, with this note:
Note: PostgreSQL versions before 9.0 would try to substitute the
variable in all three cases, leading to syntax errors.
Presumably you're using a pre-9.0 version otherwise the error wouldn't occur. In theory column names are no longer substituted with more recent versions.

Related

How to insert before update (Custom audit)

I would like execute insert before I update some rows, insert would contain information about which table is updated, which column is updated, what query is executed etc..
And I wrote something like this:
DO $$
DECLARE _new_product_id bigint := 1;
_update_by_product_id bigint:= 2;
-- BEGIN TRANSACTION
BEGIN
DECLARE product_sql_query text := 'UPDATE products SET product_id =' + _new_product_id + 'WHERE product_id =' + _update_by_product_id + ';'
INSERT INTO products_audit (ordinal_number, table_name, column_name, action_applied, query_executed, value_before_update, value_after_update, created_date )
VALUES (1, 'products', 'product_id', 'UPDATE', users_query, 1, 1, NOW());
-- END TRANSACTION
COMMIT;
END;
$$
But I'm receiving an syntax error which says:
ERROR: syntax error at or near "INSERT" LINE 9: INSERT INTO
products_audit (ordinal_number, table...
What's wrong here, I guess product_sql_query value is not correct?
The DECLARE block needs to go before the first BEGIN of the PL/pgSQL - which is not the same as a BEGIN TRANSACTION. You can't control transactions in an anonymous PL/pgSQL block.
It's unclear to me what you intend with the product_sql_query variable, but removing all the syntax errors, the block should look like this:
DO $$
DECLARE
_new_product_id bigint := 1;
_update_by_product_id bigint:= 2;
product_sql_query text;
BEGIN
product_sql_query := 'UPDATE products SET product_id = ' || _new_product_id::text ||' WHERE product_id = ' || _update_by_product_id::text;
INSERT INTO products_audit
(ordinal_number, table_name, column_name, action_applied, query_executed, value_before_update, value_after_update, created_date)
VALUES
(1, 'products', 'product_id', 'UPDATE', users_query, 1, 1, NOW());
END;
$$

PostgreSQL log trigger optimalization

I spent a lot of time trying to optimize our pgsql log trigger which started to be a problem. I did huge progress (from 18min to 2.5min by inserting 3M rows) but I would like to know if some pgSql masters will be able to do it even better.
CREATE OR REPLACE FUNCTION table_log_trig()
RETURNS trigger AS
$BODY$
DECLARE
col TEXT; -- Single column name to save
newVal TEXT; -- New value for column
oldVal TEXT; -- Old value for column
colLimit TEXT[]; -- Columns that should be logged
BEGIN
IF TG_ARGV[0] IS NOT NULL THEN
-- Trigger specifies columns to log
SELECT array_agg(unnest)
FROM unnest(string_to_array(TG_ARGV[0], ','))
INTO colLimit;
ELSE
-- Trigger with no params. Log all columns
SELECT array_agg(json_object_keys)
FROM json_object_keys(row_to_json(NEW))
WHERE json_object_keys NOT IN ('id', 'created_at', 'updated_at') -- Exceptions
INTO colLimit;
END IF;
-- Loop over columns that should be saved in log
FOREACH col IN ARRAY colLimit
LOOP
-- INSERT & UPDATE
EXECUTE 'SELECT ($1).' || col || '::text' INTO newVal USING NEW;
-- UPDATE
IF TG_OP = 'UPDATE' THEN
EXECUTE 'SELECT ($1).' || col || '::text' INTO oldVal USING OLD;
END iF;
-- Add only new or changed data
IF
newVal != oldVal OR
(oldVal IS NULL AND newVal IS NOT NULL) OR
(oldVal IS NOT NULL AND newVal IS NULL)
THEN
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
VALUES (NEW.id, col, oldVal, newVal, NOW(), 999, 'O');
END IF;
END LOOP;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
row_to_json() returns both column names and values; you may as well make use of these values, rather than extracting them later via dynamic SQL.
I haven't thoroughly tested this, let alone benchmarked it, but here's the gist of it:
CREATE OR REPLACE FUNCTION table_log_trig() RETURNS trigger AS
$$
DECLARE
OldJson JSONB = NULL;
BEGIN
IF TG_OP <> 'INSERT' THEN
OldJson := to_jsonb(old);
END IF;
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
SELECT new.id, key, OldValues.value, NewValues.value, now(), 999, 'O'
FROM jsonb_each(to_jsonb(new)) NewValues
LEFT JOIN jsonb_each(OldJson) OldValues USING (key)
WHERE
(
(TG_ARGV[0] IS NULL AND key NOT IN ('id', 'created_at', 'updated_at')) OR
(TG_ARGV[0] IS NOT NULL AND key = ANY(string_to_array(TG_ARGV[0], ',')))
) AND
OldValues.value::text IS DISTINCT FROM NewValues.value::text;
RETURN NULL;
END
$$
LANGUAGE plpgsql VOLATILE;

How to return a query result from postgresql function

I am new to Postgres, in ms sql server we can write stored procedure to perform logic and return a select statement. Likewise i wrote following pqsql function with return type table. But its showing some incorrect syntax error, but if I replace the return type with integer and comment out the select statement it works fine.
following is the function with return type table
CREATE OR REPLACE FUNCTION candidate_save
(
p_name varchar(50),
p_dob date,
p_course_level_code integer,
p_email varchar(50),
p_mob character(10),
p_sslc_regno varchar(10),
p_sslc_year_of_passing character(4),
p_sslc_board_of_examination integer,
p_password character(128),
p_ip_address varchar(15)
)
RETURNS TABLE
(
regno char(10),
name varchar(50),
dob date,
course_level_code integer,
email varchar(50),
mob character(10),
sslc_regno varchar(10),
sslc_year_of_passing character(4),
sslc_boe integer
)
AS
$BODY$
DECLARE
max_row_count integer;
random_no integer;
v_regno character(10);
BEGIN
SELECT COUNT(*) INTO max_row_count FROM candidates WHERE course_level_code = p_course_level_code;
max_row_count := max_row_count + 1;
v_regno := cast(p_course_level_code as character) || cast( trunc(random() * 89999 + 10000) as integer) || to_char(max_row_count, 'FM0000');
INSERT INTO candidates
(regno, name, dob, course_level_code, email, mob, sslc_regno, sslc_year_of_passing, sslc_board_of_examination, password, created_on, ip_address)
VALUES
(v_regno, p_name, p_dob, p_course_level_code, p_email, p_mob, p_sslc_regno, p_sslc_year_of_passing, p_sslc_board_of_examination, p_password, now(), p_ip_address);
RETURN QUERY
SELECT regno, name, dob, course_level_name, email, mob, sslc_regno, sslc_year_of_passing, c.board as sslc_boe
FROM candidates a
INNER JOIN course_levels b on a.course_level_code = b.course_level_code
INNER JOIN sslc_board_of_examinations c ON a.sslc_board_of_examination = boe_code
WHERE regno = v_regno;
END;
$BODY$
LANGUAGE plpgsql;
when creating above function following error is showing
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO candidates ( $1 , $2 , $3 , $4 , $5 , $6 , ...
^
QUERY: INSERT INTO candidates ( $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 , sslc_board_of_examination, password, created_on, ip_address) VALUES ( $9 , $10 , $11 , $12 , $13 , $14 , $15 , $16 , $17 , $18 , now(), $19 )
CONTEXT: SQL statement in PL/PgSQL function "candidate_save" near line 30
********** Error **********
ERROR: syntax error at or near "$1"
SQL state: 42601
Context: SQL statement in PL/PgSQL function "candidate_save" near line 30
but if I replace the table with integer it works fine.
CREATE OR REPLACE FUNCTION candidate_save
(
p_name varchar(50),
p_dob date,
p_course_level_code integer,
p_email varchar(50),
p_mob character(10),
p_sslc_regno varchar(10),
p_sslc_year_of_passing character(4),
p_sslc_board_of_examination integer,
p_password character(128),
p_ip_address varchar(15)
)
RETURNS integer
AS
$BODY$
DECLARE
max_row_count integer;
random_no integer;
v_regno character(10);
BEGIN
SELECT COUNT(*) INTO max_row_count FROM candidates WHERE course_level_code = p_course_level_code;
max_row_count := max_row_count + 1;
v_regno := cast(p_course_level_code as character) || cast( trunc(random() * 89999 + 10000) as integer) || to_char(max_row_count, 'FM0000');
INSERT INTO candidates
(regno, name, dob, course_level_code, email, mob, sslc_regno, sslc_year_of_passing, sslc_board_of_examination, password, created_on, ip_address)
VALUES
(v_regno, p_name, p_dob, p_course_level_code, p_email, p_mob, p_sslc_regno, p_sslc_year_of_passing, p_sslc_board_of_examination, p_password, now(), p_ip_address);
RETURN 1;
END;
$BODY$
LANGUAGE plpgsql;
if I am doing something wrong pl. let me know.
I am using Postgres 9.4
INSERT INTO candidates
(regno
^------- The error
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO candidates ( $1
The issue is that regno is a plpgsql variable, because it's a column name in the output table.
RETURNS TABLE
(
regno char(10),
Each entry in RETURNS TABLE is registered as a variable so you can assign to it and use RETURN NEXT.
When parsed and processed, plpgsql variables in statements are replaced with positional parameters like $1, $2, etc. Which is why the error is what it is, and also why some of the later entries in the insert column-name-list don't get replaced. They don't clash with a parameter name.
Use different variable names.
I think 9.5 detects parameter name clashes and reports a clearer error.
finally it started working after I changed the variable names in the return table.
Thanks to all

Dynamic SELECT in trigger function gives syntax error

Just need help as to debug some syntax errors in the below code.The code is as below and have only couple of syntax errors near keywords like Insert, Select, etc
CREATE OR REPLACE FUNCTION audit_temp() RETURNS TRIGGER LANGUAGE plpgsql AS $BODY$
DECLARE
ri RECORD;
oldValue TEXT;
newValue TEXT;
isColumnSignificant BOOLEAN;
isValueModified BOOLEAN;
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
NEW.record_modified_ = clock_timestamp();
FOR ri IN
-- Fetch a ResultSet listing columns defined for this trigger's table.
SELECT ordinal_position, column_name, data_type
FROM information_schema.columns
WHERE table_schema = quote_ident(TG_TABLE_SCHEMA)
AND table_name = quote_ident(TG_TABLE_NAME)
ORDER BY ordinal_position
LOOP
-- For each column in this trigger's table, copy the OLD & NEW values into respective variables.
-- NEW value
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO newValue USING NEW;
-- OLD value
IF TG_OP = 'INSERT' THEN -- If operation is an INSERT, we have no OLD value, so use an empty string.
oldValue := ''::varchar;
ELSE -- Else operation is an UPDATE, so capture the OLD value.
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO oldValue USING OLD;
END IF;
isColumnSignificant := (position( '_x_' in ri.column_name ) < 1) AND
(ri.column_name <> 'pkey_') AND
(ri.column_name <> 'record_modified_');
IF isColumnSignificant THEN
isValueModified := oldValue <> newValue; -- If this nthField in the table was modified, make history.
IF isValueModified THEN
/*RAISE NOTICE E'Inserting history_ row for INSERT or UPDATE.\n';*/
INSERT INTO audit_temp( operation_, table_oid_, table_name_, uuid_, column_name_, ordinal_position_of_column_, old_value_, new_value_ )
VALUES ( TG_OP, TG_RELID, TG_TABLE_NAME, NEW.pkey_, ri.column_name::VARCHAR, ri.ordinal_position, oldValue::VARCHAR, newValue::VARCHAR);
END IF;
END IF;
END LOOP;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
/*RAISE NOTICE E'Inserting history_ row for DELETE.\n';*/
-- Similar to INSERT above, but refers to OLD instead of NEW, and passes empty values for last 4 fields.
INSERT INTO audit_temp ( operation_, table_oid_, table_name_, uuid_, column_name_, ordinal_position_of_column_, old_value_, new_value_ )
VALUES ( TG_OP, TG_RELID, TG_TABLE_NAME, OLD.pkey_, ''::VARCHAR, 0, ''::VARCHAR, ''::VARCHAR );
RETURN OLD;
END IF;
/* Should never reach this point. Branching in code above should always reach a call to RETURN. */
RAISE EXCEPTION 'Unexpectedly reached the bottom of this function without calling RETURN.';
END; $BODY$;
The error is as follows & mostly around Select Insert keywords only:
>[Error] Script lines: 1-42 -------------------------
ERROR: syntax error at or near "SELECT"
Any suggestions?????
Syntax error
The offending statement is this (and the others like it):
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO newValue USING NEW;
According to the documentation parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually. So the solution would be:
EXECUTE 'SELECT (' || NEW || ').' || ri.column_name || '::text' INTO newValue;
Improvements
You can make a few improvements to your trigger function to make it faster and more efficient:
You should check isColumnSignficant before you populate oldValue and newValue.
When updating, you do not have to record the OLD values: they are already in the audit table when the data was first inserted or later updated.
When deleting, don't store empty strings and 0, just leave the columns NULL.

PostgreSQL triggers and exceptions

I'm trying to get my first ever trigger and function to work, but how I throw exceptions and return data right way?
PostgreSQL 8.4.1
CREATE TABLE "SHIFTS" (
id integer NOT NULL, -- SERIAL
added timestamp without time zone DEFAULT now() NOT NULL,
starts timestamp without time zone NOT NULL,
ends timestamp without time zone NOT NULL,
employee_id integer,
modified timestamp without time zone,
status integer DEFAULT 1 NOT NULL,
billid integer,
CONSTRAINT "SHIFTS_check" CHECK ((starts < ends))
);
-- Check if given shift time overlaps with existing data
CREATE OR REPLACE FUNCTION
shift_overlaps (integer, timestamp, timestamp)
RETURNS
boolean AS $$
DECLARE
_employeeid ALIAS FOR $1;
_start ALIAS FOR $2;
_end ALIAS FOR $3;
BEGIN
SELECT
COUNT(id) AS c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
-- Return boolean
RETURN (c > 0);
END;
$$
LANGUAGE
plpgsql
;
CREATE OR REPLACE FUNCTION
check_shift()
RETURNS trigger AS '
BEGIN
-- Bill ID is set, do not allow update
IF tg_op = "UPDATE" THEN
IF old.billid IS NOT NULL THEN
RAISE EXCEPTION "Shift is locked"
END IF;
END IF;
-- Check for overlap
IF tg_op = "INSERT" THEN
IF new.employee_id IS NOT NULL THEN
IF shift_overlaps(new.employee_id, new.starts, new.ends) THEN
RAISE EXCEPTION "Given time overlaps with shifts"
END IF;
END IF;
END IF;
-- Check for overlap
IF tg_op = "UPDATE" THEN
IF (new.employee_id IS NOT NULL) AND (new.status = 1) THEN
IF shift_overlaps(new.employee_id, new.starts, new.ends) THEN
RAISE EXCEPTION "Given time overlaps with shifts"
END IF;
END IF;
END IF;
RETURN new;
END
'
LANGUAGE
plpgsql
;
-- Shift checker trigger
CREATE TRIGGER
check_shifts
BEFORE
INSERT OR UPDATE
ON
"SHIFTS"
FOR EACH ROW EXECUTE PROCEDURE
check_shift()
;
shift_overlaps():
SQL error: ERROR: query has no destination for result data
check_shift():
SQL error: ERROR: unrecognized exception condition "Shift is locked"
You've got an error here:
SELECT
COUNT(id) AS c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
Such a select in a plpgsql procedure has to be SELECT INTO... like this:
DECLARE
c INTEGER;
BEGIN
SELECT
COUNT(id)
INTO c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
RETURN (c > 0);
END;
And here you've got to have the semicolon at the end of the line:
enter code here`RAISE EXCEPTION "Shift is locked";
Not sure what you're trying to find out. You're managing to raise your own exceptions, so that's good. I would expect that any error handling would be in the code that evokes this method.
If you want to do something inside the procedure, you need an EXCEPTION section:
[ <> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ... ] THEN
handler_statements
[ WHEN condition [ OR condition ... ] THEN
handler_statements
... ]
END;
But generally I would expect you'd handle it in the calling code.
You have to use SELECT INTO to get a value returned by a query
DECLARE
[...]
c boolean;
SELECT
COUNT(id) INTO c
FROM
"SHIFTS"
WHERE
[...]