How to refer to a value in a PostgreSQL script? - sql

I'd like to use the max value to feed the creation of a sequence. For instance, I am getting the max id value and I'd like to use this value:
DO $$
DECLARE
min_value Integer;
BEGIN
SELECT MAX(tmp.id)+1 into min_value FROM tmp;
-- raise notice 'Value: %', min_value;
CREATE SEQUENCE id_seq_tmp
INCREMENT 1
START :tmp.min_value --Getting error: syntax error at or near ":"
MINVALUE :tmp.min_value;
END $$;
How do I refer to the max value and pass to the sequence creation? I am using psql (PostgreSQL) 13.3.

The fundamental roadblock is that DDL commands ("utility statements") do not allow parameter substitution at all. You need to build the query string and execute it.
Generic SQL
I suggest format() for the task:
DO
$do$
BEGIN
EXECUTE format('
CREATE SEQUENCE id_seq_tmp
INCREMENT 1
START %1$s
MINVALUE %1$s'
, (SELECT max(tmp.id)+1 FROM tmp));
END
$do$;
The manual:
Another restriction on parameter symbols is that they only work in
SELECT, INSERT, UPDATE, and DELETE commands. In other statement types
(generically called utility statements), you must insert values
textually even if they are just data values.
See:
“ERROR: there is no parameter $1” in “EXECUTE .. USING ..;” statement in plpgsql
Creating user with password from variables in anonymous block
Operating from psql
While working from psql you can use \gexec to execute a the dynamically built DDL statement directly:
SELECT format('CREATE SEQUENCE id_seq_tmp INCREMENT 1 START %1$s MINVALUE %1$s', max(big_id)+1) FROM b2\gexec
See:
How to pass variable to PL/pgSQL code from the command line?
Filter column names from existing table for SQL DDL statement
(Either way, if the table tmp can be empty, you need to do more, as the SELECT query comes up empty.)

Related

How can I control execution of a SQL statement based on the DB2s structure?

We want to run a simple SQL statement (dropping and creating an index), but only if the old index has not already been deleted. After looking up the syntax for IF in DB2, I came up with this:
IF EXISTS (SELECT indname FROM SYSCAT.INDEXES WHERE INDNAME = 'TEST_CREATE_INDEX_OLD')
THEN
DROP INDEX TEST_CREATE_INDEX_OLD;
create index TEST_CREATE_INDEX_NEW on example_table
(
id,
another_field
);
END IF;
When run with either SQuirrel (already setup to run with db2) or via command line, this script results in an error:
An unexpected token "IF EXISTS (SELECT indname FROM SYSCAT.INDEX" was
found following "BEGIN-OF-STATEMENT". Expected tokens may include:
"".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.23.42 SQL Code:
-104, SQL State: 42601
So - what am I doing wrong? Am I missing something, or is there another way to achieve my goal (check for $thing in database, execute appropriate query) that so far has not occured to me?
If IF statement is valid only in a compound-SQL block (i.e. inside a stored-procedure/routine/function/anonymous-block).
It's not valid standalone as your question shows, and that is why Db2 throws the -104 error.
You can look up the explanation of the sqlcode -104 by looking up SQL0104N in the free online Db2 Knowledge Center at this link.
To be able to use compound-SQL in your Squirrel-SQL tool, you need to configure squirrel to use an alternative statement terminator. Google that. In the examples below, I show a statement terminator of # (to delimit the block).
Here are two different ways to do what you want with Db2-Linux/Unix/Windows, each uses an anonymous block. Other approaches are possible.
In this example the drop-index will only run if the index-name exists in the current schema:
begin
declare v_index_exists integer default 0;
select 1 into v_index_exists
from syscat.indexes
where indname = 'TEST_CREATE_INDEX_OLD';
if v_index_exists = 1
then
execute immediate('drop index TEST_CREATE_INDEX_OLD');
execute immediate('create index TEST_CREATE_INDEX_NEW on example_table ( id, another_field)');
end if;
end#
In this example the drop-index will always run, but the block won't abort if the index does'nt exist (i.e. it will continue and not throw any error).
begin
declare v_no_such_index integer default 0;
declare not_exists condition for sqlstate '42704';
declare continue handler for not_exists set v_no_such_index=1;
execute immediate('drop index TEST_CREATE_INDEX_OLD');
execute immediate('create index TEST_CREATE_INDEX_NEW on example_table ( id, another_field)');
end#
You must use another statement delimiter, if you want to use db2 compound statement.
In Squirrel: Session -> Session Properties -> SQL -> Statement Separator = #.
Indexes in Db2 are fully qualified by 2 SYSCAT.INDEXES columns: INDSCHEMA and INDNAME. So, it's advisable to use both these fields in a SELECT statement on SYSCAT.INDEXEX as in example.
You can't use static DDL statements in a compound statement. Use EXECUTE IMMEDIATE statements instead.
Below is an example emulating UPDATE INDEX for the index in a schema equal to CURRENT SCHEMA special registry set in the session.
BEGIN
IF EXISTS (SELECT indname FROM SYSCAT.INDEXES WHERE INDSCHEMA = CURRENT SCHEMA AND INDNAME = 'TEST_CREATE_INDEX_OLD')
THEN
EXECUTE IMMEDIATE 'DROP INDEX TEST_CREATE_INDEX_OLD';
EXECUTE IMMEDIATE'
create index TEST_CREATE_INDEX_NEW on example_table
(
id,
another_field
)
';
END IF;
END
#

"SELECT something INTO variable" in trigger function's code creates a table named variable

I discovered a mysterious table named num in my database which has one column named count. I had no idea how it got there, then I realized it might be caused by a misbehaving trigger.
I have a trigger function:
DECLARE num integer := 0;
BEGIN
IF ... THEN
SELECT COUNT(*) INTO num FROM ...
END IF;
IF num > 1 THEN
DELETE FROM ...
END IF;
RETURN NEW;
END;
As you can see my purpose is to count the rows returned by a query and perform some operation if it is greater than one.
Can this faulty code be responsible for the unwanted table created? If so, how to fix this?
SELECT ... INTO foo in PL/pgSQL stores the result of the SELECT in a PL/pgSQL variable foo. Whereas SELECT ... INTO foo run as an ordinary SQL statement creates a table foo to store the result.
This is what caused the confusion, the table was created when I was testing the SQL statements from the trigger function manually against the DB.

PostgreSQL variable in select and delete statements

The Problem: I have many delete lines in a PostgreSQL script where I am deleting data related to the same item in the database. Example:
delete from <table> where <column>=180;
delete from <anothertable> where <column>=180;
...
delete from <table> where <column>=180;
commit work;
There are about 15 delete statements deleting data that references <column>=180.
I have tried to replace the 180 with a variable so that I only have to change the variable, instead of all the lines in the code (like any good programmer would do). I can't seem to figure out how to do it, and it's not working.
NOTE: I am very much a SQL novice (I rarely use it), so I know there's probably a better way to do this, but please enlighten me on how I can fix this problem.
I have used these answers to try and fix it with no luck: first second third. I've even gone to the official PostgreSQL documentation, with no luck.
This is what I'm trying (these lines are just for testing and not in the actual script):
DO $$
DECLARE
variable INTEGER:
BEGIN
variable := 101;
SELECT * FROM <table> WHERE <column> = variable;
END $$;
I've also tried just delcaring it like this:
DECLARE variable INTEGER := 101;
Whenever I run the script after replacing one of the numbers with a variable this is the error I get:
SQL Error [42601]: ERROR: query has no destination for result data
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function inline_code_block line 6 at SQL statement
Can someone tell me where I'm going wrong? It would be nice to only have to change the number in the variable, instead of in all the lines in the script, and I just can't seem to figure it out.
As #Vao Tsun said, you must define a destination to your SELECT statement. Use PERFORM otherwise:
--Test data
CREATE TEMP TABLE my_table (id, description) AS
VALUES (1, 'test 1'), (2, 'test 2'), (101, 'test 101');
--Example procedure
CREATE OR REPLACE FUNCTION my_procedure(my_arg my_table) RETURNS VOID AS $$
BEGIN
RAISE INFO 'Procedure: %,%', my_arg.id, my_arg.description;
END
$$ LANGUAGE plpgsql;
DO $$
DECLARE
variable INTEGER;
my_record my_table%rowtype;
BEGIN
variable := 101;
--Use your SELECT inside a LOOP to work with result
FOR my_record IN SELECT * FROM my_table WHERE id = variable LOOP
RAISE INFO 'Loop: %,%', my_record.id, my_record.description;
END LOOP;
--Use SELECT to populate a variable.
--In this case you MUST define a destination to your result data
SELECT * INTO STRICT my_record FROM my_table WHERE id = variable;
RAISE INFO 'Select: %,%', my_record.id, my_record.description;
--Use PERFORM instead of SELECT if you want to discard result data
--It's often used to call a procedure
PERFORM my_procedure(t) FROM my_table AS t WHERE id = variable;
END $$;
--DROP FUNCTION my_procedure(my_table);

update increment of serial field from several records in postgres

I want to change the increment value of all my columns that use sequencial IDs. How can I do it?
I tried the following 2 approaches, both failed:
UPDATE information_schema.sequences SET increment=1;
This doesn't work because information_schema.sequences it's a view.
I also tried to use ALTER SEQUENCE, adding a FROM at the end, like:
ALTER SEQUENCE A.sequence_name INCREMENT BY 2 FROM (SELECT * FROM information_schema.sequences) AS A
But I get a syntax error.
How can I do this?
Try using setval from docs
OR
ALTER SEQUENCE... RESTART WITH... as described here
EDITED TWICE
Sample how to change all sequences in owned schema:
do
$$
declare
i record;
begin
for i in (select * from information_schema.sequences) loop
execute $e$ALTER SEQUENCE $e$||i.sequence_name||$e$ INCREMENT BY 2$e$;
end loop;
end;
$$
;
DO

Solution to "cannot perform a DML operation inside a query"?

I am using a Data Analysis tool and the requirement I have was to accept a value from the user, pass that as a parameter and store it in a table. Pretty straighforward so I sat to write this
create or replace
procedure complex(datainput in VARCHAR2)
is
begin
insert into dumtab values (datainput);
end complex;
I executed this in SQL Developer using the following statement
begin
complex('SomeValue');
end;
It worked fine, and the value was inserted into the table. However, the above statements are not supported in the Data Analysis tool, so I resorted to use a function instead. The following is the code of the function, it compiles.
create or replace
function supercomplex(datainput in VARCHAR2)
return varchar2
is
begin
insert into dumtab values (datainput);
return 'done';
end supercomplex;
Once again I tried executing it in SQL Developer, but I got cannot perform a DML operation inside a query upon executing the following code
select supercomplex('somevalue') from dual;
My question is
- I need a statement that can run the mentioned function in SQL Developer or
- A function that can perform what I am looking for which can be executed by the select statement.
- If it is not possible to do what I'm asking, I would like a reason so I can inform my manager as I am very new (like a week old?) to PL/SQL so I am not aware of the rules and syntaxes.
P.S. How I wish this was C++ or even Java :(
EDIT
I need to run the function on SQL Developer because before running it in DMine (which is the tool) in order to test if it is valid or not. Anything invalid in SQL is also invalid in DMine, but not the other way around.
Thanks for the help, I understood the situation and as to why it is illegal/not recommended
You could use the directive pragma autonomous_transaction. This will run the function into an independant transaction that will be able to perform DML without raising the ORA-14551.
Be aware that since the autonomous transaction is independent, the results of the DML will be commited outside of the scope of the parent transaction. In most cases that would not be an acceptable workaround.
SQL> CREATE OR REPLACE FUNCTION supercomplex(datainput IN VARCHAR2)
2 RETURN VARCHAR2 IS
3 PRAGMA AUTONOMOUS_TRANSACTION;
4 BEGIN
5 INSERT INTO dumtab VALUES (datainput);
6 COMMIT;
7 RETURN 'done';
8 END supercomplex;
9 /
Function created
SQL> SELECT supercomplex('somevalue') FROM dual;
SUPERCOMPLEX('SOMEVALUE')
--------------------------------------------------------------------------------
done
SQL> select * from dumtab;
A
--------------------------------------------------------------------------------
somevalue
Tom Kyte has a nice explanation about why the error is raised in the first place. It is not safe because it may depend upon the order in which the rows are processed. Furthermore, Oracle doesn't guarantee that the function will be executed at least once and at most once per row.
Just declare a variable to accept the return value, for example:
declare
retvar varchar2(4);
begin
retvar := supercomplex('somevalue');
end;
The select doesn't work because the function is performing an insert, if all it did was return a value then it would work.
Just execute the function in a dummy if ... end if; statement to ignore the return value:
exec if supercomplex('somevalue') then null; end if;
Or execute it as a parameter for put_line procedure to output the return value:
exec dbms_ouput ('result of supercomplex='||supercomplex('somevalue'));
result of supercomplex=done