How to find all foreign key constraints that are broken in Postgresql - postgresql

We recently discovered an issue with our database where a foreign key constraint was not working correctly. Basically the primary table did not have any primary ids that matched the foreign key in the child table. When we dropped the foreign key constraint and tried to recreate it, it then threw an error that the foreign key constraint could not be created because there were foreign keys with no matching id in the parent table. Once those were cleaned up, it allowed us to recreate the foreign key.
Of course we are wondering how this happened to begin with. I worked with Oracle for 15 years and never saw a foreign key failing in this way. But our concern right now is how many other foreign keys are not working correctly. This is a problem because we have some BEFORE DELETE triggers that fail silently when the calling function returns a null because of a foreign_key_violation (this is how we discovered the issue to begin with).
EXCEPTION
WHEN foreign_key_violation
THEN RETURN NULL;
What we want to do is get all of the foreign keys in the database (probably a few thousand), loop over them all, and check every single one against it's parent table to see if any are "broken".
Basically:
Select all foreign keys using Postgres system tables.
Loop over all of them and do something like:
select count(parent_id) from child_table
where foreign_key_id not in (
select parent_id as foreign_key_id
from parent_table
)
);
For all the ones that are not 0, drop the foreign key constraint, fix the orphaned data, and recreate the foreign key constraint.
Does this sound reasonable? Has anyone done something like this before? What is the best way to get the foreign key constraints from Postgres?
Edit:
What we realized is that if we dropped and recreated the foreign keys, it would tell us which ones were problematic.
SELECT 'Alter table ' || conrelid::regclass || ' drop constraint ' || conname || '; alter table ' || conrelid::regclass || ' add constraint ' || conname || ' ' || pg_get_constraintdef(oid) || ';'
FROM pg_constraint
WHERE contype = 'f'
AND connamespace = 'public'::regnamespace
ORDER BY conrelid::regclass::text, contype DESC;
Next, run all the commands. If there are any foreign key violations, they will show up when the alter table add constraint command tries to run. Make a note of which ones had a problem and keep running the following commands until you have a list of all issues.
The following is example of sql commands to fix the issues.
delete from child_table where id_child_table in (
select id_child_table from child_table
where id_parent_id not in (
select id_parent_id
from parent_table
)
);
alter table child_table drop constraint child_table_fk1;
alter table child_table add constraint child_table_fk1 FOREIGN KEY (id_parent_table) REFERENCES parent_table(id_parent_table) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE;

Using a basic data sample :
create table test1 (id1 serial, seq1 int, constraint pk1 primary key (id1, seq1));
create table test2 (id2 int, seq2 int, descr2 text, constraint pk2 primary key (id2, seq2), constraint fk1 foreign key (id2, seq2) references test1 (id1, seq1) ON DELETE RESTRICT ON UPDATE RESTRICT) ;
insert into test1 (seq1) values (1), (2), (3), (4), (5);
insert into test2 (id2, seq2, descr2) values (1,1,'A'), (2,2,'B'), (3,3,'C'), (4,4,'D'), (5,5,'E');
Considering the query to get the list of foreign keys (source internet) :
SELECT conrelid::regclass AS table_name,
conname AS foreign_key,
pg_get_constraintdef(oid)
FROM pg_constraint
WHERE contype = 'f'
AND connamespace = 'public'::regnamespace
ORDER BY conrelid::regclass::text, contype DESC;
Result :
table_name
foreign_key
pg_get_constraintdef
test2
fk1
FOREIGN KEY (id2, seq2) REFERENCES test1(id1, seq1) ON UPDATE RESTRICT ON DELETE RESTRICT
With some changes to get a computable resulting list :
SELECT conrelid::regclass AS ReferencingTable
, a.ReferencingKey
, b.ReferencedTable
, b.ReferencedKey
FROM pg_constraint AS pg
CROSS JOIN LATERAL
( SELECT '(' || string_agg(conrelid::regclass || '.' || fkey, ',' ORDER BY fkey.ORDINALITY) || ')' AS ReferencingKey
FROM pg_get_constraintdef(pg.oid) AS fk
CROSS JOIN LATERAL string_to_table(translate(regexp_substr(fk, '\([^\)]*\)', 1, 1), '() ', ''), ',') WITH ORDINALITY AS fkey
) AS a
CROSS JOIN LATERAL
( SELECT '(' || string_agg(ReferencedTable || '.' || rkey, ',' ORDER BY rkey.ORDINALITY) || ')' AS ReferencedKey
, ReferencedTable
FROM pg_get_constraintdef(pg.oid) AS fk
CROSS JOIN LATERAL split_part((regexp_match(fk, 'REFERENCES\s\w+'))[1], ' ', 2) AS ReferencedTable
CROSS JOIN LATERAL string_to_table(translate(regexp_substr(fk, '\([^\)]*\)', 1, 2), '() ', ''), ',') WITH ORDINALITY AS rkey
GROUP BY ReferencedTable
) AS b
WHERE contype = 'f'
AND connamespace = 'public' ::regnamespace
Result :
referencingtable
referencingkey
referencedtable
referencedkey
test2
(test2.id2,test2.seq2)
test1
(test1.id1,test1.seq1)
Creating a plpgsql function with schema_name as input data and with a dynamic query :
CREATE OR REPLACE FUNCTION test(IN schema_name text, OUT Referencing_Table text, OUT Referencing_Key text, OUT Referenced_Table text)
RETURNS setof record LANGUAGE plpgsql AS
$$
DECLARE
_row record ;
BEGIN
FOR _row IN
( SELECT conrelid::regclass AS ReferencingTable
, a.ReferencingKey
, b.ReferencedTable
, b.ReferencedKey
FROM pg_constraint AS pg
CROSS JOIN LATERAL
( SELECT '(' || string_agg(conrelid::regclass || '.' || fkey, ',' ORDER BY fkey.ORDINALITY) || ')' AS ReferencingKey
FROM pg_get_constraintdef(pg.oid) AS fk
CROSS JOIN LATERAL string_to_table(translate(regexp_substr(fk, '\([^\)]*\)', 1, 1), '() ', ''), ',') WITH ORDINALITY AS fkey
) AS a
CROSS JOIN LATERAL
( SELECT '(' || string_agg(ReferencedTable || '.' || rkey, ',' ORDER BY rkey.ORDINALITY) || ')' AS ReferencedKey
, ReferencedTable
FROM pg_get_constraintdef(pg.oid) AS fk
CROSS JOIN LATERAL split_part((regexp_match(fk, 'REFERENCES\s\w+'))[1], ' ', 2) AS ReferencedTable
CROSS JOIN LATERAL string_to_table(translate(regexp_substr(fk, '\([^\)]*\)', 1, 2), '() ', ''), ',') WITH ORDINALITY AS rkey
GROUP BY ReferencedTable
) AS b
WHERE contype = 'f'
AND connamespace = schema_name ::regnamespace )
LOOP
RETURN QUERY EXECUTE FORMAT (
'SELECT %L :: text AS Referencing_Table, %s :: text AS Referencing_Key, %L :: text AS Referenced_Table
FROM %I LEFT JOIN %I ON %s = %s
WHERE %s IS NULL'
, _row.ReferencingTable
, _row.ReferencingKey
, _row.ReferencedTable
, _row.ReferencingTable
, _row.ReferencedTable
, _row.ReferencedKey
, _row.ReferencingKey
, _row.ReferencedKey
) ;
END LOOP ;
END ;
$$ ;
SELECT * FROM test('public') should return the list of foreign keys of any table Referencing_Table in schema schema_name with no correspondance in table Referenced_Table
but not able to test the result in dbfiddle because not able to break the foreign key (superuser privilege)

Related

Select columns that are primary key or foreign key only

Assuming that I have the following table
CREATE TABLE my_table (
a TEXT NULL,
id TEXT PRIMARY KEY NOT NULL,
c TEXT NULL,
d TEXT REFERENCES other_table_1(id) NOT NULL,
e TEXT REFERENCES other_table_2(id) NOT NULL
);
I want to perform a select statement that only select important column that is primary key and foreign key only.
SELECT (...?) FROM my_table
Expected output columns only id, d, e
What is the best non-hacky way I can achieve this?
You can create a function that build the sql statement for you and after you can execute the result.
CREATE OR REPLACE FUNCTION build_select(_tbl regclass)
RETURNS text AS
$func$
SELECT format('SELECT %s FROM %s'
, string_agg(quote_ident(important_column), ', ')
, $1)
FROM (SELECT a.attname as important_column
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = $1
AND i.indisprimary
UNION
SELECT ta.attname AS important_column
FROM (
SELECT conname, conrelid, confrelid,
unnest(conkey) AS conkey, unnest(confkey) AS confkey
FROM pg_constraint
WHERE conrelid = $1
) sub
JOIN pg_attribute AS ta ON ta.attrelid = conrelid AND ta.attnum = conkey
JOIN pg_attribute AS fa ON fa.attrelid = confrelid AND fa.attnum = confkey) my_sub_query
$func$ LANGUAGE sql;
With this function, if you do:
SELECT build_select('my-schema.my-table')
Will return you:
SELECT id, d, e FROM my-schema.my-table
And you can execute this one

PostgreSQL: constraint name is not necessarily unique

I'm trying to list all the foreign keys in a schema, and, for each key, listing the columns involved.
So I'm querying the pg_catalog.pg_constraint and information_schema.columns tables.
I need a way to distinguish the keys, to do the second query and retrieve the key columns list.
I thought to use the constraint name (the conname column, in pg_catalog.pg_constraint table), but the PostgreSQL documentation about pg_constraint says that the constraint name is not necessarily unique! Why? I can't find additional information about this fact in the documentation.
Is the couple connamespace + conname unique?
This are my queries:
Retrieve the lists of foreign keys from and to the given schema:
SELECT
conname AS constraint_name,
conrelid::regclass AS referring_table,
confrelid::regclass AS referenced_table
FROM pg_catalog.pg_constraint
WHERE contype = 'f' AND ((conrelid::regclass || '' LIKE '<my_schema_name>.%') OR (confrelid::regclass || '' LIKE '<my_schema_name>.%'))
Retrieve the list of columns of a given key:
SELECT
c.column_name AS key_column
FROM information_schema.columns c
JOIN pg_catalog.pg_constraint r ON (c.table_schema || '.' || c.table_name) = (r.conrelid::regclass || '')
WHERE r.conname = '<my_constraint_name>'
Thanks for your help.
Constraint names are unique only within the object on which they are defined.
Two different tables (or domains) can have constraints with the same name.

Dynamically generated insert and select statements

I have a database with some tables in it and I want to make dynamically generated insert and select statements without the need to use the case statement in the select clause for each table.
The select statement is quite easy, the challenge is with the insert one because I have to deal with each column and its data type. I managed to overcome it by means of a case statement, but I think it's hard working for tables with lots of columns and for databases with many tables.
I wish it was possible to change hardcoded table and column names for each table and column I need the dynamically generated SQL command.
I worked out in the following select statement for the given tables of the database (testDB) I have:
use testDB;
go
set dateformat dmy;
select
'select * from ' + s.name + '.' + t.name + ';' as cmd_select,
'insert into ' + s.name + '.' + t.name + ' (' +
stuff(( select ', ' + column_name
from information_schema.columns
where table_name = t.name and ordinal_position > 1
order by ordinal_position
for xml path(''), type).value('.', 'nvarchar(max)'),
1, 2, '')
+ ') values (' +
case t.name
when 'Person' then '''xxx'''
when 'WeightHistory' then '0.0, ''' + convert(varchar, current_timestamp, 103) + ''', ''' + left(convert(varchar, current_timestamp, 108), 5) + ''', 1'
when 'WorkTime' then '''' + convert(varchar, current_timestamp, 103) + ''', ''' + left(convert(varchar, current_timestamp, 108), 5) + ''', 1, null'
when 'TimeReference' then '''07:00'', ''' + convert(varchar, current_timestamp, 103) + ''', null'
end
+ ');'as cmd_insert --,
--t.lob_data_space_id
--, s.name, t.name, *
from sys.tables as t
inner join sys.schemas as s on t.schema_id = s.schema_id
where t.lob_data_space_id = 0; /* tables that don't have LOB columns (sysdiagrams, varchar(max), xml, etc.) */
What exactly I want to know is:
Is there a better way of making the dynamically generated insert statement without using a case statement for each table and column of the database?
ADITIONAL INFO
The table definition for the above code is the following:
if not exists (select * from sys.tables where lower(name) = N'person' )
begin
create table Person.Person (
PersonID int
constraint PK_Person
primary key
identity (1, 1),
Name varchar(100)
);
end;
go
if not exists (select * from sys.tables where lower(name) = N'weighthistory' )
begin
create table dbo.WeightHistory (
WeightHistoryID int
constraint PK_WeightHistory
primary key
identity (1, 1),
MeasureValue money,
MeasureDate date,
MeasureTime time(0),
PersonID int,
constraint FK_Weight_Person foreign key (PersonID) references Person.Person (PersonID)
);
end;
go
if not exists (select * from sys.tables where lower(name) = N'worktime')
begin
create table WorkTime (
WorkTimeID int
constraint PK_WorkTime primary key
identity(1, 1),
WorkDate date,
WorkTime time(0),
PersonID int,
TimeReferenceID int,
constraint FK_WorkTime_Person foreign key (PersonID) references Person.Person (PersonID) on delete cascade,
constraint FK_WorkTime_Reference foreign key (TimeReferenceID) references Work.Timereference (TimeReferenceID)
);
end;
go
if not exists (select * from sys.tables where lower(name) = N'timereference')
begin
create table Work.TimeReference (
TimeReferenceID int
constraint PK_TimeReferene primary key
identity (1, 1),
WorkTime time(0),
WorkTimeStartDate date,
WorkTimeEndDate date
);
end;

Postgresql, select a "fake" row

In Postgres 8.4 or higher, what is the most efficient way to get a row of data populated by defaults without actually creating the row. Eg, as a transaction (pseudocode):
create table "mytable"
(
id serial PRIMARY KEY NOT NULL,
parent_id integer NOT NULL DEFAULT 1,
random_id integer NOT NULL DEFAULT random(),
)
begin transaction
fake_row = insert into mytable (id) values (0) returning *;
delete from mytable where id=0;
return fake_row;
end transaction
Basically I'd expect a query with a single row where parent_id is 1 and random_id is a random number (or other function return value) but I don't want this record to persist in the table or impact on the primary key sequence serial_id_seq.
My options seem to be using a transaction like above or creating views which are copies of the table with the fake row added but I don't know all the pros and cons of each or whether a better way exists.
I'm looking for an answer that assumes no prior knowledge of the datatypes or default values of any column except id or the number or ordering of the columns. Only the table name will be known and that a record with id 0 should not exist in the table.
In the past I created the fake record 0 as a permanent record but I've come to consider this record a type of pollution (since I typically have to filter it out of future queries).
You can copy the table definition and defaults to the temp table with:
CREATE TEMP TABLE table_name_rt (LIKE table_name INCLUDING DEFAULTS);
And use this temp table to generate dummy rows. Such table will be dropped at the end of the session (or transaction) and will only be visible to current session.
You can query the catalog and build a dynamic query
Say we have this table:
create table test10(
id serial primary key,
first_name varchar( 100 ),
last_name varchar( 100 ) default 'Tom',
age int not null default 38,
salary float default 100.22
);
When you run following query:
SELECT string_agg( txt, ' ' order by id )
FROM (
select 1 id, 'SELECT ' txt
union all
select 2, -9999 || ' as id '
union all
select 3, ', '
|| coalesce( column_default, 'null'||'::'||c.data_type )
|| ' as ' || c.column_name
from information_schema.columns c
where table_schema = 'public'
and table_name = 'test10'
and ordinal_position > 1
) xx
;
you will get this sting as a result:
"SELECT -9999 as id , null::character varying as first_name ,
'Tom'::character varying as last_name , 38 as age , 100.22 as salary"
then execute this query and you will get the "phantom row".
We can build a function that build and excecutes the query and return our row as a result:
CREATE OR REPLACE FUNCTION get_phantom_rec (p_i test10.id%type )
returns test10 as $$
DECLARE
v_sql text;
myrow test10%rowtype;
begin
SELECT string_agg( txt, ' ' order by id )
INTO v_sql
FROM (
select 1 id, 'SELECT ' txt
union all
select 2, p_i || ' as id '
union all
select 3, ', '
|| coalesce( column_default, 'null'||'::'||c.data_type )
|| ' as ' || c.column_name
from information_schema.columns c
where table_schema = 'public'
and table_name = 'test10'
and ordinal_position > 1
) xx
;
EXECUTE v_sql INTO myrow;
RETURN myrow;
END$$ LANGUAGE plpgsql ;
and then this simple query gives you what you want:
select * from get_phantom_rec ( -9999 );
id | first_name | last_name | age | salary
-------+------------+-----------+-----+--------
-9999 | | Tom | 38 | 100.22
I would just select the fake values as literals:
select 1 id, 1 parent_id, 1 user_id
The returned row will be (virtually) indistinguishable from a real row.
To get the values from the catalog:
select
0 as id, -- special case for serial type, just return 0
(select column_default::int -- Cast to int, because we know the column is int
from INFORMATION_SCHEMA.COLUMNS
where table_name = 'mytable'
and column_name = 'parent_id') as parent_id,
(select column_default::int -- Cast to int, because we know the column is int
from INFORMATION_SCHEMA.COLUMNS
where table_name = 'mytable'
and column_name = 'user_id') as user_id;
Note that you must know what the columns are and their type, but this is reasonable. If you change the table schema (except default value), you would need to tweak the query.
See the above as a SQLFiddle.

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 $$;