On 2/26/15 1:49 PM, Jim Nasby wrote: > On 2/23/15 5:09 PM, Tomas Vondra wrote: >> Over the time I've heard various use cases for this patch, but in most >> cases it was quite speculative. If you have an idea where this might be >> useful, can you explain it here, or maybe point me to a place where it's >> described? > > For better or worse, table structure is a form of documentation for a > system. As such, it's very valuable to group related fields in a table > together. When creating a table, that's easy, but as soon as you need to > alter your careful ordering can easily end up out the window. > > Perhaps to some that just sounds like pointless window dressing, but my > experience is that on a complex system the less organized things are the > more bugs you get due to overlooking something.
I agree with Jim's comments. I've generally followed column ordering that goes something like: 1) primary key 2) foreign keys 3) flags 4) other programmatic data fields (type, order, etc.) 5) non-programmatic data fields (name, description, etc.) The immediate practical benefit of this is that users are more likely to see fields that they need without scrolling right. Documentation is also clearer because fields tend to go from most to least important (left to right, top to bottom). Also, if you are consistent enough then users just *know* where to look. I wrote a function a while back that reorders columns in tables (it not only deals with reordering, but triggers, constraints, indexes, etc., though there are some caveats). It's painful and not very efficient, but easy to use. Most dimension tables that I've worked with are in the millions of rows so reordering is not problem. With fact tables, I assess on a case-by-case basis. It would be nice to not have to do that triage. The function is attached if anyone is interested. -- - David Steele [email protected]
/********************************************************************************
CATALOG_TABLE_COLUMN_MOVE Function
********************************************************************************create
or replace function _utility.catalog_table_column_move
(
strSchemaName text,
strTableName text,
strColumnName text,
strColumnNameBefore text
)
returns void as $$
declare
rIndex record;
rConstraint record;
rColumn record;
strSchemaTable text = strSchemaName || '.' || strTableName;
strDdl text;
strClusterIndex text;
begin
-- Raise notice that a reorder is in progress
raise notice 'Reorder columns in table %.% (% before %)', strSchemaName,
strTableName, strColumnName, strColumnNameBefore;
-- Get the cluster index
select pg_index.relname
into strClusterIndex
from pg_namespace
inner join pg_class
on pg_class.relnamespace = pg_namespace.oid
and pg_class.relname = strTableName
inner join pg_index pg_index_map
on pg_index_map.indrelid = pg_class.oid
and pg_index_map.indisclustered = true
inner join pg_class pg_index
on pg_index.oid = pg_index_map.indexrelid
where pg_namespace.nspname = strSchemaName;
if strClusterIndex is null then
raise exception 'Table %.% must have a cluster index before
reordering', strSchemaName, strTableName;
end if;
-- Disable all user triggers
strDdl = 'alter table ' || strSchemaTable || ' disable trigger user';
raise notice ' Disable triggers [%]', strDdl;
execute strDdl;
-- Create temp table to hold ddl
create temp table temp_catalogtablecolumnreorder
(
type text not null,
name text not null,
ddl text not null
);
-- Save index ddl in a temp table
raise notice ' Save indexes';
for rIndex in
with index as
(
select _utility.catalog_index_list_get(strSchemaName, strTableName)
as name
),
index_ddl as
(
select index.name,
_utility.catalog_index_create_get(_utility.catalog_index_get(strSchemaName,
index.name)) as ddl
from index
)
select index.name,
index_ddl.ddl
from index
left outer join index_ddl
on index_ddl.name = index.name
and index_ddl.ddl not like '%[function]%'
loop
raise notice ' Save %', rIndex.name;
insert into temp_catalogtablecolumnreorder values ('index',
rIndex.name, rIndex.ddl);
end loop;
-- Save constraint ddl in a temp table
raise notice ' Save constraints';
for rConstraint in
with constraint_list as
(
select _utility.catalog_constraint_list_get(strSchemaName,
strTableName, '{p,u,f,c}') as name
),
constraint_ddl as
(
select constraint_list.name,
_utility.catalog_constraint_create_get(_utility.catalog_constraint_get(strSchemaName,
strTableName,
constraint_list.name)) as ddl
from constraint_list
)
select constraint_list.name,
constraint_ddl.ddl
from constraint_list
left outer join constraint_ddl
on constraint_ddl.name = constraint_list.name
loop
raise notice ' Save %', rConstraint.name;
insert into temp_catalogtablecolumnreorder values ('constraint',
rConstraint.name, rConstraint.ddl);
end loop;
-- Move column
for rColumn in
with table_column as
(
select pg_attribute.attname as name,
rank() over (order by pg_attribute.attnum) as rank,
pg_type.typname as type,
case when pg_attribute.atttypmod = -1 then null else
((atttypmod - 4) >> 16) & 65535 end as precision,
case when pg_attribute.atttypmod = -1 then null else
(atttypmod - 4) & 65535 end as scale,
not pg_attribute.attnotnull as nullable,
pg_attrdef.adsrc as default,
pg_attribute.*
from pg_namespace
inner join pg_class
on pg_class.relnamespace = pg_namespace.oid
and pg_class.relname = strTableName
inner join pg_attribute
on pg_attribute.attrelid = pg_class.oid
and pg_attribute.attnum >= 1
and pg_attribute.attisdropped = false
inner join pg_type
on pg_type.oid = pg_attribute.atttypid
left outer join pg_attrdef
on pg_attrdef.adrelid = pg_class.oid
and pg_attrdef.adnum = pg_attribute.attnum
where pg_namespace.nspname = strSchemaName
order by pg_attribute.attnum
)
select table_column.*
from table_column table_column_before
inner join table_column
on table_column.rank >= table_column_before.rank
and table_column.name <> strColumnName
where table_column_before.name = strColumnNameBefore
loop
raise notice ' Move column %', rColumn.name;
strDdl = 'alter table ' || strSchemaTable || ' rename column "' ||
rColumn.name || '" to "@' || rColumn.name || '@"';
raise notice ' Rename [%]', strDdl;
execute strDdl;
strDdl = 'alter table ' || strSchemaTable || ' add "' || rColumn.name
|| '" ' || rColumn.type ||
case when rColumn.precision is not null then '(' ||
rColumn.precision || ', ' || rColumn.scale || ')' else '' end;
raise notice ' Create [%]', strDdl;
execute strDdl;
strDdl = 'update ' || strSchemaTable || ' set "' || rColumn.name || '"
= "@' || rColumn.name || '@"';
raise notice ' Copy [%]', strDdl;
execute strDdl;
strDdl = 'alter table ' || strSchemaTable || ' drop column "@' ||
rColumn.name || '@"';
raise notice ' Drop [%]', strDdl;
execute strDdl;
if rColumn."default" is not null then
strDdl = 'alter table ' || strSchemaTable || ' alter column "' ||
rColumn.name || '" set default ' || rColumn.default;
raise notice ' Default [%]', strDdl;
execute strDdl;
end if;
if rColumn.nullable = false then
strDdl = 'alter table ' || strSchemaTable || ' alter column "' ||
rColumn.name || '" set not null';
raise notice ' Not Null [%]', strDdl;
execute strDdl;
end if;
end loop;
-- Rebuild indexes
raise notice ' Rebuild indexes';
for rIndex in
select name,
ddl
from temp_catalogtablecolumnreorder
where type = 'index'
loop
begin
execute rIndex.ddl;
raise notice ' Rebuild % [%]', rIndex.name, rIndex.ddl;
exception
when duplicate_table then
raise notice ' Skip % [%]', rIndex.name, rIndex.ddl;
end;
end loop;
-- Rebuild constraints
raise notice ' Rebuild constraints';
for rConstraint in
select name,
ddl
from temp_catalogtablecolumnreorder
where type = 'constraint'
loop
begin
execute rConstraint.ddl;
raise notice ' Rebuild % [%]', rConstraint.name,
rConstraint.ddl;
exception
when duplicate_object or duplicate_table or
invalid_table_definition then
raise notice ' Skip % [%]', rConstraint.name,
rConstraint.ddl;
end;
end loop;
-- Recluster table
strDdl = 'cluster ' || strSchemaTable || ' using ' || strClusterIndex;
raise notice ' Recluster [%]', strDdl;
execute strDdl;
-- Enable all user triggers
strDdl = 'alter table ' || strSchemaTable || ' enable trigger user';
raise notice ' Enable triggers [%]', strDdl;
execute strDdl;
-- Drop temp tables
drop table temp_catalogtablecolumnreorder;
end
$$ language plpgsql security invoker;
comment on function _utility.catalog_table_column_move(text, text, text, text)
is
'Moves a column before another column in a table. For example:
{{perform _utility.catalog_table_column_move(''attribute'', ''attribute'',
''target'', ''active'');}}
will position the "target" column right before the "active" column. It''s not
currently possible to directly move a column to the
right but this can be achieved by multiple moves of columns to the left.
There are a few caveats:
* The table must have a cluster index. Moving columns is messy on the storage
and the table needs to be re-clustered afterwards.
* Column referencing triggers will not automatically be dropped or rebuilt.
* Column specific permissions are not restored after the move.
* A column cannot be moved before the primary key if there are foreign key
references from other tables.';
signature.asc
Description: OpenPGP digital signature
