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.
Related
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
#
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
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
i want to dynamically delete some tables from database. I select them first, and then i am trying to use delete statement but it doesn't works.
tmp TEXT;
FOR tmp IN SELECT names from Garbage
LOOP
DROP TABLE tmp;
END LOOP;
but unfortuntly i got errors at drop statement. It always trying to delete table "tmp" instead of deleting the value of tmp(which are some strings like "table1" "table2").
You will need a dynamic query to be executed. To do that, you need to construct your query and call EXECUTE:
DECLARE
tmp TEXT;
...
BEGIN
...
FOR tmp IN SELECT names FROM Garbage
LOOP
EXECUTE 'DROP TABLE ' || quote_ident(tmp);
END LOOP;
...
Notice that I used quote_ident to escape the name properly, it is better and safer. But, if you created a table, let's say named MyTable but didn't quoted (double-quotes) it on creation time, PostgreSQL also store its name as mytable, so quote_ident('MyTable') will generate "MyTable" which doesn't exists, you should take care of it (e.g. always use the lowercase names or always quote your IDs).
My database is separated into schemas based on clients (i.e.: each client has their own schema, with same data structure).
I also happen to have an external action that does not know which schema it should target. It comes from another part of the system that has no concepts of clients and does not know in which client's set it is operating. Before I process it, I have to find out which schema that request needs to target
To find the right schema, I have to find out which holds the record R with a particular unique ID (string)
From my understanding, the following
SET search_path TO schema1,schema2,schema3,...
will only look through the tables in schema1 (or the first schema that matches the table) and will not do a global search.
Is there a way for me to do a global search across all schemas, or am I just going to have to use a for loop and iterate through all of them, one at a time?
You could use inheritance for this. (Be sure to consider the limitations.)
Consider this little demo:
CREATE SCHEMA master; -- no access of others ..
CREATE SEQUENCE master.myseq; -- global sequence for globally unique ids
CREATE table master.tbl (
id int primary key DEFAULT nextval('master.myseq')
, foo text);
CREATE SCHEMA x;
CREATE table x.tbl() INHERITS (master.tbl);
INSERT INTO x.tbl(foo) VALUES ('x');
CREATE SCHEMA y;
CREATE table y.tbl() INHERITS (master.tbl);
INSERT INTO y.tbl(foo) VALUES ('y');
SELECT * FROM x.tbl; -- returns 'x'
SELECT * FROM y.tbl; -- returns 'y'
SELECT * FROM master.tbl; -- returns 'x' and 'y' <-- !!
Now, to actually identify the table a particular row lives in, use the tableoid:
SELECT *, tableoid::regclass AS table_name
FROM master.tbl
WHERE id = 2;
Result:
id | foo | table_name
---+-----+-----------
2 | y | y.tbl
You can derive the source schema from the tableoid, best by querying the system catalogs with the tableoid directly. (The displayed name depends on the setting of search_path.)
SELECT n.nspname
FROM master.tbl t
JOIN pg_class c ON c.oid = t.tableoid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE t.id = 2;
This is also much faster than looping through many separate tables.
You will have to iterate over all namespaces. You can get a lot of this information from the pg_* system catalogs. In theory, you should be able to resolve the client -> schema mapping at request time without talking to the database so that the first SQL call you make is:
SET search_path = client1,global_schema;
While I think Erwin's solution is probably preferable if you can re-structure your tables, an alternative that doesn't require any schema changes is to write a PL/PgSQL function that scans the tables using dynamic SQL based on the system catalog information.
Given:
CREATE SCHEMA a;
CREATE SCHEMA b;
CREATE TABLE a.testtab ( searchval text );
CREATE TABLE b.testtab (LIKE a.testtab);
INSERT INTO a.testtab(searchval) VALUES ('ham');
INSERT INTO b.testtab(searchval) VALUES ('eggs');
The following PL/PgSQL function searches all schemas containing tables named _tabname for values in _colname equal to _value and returns the first matching schema.
CREATE OR REPLACE FUNCTION find_schema_for_value(_tabname text, _colname text, _value text) RETURNS text AS $$
DECLARE
cur_schema text;
foundval integer;
BEGIN
FOR cur_schema IN
SELECT nspname
FROM pg_class c
INNER JOIN pg_namespace n ON (c.relnamespace = n.oid)
WHERE c.relname = _tabname AND c.relkind = 'r'
LOOP
EXECUTE
format('SELECT 1 FROM %I.%I WHERE %I = $1',
cur_schema, _tabname, _colname
) INTO foundval USING _value;
IF foundval = 1 THEN
RETURN cur_schema;
END IF;
END LOOP;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
If there are are no matches then null is returned. If there are multiple matches the result will be one of them, but no guarantee is made about which one. Add an ORDER BY clause to the schema query if you want to return (say) the first in alphabetical order or something. The function is also trivially modified to return setof text and RETURN NEXT cur_schema if you want to return all the matches.
regress=# SELECT find_schema_for_value('testtab','searchval','ham');
find_schema_for_value
-----------------------
a
(1 row)
regress=# SELECT find_schema_for_value('testtab','searchval','eggs');
find_schema_for_value
-----------------------
b
(1 row)
regress=# SELECT find_schema_for_value('testtab','searchval','bones');
find_schema_for_value
-----------------------
(1 row)
By the way, you can re-use the table definitions without inheritance if you want, and you really should. Either use a common composite data type:
CREATE TYPE public.testtab AS ( searchval text );
CREATE TABLE a.testtab OF public.testtab;
CREATE TABLE b.testtab OF public.testtab;
in which case they share the same data type but not any data; or or via LIKE:
CREATE TABLE public.testtab ( searchval text );
CREATE TABLE a.testtab (LIKE public.testtab);
CREATE TABLE b.testtab (LIKE public.testtab);
in which case they're completely unconnected to each other after creation.