正确的进行关系数据设计,需要一种方法保证只在表中插入合法数据。虽然可以在插入新行的时候进行检查(在另一个表上执行SELECT,以保证所有值合法并存在),但最好不要这么做,原因如下:
约束(constraint):管理如何插入或处理数据库数据的规则
DBMS通过在数据库表上实施约束来实施引用完整性。大多数约束是在表定义中定义的,比如CREATE TABLE语句或ALTER TABLE语句。
主键是一种特殊的约束,用来保证一列(或者一组列)中的值是唯一的,而且永不改动。这方便了直接或交互的处理表中的行。
表中任意行满足以下条件都可以用于主键:
一种定义主键的方法是创建它,如下所示:
CREATE TABLE Vendors
(
vend_id CHAR(10) NOT NULL PRIMARY KEY,
vend_name CHAR(50) NOT NULL,
vend_address CHAR(50) NULL,
vend_city CHAR(50) NULL,
vend_state CHAR(5) NULL,
vend_zip CHAR(10) NULL,
vend_country CHAR(50) NULL
);
分析:在此示例中给vend_id列定义添加关键字PRIMARY KEY,使其成为主键。
ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY (vend_id);
分析:这里定义相同的列为主键,但是使用的是CONSTRAINT(约束)语法。此语法也可以用于CREATE TABLE和ALTER TABLE语句。
外键是表中的一列,其值在另一个表的主键中。外键是保证引用完整性积极重要的一部分。
下面是定义外键的方法:
ALTER TABLE Orders1
ADD CONSTRAINT FK_Orders1_Customers
FOREIGN KEY (cust_id) REFERENCES Customers (cust_id);
分析:其中的表定义使用了REFERENCES关键字,它表示cust_id中的任何值都必须是Customers表的cust_id中的值。
唯一约束保证一列(或者一组列)的数据是唯一的。它类似于主键,但存在以下重要区别。
这几条与主键相对应区别
唯一约束的语法类似于其他约束的语法。唯一约束既可以用UNIQUE关键I在表定义中定义,也可以用单独的CONSTRAINT定义。
检查约束用来保证一列(或者一组列)中的数据满足一组指定的条件。检查约束的常见用途有以下几点。
换句话说,数据类型限制了列中可保存的数据的类型。检查约束在数据类型内又做了进一步的限制,这些限制及其重要,可以确保插入数据库的数据证实你想要的的数据。不需要依赖于客户端,应用程序或者用户来保证正确获取它,DBMS本身将会拒绝任何无效数据。
下面的列子对OrderItems表施加了检查约束,它保证所有物品的数量大于0。
输入
CREATE TABLE OrderItems
(
order_num INTEGER NOT NULL,
order_item INTEGER NOT NULL,
prod_id CHAR(10) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
item_price MONEY NOT NULL
);
分析:利用这个约束,任何插入(或更新)的行都会被检查,保证quantity大于0。
检查名为gender的列只包含M或F,可编写如下的ALTER TABLE语句:
输入
ADD CONSTRAINT CHECK (gender LIKE '[MF]')
索引用来排序数据以加快搜索和排序操作的速度。
使用索引的原因是:恰当的排序。
主键数据是排序的,这是DBMS的工作。因此,按住键检索特定行总是一种快速有效的操作。但是搜索其他列中的值通常效率不高。
在创建索引前,应该记住以下内容。
没有严格的规则要求什么应该索引,何时索引。大多数DBMS提供了可用来确定索引效率的使用程序,应该经常使用这些使用程序。
索引用CREATE INDEX语句创建(不同DBMS创建索引的语句变化很大)。
下面的语句在Products表的产品列上创建一个简单的索引。
输入
CREATE INDEX prod_name_ind
ON Products (prod_name);
mysql> show index from products;
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| products | 0 | PRIMARY | 1 | prod_id | A | 9 | NULL | NULL | | BTREE | | | YES | NULL |
| products | 1 | FK_Products_Vendors | 1 | vend_id | A | 3 | NULL | NULL | | BTREE | | | YES | NULL |
| products | 1 | prod_name_ind | 1 | prod_name | A | 9 | NULL | NULL | | BTREE | | | YES | NULL |
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
分析:索引必须唯一命名。这里的索引名prod_name_ind在关键字CREATE INDEX之后定义。ON用来指定被索引的表。而索引中包含的列(此例中仅有一列)在表名后的圆括号给出。
触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。触发器可以与特定表上的INSERT、UPDATE和DELETE操作(或组合)相关联。
与存储过程不一样(存储过程只是简单的存储SQL语句),触发器与单个的表相关联。与Order表上的INSERT操作相关联的触发器只在Orders表汇总插入行时执行。类似地,Customers表上对的INSERT和UPDATE操作的触发器只在表上出现这些操作时执行。
触发器内的代码具有以下数据的访问权:
INSERT操作中的所有新数据;
UPDATE操作中的所有新数据和旧数据;
DELETE操作中删除的数据。
根据所使用的DBMS的不同,触发器可在特定操作执行之前或之后执行。
下面是一些常见用途。
下面创建一个触发器,它对所有INSERT和UPDATE操作,将Customer表中的cust_name列转换为大写。
这是本例的MySQL版本
输入
CREATE TRIGGER customer_state BEFORE INSERT ON customers FOR EACH ROW
BEGIN
UPDATE Customers
SET cust_state = Upper( cust_state )
WHERE
Customers.cust_id = INSERT.cust_id;
END;
mysql> SHOW TRIGGERS;
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
| Trigger | Event | Table | Statement | Timing | Created | sql_mode | Definer | character_set_client | collation_connection | Database Collation |
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
| customer_state | INSERT | customers | BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = INSERT.cust_id;
END | BEFORE | 2020-05-11 12:35:50.24 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | root@localhost | utf8mb4 | utf8mb4_0900_ai_ci | utf8mb4_0900_ai_ci |
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
使用SELECT * FROM information_schema.triggers \G ;语句查看触发器的详细信息
mysql> SELECT * FROM information_schema.triggers \G ;
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: sys
TRIGGER_NAME: sys_config_insert_set_user
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: sys
EVENT_OBJECT_TABLE: sys_config
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
IF @sys.ignore_sys_config_triggers != true AND NEW.set_by IS NULL THEN
SET NEW.set_by = USER();
END IF;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-03 08:57:27.57
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: mysql.sys@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
*************************** 2. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: sys
TRIGGER_NAME: sys_config_update_set_user
EVENT_MANIPULATION: UPDATE
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: sys
EVENT_OBJECT_TABLE: sys_config
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
IF @sys.ignore_sys_config_triggers != true AND NEW.set_by IS NULL THEN
SET NEW.set_by = USER();
END IF;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-03 08:57:27.75
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: mysql.sys@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
*************************** 3. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: data
TRIGGER_NAME: customer_state
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: data
EVENT_OBJECT_TABLE: customers
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = INSERT.cust_id;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-11 12:35:50.24
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
3 rows in set (0.00 sec)
查看指定的某一个触发器的信息,比如查询我的触发器customer_state,使用SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME=‘customer_state’ \G;语句进行查询
mysql> SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME='customer_state' \G;
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: data
TRIGGER_NAME: customer_state
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: data
EVENT_OBJECT_TABLE: customers
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = INSERT.cust_id;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-11 12:35:50.24
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
对于组织来说,没有什么比它的数据更重要了,因此应该保护这些数据,使其不被偷到或任意浏览。
需要保护的操作有:
安全性使用SQL的GRANT和REVOKE语句来管理,不过大多数DBMS提供了交互式的管理使用程序,这些使用程序在内部使用GRANT和REVOKE语句。