create or replace procedure procedure_1()
language plpgsql
as $$
declare
precedure procedure_2()
begin
select 1;
end
begin
select 1;
end; $$
Is there any way to declare a procedure_2() inside procedure_1()?
Yes - if by "declare" you mean "create".
CREATE OR REPLACE PROCEDURE procedure_1(INOUT result int)
LANGUAGE plpgsql AS
$proc1$
BEGIN
CREATE OR REPLACE PROCEDURE procedure_2(INOUT result int)
LANGUAGE plpgsql AS
$proc2$
BEGIN
result := 2;
END
$proc2$;
result := 1;
END
$proc1$;
db<>fiddle here
You just have to get the quoting right. See:
What are '$$' used for in PL/pgSQL
Insert text with single quotes in PostgreSQL
Functions and procedures are not "declared", but "created" in Postgres. That creates an object in the database which is then visible and usable by all with appropriate permissions. (Not just a temporary object local to the procedure or transaction.)
You can, however, create a "temporary" function or procedure, with this "hack" - if that's what you had in mind:
How to create a temporary function in PostgreSQL?
I am trying to create a function to create table backup dynamically.
But I am getting error like :
ERROR: syntax error at or near "'
Here's one of my approach, which I am trying:
CREATE OR REPLACE FUNCTION public.test () RETURNS varchar AS
$BODY$ DECLARE backup_string varchar(50);
BEGIN
backup_string = (SELECT '_'||LPAD(DATE_PART('DAY',CURRENT_DATE)::VARCHAR,2,'0')||DATE_PART('MONTH',CURRENT_DATE)::VARCHAR||DATE_PART('YEAR',CURRENT_DATE)::VARCHAR||'_1');
EXECUTE 'SELECT * INTO table_name'|| backup_string ||' FROM table_name';
RETURN 'Y';
EXCEPTION WHEN others THEN RETURN 'N';
END
; $BODY$
LANGUAGE 'plpgsql'
GO
SELECT * FROM test()
I am not getting, why that execute statement giving me error like that.
I suggest so simplify your code and make use of the format() function to generate the dynamic SQL. That way you can avoid the clutter that concatenation generates and you can concentrate on the actual SQL code. In addition to that it also properly deals with identifiers that might need quoting.
When dealing with dynamic SQL it's always a good idea to store the generated SQL statement in a variable, so that it can be printed for debugging purposes if you get an error. Looking at the generated SQL usually tells you where the generation code went wrong.
CREATE OR REPLACE FUNCTION test()
RETURNS varchar
AS
$BODY$
DECLARE
l_source_table text;
l_backup_table text;
l_sql text;
BEGIN
l_source_table := 'table_name';
l_backup_table := l_source_table||'_'||to_char(current_date, 'ddmmyyyy')||'_1';
l_sql := format('create table %I as select * from %I', l_backup_table, l_source_table);
-- for debugging purposes:
raise notice 'Running: %', l_sql
EXECUTE l_sql;
RETURN 'Y';
EXCEPTION
WHEN others THEN RETURN 'N';
END;
$BODY$
LANGUAGE plpgsql;
Note that I also used variables for the source and backup table to be able to use that as a place holder for the format() function.
Online example
I've been using Postgres for a while, but I'm totally new to PL/pgSQL.
I'm struggling to get a basic for loop to work.
This works fine:
-- Without SELECT
DO $$
BEGIN
FOR counter IN 1..6 BY 2 LOOP
RAISE NOTICE 'Counter: %', counter;
END LOOP;
END; $$;
But what I really want is to iterate through the result of a SELECT query.
I keep running into this error:
Error in query: ERROR: loop variable of loop over rows must be a record or row variable or list of scalar variables
Sounds pretty obscure to me and googling did not help.
There's a table from my own data I want to use (I was hoping to use a SELECT * FROM mytable WHERE ‹whatever›), but I realize I can't even get the for loop to work with simpler data.
Take this:
-- with a SELECT
DO $$
BEGIN
RAISE NOTICE 'Get ready to be amazed…';
FOR target IN SELECT * FROM generate_series(1,2) LOOP
RAISE NOTICE 'hello'
END LOOP;
END; $$
This generates the error above too. I'd like to get a simple thing printed to get the hang of the loop syntax, something like:
hello 1
hello 2
What am I doing wrong?
The iterator must be declared
DO $$
DECLARE
target record;
BEGIN
RAISE NOTICE 'Get ready to be amazed…';
FOR target IN SELECT * FROM generate_series(1,2) LOOP
RAISE NOTICE 'hello';
END LOOP;
END; $$;
NOTICE: Get ready to be amazed…
NOTICE: hello
NOTICE: hello
I would like to write "procedures" in DB2 9.7 without defining the "CREATE PROCEDURE" -statement. Apparently this is something called "compiled" sql statement. However, I am having problems in getting valid syntax. E.g. the syntax below does not seem to work:
BEGIN
DECLARE V_SQL VARCHAR(1024);
SET V_SQL = 'BEGIN
IF EXISTS(SELECT NAME FROM SYSIBM.SYSTRIGGERS WHERE NAME = ''TRIGGER_EMPLOYEE_FOR_DELETES'') THEN
DROP TRIGGER TRIGGER_EMPLOYEE_FOR_DELETES;
END IF;
END;';
PREPARE S1 FROM V_SQL;
EXECUTE S1;
END
I have tried adding/removing ";" and statement symbol "!" but still cannot get it to work.
You cannot have a DROP TRIGGER statement within a compound SQL statement. See the DB2 documentation for compound SQL.
If you are able to move the IF statement outside of V_SQL, you could do something like this:
BEGIN
DECLARE V_SQL VARCHAR(1024);
IF EXISTS(SELECT NAME FROM SYSIBM.SYSTRIGGERS
WHERE NAME = 'TRIGGER_EMPLOYEE_FOR_DELETES'
) THEN
SET V_SQL = 'DROP TRIGGER TRIGGER_EMPLOYEE_FOR_DELETES;';
PREPARE S1 FROM V_SQL;
EXECUTE S1;
END IF;
END
Of course, this wouldn't work if you need to set your condition dynamically.
While executing below shown trigger code using ANT I am getting the error
org.postgresql.util.PSQLException: ERROR: unterminated quoted string at or near "' DECLARE timeout integer"
Position: 57
I am able to sucessfully execute the below code through PGADmin (Provided by postgres) and command line utility "psql" and the trigger function is added but while executing through ANT it fails everytime
BEGIN TRANSACTION;
CREATE OR REPLACE FUNCTION sweeper() RETURNS trigger as '
DECLARE
timeout integer;
BEGIN
timeout = 30 * 24 * 60 * 60 ;
DELETE FROM diagnosticdata WHERE current_timestamp - teststarttime > (timeout * ''1 sec''::interval);
return NEW;
END;
' LANGUAGE 'plpgsql';
-- Trigger: sweep on diagnosticdata
CREATE TRIGGER sweep
AFTER INSERT
ON diagnosticdata
FOR EACH ROW
EXECUTE PROCEDURE sweeper();
END;
I encountered this error in liquibase and this page was one of the first search results so I guess I share my solution at this page:
You can put your whole sql in a separate file and include this in the changeset.
Its important to set the splitStatements option to false.
The whole changeset would then look like
<changeSet author="fgrosse" id="530b61fec3ac9">
<sqlFile path="your_sql_file_here.sql" splitStatements="false"/>
</changeSet>
I always like to have those big SQL parts (like function updates and such) in separate files.
This way you get proper syntax highlighting when opening the sql file and dont have to intermix XML and SQL in one file.
Edit: as mentioned in the comments its worth noting that the sql change supports the splitStatements option as well (thx to AndreyT for pointing that out).
I had the same problem with the JDBC driver used by Liquibase.
It seems that the driver explodes each line ended by a semicolon and runs it as a separate SQL command. That is why the code below will be executed by the JDBC driver in the following sequence:
CREATE OR REPLACE FUNCTION test(text) RETURNS VOID AS ' DECLARE tmp text
BEGIN tmp := "test"
END;
' LANGUAGE plpgsql
Of course, this is invalid SQL and causes the following error:
unterminated dollar-quoted string at or near ' DECLARE tmp text
To correct this, you need to use backslashes after each line ended with semicolon:
CREATE OR REPLACE FUNCTION test(text)
RETURNS void AS ' DECLARE tmp text; \
BEGIN
tmp := "test"; \
END;' LANGUAGE plpgsql;
Alternatively, you can place the whole definition in one line.
I am using HeidiSQL client and this was solved by placing DELIMITER // before CREATE OR REPLACE statement. There is a also a 'Send batch in one go' option in HeidiSQL that essentially achieves the same thing.
This error arises as an interaction between the particular client used to connect to the server and the form of the function. To illustrate:
The following code will run without casualty in Netbeans 7, Squirrel, DbSchema, PgAdmin3
CREATE OR REPLACE FUNCTION author.revision_number()
RETURNS trigger AS
$BODY$
begin
new.rev := new.rev + 1;
new.revised := current_timestamp;
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Please note that the 'begin' statement comes immediately after the '$' quoted string.
The next code will halt all the above clients except PgAdmin3.
CREATE OR REPLACE FUNCTION author.word_count()
RETURNS trigger AS
$BODY$
declare
wordcount integer := 0; -- counter for words
indexer integer := 1; -- position in the whole string
charac char(1); -- the first character of the word
prevcharac char(1);
begin
while indexer <= length(new.blab) loop
charac := substring(new.blab,indexer,1); -- first character of string
if indexer = 1 then
prevcharac := ' '; -- absolute start of counting
else
prevcharac := substring(new.blab, indexer - 1, 1); -- indexer has increased
end if;
if prevcharac = ' ' and charac != ' ' then
wordcount := wordcount + 1;
end if;
indexer := indexer + 1;
end loop;
new.words := wordcount;
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
The crucial difference in the second example is the 'declare' section. The ploy of using back-slashes raises an error with PgAdmin3.
In summary I suggest trying different tools. Some tools even though they are supposed to be writing text files put invisible stuff into the text. Notoriously this occurs with the Unicode BOM which will halt any php file that tries to implement sessions or namespaces.
Whilst this is no solution I hope it helps.
I had the same problem with zeos and c++ builder.
The solution in my case:
Change the property delimiter (usually ";") to another in the component (class) I used.
dm->ZSQLProcessor1->DelimiterType=sdGo;
Perhaps Ant have something similar.
I know this question was asked a long time ago but I had kind of the same issue with a Postgresql script (run from Jenkins) using Ant's SQL Task.
I tried to run this SQL (saved in a file named audit.sql):
DROP SCHEMA IF EXISTS audit CASCADE
;
CREATE SCHEMA IF NOT EXISTS audit AUTHORIZATION faktum
;
CREATE FUNCTION audit.extract_interval_trigger ()
RETURNS trigger AS $extractintervaltrigger$
BEGIN
NEW."last_change_ts" := current_timestamp;
NEW."last_change_by" := current_user;
RETURN NEW;
END;
$extractintervaltrigger$ LANGUAGE plpgsql
;
but got the error "unterminated dollar-quoted string". No problem running it from pgAdmin.
I found out that it is not the driver that split the script at every ";" but rather Ant.
At http://grokbase.com/t/postgresql/pgsql-jdbc/06cjx3s3y0/ant-sql-tag-for-dollar-quoting I found the answer:
Ant eats double-$$ as part of its variable processing. You have to use
$BODY$ (or similar) in the stored procs, and put the delimiter on its
own line (with delimitertype="row"). Ant will cooperate then.
My Ant SQL script looks like this and it works:
<sql
driver="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/jenkins"
userid="user" password="*****"
keepformat="true"
autocommit="true"
delimitertype="row"
encoding="utf-8"
src="audit.sql"
/>
This example worked for me with PostgreSQL 14.1 and HeidiSQL 9.4.0.5125
DROP TABLE IF EXISTS emp;
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
DROP TABLE IF EXISTS EMP_AUDIT;
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
DELIMITER //
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $$
BEGIN
--
-- Create a row in emp_audit to reflect the operation performed on emp,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS emp_audit ON emp;
CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
I was receiving the same error because I had my semicolon in a new line like this:
WHERE colA is NULL
;
Make sure they are in a single line as
WHERE colA is NULL;