Get current table OID (regclass) when inserting in a table - postgresql

I have a table with a column storing table OID of table where records come from. If this field must be filled (NOT NULL) but can have a default value if not provided. I would like to store the current table OID when inserting.
CREATE TABLE t AS(
Source regclass NOT NULL DEFAULT current_table_name()::regclass
);
Is there any function (current_table_name) in postgreSQL table to perform this task?

There is no such function, but you can achieve the goal in three steps:
Create the table without DEFAULT.
Find the OID of the new table.
ALTER the table and set the DEFAULT.
It is better to use the table OID than to cast the table name to regclass, because with the latter INSERTs will suddenly start failing after the table is renamed.
Here is a DO block that would achieve that:
DO $$DECLARE
reloid oid;
BEGIN
CREATE TABLE t (source regclass NOT NULL);
SELECT t.oid INTO reloid
FROM pg_class t
JOIN pg_namespace n ON t.relnamespace = n.oid
WHERE t.relname = 't' AND n.nspname = current_schema;
EXECUTE 'ALTER TABLE t ALTER source SET DEFAULT ' || reloid;
END;$$;

Related

PostgreSQL: Alter all tables returned by a select query

I have this query running on a postgres 11.2 db:
SELECT relname
FROM pg_stat_user_tables
WHERE n_mod_since_analyze=0 AND relname LIKE '%'
AND relname NOT IN (SELECT relname FROM pg_class WHERE array_length(reloptions,1)>0);
That returns a list of tables I need to disable autovacuum for. I'm trying to come up with a way to run ALTER TABLE pg_stat_user_tables.relname SET (autovacuum_enabled = false); on all tables returned by my query statement. Googling keeps bringing me back to altering table columns. Is it possible to just take the table names returned from this query and pass it to ALTER TABLE?
Try with psql CLI and \gexec internal command:
SELECT format('ALTER TABLE %s SET (autovacuum_enabled = false);',lt.relname)
FROM
(
SELECT relname
FROM pg_stat_user_tables
WHERE n_mod_since_analyze=0
AND relname LIKE '%'
AND relname NOT IN (SELECT relname FROM pg_class WHERE array_length(reloptions,1)>0)
) lt;
\gexec
Execution output:
ALTER TABLE p SET (autovacuum_enabled = false);
ALTER TABLE
ALTER TABLE t1 SET (autovacuum_enabled = false);
ALTER TABLE
ALTER TABLE t2 SET (autovacuum_enabled = false);
ALTER TABLE

OID type in regular postgres function

I have a PL/pgSQL function, which fetch data from pg_catalog.pg_constraint table to generate restore FK statements like: ALTER TABLE table_name ADD CONSTRAINT constraint_name FOREIGN KEY (column) REFERENCES table_name (column);.
Firstly I find oid of the table, then I put that table oid into variable table_oid OID; and make some stuff with it.
My question is.. Is it OK to use OID type variable inside function or I should better use BIGINT or something?
Example:
CREATE OR REPLACE FUNCTION func()
RETURNS VOID AS
$$
DECLARE
row RECORD;
table_oid OID; --> Is it OK or I should use BIGINT?
BEGIN
...
SELECT pg_class.oid
FROM pg_catalog.pg_class
INNER JOIN pg_catalog.pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE pg_class.relname = row.table_name
AND pg_namespace.nspname = row.schema_name INTO table_oid;
...
SELECT nspname, relname, 'ALTER TABLE '||nspname||'.'||relname||' ADD CONSTRAINT '||conname||' '||pg_catalog.pg_get_constraintdef(pg_constraint.oid)||';'
FROM pg_catalog.pg_constraint
INNER JOIN pg_class ON conrelid = pg_class.oid
INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE confrelid = table_oid --> I use this variable here
OR conrelid = table_oid --> ...and here
AND contype = 'f';
END;
$$ LANGUAGE plpgsql;
There is nothing wrong with using oid like any other data type.
The only thing that you should keep in mind is that the OIDs that PostgreSQL uses internally to identify objects like tables are subject to change during an upgrade or restore from a dump. So you should not use oids to permanently store references to system objects.
The same applies to the ref* types, which are OIDs under the hood.

Make duplicate row in Postgresql

I am writing migration script to migrate database. I have to duplicate the row by incrementing primary key considering that different database can have n number of different columns in the table. I can't write each and every column in query. If i simply just copy the row then, I am getting duplicate key error.
Query: INSERT INTO table_name SELECT * FROM table_name WHERE id=255;
ERROR: duplicate key value violates unique constraint "table_name_pkey"
DETAIL: Key (id)=(255) already exist
Here, It's good that I don't have to mention all column names. I can select all columns by giving *. But, same time I am also getting duplicate key error.
What's the solution of this problem? Any help would be appreciated. Thanks in advance.
If you are willing to type all column names, you may write
INSERT INTO table_name (
pri_key
,col2
,col3
)
SELECT (
SELECT MAX(pri_key) + 1
FROM table_name
)
,col2
,col3
FROM table_name
WHERE id = 255;
Other option (without typing all columns , but you know the primary key ) is to CREATE a temp table, update it and re-insert within a transaction.
BEGIN;
CREATE TEMP TABLE temp_tab ON COMMIT DROP AS SELECT * FROM table_name WHERE id=255;
UPDATE temp_tab SET pri_key_col = ( select MAX(pri_key_col) + 1 FROM table_name );
INSERT INTO table_name select * FROM temp_tab;
COMMIT;
This is just a DO block but you could create a function that takes things like the table name etc as parameters.
Setup:
CREATE TABLE public.t1 (a TEXT, b TEXT, c TEXT, id SERIAL PRIMARY KEY, e TEXT, f TEXT);
INSERT INTO public.t1 (e) VALUES ('x'), ('y'), ('z');
Code to duplicate values without the primary key column:
DO $$
DECLARE
_table_schema TEXT := 'public';
_table_name TEXT := 't1';
_pk_column_name TEXT := 'id';
_columns TEXT;
BEGIN
SELECT STRING_AGG(column_name, ',')
INTO _columns
FROM information_schema.columns
WHERE table_name = _table_name
AND table_schema = _table_schema
AND column_name <> _pk_column_name;
EXECUTE FORMAT('INSERT INTO %1$s.%2$s (%3$s) SELECT %3$s FROM %1$s.%2$s', _table_schema, _table_name, _columns);
END $$
The query it creates and runs is: INSERT INTO public.t1 (a,b,c,e,f) SELECT a,b,c,e,f FROM public.t1. It's selected all the columns apart from the PK one. You could put this code in a function and use it for any table you wanted, or just use it like this and edit it for whatever table.

Using query to set the column type in PostgreSQL

After the excellent answer by Alexandre GUIDET, I attempted to run the following query:
create table egg (id (SELECT
pg_catalog.format_type(a.atttypid, a.atttypmod) as Datatype
FROM
pg_catalog.pg_attribute a
WHERE
a.attnum > 0
AND NOT a.attisdropped
AND a.attrelid = (
SELECT c.oid
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname ~ '^(TABLENAME)$'
AND pg_catalog.pg_table_is_visible(c.oid)
)
and a.attname = 'COLUMNNAME'));
PostgreSQL, however, complains about incorrect syntax. Specifically it says that I cannot write: create table egg (id (SELECT.
Are there any workarounds? Can't I convert the result of a query to text and reuse it as a query?
There is a much simpler way to do that.
SELECT pg_typeof(col)::text FROM tbl LIMIT 1
Only precondition is that the template table holds at least one row. See the manual on pg_typeof()
As Milen wrote, you need to EXECUTE dynamic DDL statements like this.
A much simpler DO statement:
DO $$BEGIN
EXECUTE 'CREATE TABLE egg (id '
|| (SELECT pg_typeof(col)::text FROM tbl LIMIT 1) || ')';
END$$;
Or, if you are not sure the template table has any rows:
DO $$BEGIN
EXECUTE (
SELECT format('CREATE TABLE egg (id %s)'
, format_type(atttypid, atttypmod))
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass -- name of template table
AND attname = 'col' -- name of template column
AND attnum > 0 AND NOT attisdropped
);
END$$;
These conditions seem redundant, since you look for a specific column any
format() requires Postgres 9.1+.
Related:
How to check if a table exists in a given schema
You can either convert that query to a function or (if you have Postgres 9.0) to an anonymous code block:
DO $$DECLARE the_type text;
BEGIN
SELECT ... AS datatype INTO the_type FROM <the rest of your query>;
EXECUTE 'create table egg ( id ' || the_type || <the rest of your create table statement>;
END$$;
You can either have a table a definition or a query, but not both. Maybe your thinking of the select into command.

Problem with Postgres ALTER TABLE

I have one problem with the ALTER TABLE in postgre. I want to change size of the varchar column. When I try to do this, It says that the view is dependent on that column. I can't drop the view because comething else is dependent on it. Is there any other way than to drop everything and recreate it again?
I just found one option, which is to remove the table joining from the view, when I will not change the returned columns, I can do that. But still, there is more views I'll need to change. Isn't there anything how can I say that it should be deferred and checked with commit?
I have run into this problem and couldn't find any way around it. Unfortunately, as best I can tell, one must drop the views, alter the column type on the underlying table, and then recreate the views. This can happen entirely in a single transaction.
Constraint deferral doesn't apply to this problem. In other words, even SET CONSTRAINTS ALL DEFERRED has no impact on this limitation. To be specific, constraint deferral does not apply to the consistency check that prints ERROR: cannot alter type of a column used by a view or rule when one tries to alter the type of a column underlying a view.
I'm a little late to the party, but years after this question was posted, a brilliant solution was posted via an article referenced below (not mine -- I'm simply a thankful beneficiary of his brilliance).
I just tested this on an object that is referenced (on the first level) in 136 separate views, and each of those views is referenced in other views. The solution ran in mere seconds.
So, read this article and copy and paste the table and two functions listed:
http://mwenus.blogspot.com/2014/04/postgresql-how-to-handle-table-and-view.html
Implementation example:
alter table mdm.global_item_master_swap
alter column prod_id type varchar(128),
alter column prod_nme type varchar(512);
ERROR: cannot alter type of a column used by a view or rule DETAIL:
rule _RETURN on view toolbox_reporting."Average_setcost" depends on
column "prod_id"
********** Error **********
ERROR: cannot alter type of a column used by a view or rule
And now for the PostgreSQL ninja's magic:
select util.deps_save_and_drop_dependencies('mdm', 'global_item_master_swap');
alter table mdm.global_item_master_swap
alter column prod_id type varchar(128),
alter column prod_nme type varchar(512);
select util.deps_restore_dependencies('mdm', 'global_item_master_swap');
-- EDIT 11/13/2018 --
It appears the link above might be dead. Here is the code for the two procedures:
Table that stores DDL:
CREATE TABLE util.deps_saved_ddl
(
deps_id serial NOT NULL,
deps_view_schema character varying(255),
deps_view_name character varying(255),
deps_ddl_to_run text,
CONSTRAINT deps_saved_ddl_pkey PRIMARY KEY (deps_id)
);
Save and Drop:
-- Edit 8/28/2020 --
-- This stopped working with Pg12. The fix is below to change the parameters of p_view_schema and p_view_name from varchar to name:
CREATE OR REPLACE FUNCTION util.deps_save_and_drop_dependencies(
p_view_schema name, p_view_name name)
RETURNS void
LANGUAGE plpgsql
COST 100
AS $BODY$
declare
v_curr record;
begin
for v_curr in
(
select obj_schema, obj_name, obj_type from
(
with recursive recursive_deps(obj_schema, obj_name, obj_type, depth) as
(
select p_view_schema, p_view_name, null::varchar, 0
union
select dep_schema::varchar, dep_name::varchar, dep_type::varchar, recursive_deps.depth + 1 from
(
select ref_nsp.nspname ref_schema, ref_cl.relname ref_name,
rwr_cl.relkind dep_type,
rwr_nsp.nspname dep_schema,
rwr_cl.relname dep_name
from pg_depend dep
join pg_class ref_cl on dep.refobjid = ref_cl.oid
join pg_namespace ref_nsp on ref_cl.relnamespace = ref_nsp.oid
join pg_rewrite rwr on dep.objid = rwr.oid
join pg_class rwr_cl on rwr.ev_class = rwr_cl.oid
join pg_namespace rwr_nsp on rwr_cl.relnamespace = rwr_nsp.oid
where dep.deptype = 'n'
and dep.classid = 'pg_rewrite'::regclass
) deps
join recursive_deps on deps.ref_schema = recursive_deps.obj_schema and deps.ref_name = recursive_deps.obj_name
where (deps.ref_schema != deps.dep_schema or deps.ref_name != deps.dep_name)
)
select obj_schema, obj_name, obj_type, depth
from recursive_deps
where depth > 0
) t
group by obj_schema, obj_name, obj_type
order by max(depth) desc
) loop
insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
select p_view_schema, p_view_name, 'COMMENT ON ' ||
case
when c.relkind = 'v' then 'VIEW'
when c.relkind = 'm' then 'MATERIALIZED VIEW'
else ''
end
|| ' ' || n.nspname || '.' || c.relname || ' IS ''' || replace(d.description, '''', '''''') || ''';'
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
join pg_description d on d.objoid = c.oid and d.objsubid = 0
where n.nspname = v_curr.obj_schema and c.relname = v_curr.obj_name and d.description is not null;
insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
select p_view_schema, p_view_name, 'COMMENT ON COLUMN ' || n.nspname || '.' || c.relname || '.' || a.attname || ' IS ''' || replace(d.description, '''', '''''') || ''';'
from pg_class c
join pg_attribute a on c.oid = a.attrelid
join pg_namespace n on n.oid = c.relnamespace
join pg_description d on d.objoid = c.oid and d.objsubid = a.attnum
where n.nspname = v_curr.obj_schema and c.relname = v_curr.obj_name and d.description is not null;
insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
select p_view_schema, p_view_name, 'GRANT ' || privilege_type || ' ON ' || table_schema || '.' || table_name || ' TO ' || grantee
from information_schema.role_table_grants
where table_schema = v_curr.obj_schema and table_name = v_curr.obj_name;
if v_curr.obj_type = 'v' then
insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
select p_view_schema, p_view_name, 'CREATE VIEW ' || v_curr.obj_schema || '.' || v_curr.obj_name || ' AS ' || view_definition
from information_schema.views
where table_schema = v_curr.obj_schema and table_name = v_curr.obj_name;
elsif v_curr.obj_type = 'm' then
insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
select p_view_schema, p_view_name, 'CREATE MATERIALIZED VIEW ' || v_curr.obj_schema || '.' || v_curr.obj_name || ' AS ' || definition
from pg_matviews
where schemaname = v_curr.obj_schema and matviewname = v_curr.obj_name;
end if;
execute 'DROP ' ||
case
when v_curr.obj_type = 'v' then 'VIEW'
when v_curr.obj_type = 'm' then 'MATERIALIZED VIEW'
end
|| ' ' || v_curr.obj_schema || '.' || v_curr.obj_name;
end loop;
end;
$BODY$
Restore:
CREATE OR REPLACE FUNCTION util.deps_restore_dependencies(
p_view_schema character varying,
p_view_name character varying)
RETURNS void AS
$BODY$
declare
v_curr record;
begin
for v_curr in
(
select deps_ddl_to_run
from util.deps_saved_ddl
where deps_view_schema = p_view_schema and deps_view_name = p_view_name
order by deps_id desc
) loop
execute v_curr.deps_ddl_to_run;
end loop;
delete from util.deps_saved_ddl
where deps_view_schema = p_view_schema and deps_view_name = p_view_name;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
If you don't need to change the type of the field, but just the size of it, this approach should work:
Starting with these tables:
CREATE TABLE foo (id integer primary key, names varchar(10));
CREATE VIEW voo AS (SELECT id, names FROM foo);
\d foo and \d voo both show the length as 10:
id | integer | not null
names | character varying(10) |
Now change the lengths to 20 in the pg_attribute table:
UPDATE pg_attribute SET atttypmod = 20+4
WHERE attrelid IN ('foo'::regclass, 'voo'::regclass)
AND attname = 'names';
(note: the 20+4 is some crazy postgresql legacy thing, the +4 is compulsory.)
Now \d foo shows:
id | integer | not null
names | character varying(20) |
Bonus: that was waaay faster than doing:
ALTER TABLE foo ALTER COLUMN names TYPE varchar(20);
Technically you can change the size of the table column without changing the size of the view column, but no guarantees on what side effects that will have; it's probably best to change them both at once.
source and fuller explanation: http://sniptools.com/databases/resize-a-column-in-a-postgresql-table-without-changing-data
I ran into this problem today and found a work around to avoid dropping and recreating the VIEW . I cannot just drop my VIEW because it is a master VIEW that has many dependent VIEWs built on top of it. Short of having a rebuild script to DROP CASCADE and then recreate ALL of my VIEWs this is a work around.
I change my master VIEW to use a dummy value for the offending column, altered the column in the table, and switched my VIEW back to the column. Using a setup like this:
CREATE TABLE base_table
(
base_table_id integer,
base_table_field1 numeric(10,4)
);
CREATE OR REPLACE VIEW master_view AS
SELECT
base_table_id AS id,
(base_table_field1 * .01)::numeric AS field1
FROM base_table;
CREATE OR REPLACE VIEW dependent_view AS
SELECT
id AS dependent_id,
field1 AS dependent_field1
FROM master_view;
Trying to alter base_table_field1 type like this:
ALTER TABLE base_table ALTER COLUMN base_table_field1 TYPE numeric(10,6);
Will give you this error:
ERROR: cannot alter type of a column used by a view or rule
DETAIL: rule _RETURN on view master_view depends on column "base_table_field1"
If you change master_view to use a dummy value for the column like this:
CREATE OR REPLACE VIEW master_view AS
SELECT
base_table_id AS id,
0.9999 AS field1
FROM base_table;
Then run your alter:
ALTER TABLE base_table ALTER COLUMN base_table_field1 TYPE numeric(10,6);
And switch your view back:
CREATE OR REPLACE VIEW master_view AS
SELECT
base_table_id AS id,
(base_table_field1 * .01)::numeric AS field1
FROM base_table;
It all depends on if your master_view has an explicit type that does not change. Since my VIEW uses '(base_table_field1 * .01)::numeric AS field1' it works, but 'base_table_field1 AS field1' would not because the column type changes. This approach might help in some cases like mine.
I wanted to comment on the second answer but cannot since I'm too new to stackoverflow, so here my comment:
To those interested in the original article mentioned in that answer, the blogspot entry is not available any more but the wayback machine has it still stored: https://web.archive.org/web/20180323155900/http://mwenus.blogspot.com/2014/04/postgresql-how-to-handle-table-and-view.html
Here is the article itself in case archive.org should be turned off at some future point in time:
2014-04-22
PostgreSQL: How to handle table and view dependencies
PostgreSQL is very restrictive when it comes to modyfing existing objects. Very often when you try to ALTER TABLE or REPLACE VIEW it tells you that you cannot do it, because there's another object (typically a view or materialized view), which depends on the one you want to modify. It seems that the only solution is to DROP dependent objects, make desired changes to the target object and then recreate dropped objects.
It is tedious and cumbersome, because those dependent objects can have further dependencies, which also may have other dependencies and so on. I created utility functions which can help in such situations.
The usage is very simple - you just have to call:
select deps_save_and_drop_dependencies(p_schema_name, p_object_name);
You have to pass two arguments: the name of the schema and the name of the object in that schema. This object can be a table, a view or a materialized view. The function will drop all views and materialized views dependent on p_schema_name.p_object_name and save DDL which restores them in a helper table.
When you want to restore those dropped objects (for example when you are done modyfing p_schema_name.p_object_name), you just need to make another simple call:
select deps_restore_dependencies(p_schema_name, p_object_name);
and the dropped objects will be recreated.
These functions take care about:
dependencies hierarchy
proper order of dropping and creating views/materialized views across hierarchy
restoring comments and grants on views/materialized views
Click here for a working sqlfiddle example or check this gist for a complete source code.
Autor: Mateusz Wenus o 19:32
do $$
declare gorev_lisans_ihlali_def text;
declare exec_text text;
begin
gorev_lisans_ihlali_def := pg_get_viewdef('public.gorev_lisans_ihlali');
drop view public.gorev_lisans_ihlali;
exec_text := format('create view public.gorev_lisans_ihlali as %s',
gorev_lisans_ihlali_def);
ALTER TABLE public.ara_bakis_duyma
ALTER COLUMN gain TYPE DOUBLE PRECISION;
execute exec_text;
end $$;