Adding a column with a volatile DEFAULT or changing the type of an existing column will require the entire table and its indexes to be rewritten. As an exception, when changing the type of an existing column, if the USING clause does not change the column contents and the old type is either binary coercible to the new type or an unconstrained domain over the new type, a table rewrite is not needed; but any indexes on the affected columns must still be rebuilt. Table and/or index rebuilds may take a significant amount of time for a large table; and will temporarily require as much as double the disk space.
Adding a CHECK or NOT NULL constraint requires scanning the table to verify that existing rows meet the constraint, but does not require a table rewrite.
Similarly, when attaching a new partition it may be scanned to verify that existing rows meet the partition constraint.
postgres=# \dC+ ********* QUERY ********** SELECT pg_catalog.format_type(castsource, NULL) AS "Source type", pg_catalog.format_type(casttarget, NULL) AS "Target type", CASEWHEN c.castmethod ='b'THEN'(binary coercible)' WHEN c.castmethod ='i'THEN'(with inout)' ELSE p.proname ENDAS "Function", CASEWHEN c.castcontext ='e'THEN'no' WHEN c.castcontext ='a'THEN'in assignment' ELSE'yes' ENDAS "Implicit?", d.description AS "Description" FROM pg_catalog.pg_cast c LEFTJOIN pg_catalog.pg_proc p ON c.castfunc = p.oid LEFTJOIN pg_catalog.pg_type ts ON c.castsource = ts.oid LEFTJOIN pg_catalog.pg_namespace ns ON ns.oid = ts.typnamespace LEFTJOIN pg_catalog.pg_type tt ON c.casttarget = tt.oid LEFTJOIN pg_catalog.pg_namespace nt ON nt.oid = tt.typnamespace LEFTJOIN pg_catalog.pg_description d ON d.classoid = c.tableoid AND d.objoid = c.oid AND d.objsubid =0 WHERE ( (trueAND pg_catalog.pg_type_is_visible(ts.oid) ) OR (trueAND pg_catalog.pg_type_is_visible(tt.oid) ) ) ORDERBY1, 2;
castmethod:Indicates how the cast is performed. f means that the function specified in the castfunc field is used. i means that the input/output functions are used. b means that the types are binary-coercible, thus no conversion is required.
If the cast is marked AS ASSIGNMENT then it can be invoked implicitly when assigning a value to a column of the target data type. For example, supposing that foo.f1 is a column of type text, then:
INSERT INTO foo (f1) VALUES (42);
will be allowed if the cast from type integer to type text is marked AS ASSIGNMENT, otherwise not. (We generally use the term assignment cast to describe this kind of cast.)
postgres=# select castsource::regtype,casttarget::regtype,castfunc::regproc,castcontext,castmethod from pg_cast where castsource='int'::regtype and casttarget ='boolean'::regtype; castsource | casttarget | castfunc | castcontext | castmethod ------------+------------+-----------------+-------------+------------ integer|boolean| pg_catalog.bool | e | f (1row)
postgres=# create table cas_test(id int, c1 boolean); CREATE TABLE postgres=# insert into cas_test values (1, int'1'); ERROR: column "c1" isof type boolean but expression isof type integer LINE 1: insert into cas_test values (1, int'1'); ^ HINT: You will need to rewrite or cast the expression.
所以,需要我们使用CAST的方式强转
1 2 3 4 5 6 7 8
postgres=# select castsource::regtype,casttarget::regtype,castfunc::regproc,castcontext,castmethod from pg_cast where castsource='int'::regtype and casttarget ='boolean'::regtype; castsource | casttarget | castfunc | castcontext | castmethod ------------+------------+-----------------+-------------+------------ integer|boolean| pg_catalog.bool | e | f (1row)
postgres=# insert into cas_test values (1, cast ((int'1') asboolean)); INSERT01
当然,你可以修改一下系统表,让其自动转化即可
1 2 3 4 5 6 7 8 9 10 11 12 13
postgres=# begin; BEGIN postgres=*# update pg_cast set castcontext ='a'where castsource='int'::regtype and casttarget ='boolean'::regtype; UPDATE1 postgres=*# insert into cas_test values (1, int'1'); INSERT01 postgres=*# rollback ; ROLLBACK postgres=# insert into cas_test values (1, int'1'); ERROR: column "c1" isof type boolean but expression isof type integer LINE 1: insert into cas_test values (1, int'1'); ^ HINT: You will need to rewrite or cast the expression.
这样就不会报错了。不过并不是所有的转换都有显示,只包括那些不能从某些普通规则推导出的转换。
It should be noted that pg_cast does not represent every type conversion that the system knows how to perform; only those that cannot be deduced from some generic rule. For example, casting between a domain and its base type is not explicitly represented in pg_cast. Another important exception is that “automatic I/O conversion casts”, those performed using a data type’s own I/O functions to convert to or from text or other string types, are not explicitly represented in pg_cast.
比如int到text的转换,内置转化里就没有
1 2 3 4 5 6 7 8 9
postgres=# select castsource::regtype,casttarget::regtype,castfunc::regproc,castcontext,castmethod from pg_cast where castsource='int'::regtype and casttarget ='text'::regtype; castsource | casttarget | castfunc | castcontext | castmethod ------------+------------+----------+-------------+------------ (0rows)
postgres=# create table test4(info timestamp); CREATE TABLE postgres=# insert into test4 values(text '2020-01-01'); ERROR: column "info" isof type timestampwithouttime zone but expression isof type text LINE 1: insert into test4 values(text '2020-01-01'); ^ HINT: You will need to rewrite or cast the expression.
postgres=# select castsource::regtype,casttarget::regtype,castfunc::regproc,castcontext,castmethod from pg_cast where castsource='text'::regtype and casttarget ='timestamp'::regtype; castsource | casttarget | castfunc | castcontext | castmethod ------------+-----------------------------+----------+-------------+------------ text |timestampwithouttime zone |-| a | i (1row)
postgres=# insert into test4 values(text '2020-01-01'); INSERT01
postgres=# dropcast (text astimestamp); DROP CAST postgres=# insert into test4 values(text '2020-01-01'); ERROR: column "info" isof type timestampwithouttime zone but expression isof type text LINE 1: insert into test4 values(text '2020-01-01'); ^ HINT: You will need to rewrite or cast the expression.
You can define a cast as an I/O conversion cast by using the WITH INOUT syntax. An I/O conversion cast is performed by invoking the output function of the source data type, and passing the resulting string to the input function of the target data type. In many common cases, this feature avoids the need to write a separate cast function for conversion. An I/O conversion cast acts the same as a regular function-based cast; only the implementation is different.
postgres=# \df textin List of functions Schema | Name |Result data type | Argument data types | Type ------------+--------+------------------+---------------------+------ pg_catalog | textin | text | cstring | func (1row)
postgres=# \df textout List of functions Schema | Name |Result data type | Argument data types | Type ------------+---------+------------------+---------------------+------ pg_catalog | textout | cstring | text | func (1row)
postgres=# \df int4in List of functions Schema | Name |Result data type | Argument data types | Type ------------+--------+------------------+---------------------+------ pg_catalog | int4in |integer| cstring | func (1row)
postgres=# \df timestamp_in List of functions Schema | Name |Result data type | Argument data types | Type ------------+--------------+-----------------------------+-----------------------+------ pg_catalog | timestamp_in |timestampwithouttime zone | cstring, oid, integer| func (1row)
postgres=# \df varcharin List of functions Schema | Name |Result data type | Argument data types | Type ------------+-----------+-------------------+-----------------------+------ pg_catalog | varcharin |charactervarying| cstring, oid, integer| func (1row)
postgres=# begin; BEGIN postgres=*# alter table test altercolumn id type bigint; ALTER TABLE postgres=*# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/13890/23394 (1row)
postgres=*# alter table test altercolumn info type varchar(20); ALTER TABLE postgres=*# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/13890/23394 (1row)
postgres=*# alter table test altercolumn t_time type date; ALTER TABLE postgres=*# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/13890/23397 (1row)
Increasing the length limit for a varchar or varbit column, or removing the limit altogether, no longer requires a table rewrite. Similarly, increasing the allowable precision of a numeric column, or changing a column from constrained numeric to unconstrained numeric, no longer requires a table rewrite. Table rewrites are also avoided in similar cases involving the interval, timestamp, and timestamptz types.
下面做一个简单汇总整理:
varchar(x) to varchar(y) when y>=x. It works too if going from varchar(x) to varchar or text (no size limitation)
numeric(x,z) to numeric(y,z) when y>=x, or to numeric without specifier
varbit(x) to varbit(y) when y>=x, or to varbit without specifier
timestamp(x) to timestamp(y) when y>=x or timestamp without specifier
timestamptz(x) to timestamptz(y) when y>=x or timestamptz without specifier
interval(x) to interval(y) when y>=x or interval without specifier
timestamp to text、varchar、varchar(n),char(n),需要重写
timestamp(x) to text、varchar、varchar(n)、char(n),n>=x,需要重写
postgres=# begin; BEGIN postgres=*# alter table test altercolumn id type timestamp; ERROR: column "id" cannot be cast automatically to type timestampwithouttime zone HINT: You might need to specify "USING id::timestamp without time zone".
postgres=# begin; BEGIN postgres=# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/13890/23438 (1row)
postgres=*# alter table test altercolumn id type dateusing (timestampwithtime zone 'epoch'+ id *interval'1 second'); ALTER TABLE postgres=*# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/13890/23441 (1row)
或者使用前面的知识,创建一个CAST
1 2 3 4 5 6 7 8
postgres=# createcast (intastimestamp) withinoutas assignment; CREATE CAST postgres=# begin; BEGIN postgres=*# alter table test altercolumn id type timestamp; ALTER TABLE postgres=*# rollback ; ROLLBACK
不过这种不适用于表里已有数据的情况
1 2 3 4
postgres=# insert into test values(1); INSERT01 postgres=# alter table test altercolumn id type timestamp; ERROR: invalid input syntax for type timestamp: "1"
postgres=# createor replace function cast_int_to_timestamp(int) returnstimestampas $$ selecttimestampwithtime zone 'epoch'+ $1*interval'1 second' $$ languagesql strict ; CREATEFUNCTION postgres=# createcast (intastimestamp) withfunction cast_int_to_timestamp as assignment; CREATE CAST
postgres=# begin; BEGIN postgres=*# alter table test altercolumn id type timestamp; ALTER TABLE postgres=*# select*from test; id --------------------- 1970-01-0108:00:01 (1row)
postgres=# \d test1 Table "public.test1" Column| Type |Collation| Nullable |Default --------+------+-----------+----------+--------- info | text ||| postgres=# insert into test1 values('2022-03-16 00:00:00'); INSERT01 postgres=# select*from test1; info --------------------- 2022-03-1600:00:00 (1row)
postgres=# begin; BEGIN postgres=*# select pg_relation_filepath('test1'); pg_relation_filepath ---------------------- base/13890/23444 (1row)
postgres=*# alter table test1 alterCOLUMN info type timestamp; ERROR: column "info" cannot be cast automatically to type timestampwithouttime zone HINT: You might need to specify "USING info::timestamp without time zone".
postgres=# alter table test1 alterCOLUMN info type timestampusing (info::timestamp); ALTER TABLE postgres=# select pg_relation_filepath('test1'); pg_relation_filepath ---------------------- base/13890/23583 (1row)
but any indexes on the affected columns must still be rebuilt. Table and/or index rebuilds may take a significant amount of time for a large table; and will temporarily require as much as double the disk space.
postgres=# begin; DEBUG: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0 LOG: duration: 0.049 ms statement: begin; BEGIN postgres=*# alter table test altercolumn id type int; DEBUG: rewriting table "test" DEBUG: building index "myidx2" ontable "test" serially DEBUG: index "myidx2" can safely use deduplication DEBUG: building index "myidx" ontable "test" serially DEBUG: index "myidx" can safely use deduplication DEBUG: drop auto-cascades to type pg_temp_23877 DEBUG: drop auto-cascades to type pg_temp_23877[] LOG: duration: 2.567 ms statement: alter table test altercolumn id type int; ALTER TABLE postgres=*# select pg_relation_filepath('test'),pg_relation_filepath('myidx'),pg_relation_filepath('myidx2'); LOG: duration: 0.249 ms statement: select pg_relation_filepath('test'),pg_relation_filepath('myidx'),pg_relation_filepath('myidx2'); pg_relation_filepath | pg_relation_filepath | pg_relation_filepath ----------------------+----------------------+---------------------- base/13890/23885| base/13890/23889| base/13890/23888 (1row)
修改系统表
修改系统表就不推荐了,比较危险。
这里之前朋友”乡下人”分享了一个有趣的case,引用一下:
1 2 3 4
postgres=# create table t(id numeric(5)); CREATE TABLE postgres=# alter table t alterCOLUMN id type numeric(8,2); ALTER TABLE
尝试修改元数据来修改字段类型
1 2 3 4 5 6 7 8 9 10
postgres=# SELECT atttypmod, attname FROM pg_attribute WHERE1=1 AND attrelid ='t'::regclass AND attname ='id' ORDERBY atttypmod; atttypmod | attname -----------+--------- 524294| id (1row)
postgres=# SELECT CASE atttypid WHEN21/*int2*/THEN16 WHEN23/*int4*/THEN32 WHEN20/*int8*/THEN64 WHEN1700/*numeric*/THEN CASEWHEN atttypmod =-1 THENnull ELSE ((atttypmod -4) >>16) &65535-- calculate the precision END WHEN700/*float4*/THEN24/*FLT_MANT_DIG*/ WHEN701/*float8*/THEN53/*DBL_MANT_DIG*/ ELSEnull ENDAS numeric_precision, CASE WHEN atttypid IN (21, 23, 20) THEN0 WHEN atttypid IN (1700) THEN CASE WHEN atttypmod =-1THENnull ELSE (atttypmod -4) &65535-- calculate the scale END ELSEnull ENDAS numeric_scale, * FROM pg_attribute where attrelid ='t'::regclass and attname ='id'; -[ RECORD 1 ]-----+------- numeric_precision |10 numeric_scale |3 attrelid |23903 attname | id atttypid |1700 attstattarget |-1 attlen |-1 attnum |1 attndims |0 attcacheoff |-1 atttypmod |655367 attbyval | f attalign | i attstorage | m attcompression | attnotnull | f atthasdef | f atthasmissing | f attidentity | attgenerated | attisdropped | f attislocal | t attinhcount |0 attcollation |0 attacl | attoptions | attfdwoptions | attmissingval |
ALTER TABLE table_name ADD COLUMN flag text; ——不会重写
ALTER TABLE table_name ADD COLUMN flag text DEFAULT 'default values'; ——需要重写
v11
When a column is added with ADD COLUMN and a non-volatile DEFAULT is specified, the default is evaluated at the time of the statement and the result stored in the table’s metadata. That value will be used for the column for all existing rows. If no DEFAULT is specified, NULL is used. In neither case is a rewrite of the table required.
Adding a column with a volatile DEFAULT or changing the type of an existing column will require the entire table and its indexes to be rewritten.
postgres=# insert into t3 select n,'test'from generate_series(1,10000000) as n; INSERT010000000 postgres=# \timing on Timing is on. postgres=# alter table t3 addcolumn num serial; ALTER TABLE Time: 43466.880 ms (00:43.467) postgres=# select pg_relation_filepath('t3'); pg_relation_filepath ---------------------- base/13287/16497 (1row)
删除字段
那删除字段呢?删除字段在PostgreSQL是很快的,不过缺点就是空间无法立刻回收。
The DROP COLUMN form does not physically remove the column, but simply makes it invisible to SQL operations. Subsequent insert and update operations in the table will store a null value for the column. Thus, dropping a column is quick but it will not immediately reduce the on-disk size of your table, as the space occupied by the dropped column is not reclaimed. The space will be reclaimed over time as existing rows are updated.
postgres=# \dt+ test List of relations Schema | Name | Type | Owner | Persistence | Access method| Size | Description --------+------+-------+----------+-------------+---------------+-------+------------- public | test |table| postgres | permanent | heap |42 MB | (1row)
postgres=# alter table test dropcolumn info; ALTER TABLE postgres=# \dt+ test List of relations Schema | Name | Type | Owner | Persistence | Access method| Size | Description --------+------+-------+----------+-------------+---------------+-------+------------- public | test |table| postgres | permanent | heap |42 MB | (1row)
postgres=# select*from pg_attribute where attrelid ='test'::regclass and attname notin ('tableoid','cmax','xmax','cmin','xmin','ctid','id'); attrelid | attname | atttypid | attstattarget | attlen | attnum | attndims | attcacheoff | atttypmod | attbyval | attalign | attstorage | attcompression | attnotnull | atthasdef | atthasmissing | attidentity | attgenerated | attisdropped | attislocal | attinhcount | attcollation | attacl | attoptions | attfdwoptions | attmissingval ----------+------------------------------+----------+---------------+--------+--------+----------+-------------+-----------+----------+----------+------------+----------------+------------+-----------+---------------+-------------+--------------+--------------+------------+-------------+--------------+--------+------------+---------------+--------------- 23630| ........pg.dropped.2........ |0|0|-1|2|0|-1|-1| f | i | x || f | f | f ||| t | t |0|100|||| (1row)
Unique constraints (and hence primary keys) on partitioned tables must include all the partition key columns. This limitation exists because the individual indexes making up the constraint can only directly enforce uniqueness within their own partitions; therefore, the partition structure itself must guarantee that there are not duplicates in different partitions.
postgres=# create table test(id serial,id2 int,info text) partitionbyrange(id2); CREATE TABLE postgres=# create table test_part_1 partitionof test forvaluesfrom (1) to (10); CREATE TABLE postgres=# create table test_part_2 partitionof test forvaluesfrom (10) to (20); CREATE TABLE postgres=# create table test_part_3 partitionof test forvaluesfrom (20) to (30); CREATE TABLE postgres=# create table test_part_4 partitionof test forvaluesfrom (30) to (40); CREATE TABLE postgres=# create table test_part_5 partitionof test forvaluesfrom (40) to (50); CREATE TABLE postgres=# create table test_part_default partitionof test default ; CREATE TABLE
然后针对各个子表创建主键
1 2 3 4 5 6 7 8 9 10 11 12 13 14
postgres=# alter table test_part_1 addprimary key(id); ALTER TABLE postgres=# alter table test_part_2 addprimary key(id); ALTER TABLE postgres=# alter table test_part_3 addprimary key(id); ALTER TABLE postgres=# alter table test_part_4 addprimary key(id); ALTER TABLE postgres=# alter table test_part_5 addprimary key(id); ALTER TABLE postgres=# alter sequence test_id_seq maxvalue 15; ALTER SEQUENCE postgres=# alter sequence test_id_seq cycle ; ALTER SEQUENCE
postgres=# insert into test(id2,info) select n,'test'from generate_series(1,20) as n; INSERT020 postgres=# select*from test_part_1; id | id2 | info ----+-----+------ 1|1| test 2|2| test 3|3| test 4|4| test 5|5| test 6|6| test 7|7| test 8|8| test 9|9| test (9rows)
postgres=# select*from test_part_2; id | id2 | info ----+-----+------ 10|10| test 11|11| test 12|12| test 13|13| test 14|14| test 15|15| test 1|16| test 2|17| test 3|18| test 4|19| test (10rows)