Executing a PostgreSQL query with an EXCEPTION results in two different ERROR messages - sql

I have an PostgreSQL query that includes a transaction and an exception if a column is duplicated:
BEGIN;
ALTER TABLE "public"."cars"
ADD COLUMN "top_speed" text;
EXCEPTION WHEN duplicate_column THEN NOTHING;
ROLLBACK;
In this query I am trying to add a column that already exists (playing a little bit with exceptions) and if it does then the query shall just ignore it. At the moment I am not really sure if the exception-code I am using is the right (couldn't find a site where they are described; only found this)
My Problem is if I execute this query I get the error-message:
ERROR: column "top_speed" of relation "cars" already exists
And if I execute it a second time the error-message changes to:
ERROR: current transaction is aborted, commands ignored until end of transaction block

Try an anonymous code block. As Laurenz mentioned, you were mixing PL/pgSQL and SQL commands.
Sample table
CREATE TABLE t (f1 TEXT);
Anonymous code block
DO $$
BEGIN
IF (SELECT count(column_name) FROM information_schema.columns
WHERE table_schema = 'public' AND
table_name = 't' AND
column_name = 'f2') = 0 THEN
ALTER TABLE public.t ADD COLUMN "f2" text;
END IF;
END$$;
After execution you have your new column. If the column already exists, it will do nothing.
SELECT * FROM t;
f1 | f2
----+----
0 Zeilen
In PostgreSQL 9.6+ you can use IF NOT EXISTS to check if a given column already exists in the table before creating it:
ALTER TABLE t ADD COLUMN IF NOT EXISTS f2 TEXT;
Code at db<>fiddle

Related

Postgresql query not able to add column where the column name is similar to an SQL keyword?

I have an list of internal names for different columns I need to add, and one is named end which causes an issue since this is an sql keyword.
DO
$$
BEGIN
IF NOT EXISTS (SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = lower('entity')
AND column_name = lower('end')
) THEN
ALTER TABLE public.entity
ADD COLUMN "end" TEXT NULL;
ELSE
ALTER TABLE public.entity
ALTER COLUMN "end" TYPE TEXT;
END IF;
END
$$;
I seem to have read some posts where the end keyword can be used as a column if it qouted,
but this still fails?
The error message i get is :
MessageText: syntax error at or near "end"
Why?

Redshift Alter table if not exists

I'm trying add a new field in a redshift table. But I want to add only if this field doesn't exists.
I tried wrapping it with IF NOT EXISTS. But I got following error:
Amazon](500310) Invalid operation: syntax error at or near "IF" Position: 5;
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'schema_name' and table_name='table_name' and column_name='new_field') THEN
ALTER TABLE schema_name.table_name
ADD new_field INT;
END IF;
COMMIT;
I'm not sure if I'm correctly using "IF NOT EXISTS" statement inside the BEGIN block.
Can someone please help me?
Thanks in advance!
It could be better to handle it using EXCEPTION
BEGIN
ALTER TABLE
<table_name> ADD COLUMN <column_name> <column_type>;
EXCEPTION
WHEN duplicate_column
THEN RAISE NOTICE 'column <column_name> already exists in <table_name>.';
END;
The Answer by Yauheni Khvainitski is not completely wrong. But you do have to use a SP and the only option Redshit has (at this point is to have "EXCEPTION WHEN OTHER"). An example:
CREATE OR REPLACE PROCEDURE change_column_to_big_int_TABLE_NAME_X(column_name varchar(200)) AS
$$
DECLARE
new_column_name VARCHAR;
BEGIN
SELECT INTO new_column_name (table_name)||'_new';
-- RAISE INFO 'new_table_name = % table_name = %',new_column_name, table_name;
ALTER TABLE TABLE_NAME_X ADD COLUMN "(new_column_name)" bigint;
EXCEPTION WHEN OTHERS
THEN RAISE NOTICE 'column already exists on table';
END;
$$
LANGUAGE plpgsql;
CALL change_column_to_big_int_TABLE_NAME_X('COLUMN_Y');
Some links from AWS on:
trapping errors: https://docs.aws.amazon.com/redshift/latest/dg/stored-procedure-trapping-errors.html
CREATE PROCEDURE: https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_PROCEDURE.html
Also please notice that this is valid at this point in time. Redshift seems to be always evolving.
The issue I think is that AWS Redshift does not support the IF statement, but instead uses CASE statements. The CASE statements are very similar to IF the way they implement them. But I admit, I prefer the IF statements.

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 from table that does not exist

I have a question regarding ORACLE, I wrote a PLSQL CODE that checks if a table exists, if it exists then I select something from this table..pseudocode is like:
if (table exists)
Select from table where....
the problem is that I always get an error if the table does not exist, even if the if condition is never met and the select statement is never executed.
I think it is because my code is checked at compile time: "select from.." and then it prints an error if the table does not exist. How can I solve such an issue?.. here is how my code looks like (I used generic names):
DECLARE
v_table_exists NUMBER;
BEGIN
SELECT NVL(MAX(1), 0)
INTO v_table_exists
FROM ALL_TABLES
WHERE TABLE_NAME = 'TABLE_TEST';
IF v_table_exists = 1 THEN
INSERT INTO MY_TABLE(COLUMN1, COLUMN2, COLUMN3, COLUMN4)
SELECT 1234,
5678,
T.COLUMN_TEST1,
T.COLUMN_TEST2
FROM TABLE_TEST T
WHERE T.FLAG = 1;
END IF;
END;
The issue is exactly in the fact that your procedure con not be compiled as it refers to a non existing object; you may need some dynamic SQL for this; for example:
create or replace procedure checkTable is
vCheckExists number;
vNum number;
begin
-- check if the table exists
select count(1)
into vCheckExists
from user_tables
where table_name = 'NON_EXISTING_TABLE';
--
if vCheckExists = 1 then
-- query the table with dynamic SQL
execute immediate 'select count(1) from NON_EXISTING_TABLE'
into vNum;
else
vNum := -1;
end if;
dbms_output.put_line(vNum);
end;
The procedure compiles even if the table does not exist; if you call it now, you get:
SQL> select count(1) from NON_EXISTING_TABLE;
select count(1) from NON_EXISTING_TABLE
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> exec checkTable;
-1
PL/SQL procedure successfully completed.
Then, if you create the table and call the procedure again:
SQL> create table NON_EXISTING_TABLE(a) as select 1 from dual;
Table created.
SQL> exec checkTable;
1
PL/SQL procedure successfully completed.
The same way I showed a SELECT, you can do an UPDATE or whatever SQL query you need; if you do something different from a SELECT, the INTO clause has to be removed.
For example, say you need to insert into a different table, the above code should be edited this way:
if vCheckExists = 1 then
execute immediate 'insert into target(a, b, c) select a, 1, 100 from NON_EXISTING_TABLE';
end if;
Everything will need to be done in Dynamic SQL (DBMS_SQL) or EXECUTE_IMMEDIATE otherwise your code will never compile (or package will be invalided) if table does not exists.
DBMS_SQL Example
EXECUTE_IMMEDIATE Example
According to this article, in Oracle Database Server static SQL is indeed checked at compile time to ensure referenced objects exist.
So I advise you to use dynamic SQL instead of static SQL, through a varchar for example.

How to create sequence if not exists

I tried to use code from Check if sequence exists in Postgres (plpgsql).
To create sequence if it does not exists. Running this code two times causes an exception:
sequence ... already exists.
How to create sequence only if it does not exist?
If the sequence does not exist, no message should be written and no error should occur so I cannot use the stored procedure in the other answer to this question since it writes message to log file every time if sequence exists.
do $$
begin
SET search_path = '';
IF not EXISTS (SELECT * FROM pg_class
WHERE relkind = 'S'
AND oid::regclass::text = 'firma1.' || quote_ident('myseq'))
THEN
SET search_path = firma1,public;
create sequence myseq;
END IF;
SET search_path = firma1,public;
end$$;
select nextval('myseq')::int as nr;
Postgres 9.5 or later
IF NOT EXISTS was added to CREATE SEQUENCE in Postgres 9.5. That's the simple solution now:
CREATE SEQUENCE IF NOT EXISTS myschema.myseq;
But consider details of the outdated answer anyway ...
And you know about serial or IDENTITY columns, right?
Auto increment table column
Postgres 9.4 or older
Sequences share the namespace with several other table-like objects. The manual:
The sequence name must be distinct from the name of any other
sequence, table, index, view, or foreign table in the same schema.
Bold emphasis mine. So there are three cases:
Name does not exist. -> Create sequence.
Sequence with the same name exists. -> Do nothing? Any output? Any logging?
Other conflicting object with the same name exists. -> Do something? Any output? Any logging?
Specify what to do in either case. A DO statement could look like this:
DO
$do$
DECLARE
_kind "char";
BEGIN
SELECT relkind
FROM pg_class
WHERE oid = 'myschema.myseq'::regclass -- sequence name, optionally schema-qualified
INTO _kind;
IF NOT FOUND THEN -- name is free
CREATE SEQUENCE myschema.myseq;
ELSIF _kind = 'S' THEN -- sequence exists
-- do nothing?
ELSE -- object name exists for different kind
-- do something!
END IF;
END
$do$;
Object types (relkind) in pg_class according to the manual:
r = ordinary table
i = index
S = sequence
v = view
m = materialized view
c = composite type
t = TOAST table
f = foreign table
Related:
How to check if a table exists in a given schema
I went a different route: just catch the exception:
DO
$$
BEGIN
CREATE SEQUENCE myseq;
EXCEPTION WHEN duplicate_table THEN
-- do nothing, it's already there
END
$$ LANGUAGE plpgsql;
One nice benefit to this is that you don't need to worry about what your current schema is.
If you don't need to preserve the potentially existing sequence, you could just drop it and then recreate it:
DROP SEQUENCE IF EXISTS id_seq;
CREATE SEQUENCE id_seq;
Postgres doesn't have CREATE SEQUENCE IF NOT EXISTS and if the table has default value using the sequence if you just drop the sequence, you might get error:
ERROR: cannot drop sequence (sequence_name) because other objects depend on it SQL state: 2BP01
For me, this one can help:
ALTER TABLE <tablename> ALTER COLUMN id DROP DEFAULT;
DROP SEQUENCE IF EXISTS <sequence_name>;
CREATE sequence <sequence_name>;
The information about sequences can be retrieved from information_schema.sequences (reference)
Try something like this (untested):
...
IF not EXISTS (SELECT * FROM information_schema.sequences
WHERE sequence_schema = 'firma1' AND sequence_name = 'myseq') THEN
...
I have a function to clean all tables in my database application at any time. It is build dynamically, but the essence is that it deletes all data from each table and resets the sequence.
This is the code to reset the sequence of one of the tables:
perform relname from pg_statio_all_sequences where relname = 'privileges_id_seq';
if found then
select setval ('privileges_id_seq',1, false) into i_result;
end if;
Hope this helps,
Loek
I am using postgres 8.4, I see that you use 9.2. Could make a difference where the information is stored.