Error while trying to insert data into timestamp field using plpgsql - postgresql

I have the following plpgsql function:
CREATE OR REPLACE FUNCTION test_func(OUT pid bigint)
RETURNS bigint AS
$BODY$
DECLARE
current_time timestamp with time zone = now();
BEGIN
INSERT INTO "TEST"(
created)
VALUES (current_time) RETURNING id INTO pid;
END
$BODY$
LANGUAGE plpgsql;
select * from test_func();
The above gives an error:
column "created" is of type timestamp with time zone but expression is of type time with time zone
Insertion query without function:
INSERT INTO "TEST"(
created)
VALUES (now()) RETURNING id INTO pid;
or if now() is used directly without defining variable it works.

CURRENT_TIME is a reserved word (and a special function), you cannot use it as variable name. You don't need a variable here to begin with:
CREATE OR REPLACE FUNCTION test_func(OUT pid bigint) AS
$func$
BEGIN
INSERT INTO "TEST"(created)
VALUES (now())
RETURNING id
INTO pid;
END
$func$
LANGUAGE plpgsql;
now() is a STABLE function. It does not change across the same transaction. There is no need to capture the result into a variable.
How do IMMUTABLE, STABLE and VOLATILE keywords effect behaviour of function?

Related

How to execute a query with a column name passed as parameter to a plpgsql function?

I have a table with multiple columns in PostgreSQL. I try to make a function returning a table with a few default columns and a variable column. The column name should be passed as function parameter. Example:
SELECT * FROM get_gas('temperature');
This is my code right now:
CREATE OR REPLACE FUNCTION get_gas(gas text)
RETURNS TABLE (id INTEGER, node_id INTEGER,
gas_name DOUBLE PRECISION,
measurement_timestamp timestamp without time zone )
AS
$$
BEGIN
SELECT measurements_lora.id, measurements_lora.node_id, gas, measurements_lora.measurement_timestamp
AS measure
FROM public.measurements_lora;
END
$$ LANGUAGE plpgsql;
When passing, for example, 'temperature' as column name (gas), I want to get a table with these columns from the function call.
id - node_id - temperature - measurement_timestamp
How would I achieve this?
You can use EXECUTE statement.
CREATE OR REPLACE FUNCTION get_gas(gas text) RETURNS TABLE (f1 INTEGER, f2 INTEGER, f3 DOUBLE PRECISION, f4 timestamp without time zone ) AS
$$
DECLARE
sql_to_execute TEXT;
BEGIN
SELECT 'SELECT measurements_lora.id,
measurements_lora.node_id, '
|| gas ||',
measurements_lora.measurement_timestamp AS measure
FROM public.measurements_lora '
INTO sql_to_execute;
RETURN QUERY EXECUTE sql_to_execute;
END
$$ LANGUAGE plpgsql;
This will create a variable sql_to_execute with your field and. QUERY EXECUTE will execute your interpreted query.
EDIT 1: Look at the another answer the concernings about security issues.
If you really need dynamic SQL in a PL/pgSQL function (which you don't), be sure to defend against SQL injection! Like:
CREATE OR REPLACE FUNCTION get_gas(gas text)
RETURNS TABLE (id integer
, node_id integer
, gas_name double precision
, measurement_timestamp timestamp)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
'SELECT m.id, m.node_id, m.%I, m.measurement_timestamp
FROM public.measurements_lora m'
, gas
);
END
$func$;
The format specifier %I in format() double-quotes identifiers where needed,
See:
SQL injection in Postgres functions vs prepared queries
Insert text with single quotes in PostgreSQL

Interpreting TG_TABLE_NAME as a value to update row in other table

I want to update a row in the master_table_info table with the latest timestamp whenever certain other tables are updated. Each row in the table corresponds to another table. I've created this function, but I cannot get TG_TABLE_NAME to be interpreted as a variable value and not a new column. I thus get the error column some_table does not exist. How do I interpret it as a value?
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
EXECUTE format('
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = %I', TG_TABLE_NAME);
RETURN NULL;
END;
$$
language plpgsql;
CREATE TRIGGER master_table_timestamp
BEFORE UPDATE ON some_table
EXECUTE PROCEDURE master_table_timestamp();
EDIT
Based on the answer/comments so far and reading up the trigger documentation, I realized that I should use TG_TABLE_NAME and change to an AFTER trigger. However, modifying the table with the trigger produces no changes on master_table_info. What could be wrong?
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = TG_TABLE_NAME;
RETURN new;
END;
$$
language plpgsql;
CREATE TRIGGER master_table_timestamp
AFTER UPDATE ON some_table
EXECUTE PROCEDURE master_table_timestamp();
2nd Edit
This code in my edit above (based on assistance from the answers) is correct. I just needed to force a manual refresh of the table for it to correctly show.
%I replaces the placeholder as an identifier. So the generated SQL would be
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = some_table;
To replace a literal value you would need %L as a placeholder in the String. The %L placeholder takes care of properly quoting values, so if you use that, the generated string would be:
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = 'some_table';
which is what you expected.
However there is no need for dynamic SQL to begin with. As you use that function in a before trigger it is important that you return a non-null value from it, otherwise the UPDATE statement would be cancelled.
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = TG_TABLE_NAME;
RETURN new; --<< return a NON-NULL value here!
END;
$$
language plpgsql;

How to convert a text variable into a timestamp in postgreSQL?

I know how to convert a text to timestamp in postgreSQL using
SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY')
but how can I convert a text variable (inside a function) to timestamp??
In my table (table_ebscb_spa_log04) "time" is a character varying column, in which I have placed a formated date time (15-11-30 11:59:59.999 PM).
I have tried this function, in order to convert put the date time text into a variable (it always change) and convert it into timestamp...
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
timeascharvar character varying;
timeastistamp timestamp;
BEGIN
IF NEW.time_type = 'Lap' THEN
SELECT t.time FROM table_ebscb_spa_log04 t INTO timeascharvar;
SELECT to_timestamp('timeascharvar', 'yy-mm-dd HH24:MI:SS.MS') INTO timeastistamp;
END IF;
RETURN timeastistamp;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;
but whenever I run it in the table, it shows this ERROR message...
It seems that "to_timestamp" waits for a number to be the year, how can I get it to recognize the variable as if it were numbers?
The first parameter to to_timestamp should be your var not a string containing the name of your var:
to_timestamp(timeascharvar, 'yy-mm-dd HH24:MI:SS.MS')

PL/Proxy returning Unsupported Type on Stored Procedure Call

I've setup a Stored Procedure in PL/Proxy to make a query, and receive some RECORDs back.
In PL/Proxy:
CREATE OR REPLACE FUNCTION query_autocomplete(q text, i_id bigint)
RETURNS SETOF RECORD AS $$
CLUSTER 'autocompletecluster';
RUN ON i_id;
$$ LANGUAGE plproxy;
In each Partition:
CREATE OR REPLACE FUNCTION query_autocomplete(q text, i_id bigint)
RETURNS SETOF RECORD AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN EXECUTE q
LOOP
RETURN NEXT rec;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
As you've likely guessed, this is hitting a defined SERVER in PGSQL called 'autocompletecluster'. The query string that I'm sending through is as follows:
$sql = "SELECT * FROM autocomplete WHERE member_id = :memberId";
$query = $this->db->prepare("SELECT query_autocomplete('{$sql}',1234");
It's returning the following:
SQLSTATE[XX000]: Internal error: 7 ERROR: PL/Proxy function public.query_autocomplete(0): unsupported type
The table that query is hitting is defined as such:
CREATE TABLE autocomplete (
id character varying(100) NOT NULL,
extra_data hstore,
username character varying(254),
member_id bigint
);
What am I doing wrong?
The error strongly suggests that PL/Proxy doesn't support SETOF RECORD. Try instead defining your functions to return autocomplete%rowtype or, failing that, RETURNS TABLE (...) with a matching columns-set.

Declare variable set = select

How do I declare a variable for used in a PostgreSQL 9.3 query?
CREATE or replace FUNCTION public.test()
returns int4
AS
$BODY$
DECLARE
cod_process bigint :=30001;
cod_instance bigint ;
utc_log timestamp without time zone := localtimestamp;
cod_log_type varchar(100) :='information ';
txt_log_text varchar(100):= 'start process';
txt_log varchar(100):= txt_log_text||'_'||cod_process;
set cod_instance= select max(cod_instance) as cod_instance from public.instance where public.instance.cod_process=cod_process;
BEGIN
INSERT INTO public.log (cod_process, cod_instance, utc_log,cod_log_type,txt_log)
VALUES (cod_process, cod_instance, utc_log,cod_log_type,txt_log );
RETURN 11;
END;
$BODY$ LANGUAGE 'plpgsql';
ERROR: type "cod_instance" does not exist
SQL state: 42704
Character: 383
Your demo function would work like this:
CREATE or replace FUNCTION public.test()
RETURNS int4 AS
$func$
DECLARE
_cod_process bigint := 30001;
_cod_instance bigint := (SELECT max(cod_instance)
FROM public.instance
WHERE cod_process = _cod_process);
_utc_log timestamp := localtimestamp;
_cod_log_type varchar(100) := 'information';
_txt_log_text varchar(100) := 'start process';
_txt_log varchar(100) := txt_log_text || '_' || cod_process;
BEGIN
INSERT INTO public.log
( cod_process, cod_instance, utc_log, cod_log_type, txt_log)
VALUES (_cod_process, _cod_instance, _utc_log, _cod_log_type, _txt_log);
RETURN 11;
END
$func$ LANGUAGE plpgsql;
Major points
You cannot use SET to assign a variable. That's taken to be the SQL command SET for setting run-time parameters.
But you can assign a variable at declaration time, even use a subquery for that.
UseLANGUAGE plpgsql, not LANGUAGE 'plpgsql'. It's an identifier.
#a_horse_with_no_name already wrote about naming conflicts.
Using a clean format goes a long way when debugging code ...
But you can probably simplify to:
CREATE OR REPLACE FUNCTION public.test(_cod_process bigint = 30001)
RETURNS integer AS
$func$
INSERT INTO public.log
(cod_process, cod_instance , utc_log, cod_log_type , txt_log)
SELECT $1, max(cod_instance), now() , 'information', 'start process_' || $1
FROM public.instance
WHERE cod_process = $1
GROUP BY cod_process
RETURNING 11
$func$ LANGUAGE sql;
Call:
SELECT public.test(); -- for default 30001
SELECT public.test(1234);
And depending on the actual data type of utc_log you probably want now() AT TIME ZONE 'UTC':
Ignoring timezones altogether in Rails and PostgreSQL
You need to run the select using the into clause inside the actual code block, not in the declare block:
begin
select max(cod_instance)
into cod_instance
from public.instance
where public.instance.cod_process=cod_process;
....
end;
It's usually not such a good idea to give variables (or parameters) the same name as columns in the table. There are certain cases where this can confuse the parser. To avoid any potential problems, try to use different names for your variables, e.g. by prefixing them (e.g. l_cod_process instead of cod_process or l_cod_instance instead of cod_instance)
More details on variable assignment can be found in the manual: http://www.postgresql.org/docs/current/static/plpgsql-statements.html