Postgresql学习笔记之——逻辑结构管理之表

一、表的创建

Postgresql数据库中,支持标准的创建表的语法,最简单的语法如下:

CREATE TABLE table_name(
col01_name data_type,
col02_name data_type,
col03_name data_type,
col04_name data_type
);

示例:

postgres=# create table tb_test(id int,name text,age text,sex boolean);
CREATE TABLE

一般在创建表时都需要主键,一个字段的主键直接在字段定义后面加上 “primary key” 关键字来指定表的主键,例如:

postgres=# create table tb_test01(id int primary key,name text,age text,sex boolean);
CREATE TABLE
postgres=# \d tb_test01
tb_test01       tb_test01_pkey  
postgres=# \d tb_test01
             Table "public.tb_test01"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | text    |           |          | 
 sex    | boolean |           |          | 
Indexes:
    "tb_test01_pkey" PRIMARY KEY, btree (id)
    
postgres=# \d tb_test01_pkey
    Index "public.tb_test01_pkey"
 Column |  Type   | Key? | Definition 
--------+---------+------+------------
 id     | integer | yes  | id
primary key, btree, for table "public.tb_test01"

PS:创建表带有主键时会生成相关的索引对象tb_test01_pkey

如果时两个及以上的字段组成主键,则称之为复合主键,复合主键就不能使用上面的语法,需要使用约束子句的语法,语法如下:

CONSTRAINT constraint_name PRIMARY KEY (col1_name, col2_name)

示例:

postgres=# create table tb_test02(pid int,cid int,name text,age text,sex boolean, constraint pk_test02_cid_pid primary key(pid, cid));
CREATE TABLE
postgres=# \d tb_test02
             Table "public.tb_test02"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 pid    | integer |           | not null | 
 cid    | integer |           | not null | 
 name   | text    |           |          | 
 age    | text    |           |          | 
 sex    | boolean |           |          | 
Indexes:
    "pk_test02_cid_pid" PRIMARY KEY, btree (pid, cid)

从示例中看除,约束子句放到了列定义的最后,与最后一个列的定义用逗号 “,” 隔开。

PS:约束有一下几种:
1.主键约束
2.唯一约束
3.check约束
4.非空约束
5.外键约束 具体后面会详细介绍。

除以上介绍的创建表的方式以外,还可用其他表为模板创建新表,例如:

postgres=# create table tb_people(id int primary key,name text,age int,sex boolean,addr text);
CREATE TABLE
postgres=# create table tb_kid(like tb_people);
CREATE TABLE
postgres=# \d tb_kid 
               Table "public.tb_kid"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 sex    | boolean |           |          | 
 addr   | text    |           |          | 

postgres=# \d tb_people
             Table "public.tb_people"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 sex    | boolean |           |          | 
 addr   | text    |           |          | 
Indexes:
    "tb_people_pkey" PRIMARY KEY, btree (id)

需要注意的是使用以上方式创建的表值时复制了表结构,表上的约束没有复制过来,如果要完全复制源表上的约束和其他信息,需要加上参数 “INCLUDING”,它附带的选项包括:

1.INCLUDING COMMENTS
复制的列、约束和索引的注释将被复制。默认的行为是排除注释,导致新表中复制的列和约束没有注释。

2.INCLUDING CONSTRAINTS
检查约束将被复制。列约束和表约束之间没有区别。非空约束总是复制到新表中。
CHECK constraints will be copied. No distinction is made between column constraints and table constraints. Not-null constraints are always copied to the new table.

3.INCLUDING DEFAULTS
复制的列定义的默认表达式将被复制。否则,将不会复制默认表达式,从而导致新表中复制的列具有空默认值。注意,复制调用数据库修改函数(如nextval)的默认值可能会在原始表和新表之间创建功能链接。

4.INCLUDING GENERATED
复制列定义的任何生成表达式都将被复制。默认情况下,新列将是常规的基本列。

5.INCLUDING IDENTITY
复制列定义的任何标识规范都将被复制。为新表的每个标识列创建一个新序列,与与旧表关联的序列分离。

6.INCLUDING INDEXES
原始表上的索引、主键、惟一和排除约束将在新表上创建。新索引和约束的名称是根据默认规则选择的,而不管原始索引和约束是如何命名的。(这种行为可以避免新索引可能出现的重复命名错误。)

7.INCLUDING STATISTICS
扩展的统计信息被复制到新表中。

8.INCLUDING STORAGE
复制的列定义的存储设置将被复制。默认行为是排除存储设置,导致新表中复制的列具有类型特定的默认设置。

9.INCLUDING ALL
包含所有选项是选择所有可用的单独选项的缩写形式。(在包含ALL之后再编写单独的exclude子句来选择除某些特定选项之外的ALL可能会有用。)

示例:

postgres=# create table tb_people(id int primary key,name text,age int,sex boolean,addr text);
CREATE TABLE
postgres=# create table tb_kid(like tb_people including all);
CREATE TABLE
postgres=# \d tb_people
             Table "public.tb_people"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 sex    | boolean |           |          | 
 addr   | text    |           |          | 
Indexes:
    "tb_people_pkey" PRIMARY KEY, btree (id)

postgres=# \d tb_kid
               Table "public.tb_kid"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 sex    | boolean |           |          | 
 addr   | text    |           |          | 
Indexes:
    "tb_kid_pkey" PRIMARY KEY, btree (id)

二、表的存储属性

介绍表的存储属性前,先说一下Postgresql中的TOAST技术。
TOAST-The Oversized-Attribute Storage Technique 的缩写,作用是用于存储一个大字段的值。

由于Postgresql的页面大小是固定的(通常是8KB),并且不允许数据行跨越多个页面,由此不可能直接在一个页面上存储非常大的字段值,为了突破这个限制,大的字段值通常被压缩或切片成多个物理行存储到另一张系统表中(TOAST表)。

在Postgresql中只有特定字段类型支持TOAST,像整数、浮点数等不太可能太大值的数据类型是不需要使用TOAST的。另外TOAST支持的数据类型必须是变长的。在变成类型中前4个字节(32bit)记录数值总长度(包括长度本身)。,称为长度字,长度字后面存储具体的内容或一个指针,TOAST采用最高的两个二进制位用于标识压缩与行外存储,后面的30bit是长度值,代表“大字段“的逻辑长度被限制在了1GB 。

最高的连个二级制标志位,一个表示是否压缩,一个表示是否是行外存储。
两个位都是零,表示数值未经过TOAST方式的数值;
第32位为1,表示该数值被压缩,使用前必须先解压缩;
第31位为1,表示该数值采用行外存储,此时只是存储着一个指针,该指针指向存储实际数据的TOAST表中。

另外30个位表示数据的实际尺寸,而不是解压缩或者从线外数据抓过来之后的逻辑尺寸。

行外数据被分裂成(如果压缩过,以压缩后为参考)最多TOAST_MAX_CHUNK_SIZE(这个数值略小于BLCKSZ的4分之一,或者缺省 2K字节)字节的块,每个块都作为独立的行在TOAST表里为所属表存储。每个TOAST表都有字段chunk_id(一个表示特定TOAST值的OID),chunk_seq(一个序列号,存储该块在数值中的位置)和 chunk_data(实际数据)。在chunk_id和chunk_seq上有一个唯一索引,提供对数值的快速检索。

只 有表中存储超过BLCKSZ的4分之一字节(通常是2Kb)的行才会触发,对字段进行压缩和行外存储,直到小于BLCKSZ小于4分之一字节,或者无法得到更好的结果的 时候才停止。UPDATE操作过程中,未改变的字段的数值通常原样保存;因此UPDATE行外存储的记录时,如果行外数据值没有变化,将不会带来 TOAST开销存在。
TOAST代码识别四种不同的存储可TOAST字段的策略:

PLAIN避免压缩或者行外存储。只对那些非TOAST数据类型才有效。

EXTENDED允许压缩和行外存储。大多数TOAST数据类型的缺省值。首先进行压缩,如果行仍然太大,则进行行外存储。

EXTERNAL允许行外存储,不许压缩。使用 EXTERNAL将令那些在 text 和 bytea 字段上的子字串操作更快(代价是增加了存储空间),因此这些操作是经过优化的:如果行外数据没有压缩,那么它们只抓取需要的部分。

MAIN允许压缩,不允许行外存储。当数据值压缩过后仍然太大将会采用行外存储。 每个可以 TOAST 的数据类型都为该数据类型的字段声明一个缺省策略,但是特定表的字段的存储策略可以用ALTER TABLE SET STORAGE修改。

优点:
相对直接的存储方式来说,数据经过TOAST方式后,单个或者连续数据块中能够存储更多的数据值,对于访问非“大字段”时,能够大量减少扫描块数或者物理IO次数;
对于极少访问的含“大字段”记录,经过手动修改存储属性,采用TOAST方式,即便值小于2K的情况下同样能够带来很好的效果。

示例:

1.先创建一张表:

postgres=# create table tb_people(id int primary key,name text,age int,sex boolean,addr text);
CREATE TABLE

2.查看表的属性:

postgres=# \d+ tb_people
                                 Table "public.tb_people"
 Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
--------+---------+-----------+----------+---------+----------+--------------+-------------
 id     | integer |           | not null |         | plain    |              | 
 name   | text    |           |          |         | extended |              | 
 age    | integer |           |          |         | plain    |              | 
 sex    | boolean |           |          |         | plain    |              | 
 addr   | text    |           |          |         | extended |              | 
Indexes:
    "tb_people_pkey" PRIMARY KEY, btree (id)
Access method: heap

有此看出表中列的数据类型中,interger默认TOAST策略为plain,而text为extended。PG资料告诉我们,如果表中有字段需要TOAST,那么系统会自动创建一张TOAST表负责行外存储,通过一下方式查看:

postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='tb_people';
  relname  | relfilenode | reltoastrelid 
-----------+-------------+---------------
 tb_people |       16547 |         16550
(1 row)

通过上诉语句,我们查到tb_people表的oid为16547 ,其对应TOAST表的oid为16550(关于oid和pg_class的概念,请参考PG官方文档),那么其对应TOAST表名则为:pg_toast.pg_toast_16547 (注意这里是tb_people表的oid),我们看下其定义:

postgres=# \d pg_toast.pg_toast_16547;
TOAST table "pg_toast.pg_toast_16547"
   Column   |  Type   
------------+---------
 chunk_id   | oid
 chunk_seq  | integer
 chunk_data | bytea

TOAST表有3个字段:

chunk_id:用来表示特定TOAST值的OID,可以理解为具有同样chunk_id值的所有行组成原表(这里的blog)的TOAST字段的一行数据

chunk_seq:用来表示该行数据在整个数据中的位置

chunk_data:实际存储的数据。

数据库表的查询:

1.查询一张表的关联关系及物理文件位置:
例如: mydb数据库下有一张test表,表属于test模式,表存放的表空间为tbs_test
查询表所在的物理位置:

select pg_relation_filepath('test');

pg_relation_filepath('test')
---------------------------------
pg_tblspc/16385/PG_11_201809051/16386/16416

其中pg_tblspc是数据库目录下的存放自定义表空间软连接文件的目录。
(1)16385是表test存放的表空间的oid(可以通过select oid,spcname from pg_tablespace where spcname=‘tbs_test’ 进行查询 )。
(2)16386是表test所在数据库database的oid(可以通过select oid,datname from pg_database where datname=‘test’ 查询到)。
(3)16416就是表test的oid。

2.查询表的大小:

select pg_size_pretty(pg_relation_size('test'::regclass));
 pg_size_pretty 
----------------
 8192 bytes
(1 row)

表的大小如果超出1G后,表的数据文件就变成、.1两个,也就是被分片存放了。另外在查询表物理位置时可以看到诸如 _fsm、_vm这样的文件,这两个分别是空闲空间映射表文件和可见性映射表文件,分别表示表中可用的空间及跟踪哪些页面只包含已知对所有活动事务可见的元祖,下图显示了Postgresql数据目录、表空间以及数据文件的结构概貌:
在这里插入图片描述

表其他存储属性

Postgresql的表中可以设定一下参数:fillfactor 和 toast.fillfactor
其中 fillfactor 为表的填充因子,toast.fillfactor是这个表的TOAST表的填充因子。

二、临时表

在Postgresql中有两类临时表,一种是会话级的临时表,在整个会话的整个生命周期中数据一直存在;一种是事务级的临时表,事务级别的临时表数据只存在于这个事务的生命周期中。

不管是会话级临时表还是事务级临时表,当会话结束时,临时表就会消失,这与Oracle不同,Oracle中临时表的数据会消失,但是表本身还会继续存在。

在不同会话中创建同一个名称的临时表,实际上是创建了不同的两个临时表,例如:

postgres=# create temporary table tb_temp01(id int primary key,name text);
CREATE TABLE
postgres=# \d
            List of relations
  Schema   |   Name    | Type  |  Owner   
-----------+-----------+-------+----------
 pg_temp_3 | tb_temp01 | table | postgres
 public    | tb_kid    | table | postgres
 public    | tb_mytest | table | postgres
 public    | tb_people | table | postgres
 public    | tb_public | table | postgres
 public    | tb_test   | table | postgres
 public    | tb_test01 | table | postgres
 public    | tb_test02 | table | postgres
(8 rows)

从上面可以看出,创建的临时表tb_temp01所属的schema名称为 “pg_temp_3” ,下面从新开启另一个psql会话,然后执行相同创建临时表的语句:

postgres=# create temporary table tb_temp01(id int primary key,name text);
CREATE TABLE
postgres=# \d
            List of relations
  Schema   |   Name    | Type  |  Owner   
-----------+-----------+-------+----------
 pg_temp_4 | tb_temp01 | table | postgres
 public    | tb_kid    | table | postgres
 public    | tb_mytest | table | postgres
 public    | tb_people | table | postgres
 public    | tb_public | table | postgres
 public    | tb_test   | table | postgres
 public    | tb_test01 | table | postgres
 public    | tb_test02 | table | postgres
(8 rows)

可以看出与另一个会话的区别:
1.执行\d命令时看不到另一个会话创建的临时表;
2.相同命令常见的临时表具有相同名称和字段,但是所属schema不同。
3.对比可以看出临时表创建时会赋予特殊的schema,名称为 “pg_temp_X ”,其中X代表一个数字。

另外,不同会话之间时无法访问其他会话的临时表的。默认创建的临时表时会话级的临时表。如果想要创建事务级临时表可以加 “ON COMMIT DELETE ROWS”,,例如:

postgres=# create temporary table tb_temp02(id int primary key,name text) on commit delete rows;
CREATE TABLE
postgres=# begin;
BEGIN
postgres=# insert into tb_temp02 values(1,'zhang');
INSERT 0 1
postgres=# insert into tb_temp02 values(2,'sun');
INSERT 0 1
postgres=# insert into tb_temp02 values(3,'Li');
INSERT 0 1
postgres=# select * from tb_temp02 ;
 id | name  
----+-------
  1 | zhang
  2 | sun
  3 | Li
(3 rows)

postgres=# end;
COMMIT
postgres=# select * from tb_temp02 ;
 id | name 
----+------
(0 rows)

示例中看出,事务结束,临时表中的数据已经消失。

实际上 “ON COMMIT” 子句有以下三种形式:

1.ON COMMIT PRESERVE ROWS : 若不带 on commit 子句,默认情况下,数据会一致存在于整个会话周期中。

2.ON COMMIT DELETE ROWS :数据只存在于事务周期中,事务一提交,数据就会消失。

3.ON COMMIT DROP :数据只存在于事务周期中,事务一提交,临时表就消失,这种情况下就不同于示例中那种临时表的创建方式,创建表语句和插入数据语句必须都在一个事务周期中。

PS:
1.创建临时表的关键字 “TEMPARARY” 可以简写为 “TEMP”:
2.Postgresql为了与其他类型数据库保持兼容性,同样支持关键字 LOCAL和GLOBAL,但是这两个关键词没有作用。

3.默认值

在创建表时候,可以为一个字段指定默认值,如果插入一行数据,设置默认值的列没有插入数据,那么它会自动填充设置的默认值。例如:

创建一个员工表,其中的城市 city 字段默认值为 “北京”,如果插入一条员工信息,没有指定所属城市,“city”字段自动设定为“北京”:

postgres=# create table tb_emp(id int,name text,age int,city text default '北京');
CREATE TABLE
postgres=# \d tb_emp 
                 Table "public.tb_emp"
 Column |  Type   | Collation | Nullable |   Default    
--------+---------+-----------+----------+--------------
 id     | integer |           |          | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 city   | text    |           |          | '北京'::text

postgres=# insert into tb_emp values(1,'zhang',23,'河北');
INSERT 0 1
postgres=# insert into tb_emp values(2,'Lee',30);
INSERT 0 1
postgres=# select * from tb_emp ;
 id | name  | age | city 
----+-------+-----+------
  1 | zhang |  23 | 河北
  2 | Lee   |  30 | 北京
(2 rows)

PS:
1.默认值设置的关键词为“DEFAULT”;
2.数据更新时可以指定为default,例如:
UPDATE tb_emp SET city=default where id=1;
3.默认值可以是一个表达式。

四、约束

Postgresql中约束有以下几类:主键约束、检查约束、非空约束、唯一约束、外键约束。

主键约束

常见的约束类型,一般在数据库创建表时都需要在主键列上创建主键,主键创建后会在指定的列上生成主键约束。

主键约束其实是两种约束合并后的约束,分别是唯一约束和非空约束,创建后,主键列的值必须唯一而且不能为空。

检查约束

检查约束也是常见的约束类型,在指定的列上创建后,字段的值必须满足检查约束中的条件才能允许数据插入表中,不然会报错,例如:

postgres=# create table tb_test03(id int primary key,name text,age int CHECK(age>=0 and age<=150));
CREATE TABLE
postgres=# \d tb_test03
             Table "public.tb_test03"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
Indexes:
    "tb_test03_pkey" PRIMARY KEY, btree (id)
Check constraints:
    "tb_test03_age_check" CHECK (age >= 0 AND age <= 150)

postgres=# insert into tb_test03 values(1,'zhang',23);
INSERT 0 1
postgres=# insert into tb_test03 values(2,'Lee',-1);
ERROR:  new row for relation "tb_test03" violates check constraint "tb_test03_age_check"
DETAIL:  Failing row contains (2, Lee, -1).

以上是一种简单的约束设置方式,除此之外还可以给约束指定一个名字,这样方便修改约束或者报错时根据约束名称更快定位错误原因。

create table tb_test03(
id int primary key,
name text,
age int constraint check_test03_age CHECK(age>=0 and age<=150));

检查约束可以同时指定多个字段进行配置,例如:

CREATE TABLE books(
id int primary key,
name text,
price numeric CHECK(price > 0),
discounted_price numeric CHECK(discounted_price > 0),
CHECK (price > discounted_price)
);

以上示例中对于书的价格price和discounted_price分别设置检查约束,可以称此类为字段约束,第三个CHECK定义了价格price必须大于打折价discounted_price,称此类约束为表约束。

在一些其他的数据库中,字段约束可以写成表约束,但是反过来很可能就会报错,因为系统会认为字段约束只会引用它所从属的字段。虽然在Postgresql中没有这种限制,但是建议在定义时要分别定义,一面出现兼容错误。

多字段约束也可以写成如下格式:

CREATE TABLE books(
id int primary key,
name text,
price numeric,
discounted_price numeric,
 CHECK(price > 0)CHECK(discounted_price > 0),
CHECK (price > discounted_price)
);

或者:

CREATE TABLE books(
id int primary key,
name text,
price numeric,
discounted_price numeric,
CHECK (price > 0 and discounted_price > 0 and price > discounted_price)
);

当然,每个约束都可以定义属于自己的名称。

PS:对于检查约束,如果填充到字段中的值为NULL,检查约束也是满足条件的。如果要确保值没有NULL,可以加非空约束。

非空约束

非空约束就是简单的限制表的字段不能出现空值NULL,例如:

create table tb_test03(
id int primary key,
name text not null,
age int
);

非空约束一般会写成字段约束,它也可以等效于检查约束:

CHECK(name is not null)

如果想要加多个约束,只要一个接着一个写就可以了,没有顺序限制:

create table tb_test03(
id int primary key,
name text,
age int not null CHECK(age>0)
);
唯一约束

唯一约束限制了表中的一个列或者多个列的值都是唯一的,没有重复值。

CREATE TABLE books(
id int primary key,
name text UNIQUE,
price numeric
);

或者:

CREATE TABLE books(
id int primary key,
name text,
price numeric,
UNIQUE(name)
);
外键约束

外键约束时表之间关系的一种约束,用于约束本表的一个或者多个字段的值必须出现在另一个表的一个或多个字段中,也可以称这种约束为两个表之间的参照完整性约束。

例如 “学生表” 与 “班级表” 之间的关系,一个学生必定是属于一个班级:

postgres=# create table class(id int primary key,class_no int unique,class_name text);
CREATE TABLE
postgres=# create table students(id int primary key,name text,age int,cls_no int REFERENCES class(class_no));
CREATE TABLE
postgres=# \d students
              Table "public.students"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 cls_no | integer |           |          | 
Indexes:
    "students_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "students_cls_no_fkey" FOREIGN KEY (cls_no) REFERENCES class(class_no)

postgres=# \d class
                 Table "public.class"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 id         | integer |           | not null | 
 class_no   | integer |           |          | 
 class_name | text    |           |          | 
Indexes:
    "class_pkey" PRIMARY KEY, btree (id)
    "class_class_no_key" UNIQUE CONSTRAINT, btree (class_no)
Referenced by:
    TABLE "students" CONSTRAINT "students_cls_no_fkey" FOREIGN KEY (cls_no) REFERENCES class(class_no)


注意:

1.创建students表时,因为外键依赖于class表,所以先创建class表,
2.在向students表中插入数据时,cls_no列的值必须在class表的class_no列中存在,不然会报错。

五、修改表

对已经创建好的表可以通过 “ALTER TABLE” 命令根据实际需求进行修改,主要包括:

增加字段;
删除字段;
添加约束;
删除约束;
修改默认值;
修改字段数据类型;
重命名字段;
重命名表。

增加字段

对学生表 “students” 添加一个性别 “sex” 字段:

postgres=# \d students
              Table "public.students"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 cls_no | integer |           |          | 
Indexes:
    "students_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "students_cls_no_fkey" FOREIGN KEY (cls_no) REFERENCES class(class_no)
    
postgres=# ALTER TABLE students ADD COLUMn sex boolean;
ALTER TABLE
postgres=# \d students
              Table "public.students"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 age    | integer |           |          | 
 cls_no | integer |           |          | 
 sex    | boolean |           |          | 
Indexes:
    "students_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "students_cls_no_fkey" FOREIGN KEY (cls_no) REFERENCES class(class_no)

当然也可以在添加字段是加上字段的约束。

删除字段

删除字段的命令如下:

ALTER TABLE students DROP COLUMN sex;

在删除字段是,字段上的数据和约束都会删除点,如果这个字段的值被另外一个表的字段外键引用,删除时会报错,想要删除,加上关键词 “CASCODE” :

ALTER TABLE students DROP COLUMN sex CASCODE;
增加约束

增加约束语句:

ALTER TABLE students ADD CHECK(age>0);
ALTER TABLE students ADD CONSTRAINT stu_check_age CHECK(age>0);

非空约束的增加有点不一样:

ALTER TABLE students ALTER COLUMN name SET NOT NULL;

在添加约束时要注意一点,约束所在的列的值要符合指定的约束,不然会报错,导致约束添加失败。

删除约束

示例:

ALTER TABLE students DROP CONSTRAINT stu_check_age ;

删除约束时需要指定约束具体名字,可以通过\d命令查看表的约束对应的名字。

注意删除非空约束时,因为非空约束没有名字,所以需要用一下语句进行删除:

ALTER TABLE students ALTER COLUMN age DROP NOT NULL;
修改默认值

要给指定字段设置默认值,使用如下命令:

ALTER TABLE students ALTER COLUMN age SET DEFAULT 18;

注意添加默认值时,对现有的列的值不影响,值时对以后的insert有影响。

删除默认值的命令如下示例:

ALTER TABLE students ALTER COLUMN age DROP DEFAULT;

实际上删除默认值,就是将默认值设为NULL,需要注意的时,如果删除默认值的列上并未设置默认值,以上语句也不会报错。

修改字段数据类型

修改字段数据类型的语法如下:

ALTER TABLE students ALTER COLUMN age TYPE text;

在修改数据类型是,字段中的值要符合新数据类型,不然会报错,例如将字符串字段数据类型修改成int数据类型,如果字段值都是整数则转换没有问题,如果有字母则会报错,还有如果修改的时字段属性长度,例如varchar(10)改成varchar(8),如果字段中有数据库长度超过8,命令仍会报错。

Postgresql在进行字段数据类型转换时,字段上的约束也会相应转换,但有时也会转换失败或出现奇怪结果,所以在修改字段数据类型前,建议删除字段上的约束后进行。修改完成后在加上约束。

重命名字段、表

重命名字段:

ALTER TABLE students RENAME COLUMN name TO stu_name;

重命名表:

ALTER TABLE class RENAME TO classes;
六、表继承及分区表
表继承

表继承时Postgresql中特有的。假设有一个表 “persons” :

postgres=# create table persons(id int primary key,name text,sex boolean,age int);
CREATE TABLE
postgres=# create table employee( emp_no  int ) INHERITS (persons);
CREATE TABLE
postgres=# \d persons
              Table "public.persons"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 sex    | boolean |           |          | 
 age    | integer |           |          | 
Indexes:
    "persons_pkey" PRIMARY KEY, btree (id)
Number of child tables: 1 (Use \d+ to list them.)

postgres=# \d employee
              Table "public.employee"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           |          | 
 sex    | boolean |           |          | 
 age    | integer |           |          | 
 emp_no | integer |           |          | 
Inherits: persons

向employee中插入几条数据,然后查看两个表数据情况:

postgres=# insert into employee values(1,'zhang',true,23,0210);
INSERT 0 1
postgres=# insert into employee values(2,'Lee',true,27,0211);
INSERT 0 1
postgres=# insert into employee values(3,'Smith',true,22,0213);
INSERT 0 1
postgres=# select * from persons;
 id | name  | sex | age 
----+-------+-----+-----
  1 | zhang | t   |  23
  2 | Lee   | t   |  27
  3 | Smith | t   |  22
(3 rows)

postgres=# select * from employee ;
 id | name  | sex | age | emp_no 
----+-------+-----+-----+--------
  1 | zhang | t   |  23 |    210
  2 | Lee   | t   |  27 |    211
  3 | Smith | t   |  22 |    213
(3 rows)

以上示例可以看出出入employee表数据,它所继承的父表persons同样会插入数据。
同样如果修改employee表中的数据库,persons表中数据也会更改:

postgres=# update employee set sex=false where id=2;
UPDATE 1
postgres=# select * from employee ;
 id | name  | sex | age | emp_no 
----+-------+-----+-----+--------
  1 | zhang | t   |  23 |    210
  3 | Smith | t   |  22 |    213
  2 | Lee   | f   |  27 |    211
(3 rows)

postgres=# select * from persons ;
 id | name  | sex | age 
----+-------+-----+-----
  1 | zhang | t   |  23
  3 | Smith | t   |  22
  2 | Lee   | f   |  27
(3 rows)

但是插入数据到persons表,那employee表则不会变化,例如:

postgres=# insert into persons values(4,'SCOTT',true,30);
INSERT 0 1
postgres=# select * from persons ;
 id | name  | sex | age 
----+-------+-----+-----
  4 | SCOTT | t   |  30
  1 | zhang | t   |  23
  3 | Smith | t   |  22
  2 | Lee   | f   |  27
(4 rows)

postgres=# select * from employee ;
 id | name  | sex | age | emp_no 
----+-------+-----+-----+--------
  1 | zhang | t   |  23 |    210
  3 | Smith | t   |  22 |    213
  2 | Lee   | f   |  27 |    211
(3 rows)

如果想查询父表中有的数据,而employee表中没有的数据,使用关键词 “ONLY” :

postgres=# select * from ONLY persons;
 id | name  | sex | age 
----+-------+-----+-----
  4 | SCOTT | t   |  30
(1 row)

注意:

1.所有父表的检查约束和非空约束都会自动被所有子表继承,不过其他类型(唯一、主键、外键)则不会被继承。

2.一个表可以继承多个父表,这种情况下它拥有所有父表字段总和,并且子表自己定义字段也在其中。

3.如果继承的父表中有相同名称的字段或者子表定义的字段与父表有相同名称的,那么这些字段就会被融合,子表显示一个字段。如果融合字段数据类型不同则会创建时报错。融合字段会继承所有父表对应字段的检查和非空约束。

4.采用SELECT、UPDATE、DELETE等命令操作父表时,也会同时访问或操作相应的子表,而是用ALTER TABLE 修改父表结构定义时,大多数情况下也会同时修改子表的结构定义,但是 “REINDEX” 、“VACUUM” 命令不会影响到子表。

5.唯一约束、外键约束的使用域也不会扩大到子表上。

分区表

在此简单介绍一下通过继承方式创建的分区表,适用版本为Postgresql 9.X版本(详细的分区表会单独写一遍介绍),表分区是把逻辑上的一个大表分割成物理上的几个小块。分区的好处在于:

1.删除历史数据更快,比如按照时间分区的表,删除历史数据可以直接删除历史分区即可,如果不是分区表,在使用delete删除时会特别的慢,而且数据量过大会导致VACUUM超载。

2.某些类型的查询性能可以得到极大的提升,特别是表中访问率比较高的数据行唯一一个或者少数几个分区上的情况。

3.当查询或更新一个分区的大部分记录时,连续扫描那个分区而不是使用索引离散地访问整个表,可以获得较大的性能提升。

4.很少查询使用的历史数据可以通过不同的表空间存储位置,存放到一般存储介质上。

判断什么时候用分区表,一般会在表的大小超过了数据库服务器物理内存大小的时候使用。使用继承实现分区表时,一般会让父表为空,数据都存储在子表中。

创建分区表的步骤如下:

1.创建“父表”,所有分区都从它继承。这个表中没有数据,建议不要在父表上定义任何检查约束,除非需要约束所有分区。同样,建议不要定义任何索引和唯一约束。

2.创建几个“子表”,每个都是从父表上继承,通常,这些字表不会增加任何字段。这些字表就可以成为分区,实际还是PG的普通表。

3.给分区子表增加约束,定义每个分区允许的键值。

4.对于每个分区,在关键字字段上创建一个索引(如果需要字段值唯一,可添加唯一索引),其他字段也可以根据实际需要田间索引。

5.定义一个规则或者触发器,把对主表(父表)的数据插入重定向到合适的分区表上。

6.确保在配置文件postgresql.conf里的参数 constraint_exclusion 是打开的,打开后如果查询的where子句中的过滤条件与分区的约束条件匹配,那么这个查询会只能的只查询这个分区,而不会查询其他分区。

示例:
一、使用函数触发器的方式定位数据插入。

1.创建父表-销售明细,然后创建分区表(子表)以成父表,按照销售日期来进行分区,每个月为一个分区子表:

postgres=# create table sales_detail(product_in int not null, price numeric(12,2),amount int not null,sale_date date not null,buyer varchar(40),buyer_contact text);
CREATE TABLE
postgres=# create table sales_detail_y2020m01 (check (sale_date >= date '2020-01-01' and sale_date < date '2020-02-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m02 (check (sale_date >= date '2020-02-01' and sale_date < date '2020-03-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m03 (check (sale_date >= date '2020-03-01' and sale_date < date '2020-04-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m04 (check (sale_date >= date '2020-04-01' and sale_date < date '2020-05-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m05 (check (sale_date >= date '2020-05-01' and sale_date < date '2020-06-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m06 (check (sale_date >= date '2020-06-01' and sale_date < date '2020-07-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m07 (check (sale_date >= date '2020-07-01' and sale_date < date '2020-08-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m08 (check (sale_date >= date '2020-08-01' and sale_date < date '2020-09-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m09 (check (sale_date >= date '2020-09-01' and sale_date < date '2020-10-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m10 (check (sale_date >= date '2020-10-01' and sale_date < date '2020-11-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m11 (check (sale_date >= date '2020-11-01' and sale_date < date '2020-12-01')) inherits (sales_detail);
CREATE TABLE
postgres=# create table sales_detail_y2020m12 (check (sale_date >= date '2020-12-01' and sale_date < date '2021-01-01')) inherits (sales_detail);
CREATE TABLE

2.每个分区都是一张完整的从父表sales_detail中继承定义的,实际上父表sales_detail中是不存放数据的,通过存储过程或者触发器将存放到sales_detail(父表)中的数据,分别插入各个分区子表中,每个分区子表中增加了一个检查约束,直插入属于本月内的数据。

为了查询或者插入时提升性能,还可以在各个分区子表上的 sale_date字段上创建索引:

postgres=# create index sales_detail_y2020m01_sale_date on sales_detail_y2020m01(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m02_sale_date on sales_detail_y2020m02(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m03_sale_date on sales_detail_y2020m03(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m04_sale_date on sales_detail_y2020m04(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m05_sale_date on sales_detail_y2020m05(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m06_sale_date on sales_detail_y2020m06(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m07_sale_date on sales_detail_y2020m07(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m08_sale_date on sales_detail_y2020m08(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m09_sale_date on sales_detail_y2020m09(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m10_sale_date on sales_detail_y2020m10(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m11_sale_date on sales_detail_y2020m11(sale_date);
CREATE INDEX
postgres=# create index sales_detail_y2020m12_sale_date on sales_detail_y2020m12(sale_date);
CREATE INDEX

当然根据实际需要还可以在其他字段上添加索引。

3.在插入数据前要能将数据自动正确的插入各个对应日期的分区子表,需要创建一个触发器:

# 创建函数
create or replace function sales_detail_insert_trigger()
returns trigger as $$
BEGIN
	IF (new.sale_date >= date '2020-01-01' and new.sale_date < date '2020-02-01' ) then
		insert into sales_detail_y2020m01 values(new.*);
	ELSIF (new.sale_date >= date '2020-02-01' and new.sale_date < date '2020-03-01' ) then
		insert into sales_detail_y2020m02 values(new.*);
	ELSIF (new.sale_date >= date '2020-03-01' and new.sale_date < date '2020-04-01' ) then
		insert into sales_detail_y2020m03 values(new.*);
	ELSIF (new.sale_date >= date '2020-04-01' and new.sale_date < date '2020-05-01' ) then
		insert into sales_detail_y2020m04 values(new.*);
	ELSIF (new.sale_date >= date '2020-05-01' and new.sale_date < date '2020-06-01' ) then
		insert into sales_detail_y2020m05 values(new.*);
	ELSIF (new.sale_date >= date '2020-06-01' and new.sale_date < date '2020-07-01' ) then
		insert into sales_detail_y2020m06 values(new.*);
	ELSIF (new.sale_date >= date '2020-07-01' and new.sale_date < date '2020-08-01' ) then
		insert into sales_detail_y2020m07 values(new.*);
	ELSIF (new.sale_date >= date '2020-08-01' and new.sale_date < date '2020-09-01' ) then
		insert into sales_detail_y2020m08 values(new.*);
	ELSIF (new.sale_date >= date '2020-09-01' and new.sale_date < date '2020-10-01' ) then
		insert into sales_detail_y2020m09 values(new.*);
	ELSIF (new.sale_date >= date '2020-10-01' and new.sale_date < date '2020-02-01' ) then
		insert into sales_detail_y2020m10 values(new.*);
	ELSIF (new.sale_date >= date '2020-11-01' and new.sale_date < date '2020-02-01' ) then
		insert into sales_detail_y2020m11 values(new.*);
	ELSIF (new.sale_date >= date '2020-12-01' and new.sale_date < date '2021-01-01' ) then
		insert into sales_detail_y2020m12 values(new.*);
	ELSE
		raise exception 'Date out of range,fix the sales_detail_insert_trigger() function !';
	end if;
	return null;
END;
$$ LANGUAGE plpgsql;

# 创建触发器
CREATE TRIGGER insert_sales_detail_trigger
	BEFORE INSERT ON sales_detail
	FOR EACH ROW EXECUTE PROCEDURE sales_detail_insert_trigger();

然后向表中插入测试数据:

# 常见生成随机数的函数
CREATE OR REPLACE FUNCTION get_random_number(integer, integer) RETURNS integer AS
$BODY$
DECLARE
	start_int ALIAS FOR $1;
	end_int ALIAS FOR $2;
BEGIN
	RETURN trunc(random() * (end_int-start_int + 1) + start_int);
END;
$BODY$
LANGUAGE plpgsql;

# 使用方式如下:
SELECT get_random_number(1, 10);

# 创建生成类似日期型的整数的函数
CREATE OR REPLACE FUNCTION get_random_date(start_date date, end_date date) RETURNS integer AS
$BODY$
DECLARE
	interval_days integer;
	random_days integer;
	random_date date;
BEGIN
	interval_days := end_date - start_date;
	random_days := get_random_number(0, interval_days);
	random_date := start_date + random_days;
	RETURN date_part('year', random_date) * 10000 + date_part('month', random_date) * 100 + date_part('day', random_date);
END;
$BODY$
LANGUAGE plpgsql;

#使用方式如下
select get_random_date('2020-01-01', '2020-12-31');

integer
---------
20200210

# 转换成日期方式如下:
select cast(get_random_date('2020-01-01', '2020-12-31') as varchar)::date;
date
------
2020-02-10

# 插入测试数据的insert语句(多次执行插入更多测试数据)
insert  into sales_detail select get_random_number(100, 300),get_random_number(170, 1222),get_random_number(1, 100),cast(get_random_date('2020-01-01', '2020-12-31') as varchar)::date,'zhang'||get_random_number(1, 3000),'zhang'||get_random_number(1, 3000) from generate_series(1,100);

数据可以看到被按照日期分别插入了分区子表中。但同样在父表中也插入了所有数据。

另外,如果删除了分区子表sales_detail_y2020m06 ,触发器函数引用了这张表,但是不会导致触发器失效,数据再插入时,如果没有6月份的数据,函数、触发器正常使用,如果有就会报错会提示sales_detail_y2020m06 不存在,导致插入数据失败。

2.使用Postgresql中的规则来定位数据插入

规则创建方式:

CREATE RULE sales_detail_insert_y2020m01 AS
ON INSERT TO sales_detail WHERE
( sale_date >= date ‘2020-01-01’ AND sale_date < date ‘2020-02-01’ )
DO INSTEAD
INSERT INTO sales_detaily2020m01 VALUES (NEW.*);

CREATE RULE sales_detail_insert_y2020m02 AS
ON INSERT TO sales_detail WHERE
( sale_date >= date ‘2020-02-01’ AND sale_date < date ‘2020-03-01’ )
DO INSTEAD
INSERT INTO sales_detaily2020m02 VALUES (NEW.*);

CREATE RULE sales_detail_insert_y2020m12 AS
ON INSERT TO sales_detail WHERE
( sale_date >= date ‘2020-12-01’ AND sale_date < date ‘2021-01-01’ )
DO INSTEAD
INSERT INTO sales_detaily2020m12 VALUES (NEW.*);

使用规则方式重定位数据的插入,有以下缺点:

1.相较于触发器,规则有着明显多的开销,每次检查都会有。不过对于批量数据插入时规则只会生成一次开销,那样就比触发器消耗更低。

2.对于使用COPY命令来进行数据的插入,规则是不会触发的,而触发器可以正常使用。

3.插入的数据超出范围,触发器会报错,但是规则不会,规则会将数据插入主表,分区子表不会插入。

继承式的分区优化方式

打开约束排除(constraint_exclusion)也一种查询优化技巧,在PG中如果参数 constraint_exclusion 时默认值 partition,在SQL查询中将where子句的过滤条件与表上的CHECK条件进行对比,就会跳过相应的分区表,提高性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Major_ZYH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值