MySQL快速入门
MySQL是一款流行的关系型数据库, 结构化查询语言SQL(Structured Query Language)是关系型数据库通用的操作语言, 但是不同数据库系统的行为还是有着细微的不同, 本文将以MySQL为例进行介绍.
一个MySql服务实例下可以维护多个database, 其中部分数据库用于维护用户和权限信息. Mysql提供了一些数据库管理语句:
-
SHOW DATABASES;
显示当前实例所拥有的数据库 -
DROP DATABASES db_name;
删除指定数据库 -
CREATE DATABASE [IF NOT EXISTS] db_name;
创建数据库 -
use db_name;
进入数据库
MySQL将数据存储在数据表中, 数据表由数据库来管理. 此外, 有一些命令可以查看数据表的元信息:
-
SHOW TABLES
显示当前数据库所有数据表 -
DESC <table_name>
或DESCRIBE <table_name>
显示表结构
DDL
数据定义语言DDL(Data Definition Language)用于操作数据表等数据库对象,对数据表、索引等进行定义。
数据表table是行列的逻辑结构, 每行是一条记录, 每列是记录的一个字段. 同一张表的所有记录拥有相同的结构, 创建表时最重要的信息就是表的结构信息.
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(20) DEFAULT "",
`password` CHAR(20) DEFAULT "",
`gender` CHAR(1),
`age` INT
);
SQL语句不区分大小写, 通常我们将SQL关键字大写, 自定义标识符小写. 用反点号包括自定义标识符可以避免它们被认为是SQL关键字.
SQL标识符以字母开头, 可以使用字母, 数字或三个特殊符号#
, _
, $
.标识符用于表示表, 列和其它对象的名称.
所有DDL语句中的DEFAULT
项都是可以省略的.
MySQL中常用的数据类型有:
INT
: 32位有符号整数DOUBLE
双精度浮点数DECIMAL
精确小数CHAR(limit)
, 定长字符串VARCHAR(limit)
变长字符串TEXT
长文本数据DATE
日期DATETIME
日期和时间TIMESTAMP
UNIX时间戳
使用drop
语句来删除数据表:
DROP TABLE `user`;
表在创建完成后仍可以修改其结构, 不过要注意对其中数据的影响:
- 删除列
ALTER TABLE `user` DROP `age`;
- 增加列:
ALTER TABLE `user` ADD `age` INT DEFAULT 0;
- 修改列的类型或名称
ALTER TABLE `user` CHANGE `gender` `sex` CHAR(1) DEFAULT "M";
ALTER TABLE `user` MODIFY `gender` INT DEFAULT 0;
DML
数据操作语言DML(Data Manipulation Language), 用于操作表中的数据。
插入一条记录:
INSERT INTO `user` (`username`, `password`) VALUES ("abcd", "1234");
INSERT INTO `user` VALUES ("abcd", "1234");
要向所有没有默认值且不允许为空的列插入数据.
更新已存在的记录:
UPTATE `user` SET `username`="abc" WHERE `id`=1;
update
可以更新所有符合WHERE
子句的记录, 当没有WHERE
子句时更新该表所有记录.
当键重复时更新记录, 否则插入记录:
INSERT INTO `user` VALUES ("a", "2") ON DUPLICATE KEY UPDATE `username`="a", "password"=2;
删除记录:
DELETE FROM `user` WHERE `id`=1;
删除所有符合WHERE
子句的记录, 当没有WHERE
子句时删除该表所有记录.
SELECT
查询语句SELECT是最灵活最复杂也是最重要的SQL语句.
我们创建用户user
和文章post
两张表做为示例:
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(20) DEFAULT "",
`password` CHAR(20) DEFAULT "",
`gender` CHAR(1),
`age` INT
);
CREATE TABLE `post` (
id INT PRIMARY KEY AUTO_INCREMENT,
uid INT,
title TEXT,
CONTENT TEXT
);
查询user
表中所有记录的所有字段.
SELECT * FROM `user`;
SELECT可以可以查询某个表中符合条件记录的某些字段.如所有男性用户的username:
SELECT `username`
FROM `user`
WHERE `gender`='M';
我们可以使用LIMIT限制返回结果的数量:
SELECT * FROM `user` LIMIT 10 OFFSET 10;
上述查询跳过前10个(OFFSET 10)结果, 并返回最多10条记录(LIMIT 10)。
在多表查询时可能需要指定列的所在的表:
SELECT `user`.`username`
FROM `user`
WHERE `user`.`gender`='M';
为了简化表达式可以为列和表指定别名:
SELECT u.`username` AS name
FROM `user` u
WHERE u.`gender`='M';
表中可能出现用户名相同的情况, 使用DISTINCT
函数去除重复的用户名:
SELECT DISTINCT(`username`)
FROM `user`;
聚集
MySQL可以进行取平均值, 求和等聚集操作.
SELECT AVG(`age`) FROM `user`;
上述语句查询user
表中所有记录age
字段的平均值. 可以使用GROUP BY
子句指定分组聚集方式.
SELECT AVG(`age`) FROM `user`
GROUP BY `gender`;
上述语句将gender
字段相同的记录视作一组, 求出age
字段的平均值. 我们可以附加where子句查询男性用户的平均年龄:
SELECT gender, AVG(`age`) FROM `user`
WHERE gender=`M`
GROUP BY gender;
WHERE
子句中无法使用聚集函数, 可以使用HAVING
子句完成该功能, 比如查询发表文章超过3篇的作者:
SELECT uid FROM `post`
HAVING count(id) > 3
GROUP BY uid;
常用的聚集函数有:
-
SUM
求和 -
AVG
求平均值 -
MIN
求最小值 -
MAX
求最大值 -
COUNT
求记录数
此外还有一些工具函数也顺便介绍:
-
LENGETH(txt)
,LEN(txt)
: 求文本长度 -
REPLACE(txt, from, to)
替换文本 -
UCASE(txt)
,LCASE(txt)
: 转为大写 / 小写 -
mid(txt, start, len)
: 提取子串
排序
ORDER BY 语句可以根据结果中某一列进行排序:
SELECT * FROM user ORDER BY age DESC LIMIT 10;
把user表中的记录根据age字段进行降序排列, 并返回前10条记录。
SELECT * FROM user ORDER BY age ASC LIMIT 10;
把user表中的记录根据age字段进行升序排列, 并返回前10条记录。
ORDER BY 语句可以进行多列排序:
SELECT * FROM user ORDER BY age DESC, username ASC;
把user表中的记录根据age字段进行升序排列,age相同的根据username字典序升序排列。
ORDER BY 语句可以根据聚集函数进行排序:
SELECT post_id, count(*) as comment_count
FROM comment
GROUP BY post_id
ORDER BY count(*) DESC;
根据文章的评论数进行降序排列。
联合查询
现在查询所有女性用户发表的文章:
SELECT p.`id` AS post_id, `title`, `content`, u.`username` AS author
FROM `user` u, `post` p
WHERE
p.`uid`=u.`id`
AND u.`gender`='F';
字符串可以使用单引号
'
或双引号"
表示.
上述查询将搜索user
和post
表中记录所有组合, 满足条件p.uid=u.id
的user
记录和post
记录将组合为同一条记录. 然后查询组合记录中符合条件u.
gender='F'
的记录.
使用等价的join
操作来代替上述查询:
SELECT p.`id` AS post_id, title, content, u.`username` AS author
FROM `post` p
JOIN `user` u
ON p.`uid`=u.`id`
WHERE u.`gender`='F';
join
操作有3种:
-
INNER JOIN
,JOIN
: 查询出的记录必须满足ON
条件,若左表或右表中没有对应的记录,查询结果中均不会包含相应记录。 -
LEFT JOIN
: 查询左表post
中所有记录, 即使右表中没有对应的记录. 当没有右表记录时, 查询结果中右表的字段为空值. -
REIGHT JOIN
查询右表user
中所有记录, 即使左表中没有对应记录. 当没有左表记录时, 查询结果中左表的字段为空值. -
OUTER JOIN
:LEFT JOIN
和RIGHT JOIN
结果的并集,即左右表只有一个表中有记录即可。
注意若user
表中有m条记录, post
表中有n条记录则连接查询将会扫描m*n
条记录. 这在表中数据量较大时会非常耗时.
IN 和 EXISTS 查询
使用IN
和EXISTS
子查询可以完成连接查询的任务.
SELECT *
FROM `post`
WHERE `uid` IN (
SELECT `user`.`id` FROM `user` WHERE `gender`='F'
);
IN
运算符将匹配uid
和子查询的结果, 若记录的uid
在结果集中则IN
条件为真.
IN
运算符后可以直接书写列表:
SELECT * FROM user
WHERE username in ('a', 'b');
查询username为'a'或'b'的用户。
EXISTS
运算符可以起到类似的作用:
SELECT *
FROM `post`
WHERE EXISTS (
SELECT `user`.`id` FROM `user` WHERE `gender`='F'
AND `user`.`id`=`post`.`uid`
);
在EXISTS子查询中可以访问外表,如上例exists子查询中访问了post
表。
上述SQL语句查询使EXISTS子查询SELECT user.id FROM user WHERE gender='F' AND user.id=post.uid
结果不为空集的post
记录。即查询女性作者发表的所有文章,与本节开头的IN查询作用相同。
IN
和EXISTS
都有对应的否定形式:
SELECT *
FROM `post`
WHERE `uid` NOT IN (
SELECT `user`.`id` FROM `user` WHERE `gender`='F'
);
SELECT *
FROM `post`
WHERE NOT EXISTS (
SELECT `user`.`id` FROM `user` WHERE `gender`='F'
AND `user`.`id`=`post`.`uid`
);
子查询
SELECT * FROM
语句可以把另外一个查询结果作为数据源。
添加两张表:
CREATE TABLE `likes` (
`uid` INT,
`post_id` INT,
PRIMARY KEY (`uid`, `post_id`)
);
CREATE TABLE `comment` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`uid` INT,
`post_id` INT,
`content` TEXT,
);
我们要搜索所有文章的点赞like
数和评论comment
数:
SELECT
lc.post_id,
lc.like_count,
cc.comment_count
FROM (
SELECT `post_id`, count(*) AS like_count FROM `likes` GROUP BY `post_id`
) lc
OUTER JOIN (
SELECT `post_id`, count(*) AS comment_count FROM `comment` GROUP BY `post_id`
) cc
ON lc.post_id = cc.post_id;
上述语句中我们将查询语句SELECT post_id, count(*) AS like_count FROM likes GROUP BY post_id
的结果作为一个数据表, 并给它一个别名lc
。
类似地,我们将另一个查询的结果作为数据表cc
, 然后将两个数据表进行JOIN。
视图
MySQL可以根据查询创建视图view
对象, 可以用访问数据表的方式访问视图. 视图只保存查询操作不保存数据, 视图在被访问时从数据表中取出数据.
CREATE VIEW `post_detail` AS
SELECT p.`id` AS post_id, title, content,
p.`uid` AS author_id, u.`username` AS author_name
FROM `post` p
JOIN `user` u
ON p.`uid`=u.`id`
WHERE u.`gender`='F';
SELECT * FROM `post_detail`;
约束
约束是在表上强制执行的校验规则, 用于保证数据的完整性. 约束分为对单列的约束和对多个列集合的约束.
非空约束
MySQL中所有数据类型都可以使用null, 通常使用field IS NULL
来判断某个字段是否为空.
非空约束限制某一列不允许出现null值, 在建表时添加非空约束:
CREATE TABLE `user` {
`username` VARCHAR(20) NOT NULL
}
也可以随时添加或删除非空约束:
ALTER TABLE `user` modify `username` VARCHAR(20) NULL;
ALTER TABLE `user` modify `username` VARCHAR(20) NOT NULL;
唯一约束
唯一约束要求单列或列的集合不允许出现多个相同的非NULL值, 不允许username
列出现重复值:
CREATE TABLE `user` {
`username` VARCHAR(20) UNIQUE
}
允许username
或email
重复, 但不允许任意两条记录在username
相同时email
也相同.
CREATE TABLE `user` {
`username` VARCHAR(20),
`email` VARCHAR(20),
UNIQUE(`username`, `email`)
}
为了便于后续操作, 最好使用命名约束:
CREATE TABLE `user` {
username VARCHAR(20),
email VARCHAR(20),
CONSTRAINT uc_user UNIQUE(username, email)
}
使用ALTER
添加唯一约束:
ALTER TABLE `user` ADD UNIQUE(`username`);
ALTER TABLE `user` ADD CONSTRAINT uc_user UNIQUE(`username`, `email`);
撤销唯一约束:
ALTER TABLE `user` DROP CONSTRAINT uc_user;
主键约束
主键是表上的一列或几列的集合, 主键列必须非空且唯一. 主键必须可以唯一确定表中的记录, 即不存在主键列相同的两条记录.
每个表最多包含一个主键约束:
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(20) DEFAULT "",
);
CREATE TABLE `user` {
username VARCHAR(20),
email VARCHAR(20),
CONSTRAINT user_key PRIMARY KEY(username, email)
}
使用ALTER
修改主键约束:
ALTER TABLE `user` ADD PRIMARY KEY(`id`);
ALTER TABLE `user` ADD CONSTRAINT user_key PRIMARY KEY(`username`, `email`);
因为最多有一个主键约束, 所以删除时不用指定约束对象:
ALTER TABLE `user` DROP PRIMARY KEY;
外键约束
外键约束将数据表B上的某列或列集合与数据表A的主键关联, 外键列的结构必须与被参照主键的结构一致, 但允许外键列的值重复. 数据表B上每条记录的外键列必须与被参照表A的某条记录的主键相同.
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(20) DEFAULT "",
`password` CHAR(20) DEFAULT "",
`gender` CHAR(1),
`age` INT
);
CREATE TABLE `post` (
id INT PRIMARY KEY AUTO_INCREMENT,
uid INT,
title TEXT,
CONTENT TEXT,
FOREIGN KEY(uid) REFERENCES user(`id`)
);
建立在post
表上的外键可以保证post
表中每条记录的uid都指向user
表中一条记录. 避免存在找不到作者的文章.
当删除被参照表上的某条记录时, 必须删除所有参照它的记录. 即删除用户前必须前必须删除他发表的所有文章, 以保证外键约束不被破坏.
在拥有外键约束的表post
上添加数据时必须扫描一遍被参照的user
, 这可能消耗很多时间, 在使用外键时必须要考虑到这一点.