PostgreSQL 从入门到出门 第 6 篇 管理数据表

版权声明:本站不全为博主原创文章,欢迎转载,转载记得标明出处。^-^ https://blog.csdn.net/horses/article/details/87168403

在关系型数据库中,用于存储数据的对象主要是表(table)。在上一篇中,我们已经了解到,表包含在模式中,模式包含在数据库中。接下来我们介绍如何管理数据库中的表,包括创建表、修改表以及删除表等操作。

创建表

在 PostgreSQL 中,使用CREATE TABLE语句创建一个新表:

CREATE TABLE table_name
(
  column_name data_type column_constraint,
  column_name data_type,
  ...,
  table_constraint
);

该语句包含以下内容:

  • 首先,table_name 指定了新表的名称。
  • 括号内是字段的定义,column_name 是字段的名称,data_type 是它的类型,column_constraint 是可选的字段约束;多个字段使用逗号进行分隔。
  • 最后,table_constraint 是可选的表级约束。

来看一个具体的例子:

CREATE TABLE departments
    ( department_id    INTEGER NOT NULL PRIMARY KEY
    , department_name  CHARACTER VARYING(30) NOT NULL
    ) ;

以上语句创建了一个新的部门表(departments)。它包含两个字段,部门编号(department_id)是一个整数类型(INTEGER),字段的值不可以为空(NOT NULL),同时它还是这个表的主键(PRIMARY KEY);部门编号(department_name)是一个可变长度的字符串,也不允许为空。

PostgreSQL 提供了丰富的内置数据类型,同时还允许用户自定义数据类型。最常见的基本数据类型包括:

  • 字符类型,包括定长字符串 CHAR(n),变长字符串 VARCHAR(n),以及支持更大长度的字符串 TEXT。
  • 数字类型,包括整数类型 SMALLINT、INTEGER、BIGINT,精确数字 NUMERIC (p, s),浮点数 REAL、DOUBLE PRECISION。
  • 时间类型,包括日期 DATE、时间 TIME、时间戳 TIMESTAMP 。

更多详细的数据类型介绍可以查看官方文档

PostgreSQL 支持 SQL 标准中的所有字段约束和表约束。其中,字段约束包括:

  • NOT NULL,非空约束,该字段的值不能为空(NULL)。
  • UNIQUE,唯一约束,该字段每一行的值不能重复。不过,PostgreSQL 允许该字段存在多个 NULL 值,并且将它们看作不同的值。需要注意的是 SQL 标准只允许 UNIQUE 字段中存在一个 NULL 值。
  • PRIMARY KEY ,主键约束,包含了 NOT NULL 约束和 UNIQUE 约束。如果主键只包含一个字段,可以通过列级约束进行定义(参考上面的示例);但是如果主键包含多个字段(复合主键)或者需要为主键指定一个自定义的名称,需要使用表级约束进行定义(参见下文示例)。
  • REFERENCES,外键约束,字段中的值必需已经在另一个表中存在。外键用于定义两个表之间的参照完整性(referential integrity),例如,员工的部门编号字段必须是一个已经存在的部门。
  • CHECK,检查约束,插入或更新数据时检查数据是否满足某个条件。例如,产品的价格必需大于零。
  • DEFAULT,默认值,插入数据时,如果没有为这种列指定值,系统将会使用默认值代替。

表级约束和字段约束类似,只不过它是基于整个表定义的约束,还能够为约束指定自定义的名称。PostgreSQL 支持的表级约束包括:

  • UNIQUE (column1, …),唯一约束,括号中的字段值或字段值的组合必须唯一。
  • PRIMARY KEY(column1, …),主键约束,定义主键或者复合主键。
  • REFERENCES,定义外键约束。
  • CHECK,定义检查约束。

以下示例创建了员工表(employees):

CREATE TABLE employees
    ( employee_id    INTEGER NOT NULL
    , first_name     CHARACTER VARYING(20)
    , last_name      CHARACTER VARYING(25) NOT NULL
    , email          CHARACTER VARYING(25) NOT NULL
    , phone_number   CHARACTER VARYING(20)
    , hire_date      DATE NOT NULL
    , salary         NUMERIC(8,2)
    , commission_pct NUMERIC(2,2)
    , manager_id     INTEGER
    , department_id  INTEGER
	, CONSTRAINT     emp_emp_id_pk
                     PRIMARY KEY (employee_id)
    , CONSTRAINT     emp_salary_min
                     CHECK (salary > 0) 
    , CONSTRAINT     emp_email_uk
                     UNIQUE (email)
	, CONSTRAINT     emp_dept_fk
                     FOREIGN KEY (department_id)
                      REFERENCES departments(department_id)
    , CONSTRAINT     emp_manager_fk
                     FOREIGN KEY (manager_id)
                      REFERENCES employees(employee_id)
    ) ;

员工表包含以下字段和约束:

  • employee_id,员工编号,整数类型,主键(通过表级约束为主键指定了名称 emp_emp_id_pk);
  • first_name,名,字符串;
  • last_name,姓,字符串,不能为空;
  • email,电子邮箱,字符串,不能为空,必须唯一(emp_email_uk);
  • phone_number,电话号码,字符串;
  • hire_date,雇佣日期,日期类型,不能为空;
  • salary,薪水,数字类型,必须大于零(emp_salary_min);
  • commission_pct,佣金百分比,数字类型;
  • manager_id,经理编号,外键(通过外键 emp_manager_fk 引用员工表的员工编号);
  • department_id,部门编号,外键(通过外键 emp_dept_fk 引用部门表 departments 的编号 department_id)

下图是这两个表的实体关系图:

E-R
除了自己定义表的结构之外,PostgreSQL 还提供了另一个创建表的方法,就是通过一个查询的结果创建新表:

CREATE TABLE table_name
AS query;

或者:

SELECT ...
INTO new_table
FROM ...;

例如,我们可以基于 employees 复制出两个新的表:

CREATE TABLE emp1
AS
SELECT *
FROM employees;

SELECT *
INTO emp2
FROM employees;

这种方法除了复制表结构之外,还可以复制数据。具体说明请参考官方文档中的 CREATE TABLE AS 语句和 SELECT INTO 语句。

模式搜索路径

在 PostgreSQL 中,表属于某个模式(schema)。当我们创建表时,更完整的语法应该是:

CREATE TABLE schema_name.table_name
...

访问表的时候也是一样。但是我们在前面创建示例表的时候,并没有加上模式名称的限定。这里涉及到一个模式的搜索路径概念。

我们先看一下当前的搜索路径:

testdb=> SHOW search_path;
   search_path   
-----------------
 "$user", public
(1 row)

搜索路径是一个逗号分隔的模式名称。当我们使用表的时候,PostgreSQL 会依次在这些模式中进行查找,返回第一个匹配的表名;当我们创建一个新表时,如果没有指定模式名称,PostgreSQL 会在第一个模式中进行创建。

第一个模式默认为当前用户名,如果不存在该模式,使用后面的公共模式(public)。

testdb=> SELECT user;
 user 
------
 tony
(1 row)

testdb=> \dn
  List of schemas
  Name  |  Owner   
--------+----------
 app    | tony
 public | postgres
(2 rows)

当前用户名为 tony,但是不存在名为 tony 的模式,因此我们创建的表会位于 public 模式中。

testdb=> \d
          List of relations
 Schema |    Name     | Type  | Owner 
--------+-------------+-------+-------
 public | departments | table | tony
 public | emp1        | table | tony
 public | emp2        | table | tony
 public | employees   | table | tony
(4 rows)

我们可以通过SET命令修改默认的搜索路径:

SET search_path TO app,public;

此时,如果我们再创建新表而不指定模式名称时,默认会在模式 app 中创建。

除了表之外,其他的模式对象,例如索引、函数、类型等等,也遵循相同的原则。

如果想要了解更多关于模式的信息,可以参考官方文档

修改表

当我们创建好一个表之后,可能会由于业务变更或者其他原因需要修改它的结构。PostgreSQL 使用ALTER TABLE语句修改表的定义:

ALTER TABLE name action;

其中的 action 表示要执行的操作。常见的修改操作包括:

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

添加字段

为表添加一个字段的命令如下:

ALTER TABLE table_name
ADD COLUMN column_name data_type column_constraint;

添加字段与创建表时的字段选项相同,包含字段名称、字段类型以及可选的约束。

假设我们已经创建了一个产品表 products:

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

通过以下语句为产品表增加一个新的字段 description:

ALTER TABLE products ADD COLUMN description text;

对于表中已经的数据,新增加的列将会使用默认值进行填充;如果没有指定 DEFAULT 值,使用空值填充。

添加字段时还可以定义约束。不过需要注意的是,如果表中已经存在数据,新增字段的默认值有可能会违反指定的约束。

-- 插入一行数据
testdb=> INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', 9.99);
INSERT 0 1

testdb=> ALTER TABLE products ADD COLUMN notes text not null;
ERROR:  column "notes" contains null values        

以上语句出错的原因在于新增的字段 desc 存在非空约束,但是对于已有的数据该字段的值为空。解决的方法有两个:添加约束的同时指定一个默认值;添加字段时不指定约束,将所有数据的字段值手动填充(UPDATE)之后,再添加约束。

以下语句为新增的字段指定了一个默认值:

testdb=> ALTER TABLE products ADD COLUMN notes text DEFAULT 'new product' not null;
ALTER TABLE

testdb=> SELECT * FROM products;
 product_no |  name  | price | description |    notes    
------------+--------+-------+-------------+-------------
          1 | Cheese |  9.99 |             | new product
(1 row)

删除字段

删除一个字段的语句如下:

ALTER TABLE table_name
DROP COLUMN column_name;

我们将产品表中的 notes 字段删除:

estdb=> \d products;
                      Table "public.products"
   Column    |  Type   | Collation | Nullable |       Default       
-------------+---------+-----------+----------+---------------------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 
 description | text    |           |          | 
 notes       | text    |           | not null | 'new product'::text
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_description_check" CHECK (description <> ''::text)

testdb=> ALTER TABLE products DROP COLUMN notes;
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)

删除字段后,相应的数据也会自动删除。同时,该字段上的索引或约束(products_description_check)也会同时被删除。但是,如果该字段被其他对象(例如外键引用、视图、存储过程等)引用,无法直接删除。

testdb=> ALTER TABLE departments DROP COLUMN department_id;
ERROR:  cannot drop column department_id of table departments because other objects depend on it
DETAIL:  constraint emp_dept_fk on table employees depends on column department_id of table departments
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

由于 departments 表的 department_id 是 employees 表的外键引用列,无法直接删除该字段。通过提示可以看出,在DROP的最后加上CASCADE选项即可级联删除依赖的对象。

testdb=> ALTER TABLE departments DROP COLUMN department_id CASCADE;
NOTICE:  drop cascades to constraint emp_dept_fk on table employees
ALTER TABLE

字段 department_id 被删除,同时 employees 表中的外键也被级联删除。

添加约束

添加约束时通常使用表级约束语法:

ALTER TABLE table_name ADD table_constraint;

其中,table_constraint 可以参考前文。

以下是为产品表 products 增加约束的一些示例:

testdb=> ALTER TABLE products ADD CONSTRAINT products_price_min CHECK (price > 0);
ALTER TABLE
testdb=> ALTER TABLE products ADD CONSTRAINT products_name_uk UNIQUE (name);
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
    "products_name_uk" UNIQUE CONSTRAINT, btree (name)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

对于非空约束(NOT NULL),可以使用以下语法:

ALTER TABLE table_name ALTER COLUMN column_name SET NOT NULL;

我们将产品表的 name 字段设置为非空:

testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           | not null | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
    "products_name_uk" UNIQUE CONSTRAINT, btree (name)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

添加约束时,系统会检验已有数据是否满足条件,如果不满足将会添加失败。

删除约束

删除约束通常需要知道它的名称,可以通过 psql 工具的\d table_name命令查看表的约束。

ALTER TABLE table_name DROP CONSTRAINT constraint_name [ RESTRICT | CASCADE ];

RESTRICT是默认值,如果存在其他依赖于该约束的对象,需要使用CASCADE执行级联删除。例如,外键约束依赖于被引用字段上的唯一约束或主键约束。

我们删除产品表 name 字段上的唯一约束:

testdb=> ALTER TABLE products DROP CONSTRAINT products_name_uk;
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           | not null | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

删除非空约束也需要使用单独的语法:

ALTER TABLE table_name ALTER COLUMN column_name DROP NOT NULL;

以下语句将会删除产品表 name 字段上的非空约束:

testdb=> ALTER TABLE products ALTER COLUMN name DROP NOT NULL;
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

修改字段默认值

如果想要为某个字段设置或者修改默认值,可以使用以下语句:

ALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT value;

我们将为产品表的价格设置一个默认值:

testdb=> ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 7.77
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

同样,可以删除已有的默认值:

ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT;

以下语句可以删除产品表中的价格默认值:

testdb=> ALTER TABLE products ALTER COLUMN price DROP DEFAULT;
ALTER TABLE
testdb=> \d products;
                Table "public.products"
   Column    |  Type   | Collation | Nullable | Default 
-------------+---------+-----------+----------+---------
 product_no  | integer |           | not null | 
 name        | text    |           |          | 
 price       | numeric |           |          | 
 description | text    |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

删除字段的默认值相当于将它设置为空值(NULL)。

修改字段数据类型

通常来说,可以将字段的数据类型修改为兼容的类型。

ALTER TABLE table_name ALTER COLUMN column_name TYPE new_data_type;

以下语句将产品表的 price 字段的类型修改为 numeric(10,2):

testdb=> ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);
ALTER TABLE
testdb=> \d products;
                   Table "public.products"
   Column    |     Type      | Collation | Nullable | Default 
-------------+---------------+-----------+----------+---------
 product_no  | integer       |           | not null | 
 name        | text          |           |          | 
 price       | numeric(10,2) |           |          | 
 description | text          |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_min" CHECK (price > 0::numeric)

因为已有的数据能够隐式转换为新的数据类型,上面的语句能够执行成功。如果无法执行隐式转换(例如将字符串 ‘1’ 转换为数字 1),可以使用USING执行显式转换。

ALTER TABLE table_name ALTER COLUMN column_name TYPE new_data_type USING expression;

我们先为产品表增加一个字符串类型的字段 level,然后将其修改为整数类型。

testdb=> ALTER TABLE products ADD COLUMN level VARCHAR(10);
ALTER TABLE
testdb=> ALTER TABLE products ALTER COLUMN level TYPE INTEGER;
ERROR:  column "level" cannot be cast automatically to type integer
HINT:  You might need to specify "USING level::integer".

testdb=> ALTER TABLE products ALTER COLUMN level TYPE INTEGER USING level::integer;
ALTER TABLE

重命名字段

使用以下语句可以修改表中字段的名称:

ALTER TABLE table_name
RENAME COLUMN column_name TO new_column_name;

我们将产品表的字段 product_no 改名为 product_number:

ALTER TABLE products
RENAME COLUMN product_no TO product_number;

重命名表

如果需要修改表的名称,可以使用以下语句:

ALTER TABLE table_name
RENAME TO new_name;

例如,将产品表的名称改为 items 的命令如下:

ALTER TABLE products
RENAME TO items;

关于修改表的完整功能参考官方文档中的ALTER TABLE语句。

删除表

删除表可以使用DROP TABLE语句:

DROP TABLE [ IF EXISTS ] name [ CASCADE | RESTRICT ];

其中,name 表示要删除的表;如果使用了 IF EXISTS,删除一个不存在的表不会产生错误,而是显示一个信息。

以下语句将会删除表 emp1:

testdb=> DROP TABLE emp1;
DROP TABLE

如果被删除的表存在依赖于它的视图或外键约束,需要指定 CASCADE 选项,执行级联删除。

当我们使用CREATE TABLE语句创建一个新的表时,PostgreSQL 帮我们完成了所有的操作,包括更新系统表中的信息,在文件系统中生成相应的文件。那么 PostgreSQL 怎么知道应该在什么位置创建系统文件呢?或者当我们查询表的数据时,它如何知道数据文件所在的物理位置呢?

在 PostgreSQL 中,它是通过表空间(Tablespaces)来实现逻辑对象(表、索引等)与物理文件之间的映射。下一篇我们介绍一些关于 PostgreSQL 表空间和物理结构的知识。

人生本来短暂,你有何必匆匆!点个赞再走吧!

猜你喜欢

转载自blog.csdn.net/horses/article/details/87168403