I am trying to get the value of a column so that it can be inserted into a table that holds the column name and column value of the inserted row, however, I have not been able to get the value of the column that I need. Normally, I would be able to use value := NEW.column_name but each table has a unique key column name that is in the table name itself (I know, that's bad), but I already have a way to get the column name that I want, it's getting the NEW value of that column that's the problem.
CREATE OR REPLACE FUNCTION trgfn_keyvalue_insert()
RETURNS trigger AS
$BODY$
DECLARE
key_column_value character varying;
key_column_name character varying;
part text;
part_array text[] := array['prefix_','_suffix'];
BEGIN
key_column_name := TG_TABLE_NAME; --parsing the table name to get the desired column name
FOREACH part IN ARRAY part_array LOOP
key_column_name = regexp_replace(cat, part, '');
END loop;
IF TG_OP = 'INSERT' THEN
EXECUTE 'SELECT $1 FROM $2' --This is where I'd like to get the
INTO key_column_value --value of the column
USING key_column_name, NEW;
INSERT INTO inserted_kvp
(table_name, key, value)
VALUES
(TG_TABLE_NAME, key_column_name, key_column_value);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
So, when I INSERT into a table:
CREATE TABLE prefix_kvp1_suffix AS id SERIAL, kvp1 CHARACTER VARYING;
CREATE TABLE prefix_kvp2_suffix AS id SERIAL, kvp2 CHARACTER VARYING;
INSERT INTO prefix_kvp1_suffix VALUES (1, 'value1');
INSERT INTO prefix_kvp2_suffix VALUES (1, 'value2');
I would like for the inserted_kvp table to have the following:
| table_name |key |value |
--------------------------------
|prefix_kvp1_suffix|kvp1|value1|
|prefix_kvp2_suffix|kvp2|value2|
Instead, I get the following error when inserting:
ERROR: syntax error at or near "$2"
LINE 1: SELECT $1 FROM $2
^
QUERY: SELECT $1 FROM $2
CONTEXT: PL/pgSQL function worldmapkit.trgfn_keyvalue_insert() line 13 at EXECUTE statement
I have tried different variations of getting this value by using EXECUTE format() and a few other ways, but I am still not having any luck. Any help is appreciated!
After much fiddling, I found the answer to my question. The correct syntax for the EXECUTE statement above is:
EXECUTE format('SELECT $1.%I', key_column_name)
INTO key_column_value
USING NEW;
This will get the column value of the NEW record. Hopefully, this will help out someone in a similar situation.
Related
I need to add a trigger that raises a warning message when a certain out of bounds (not 2, 3, 5-7) numeric value is inserted into or altered in an EXISTING row in a "grade" column in the sql table. This code example raises such a message ONLY when a NEW row is created.
How to raise the message when a value in the EXISTING row is altered?
Values in the "grade" column are tied via key to a column in another table "grade_salary" where they are stored. How to write the insertion/alteration check in such a way that raises the message without specifying the concrete correct values (2, 3, 5-7), but stating only that "IF changed value lies outside of the values specified in column "grade" of the table "grade_salary" THEN raise the error message" (and not let the value be modified)?
CREATE TRIGGER person
BEFORE INSERT ON hr."position"
FOR EACH ROW EXECUTE PROCEDURE person();
CREATE OR REPLACE FUNCTION person()
RETURNS TRIGGER
SET SCHEMA 'hr'
LANGUAGE plpgsql
AS $$
BEGIN
IF ((NEW.grade < 2) or (NEW.grade > 3 and NEW.grade < 5)
or (NEW.grade > 7)) THEÎ
RAISE EXCEPTION 'Incorrect value';
END IF;
RETURN NEW;
END;
$$;
One checks whether the new value corresponds to an existing one in a column with:
IF new_value not in (SELECT DISTINCT grade FROM grade_salary)
THEN RAISE EXCEPTION 'Inadmissible value.'
In full:
CREATE OR REPLACE FUNCTION person()
RETURNS TRIGGER
SET SCHEMA 'hr'
LANGUAGE plpgsql
AS $$
declare
new_value numeric;
begin
select new.grade from "position" into new_value;
IF new_value not in (SELECT DISTINCT grade FROM grade_salary)
THEN RAISE EXCEPTION 'Inadmissible value.';
END IF;
RETURN NEW;
END;
$$;
My table schema:
CREATE TABLE project_sectors(
sector_id int GENERATED ALWAYS AS IDENTITY,
sector_name varchar(256),
project_count int,
PRIMARY KEY (sector_id)
);
And I am trying to execute a query for many tables with some particular column name:
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name FROM information_schema.columns WHERE column_name = 'project_name'
LOOP
RAISE NOTICE 'INSERT METADATA FOR: %', t;
EXECUTE 'INSERT INTO project_sectors VALUES ($1, 0)'
USING t;
end loop;
end
$$ language 'plpgsql';
Once I try to run the query I get:
[42804] ERROR: column "sector_id" is of type integer but expression is of type text Hint: You will need to rewrite or cast the expression. Where: PL/pgSQL function inline_code_block line 9 at EXECUTE
When previously the EXECUTE statement was
EXECUTE format('INSERT INTO megaproject_sectors VALUES (''%I'', 0)', t)
I would get the error
ERROR: invalid input syntax for type integer: "railway"
railway is the value of t.
Why is it trying to insert data into GENERATED ALWAYS column?
Why is it trying to insert data into GENERATED ALWAYS column?
Because you are not specifying the target columns in your INSERT statement, so Postgres uses them from left to right.
It is good coding practice to always specify the target columns. As your table name is hardcoded, the dynamic SQL is unnecessary as well:
INSERT INTO project_sectors (sector_name, sector_count) VALUES (t.table_name, 0)
Note that in other database products, specifying less values than the table has columns would result in an error. So in e.g. Oracle your statement would result in "ORA-00947: not enough values"
Description
I am creating a postgresql function and encountered a problem. I am reading data from table and based on that data i want to update data or not.
but for selection i need to either create a temp table or create another function that return a single decimal value.
Here is my code
Declare command text := 'select distance from road where gid ='|| id;
Execute command;
i am stuck at this point
i dont know what to do as i am new to postgresql
What i need
i want to apply condition on distance returned by this query
for example
IF distance < 100
THEN
(Insert into another table)
END;
What i tried
select distance into varDistance from road where gid ='|| id;
i go through Select Into command and came to know that this should be same as table . which is not acceptable to me .
Is this possible to have double type variable and after query i get my varibale initialed with value? Or else solution
It's unclear to me what you are trying to do, but to read a single value from a table, you would need the select into
Something along the lines:
create function some_function(p_id integer)
returns ...
as
$$
declare
l_distance double precision;
begin
select distance
into l_distance
from road
where id = p_id; --<< this is the parameter
if l_distance < 100 then
insert into some_other_table (...)
values (...)
end if;
end;
$$
language plpgsql;
From the little information you have provided, I don't see any reason for dynamic SQL.
If you do need dynamic SQL, use the format() function to create the SQL string with a placeholder, then use execute with an into and using clause
l_sql := format('select distance from %I gid = $1', l_table_name);
execute l_sql
into l_distance
using p_id; --<< this is the parameter
My task is pretty much simple let's say I have table having 3 columns i.e. temp(ids, name_c, UID_c) and I have first two column values and 3rd column is nullable. What I want to do is whenever these two value is inserted the value of third column must be updated (after insertion) with new value. i.e concatenation of both values.
For Ex.
insert into temp(ids, name_c) values(did.nextval,'Andrew');
The result should be
1 Andrew Andrew_1
So I am using trigger for this purpose
create or replace trigger triggerDemo
after INSERT
on temp
for each row
declare
/* pragma autonomous_transaction;*/
user_name varchar2(50);
current_val number;
begin
select did.currval into current_val from dual; /* did is sequence */
select names into user_name from temp where ids = current_val;
update temp set uid_c = user_name||'_'||ids where ids = current_val;
end;
When I am inserting the values I get this error
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
First, you want a before insert trigger, not an after insert trigger. Second, decide whether you want to calculate the id on input or in the trigger. You can do something like this:
create or replace trigger triggerDemo before INSERT on temp
for each row
/* pragma autonomous_transaction;*/
begin
if :new.current_val is null then
select did.currval into :new.ids from dual; /* did is sequence */
end;
select :new.user_name || '_' || :new.ids into :new.uid_c from dual;
end;
Ok, here is the layout:
I have a bunch of uuid data that is in varchar format. I know uuid is its own type. This is how I got the data. So to verify this which ones are uuid, I take the uuid in type varchar and insert it into a table where the column is uuid. If the insert fails, then it is not a uuid type. My basic question is how to delete the bad uuid if the insert fails. Or, how do I delete out of one table if an insert fails in another table.
My first set of data:
drop table if exists temp1;
drop sequence if exists temp1_id_seq;
CREATE temp table temp1 (id serial, some_value varchar);
INSERT INTO temp1(some_value)
SELECT split_part(name,':',2) FROM branding_resource WHERE name LIKE '%curric%';
create temp table temp2 (id serial, other_value uuid);
CREATE OR REPLACE function verify_uuid() returns varchar AS $$
DECLARE uu RECORD;
BEGIN
FOR uu IN select * from temp1
LOOP
EXECUTE 'INSERT INTO temp2 values ('||uu.id||','''|| uu.some_value||''')';
END LOOP;
END;
$$
LANGUAGE 'plpgsql' ;
select verify_uuid();
When I run this, I get the error
ERROR: invalid input syntax for uuid:
which is what I expect. There are some bad uuids in my data set.
My research led me to Trapping Errors - Exceptions with UPDATE/INSERT in the docs.
Narrowing down to the important part:
BEGIN
FOR uu IN select * from temp1
LOOP
begin
EXECUTE 'INSERT INTO temp2 values ('||uu.id||','''|| uu.some_value||''')';
return;
exception when ??? then delete from temp1 where some_value = uu.some_value;
end;
END LOOP;
END;
I do not know what to put instead of ???. I think it relates to the ERROR: invalid input syntax for uuid:, but I am not sure. I am actually not even sure if this is the right way to go about this?
You can get the SQLSTATE code from psql using VERBOSE mode, e.g:
regress=> \set VERBOSITY verbose
regress=> SELECT 'fred'::uuid;
ERROR: 22P02: invalid input syntax for uuid: "fred"
LINE 1: SELECT 'fred'::uuid;
^
LOCATION: string_to_uuid, uuid.c:129
Here we can see that the SQLSTATE is 22P02. You can use that directly in the exception clause, but it's generally more readable to look it up in the manual to find the text representation. Here, we see that 22P02 is invalid_text_representation.
So you can write exception when invalid_text_representation then ...
#Craig shows a way to identify the SQLSTATE.
You an also use pgAdmin, which shows the SQLSTATE by default:
SELECT some_value::uuid FROM temp1
> ERROR: invalid input syntax for uuid: "-a0eebc999c0b4ef8bb6d6bb9bd380a11"
> SQL state: 22P02
I am going to address the bigger question:
I am actually not even sure if this is the right way to go about this?
Your basic approach is the right way: the 'parking in new york' method (quoting Merlin Moncure in this thread on pgsql-general). But the procedure is needlessly expensive. Probably much faster:
Exclude obviously violating strings immediately.
You should be able to weed out the lion's share of violating strings with a much cheaper regexp test.
Postgres accepts a couple of different formats for UUID in text representation, but as far as I can tell, this character class should covers all valid characters:
'[^A-Fa-f0-9{}-]'
You can probably narrow it down further for your particular brand of UUID representation (Only lower case? No curly braces? No hyphen?).
CREATE TEMP TABLE temp1 (id serial, some_value text);
INSERT INTO temp1 (some_value)
SELECT split_part(name,':',2)
FROM branding_resource
WHERE name LIKE '%curric%'
AND split_part(name,':',2) !~ '[^A-Fa-f0-9{}-]';
"Does not contain illegal characters."
Cast to test the rest
Instead of filling another table, it should be much cheaper to just delete (the now few!) violating rows:
CREATE OR REPLACE function f_kill_bad_uuid()
RETURNS void AS
$func$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT * FROM temp1
LOOP
BEGIN
PERFORM rec.some_value::uuid; -- no dynamic SQL needed
-- do not RETURN! Keep looping.
RAISE NOTICE 'Good: %', rec.some_value; -- only for demo
EXCEPTION WHEN invalid_text_representation THEN
RAISE NOTICE 'Bad: %', rec.some_value; -- only for demo
DELETE FROM temp1 WHERE some_value = rec.some_value;
END;
END LOOP;
END
$func$ LANGUAGE plpgsql;
No need for dynamic SQL. Just cast. Use PERFORM, since we are not interested in the result. We just want to see if the cast goes through or not.
Not return value. You could count and return the number of excluded rows ...
For a one-time operation you could also use a DO statement.
And do not quote the language name 'plpgsql'. It's an identifier, not a string.
SQL Fiddle.