I can't run this block in PostgreSQL 8.2.
DECLARE
curtime char;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
RETURN curtime;
END;
When I try it shows the error:
ERROR: syntax error at or near "char"
SQL state: 42601
It sounds like you're trying to run a PL/PgSQL code block stand-alone, without wrapping it up in a function using CREATE OR REPLACE FUNCTION. That won't work, you need to include it in a function or (from PostgreSQL 9.0) a DO block. PL/PgSQL and plain SQL are different languages so you can't just run PL/PgSQL code directly.
It'd help if you explained why you're trying to write the code you pasted. I suspect you're trying to solve a problem that's better handled with a trigger function like an audit trigger.
Some important notes:
You need to update PostgreSQL: PostgreSQL 8.2 is dangerously out of date and unsupported. security and bug fixes are no longer being released. Upgrade urgently to a supported version, but make sure to read the release notes for each major ".0" version like "8.3.0", "8.4.0", etc for migration and compatibility advice.
Avoid 'now': Also, instead of using 'now' you should usually use the current date/time functions, particularly current_timestamp.
current_timestamp is stable: The hoop-jumping you are doing is probably unnecessary because the value of current_timestamp (and 'now'::timestamp) doesn't change for the duration of a transaction. Eg:
regress=# BEGIN;
regress=# SELECT current_timestamp;
2012-08-14 14:52:43.382596+08
regress=# SELECT pg_sleep(5);
regress=# SELECT current_timestamp;
2012-08-14 14:52:43.382596+08
Details
Your intention appears to be something like the following (incorrect, do not use) code:
CREATE OR REPLACE FUNCTION some_function(logtxt text) RETURNS timestamptz AS $$
DECLARE
curtime char;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
RETURN curtime;
END;
$$ LANGUAGE 'plpgsql';
but you've misused the char datatype, which requires a length parameter. It defaults to 1 if not supplied so you'll get:
regress=# SELECT some_function();
ERROR: value too long for type character(1)
CONTEXT: PL/pgSQL function "some_function" line 5 at assignment
NEVER use the char datatype in SQL; use varchar or text. For cross-database portability varchar(n) where n is a maximum length is required; if portability isn't needed use text.
If you change char to text in the above, your code might run, but it still doesn't make any sense. I strongly suspect that you really want to write:
CREATE OR REPLACE FUNCTION some_function(logtxt text) RETURNS timestamptz AS $$
BEGIN
INSERT INTO logtable VALUES (logtxt, current_timestamp);
RETURN current_timestamp;
END;
$$ LANGUAGE 'plpgsql';
... but you didn't know about the current date/time functions.
Even that's too much, really. I think you're trying to solve a problem that's a better fit for a trigger.
Related
I have Postgres function that needs to iterate on an ARRAY of tables_name and should save the value that will be returned from the query each time to array.
maybe this is not correct way so if there is better ways to do it I'll be glad to know :)
I've try with format function to generate different queries each time.
CREATE OR REPLACE FUNCTION array_iter(tables_name text[],idd integer)
RETURNS void
LANGUAGE 'plpgsql'
AS $BODY$
declare
current_table text;
current_height integer :=0;
quer text;
heights integer[];
begin
FOREACH current_table IN ARRAY $1
LOOP
quer:=format('SELECT height FROM %s WHERE %s.idd=$2', current_table);
current_height:=EXECUTE quer;
SELECT array_append(heights, current_height);
END LOOP;
RAISE NOTICE '%', heights;
end;
$BODY$;
First off you desperately need to update your Postgres as version 9.1 is no longer supported and has not been for 5 years (Oct 2016). I would suggest going to v13 as it is the latest, but an absolute minimum to 10.12. That will still has slightly over a year (Nov 2022) before it looses support. So with that in mind.
The statement quer:=format('SELECT height FROM %s WHERE %s.idd=$2', current_table); is invalid, it contains 2 format specifiers but only 1 argument. You could use the single argument by including the argument number on each specifier. So quer:=format('SELECT height FROM %1s WHERE %1s.idd=$2', current_table);. But that is not necessary as the 2nd is a table alias which need not be the table name and since you only have 1 table is not needed at all. I would however move the parameter ($2) out of the select and use a format specifiers/argument for it.
The statement current_height:=EXECUTE quer; is likewise invalid, you cannot make the Right Val of assignment a select. For this you use the INTO option which follows the statement. execute query into ....
While SELECT array_append(heights, current_height); is a valid statement a simple assignment heights = heights || current_height; seems easier (at least imho).
Finally there a a couple omissions. Prior to running a dynamic SQL statement it is good practice to 'print' or log the statement before executing. What happens when the statement has an error. And why build a function to do all this work just to throw away the results, so instead of void return integer array (integer[]).
So we arrive at:
create or replace function array_iter(tables_name text[],idd integer)
returns integer[]
language plpgsql
as $$
declare
current_table text;
current_height integer :=0;
quer text;
heights integer[];
begin
foreach current_table in array tables_name
loop
quer:=format('select height from %I where id=%s', current_table,idd);
raise notice 'Running Query:: %',quer;
execute quer into current_height;
heights = heights || current_height;
end loop;
raise notice '%', heights;
return heights;
exception
when others then
raise notice 'Query failed:: SqlState:%, ErrorMessage:%', sqlstate,sqlerrm;
raise;
end;
$$;
This does run on version as old as 9.5 (see fiddle) although I cannot say that it runs on the even older 9.1.
This covers most use cases How do you use variables in a simple PostgreSQL script? but not the select clause.
This code produces an error column "ct" does not exist"
DO
$$
declare CT timestamp := '2020-09-04 23:59:59';
select CT,5 from job;
$$;
I can see why it would interpret CT as a column name. What's the Postgres syntax required to refer to a variable in the context of the select clause?
I would expect that query to return
'2020-09-04 23:59:59',5
for each row in the job table.
Addendum to the accepted answer
My use case doesn't return rows. Instead, the result of the select is consumed by an insert statement. I'm transforming rows from staging tables into other tables and adding value like the import date and the identity owning the inserts. It's these values that are provided by the variables - they are used in several such transforms and the point of the variable is to let me set each value once up the top of the script.
Because the rows are consumed like this, it turns out that I don't need a function wrapping this code. It's a bit inconvenient to test since I can't run the select and look at the outcome without copying it and pasting in literals, but at least it's possible to use variables. My working script looks like this:
do
$$
declare ct timestamp := '2020-09-04 23:59:59';
declare cb int := 2;
declare iso8601 varchar(50) := 'YYYY-MM-DD HH24:MI:SS';
declare USAdate varchar(50) := 'MM-DD-YYYY HH24:MI:SS';
begin
delete from dozer_wheel_loader_equipment_movement where created = ct;
INSERT INTO dozer_wheel_loader_equipment_movement
(site, primary_category_id, machine, machine_class, x, y, z, timestamp_local, created, created_by)
select site ,mc.id ,machine , machineclass ,x,y,z,to_timestamp(timestamplocal, iso8601), ct, cb
from stage_dozer_csv d join machine_category mc on d.primarycategory = mc.short_code;
...
end
$$
There is a lot of worthwhile related reading at How to declare a variable in a PostgreSQL query
There are few things about variables in PostgreSQL.
Variable can not be used in Plain SQL in Postgres. So you have to use any pl language i.e. plpgsql to use this. You have tried the same in your example.
In your DO block you have missed the Begin and End, So you have to write it like below
DO
$$
begin
declare CT timestamp := '2020-09-04 23:59:59';
select CT,5 from job;
end
$$;
But when you read the official documentation of DO Statement, it says DO will allow to run the anonymous code but it returns void, that's why above code will throw following error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function inline_code_block line 4 at SQL statement
So there is only one way - wrap this code block in a Function like below:
create or replace function func() returns table(col1 timestamp, col2 int )
AS
$$
declare ct timestamp := '2020-09-04 23:59:59';
begin
return query
select CT,5 from job;
end;
$$
language plpgsql
and you can call it like below:
select * from func()
DEMO
Conclusion
You can not use variable in normal SQL statement in Postgres.
You have to use any Procedural Language i.e. plpgsql to use variable.
DO Block doesn't return any value so you can not use select statement like above in DO block. It is good for non-returning queries i.e. insert, update, delete or grant etc.
Only way to return a value from procedural language code block is - you have to wrap it in a suitable PostgreSQL Function.
This might be a stupid question but pardon me, I'm trying to convert one of my MariaDB database into a PostgreSQL database. Here I'm getting an error while executing this function.
I cannot find what's wrong here,
create function tg_prodcut_insert()
returns trigger as '
BEGIN
SET NEW.id = CONCAT(1, LPAD(INSERT INTO product_seq VALUES (NULL) returning id, 6, 0));
END;
' LANGUAGE 'plpgsql';
Error is pointing to the 1 in CONCAT method, The type of id I'm trying to SET is char(7)
EDIT
I also tried this, this won't work either,
create function tg_orders_insert()
returns trigger as '
BEGIN
INSERT INTO order_seq VALUES (NULL);
SET NEW.id = CONCAT('1', LPAD(LAST_INSERT_ID(), 6, 0));
END;
' LANGUAGE 'plpgsql';
Thanks in advance.
It seems you are trying to simulate some kind of sequence with that code by inserting into a table and then getting the auto_increment value from that.
This can be done much more efficiently using a sequence in Postgres.
The error you get also isn't caused by the concat() function but because you are using the wrong syntax.
Value assignment is done using := in PL/pgSQL.
And there is also no last_insert_id() function in Postgres. To get the next value from a sequence use nextval(), to get the most recently generated value, you can use lastval() but that's not necessary here.
create sequence product_id_seq;
create function tg_product_insert()
returns trigger as
$$
BEGIN
NEW.id := concat('ORD', to_char(nextval('product_id_seq'), 'FM00000000'));
return new;
END;
$$
LANGUAGE plpgsql;
you will need to create a before trigger for that to work:
create trigger product_seq_trigger
before insert on product
for each row
execute procedure tg_product_insert();
Online example
But it would be a lot more efficient to switch to a proper identity column instead and get rid of the trigger.
We started to use Postgres much more recently, having moved from SQL Server. I've noticed that Postgres parser/compiler allows creation of functions that (it seems to me) can be rejected at creation time.
One example of what I'm talking about is select statements in plpgsql blocks:
create or replace function test() returns void as $$
begin
select * from pg_database;
end;
$$ language plpgsql;
This function fails at runtime with "query has no destination for result data". Why would this error not be caught at function creation time? Is there any case when using select without 'return' in plpgsql block is allowed?
The other type of errors that are not always caught at compile time is type mismatch errors between a declared return type and actual type of the value. These are caught in simple cases, but start to make it to runtime in more complicated functions. I suspect there's some limitations in Postgres type inference/analysis, is there any additional information on this available?
tldr: Is there any way to make Postgres parser/compiler fail more on function creation?
Is there any way to make Postgres parser/compiler fail more on function creation?
That's the purpose of the plpgsql_check extension. It won't prevent the function to be created, though.
test=# create extension plpgsql_check;
CREATE EXTENSION
test=# create or replace function test() returns void as $$
begin
select * from pg_database;
end;
$$ language plpgsql;
test=# select * from plpgsql_check_function('test');
plpgsql_check_function
----------------------------------------------------------------------
error:42601:3:SQL statement:query has no destination for result data
I am trying to convert an oracle stored procedure to Postgres function/procedure.
I did some research and read many forums to prepare the syntax for the stored procedure in postgres.
But getting an error for declaring an integer variable.
My code is like below:
The purpose of my procedure is to load one-month records into another month (EX: load Jan 2020 data into March 2020)
Postgres Procedure:
CREATE OR REPLACE FUNCTION Corporate.copy_forecast(code OUT integer, message OUT VARCHAR)
LANGUAGE plpgsql
AS $$
DECLARE
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid) into v_previous_month, max(cycleid)+1 into v_current_month from Corporate.forecast;
INSERT INTO Corporate.forecast
(SELECT v_current_month,lob,delivery,forecast_val
FROM Corporate.forecast
WHERE month= v_previous_month);
code:=1;
message:='Sucussfully loaded previous month forecast to current month';
exception
when others then
code:=0;
message:='Failed';
END;
$$
Please help me to fix the above procedure.
There are several problems with your code:
The first one is simple: you say LANGUAGE sql, but you write PL/pgSQL code.
That explains the error message you get. Use LANGUAGE plpgsql if you want to write PL/pgSQL.
You are using variables that are the same as column names, which leads to ambiguity. For example, you declare
current_month integer;
previous_month integer;
but you have a WHERE clause
WHERE current_month = previous_month
where obviously one of them should refer to a variable and the other to a table column. That will not work and cause errors.
The best and simplest solution is to always use variable names that are different from column names. A simple method is to start all variable names with v_.
A second option is to always qualify columns with the table name and variables with the function name.
You do not have a problem declaring an integer variable it is much larger you have structural problems. Below I indicate those issues and will then show corrections. The indicator "--<< ..." discusses the line(s) just above it.
CREATE OR REPLACE FUNCTION copy_forecast(code OUT integer, message OUT VARCHAR)
--<< Improper format Should be (out code integer, out message varchar)
--<< and while not invalid IMHO bad design a function/procedure should just do its job correctly or raise an exception
returns ???
--<< This is missing. A Postgres Function MUST declare what it returns, if nothing then RETURNS VOID. But I guess this was an Oracle Procedure.
LANGUAGE plpgsql
AS $$
DECLARE
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid) into v_previous_month, max(cycleid)+1 into v_current_month from forecast;
--<< Invalid format Should Be select var1,var2 into local1, local2 ...
INSERT INTO forecast
--<< Very dangerous. If table ever changes this will fail.
(SELECT v_current_month,lob,delivery,forecast_val
FROM forecast
WHERE month= v_previous_month);
code:=1;
message:='Successfully loaded previous month forecast to current month';
exception
when others then
code:=0;
message:='Failed'
--<< Very dangerous (all 3 lines). When an error occurs you will never know what it is. See insert above
;
END;
$$;
The following corrects the errors indicated above. Again the indicator (--*) discussed the line(s) above it.
create or replace function copy_forecast()
returns boolean
language plpgsql
as $$
declare
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid)
, max(cycleid)+1
into v_previous_month
, v_current_month
from forecast;
insert into forecast (cycleid,lob,delivery,forcast_val )
select v_current_month,lob,delivery,forecast_val
from forecast
where month= v_previous_month);
--* insert will fail I cannot resolve. MONTH not inserted, but must exist on table.
--* It seems you are using cycleid and month as synonyms
return True;
--* you can leave the out parameters if desirded. Set them before the return
exception
when others then
-- Log the error and debug information here
return false;
--* you can leave out parameters if desired Set them before the return.
end;
This should correct the structure issues with your function. However, the logical issue with month/cycleid remains.
However, this does not actually get you to your goal. As stated "the purpose of my procedure is to load one-month records into another month (EX: load Jan 2020 data into March 2020)". This function CANNOT do that. It can only copy the latest month/cycleid to the next month/cycleid; so Jan->Feb, Feb->Mar, ... Nov->Dec. But Dec->Jan would fail unless month/cycleid can be 13, and subsequent months would continue increasing month/cycle. Accomplishing your goal required INPUT parameters for the Source(from) and Target(to). And as #LaurenzAlbe said "no moving targets". That would be another question.