sql操作
关系的完整性约束
实体完整性: 主键不为空
参照完整性: 或为空, 或者等于另一个关系的主码值
用户定义完整性: 用于设置某个属性的取值范围
SQL即Structured Query Language
DDL(Data Definition Language数据定义语言)
用来建立数据库、数据库对象和定义其列
CREATE 、DROP 、ALTER 等
DCL(Data Control Language, 数据控制语言)
用来控制存取许可、存取权限等;
GRANT、REVOKE 等;
DML(Data Manipulation Language数据操作语言)
查询、插入、删除和修改数据库中的数据;
SELECT、INSERT、 UPDATE 、DELETE等;
linux下mysql表大小写敏感
windows下mysql表大小写不敏感
MySQL配置
修改字符集以显示中文
/etc/mysql/mysql.conf.d/mysqld.cnf
在[mysqld]下追加:
character-set-server=utf8
/etc/mysql/conf.d/mysql.cnf
在[mysql]下追加:
default-character-set=utf8
远程登陆与授权
在/etc/mysql/mysql.conf.d/mysqld.cnf中的mysqld修改bind-address为0.0.0.0
Windows中启动/停止数据库
net start MySQL
net stop MySQL
linux中启动/停止数据库
service mysql start
service mysql restart
service mysql stop
登陆服务器
mysql -hHostName -PPort -uUserName –pPassWord
登陆授权:
mysql> GRANT ALL PRIVILEGES ON *.* TO root@"%" IDENTIFIED BY "zyb";
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
快捷键
放弃正在输入的命令:\c
显示命令清单:\h
退出mysql程序:\q
查看MySQL服务器状态信息:\s
账户相关
创建用户
CREATE USER UserName IDENTIFIED BY '123456';
删除用户
DROP USER UserName;
查看当前用户
SELECT USER();
查看所有用户
SELECT * FROM mysql.user;
SELECT user FROM mysql.user;
创建用户并授予权限, *.*任意数据库任意表, %任意IP地址/主机名称
GRANT SELECT,INSERT, UPDATE, DELETE ON *.* TO UserName@'%' IDENTIFIED BY '123456';
对已经创建用户赋予权限
GRANT ALL PRIVILEGES ON *.* TO UserName@'%';
回收权限
REVOKE ALL PRIVILEGES ON *.* FROM UserName@'%';
修改密码
mysqladmin -uUserName -pOldPassWord password pNewPassWord
mysqladmin -uabc -p123456 password 123(要有权限才可以修改)
设置字符集
alter database dbname character set gbk;
alter database dbname character set utf8;
数据库相关
创建数据库
CREATE DATABASE DBName [数据库选项];
删除数据库
DROP DATABASE DBName;
选定数据库
USE DBName;
查询创建数据库使用命令
SHOW CREATE DATABASE DBName;
显示所有数据库
SHOW DATABASES;
导入数据库
create database dbName;
use dbName;
source Path_to_table;
表项
创建表
CREATE TABLE t_emp(empno INT, ename VARCHAR(20), esex CHAR(2));
删除表
DROP TABLE t_emp;
修改表
ALTER TABLE t_emp MODIFY ename VARCHAR(30); 修改表项
ALTER TABLE t_emp DROP esex; 删除表项
ALTER TABLE t_emp ADD esex CHAR(2); 增加表项
显示表结构
DESC t_emp;
显示当前数据库中所有表
SHOW TABLES;
显式指定数据库中的所有表
SHOW TABLES FROM DBName;
数据项
增
INSERT INTO t_emp(empno, ename, esex) VALUES(1000, 'tom', 'm');
INSERT INTO t_emp(empno, ename, esex) VALUES(1001, NULL, 'm'); null代表不赋值
INSERT INTO t_emp(empno, ename) VALUES(1001, 'jerry'); 只插入两个字段
INSERT INTO t_emp VALUES(1000, 'maggie', 'f'); 字段列表可以省略, 此时values内不能省
INSERT INTO t_emp VALUES(1004, '张三', '男'); 不更改客户端字符集为gbk会出错, 插入男有问题
SHOW VARIABLES like 'CHARACTER_SET%'; 查看字符集
SET NAMES gbk; 更改客户端字符集, 服务器端字符集并不改变
删
DELETE FROM t_emp WHERE ename = 'maggie';
DELETE FROM t_emp; 删除所有记录
改
UPDATE t_emp SET empno = '1003' WHERE ename = 'maggie';
UPDATE t_emp SET esec = 'f' WHERE esex = 'm';
查
SELECT * FROM t_emp;
约束
主键约束
主键不能为空, 且不能重复
CREATE TABLE t_emp(empno INT NOT NULL PRIMARY KEY, ename VARCHAR(20), esex CHAR(2)); 普通字段也可以设置not null
CREATE TABLE t_emp(empno INT, ename VARCHAR(20), esex CHAR(2), PRIMARY KEY(empno, esex)); 主键关键字在末尾
CREATE TABLE t_emp(empno INT, ename VARCHAR(20), esex CHAR(2), CONSTRAINT PK_EMPNO PRIMARY KEY(empno)); 增加约束名称
CREATE TABLE t_emp(empno INT, ename VARCHAR(20), esex CHAR(2)); 先建表, 后指定主键
ALTER TABLE t_emp ADD CONSTRAINT PK_EMPNO PRIMARY KEY(empno, ename); 可以指定多个字段作为主键
INSERT INTO t_emp VALUES(1000, 'Tom', 'm');
INSERT INTO t_emp VALUES(1000, 'Jerry', 'm'); 插入失败, 主键重复, 练习时可以插入
INSERT INTO t_emp VALUES(NULL, 'Jerry', 'm'); 失败, 主键为空
ALTER TABLE t_emp DROP CONSTRAINT PRIMARY KEY; 只能这么删除, KEY后面不能加括号
参照完整性约束
CREATE TABLE t_emp(empno INT, deptno INT, ename VARCHAR(20), esex CHAR(2));
ALTER TABLE t_emp ADD CONSTRAINT PRIMARY KEY(empno);
CREATE TABLE t_dept(deptno INT, dname VARCHAR(20));
ALTER TABLE t_dept ADD CONSTRAINT PRIMARY KEY(deptno);
ALTER TABLE t_emp ADD CONSTRAINT FK_DEPTNO FOREIGN KEY(deptno) REFERENCES t_dept(deptno); 添加外键, 参照完整性约束, key和t_dept内名称可以不同, 但引用类型必须相同
set names gbk;
INSERT INTO t_dept VALUES(2001, '人事部');
INSERT INTO t_dept VALUES(2002, '技术部');
INSERT INTO t_emp values(1001, 2001, 'Tom', 'm'); 插入成功
INSERT INTO t_emp values(1002, 2003, 'Jerry', 'm'); 插入失败, 没有编号2003的部门
CREATE TABLE t_test(id INT auto_increment PRIMARY KEY, name VARCHAR(30), age INT DEFAULT 20); 自动增长必须是主键, 否则出错
INSERT INTO TABLE t_test VALUES(NULL, 'aaa'); 写法错误
INSERT INTO TABLE t_test VALUES(NULL, 'aaa', NULL); 错误, values默认所有字段都要给出
INSERT INTO t_test(name) VALUES('bbb'); 插入成功
ALTER TABLE t_emp DROP FOREIGN KEY FK_DEPTNO; 使用键名删除外键约束
用户自定完整性约束
CREATE table t_test2(id INT, name VARCHAR(30), age INT);
ALTER TABLE t_test2 ADD CONSTRAINT CC_AGE CHECK(age >= 18 and age <= 60); 约束名称CC_AGE
ALTER TABLE t_test2 ADD CONSTRAINT CC_AGE CHECK(LENGTH(name) > 2);
约束保存在information_schema中的TABLE_CONSTRAINTS里面
类型
数值
整型
数值类型: (1) 整数; (2) 小数
整型: tinyint(1字节), smallint(2字节), mediumint(3字节), int(4字节), bigint(8字节)
bool是tinyint(1)的别名,
修饰: unsigned(无符号), zerofill(前导用0填充)
tinyint[(M)] [unsigned] [zerofill]
M显式宽度
CREATE TABLE t_number1(a TINYINT, b TINYINT UNSIGNED);
INSERT INTO t_number1 VALUES(100, 200);
INSERT INTO t_number1 VALUES(-129, 200); 错误
INSERT INTO t_number1 VALUES(128, 200); 错误
INSERT INTO t_number1 VALUES(127, 200);
INSERT INTO t_number1 VALUES(127, -1); 错误
create table t_number2(a int(4) zerofill); 显示宽度(超过宽度仍能显示)4位, 不足位数用前导zerofill 0填充
INSERT INTO t_number2 VALUES(12); 查询显示0012
INSERT INTO t_number2 VALUES(123456); 查询显示123456
create table t_number3(a bool); bool是tinyint(1)的别名
INSERT INTO t_number3 VALUES(4); 插入成功, 查询显示4
小数
浮点数: float(4个字节, 精度6位), double(8个字节, 精度16位)
DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]
M总位数,D是小数点后面的位数
real是double的别名
CREATE TABLE t_number4(a FLOAT, b DOUBLE);
INSERT INTO t_number4 values(12345678.12345, 12345678.12345); 精度丢失, float精度6位, double精度17位, 查询结果12345700, 12345678.12345
INSERT INTO t_number4 values(12345678.12345, 123456789123456789.12345); 精度丢失, 查询结果12345700, 1.2345678912345678e17
INSERT INTO t_number4 values(12345678.12345, 12345678912345.12345); 精度丢失, 查询结果12345700, 12345678912345.123
CREATE TABLE t_number5(a DOUBLE(5, 2)); 整数位3位, 小数位2位
四舍五入后需满足-999.99 ~999.99, 否则错误
DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]
M总位数, 缺省是10; D小数位数, 缺省是0
decimal取值范围与double是一样的,但是有更高的精度。 但存储方式与double不同, 存储效率没有double高
numeric是decimal的别名
CREATE TABLE t_number6(a DECIMAL(30, 6)); decimal第二位缺省表示0, decimal是点的, 小数点固定, numeric同decimal, 是decimal的别名
INSERT INTO t_number6 VALUES(123456789123456789.12345); 取值范围与double相同, 精度更高 查询显示123456789123456789.123450
CREATE TABLE t_number7(a DECIMAL(5, 2)); 存储范围: -999.99 ~999.99
日期和时间
类型 | 显示格式 | 取值 | 存储空间 |
---|---|---|---|
DATETIME | YYYY-MM-DD HH:MM:SS |
‘1000-01-01 00:00:00’到’9999-12-31 23:59:59’ | 8 |
TIMESTAMP | YYYY-MM-DD HH:MM:SS |
‘1970-01-01 00:00:00’到2038-01-19 03:14:07’ | 4 |
DATE | YYYY-MM-DD |
‘1000-01-01’到’9999-12-31’ | 3 |
TIME | HH:MM:SS |
‘-838:59:59’到’838:59:59’ | 3 |
YEAR | YYYY |
1901到2155 | 1 |
以字符串形式插入, 以整型存储, TIME可以表示两个时间的时间差, 因此可以是负数
CREATE TABLE t_datetime(a datetime);
INSERT INTO t_datetime VALUES('2014-01-15 10:10:10'); 插入以字符串形式插入
INSERT INTO t_datetime VALUES('9999-12-31 23:59:59');
INSERT INTO t_datetime VALUES('10000-01-01 00:00:00'); 插入错误
INSERT INTO t_datetime VALUES('999-01-01 00:00:00'); 插入成功
INSERT INTO t_datetime VALUES('99-01-01 00:00:00'); 存在自动转化
INSERT INTO t_datetime VALUES('69-01-01 00:00:00'); 存在自动转化
CREATE TABLE t_timestamp(a datetime); timestamp存储转化为秒, 占4个字节, 有时间限制
INSERT INTO t_timestamp VALUES('2014-01-15 10:10:10');
INSERT INTO t_timestamp VALUES('2038-01-19 03:14:07');
INSERT INTO t_timestamp VALUES('2038-01-19 03:14:08'); 北京时区存在+8问题
INSERT INTO t_timestamp VALUES('2038-01-19 11:14:07'); 北京时区
INSERT INTO t_timestamp VALUES('2038-01-19 11:14:08'); 超出范围
CREATE TABLE t_date(a date);
INSERT INTO t_date VALUES('2012-01-01');
CREATE TABLE t_time(a time);
INSERT INTO t_time VALUES('23:12:12');
INSERT INTO t_time VALUES('823:12:12');
INSERT INTO t_time VALUES('3 23:12:12'); 3天23小时...
create table t_year(a year);
INSERT INTO t_year VALUES(2000); 可以直接用整数
INSERT INTO t_year VALUES('2155');
INSERT INTO t_year VALUES(2156);
字符串
char(M), varchar(M): 这里的M表示字符数, varchar会多一个或两个字节存放字符串长度
CHAR列的长度固定为创建表时声明的长度。长度可以为从0到255的任何值。当保存CHAR值时,在它们的右边填充空格以达到指定的长度。当检索到CHAR值时,尾部的空格被删除掉。在存储或检索过程中不进行大小写转换。
VARCHAR列中的值为可变长字符串。长度可以指定为0到65,535之间的值。(VARCHAR的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是65,532字节)。
CREATE TABLE t_varchar(a VARCHAR(65535)); 不能创建成功, utf8一个字符占用3个字节
CREATE TABLE t_varchar(a VARCHAR(21845)); 不能创建成功, 额外长度没有存储空间
CREATE TABLE t_varchar(a VARCHAR(21844)); 成功
BINARY和VARBINARY类类似于CHAR和VARCHAR,不同的是它们包含二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。
tinytext
text
mediumtext
longtext
枚举&集合
枚举看过去像是字符串,实际上保存的是整数
CREATE TABLE t_enum(sex enum('male', 'female')); 实际插入的是整数
INSERT INTO t_enum VALUES('male');
INSERT INTO t_enum VALUES('female');
INSERT INTO t_enum VALUES(NULL); 插入成功
INSERT INTO t_enum VALUES('other'); 错误
INSERT INTO t_enum VALUES(1);
SELECT sex + 0 FROM t_enum; 数字形式显示变量
集合实际按二进制1, 10, 100, 1000 大小进行存储
create table t_set(favourite set('dog', 'cat', 'bird'));
INSERT INTO t_set VALUES('dog,cat'); 逗号不能有空格
INSERT INTO t_set VALUES('dog,bird');
INSERT INTO t_set VALUES('dog, cat'); 插入失败
INSERT INTO t_set VALUES('dog,pig'); 插入失败
SELECT favourite+0 FROM t_set;
查询
SELECT <列名> FROM <表名> [WHERE <查询条件表达式>] [ORDER BY <排序的列名> [ASC或DESC]]
SELECT
*
FROM
emp
WHERE
sal < 800 OR sal > 1500
ORDER BY
2 DESC;
数据查询-列
查询指定列
SELECT empno, ename, job FROM emp;
查询所有列
SELECT * FROM emp;
数据查询-列(别名)
列--别名
SELECT empno AS '工号', ename AS '姓名' FROM emp; 查询出来的列显示别名
SELECT empno '工号', ename '姓名' FROM emp; AS可以省略
数据查询-列(常量列)
使用常量列
SELECT empno, 'C++教程网' FROM emp; 末尾单独最加一列
SELECT empno 'C++教程网' FROM emp; 两个意思
数据查询-列(连接符函数)
连接符, concat连接函数
SELECT empno, CONCAT(ename,'#') FROM emp; 连接符, 查询出员工名称, 并在后面加一个#号, 也相当于查询员工名称了
数据查询-where
查询部分行
SELECT empno, ename, job FROM emp WHERE ename = 'SMITH';
SELECT empno, ename, job FROM emp WHERE ename <> 'SMITH'; 这里的<>也可以用!=
SELECT ename, ename, sal FROM emp WHERE sal >= 1500;
数据查询-条件(and,or)
查询部分行 and or
SELECT * FROM EMP WHERE deptno=30 and sal>1500;
SELECT * FROM EMP WHERE job='MANAGER' or job='SALESMAN'
数据查询-between and
SELECT * FROM emp WHERE deptno = 30 AND sal >1500;
SELECT * FROM emp WHERE sal BETWEEN 800 AND 1500; 使用BETWEEN, 包括两个端点
SELECT * FROM emp WHERE sal >= 800 AND sal <= 1500; 与上面结果相同
数据查询-空值
SELECT empno, ename, sal, comm FROM emp WHERE comm IS NULL; 查询comm字段空值
SELECT empno, ename, sal, comm FROM emp WHERE comm IS NOT NULL; 查询comm字段非空值
SELECT * FROM emp WHERE sal NOT BETWEEN 800 AND 1500;
SELECT * FROM emp WHERE sal < 800 OR sal > 1500;
数据查询-in
SELECT * FROM emp WHERE ename IN ('SMITH', 'KING'); 在集合中查询
模糊查询-like
查询时,字段中的内容并不一定与查询内容完全匹配,只要字段中含有这些内容
通配符: %
(0个多个字符)
通配符: _
(单个字符)
SELECT * FROM emp WHERE ename LIKE 'S%'; 模糊查询
SELECT * FROM emp WHERE ename LIKE 'S_ITH';
数据查询-排序
按单列名排序
SELECT * FROM emp ORDER BY ename DESC; 降序排序, 默认升序, asc
SELECT * FROM emp ORDER BY ename ASC;
SELECT empno, ename, job FROM emp ORDER BY 2 DESC; 以查询的第二个字段进行降序
按多个列名排序
SELECT * FROM emp ORDER BY job ASC, sal DESC; # 按job升序排列, job相同时sal降序排列, 注意这里排序不能使用AND连接
关键字
limit, distinct, union, union all
limit: 用于分页查询
SELECT * FROM emp ORDER BY sal LIMIT 5; 只显示前5条记录
limit可用于分页查询, 第二个字段表示查询的行数
SELECT * FROM emp ORDER BY sal LIMIT 0, 5; 从0开始5行
SELECT * FROM emp ORDER BY sal LIMIT 5, 5; 从5开始5行
SELECT * FROM emp ORDER BY sal LIMIT 10, 5; 从10开始5行
distinct: 去除重复
SELECT job, deptno FROM emp; 省略了all, 默认带all选项
SELECT ALL job, deptno FROM emp; 同上
SELECT DISTINCT job, deptno FROM emp; 去除重复记录
查询有员工的部门信息
select * from dept where deptno in (SELECT DISTINCT deptno from emp);
union: 即联合查询, 多个集合联合在一起, 自动去掉结果集中的重复行, 并以第一列结果进行升序排列
SELECT empno, ename, job FROM emp WHERE job = 'SALESMAN'
UNION
SELECT empno, ename, job FROM emp WHERE job = 'MANAGER'; 去重复
SELECT empno, ename, job FROM emp WHERE job = 'SALESMAN' OR job = 'MANAGER'; 没有去重
炫个技
SELECT empno, ename, job FROM emp WHERE job = 'SALESMAN'
UNION
SELECT empno, ename, job FROM emp WHERE job = 'MANAGER' ORDER BY 2 DESC;
union all: 有重复并集, 不去掉重复行, 并且不对结果进行排序
SELECT job, sal FROM emp WHERE empno = 7902
UNION ALL
SELECT job, sal FROM emp WHERE empno = 7788; 没有去除重复结果
SELECT job, sal FROM emp WHERE empno = 7902 or empno = 7788; 与上面结果相同
SELECT job, sal FROM emp WHERE empno = 7902
UNION
SELECT job, sal FROM emp WHERE empno = 7788; 去除重复结果
多表查询/连接查询
- 交叉连接, 实际上后面都是交叉连接情况下加些过滤条件
- 内连接: 等值连接, 不等值连接, 自身连接
- 外连接: (1)左外连接; (2)右外连接; (3)全连接
- 自然连接
交叉查询
交叉连接是不带WHERE 子句的多表查询,它返回被连接的两个表所有数据行的笛卡尔积。返回到结果集合中的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。即表1中的每条数据分别与表2中的每条数据相连
SELECT * FROM emp, dept;
内连接
内连接(等值连接)
等值连接:在连接条件中使用等于号(=)运算符比较被连接列的列值,其查询结果中列出被连接表中的所有列,包括其中的重复列
SELECT * FROM emp INNER JOIN dept ON emp.deptno = dept.deptno; 根据deptno排序
SELECT * FROM emp, dept WHERE emp.deptno = dept.deptno; 常用
内连接(不等连接), 相当于把两个相同的表连接起来, 即两个表合并为一个表
不等连接: 在连接条件使用除等于运算符以外的其它比较运算符比较被连接的列的列值。这些运算符包括>、>=、<=、<、!>、!<和<>
SELECT * FROM emp INNER JOIN dept ON emp.deptno > dept.deptno;
SELECT * FROM emp, dept WHERE emp.deptno > dept.deptno;
内连接(自身连接) 查询员工及其领导名称,格式如下: 员工 领导
SELECT A.ename 员工, B.ename 领导 FROM emp A, emp B WHERE A.mgr = B.empno;
外连接
外连接(左连接)
返回包括左表中的所有记录和右表中联结字段相等的记录;即左外连接就是在等值连接的基础上 加上 主表中的 未匹配数据
插入一个员工的部门号在dept中不存在时, 内连接与外连接有区别了
INSERT INTO emp(empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES(9999, 'XXXX', 'CLERK', 7782, '1982-01-23', 1300, null, 90);
SELECT * FROM emp LEFT OUTER JOIN dept ON emp.deptno = dept.deptno; outer可省略
SELECT * FROM emp LEFT JOIN dept ON emp.deptno = dept.deptno; 省略OUTER, 部门号为90的没有查询出来
SELECT * FROM emp INNER JOIN dept ON emp.deptno = dept.deptno; emp表取出一条数据与dept表进行连接, 部门号90的查询出来
SELECT * FROM emp, dept WHERE emp.deptno = dept.deptno;
外连接(右连接)
返回包括右表中的所有记录和左表中联结字段相等的记录;即右外连接是在等值连接的基础上加上被连接表的不匹配数据
SELECT * FROM emp RIGHT OUTER JOIN dept ON emp.deptno = dept.deptno; outer可省略
SELECT * FROM emp RIGHT JOIN dept ON emp.deptno = dept.deptno; 如40号部门在员工表中无记录, 此时也连接过来了
SELECT * FROM emp INNER JOIN dept ON emp.deptno = dept.deptno; 40号部门在员工表中无记录, 此时没有连接
SELECT * FROM emp, dept WHERE emp.deptno = dept.deptno; 结果同上
外连接(全连接)
全外连接是在等值连接的基础上将左表和右表的未匹配数据都加上
SELECT * FROM emp FULL OUTER JOIN dept ON emp.deptno = dept.deptno; outer可省略
SELECT * FROM emp FULL JOIN dept ON emp.deptno = dept.deptno; 注:MySQL不支持该语法
SELECT * FROM emp full [outer] join dept ON emp.deptno=dept.deptno
使用union模拟全连接
SELECT * FROM emp LEFT JOIN dept ON emp.deptno = dept.deptno
UNION
SELECT * FROM emp RIGHT JOIN dept ON emp.deptno = dept.deptno; emp中部门号90与未dept中并未显示的40都选中出来
外连接省略OUTER
SELECT * FROM emp LEFT OUTER JOIN dept ON emp.deptno = dept.deptno
UNION
SELECT * FROM emp RIGHT OUTER JOIN dept ON emp.deptno = dept.deptno;
自然连接
自然连接:在连接条件中使用等于(=)运算符比较被连接列的列值,但它使用选择列表指出查询结果集合中所包括的列,并删除连接表中的重复列
SELECT * FROM emp NATURAL JOIN dept; 把deptno放在第一列, 并且不会有重复的deptno列
SELECT * FROM emp NATURAL LEFT JOIN dept; 自然连接+左连接, 即emp中部门90的连接出来了
SELECT * FROM emp NATURAL RIGHT JOIN dept; 自然连接+右连接, 即dept中部门号40的连接出来
子查询
子查询即一个查询语句嵌到另一个查询语句的子句中;
可以出现在另一个查询的列中,where子句中,from子句中等. 列与WHERE中相当于连接, FROM中相当于构建一张表
思考:查询员工及其领导名称,格式如: 员工 领导
SELECT A.ename 员工, B.ename 领导 FROM emp A, emp B WHERE A.mgr = B.empno; 自身连接, 不显示总裁, 因为总裁不满足WHERE条件, 总裁的mgr为空
子查询, 多一个, 总裁的领导为null也显示出来了, 从外层emp表找出员工, 内层表找出emp等于外层的mgr的表
SELECT ename 员工, (SELECT ename FROM emp WHERE empno = e.mgr) 领导 FROM emp e; 显示总裁
有些时候连接查询,也可以用子查询来实现
列出所有“CLERK”(办事员)的姓名及其部门名称
选出所有部门为CLERK的员工
SELECT
ename 员工
FROM
emp
WHERE
emp.job = 'CLERK';
选出所有部门为CLERK的员工, 但这里包含连接的功能, 并没有过滤外层所过滤的选项, 所以会出现xxxx(部门号不在deptno中)的部门为null
SELECT
ename 员工,
( SELECT dname FROM dept WHERE dept.deptno = emp.deptno ) 部门 # 这里只是起连接的功能, 没有过滤功能
FROM
emp
WHERE
emp.job = 'CLERK';
与上面原理相同, 只不过把xxxx在最外层过滤掉了
SELECT
ename 员工,
(SELECT dname FROM dept WHERE deptno = emp.deptno) 部门
FROM
emp
WHERE
emp.job = 'CLERK'
AND emp.deptno IN (SELECT deptno FROM dept); # 外层这里过滤xxxx, 子查询出现在WHERE中
实际是交叉查询然后过滤. 不会出现部门号90, 因为没有与90连接相等的条件
SELECT
ename 员工,
dname 部门
FROM
emp,
dept
WHERE
job = 'CLERK'
AND emp.deptno = dept.deptno;
子查询在FROM中, 相当于重新构建一张表, 这里不会过滤xxxx
SELECT
ename,
dname
FROM
(SELECT ename, (SELECT dname FROM dept WHERE deptno = emp.deptno) dname FROM emp WHERE job = 'CLERK') a # 子查询出现在from中, 这里a不加出错
子查询在FROM中, 相当于重新构建一张表, WHERE过滤xxxx
SELECT
ename,
dname
FROM
(SELECT ename, (SELECT dname FROM dept WHERE deptno = emp.deptno) dname FROM emp WHERE job = 'CLERK') a # 子查询出现在from中, 这里a不加出错
WHERE
dname IS NOT NULL;
左连接, 重新构建一个表, 会显式xxxx
SELECT
ename,
dname
FROM
emp
LEFT JOIN dept ON emp.deptno = dept.deptno
WHERE
job = 'CLERK';
左连接, 重新构建一个表, WHERE过滤xxxx
SELECT
ename,
dname
FROM
emp
LEFT JOIN dept ON emp.deptno = dept.deptno
WHERE
job = 'CLERK'
AND dname IS NOT NULL;
列出薪金比'SMITH'高的员工
子查询出现在where语句中, 并且子查询只有一个条记录, 一条记录中只有一个字段, 若有多个字段不能这么使用
SELECT
*
FROM
emp
WHERE
sal > ( SELECT sal FROM emp WHERE ename = 'SMITH' ); # 子查询中SELECT只会查出一个字段
列出受雇日期早于其直接上级的所有员工
SELECT
*
FROM
emp e
WHERE
e.hiredate < (SELECT hiredate FROM emp WHERE empno=e.mgr); # 子查询中SELECT只会查出一个字段
any/all
<any,小于子查询中的某个值。等价于<max
>any,大于子查询中的某个值。等价于>min
>all,大于子查询中的所有值。等价于>max
<all,小于子查询中的所有值。等价于<min
这里子查询出现在WHERE中, 都只会查出一个字段
查询薪金小于销售员某个员工的员工信息
SELECT
*
FROM
emp
WHERE
sal < ANY ( SELECT sal FROM emp WHERE job = 'SALESMAN' );
SELECT
*
FROM
emp
WHERE
sal < ( SELECT max( sal ) FROM emp WHERE job = 'SALESMAN' );
查询薪金大于所有销售员的员工信息
SELECT
*
FROM
emp
WHERE sal > ALL ( SELECT sal FROM emp WHERE job = 'SALESMAN' );
SELECT
*
FROM
emp
WHERE
sal > ( SELECT max(sal) FROM emp WHERE job = 'SALESMAN' );
exists 存在性条件判断
若内层查询非空,则外层的where子句返回真值,否则返回假。not exists相反。
列出与“SCOTT”从事相同工作的所有员工
SELECT
*
FROM
emp e
WHERE
EXISTS ( SELECT * FROM emp WHERE ename = 'SCOTT' AND e.job = job );
过滤SCOTT自身
SELECT
*
FROM
emp e
WHERE
EXISTS ( SELECT * FROM emp WHERE ename = 'SCOTT' AND e.job = job )
AND ename <> 'SCOTT'; # 不显示SCOTT本身
SELECT
*
FROM
emp
WHERE
job = ( SELECT job FROM emp WHERE ename = 'SCOTT' );
聚合函数
group by/having
group by与子查询, 分组之后的字段选择只能是分组字段与聚合函数
SELECT
分组字段或聚合函数
FROM
表
GROUP BY
分组字段 HAVING 条件 # 不能再用where条件
ORDER BY
字段
聚合函数一般用于统计,常用如下:
count(field) 记录数
avg(field) 平均值
min(field) 最小值
max(field) 最大值
sum(field) 总和
下面两个结果一样但是后者效率高, 因为只统计一个字段
SELECT count( * ) AS 记录数 FROM emp; 统计整个表的字段, 即使某一行的所有字段都为NULL也是一条记录
SELECT count( 0 ) AS 记录数 FROM emp; 统计这张表有几行字段, 只统计行首, 行首某一行为null也算一条
SELECT count( comm ) AS 记录数 FROM emp; 统计非空字段, comm为空不记录
SELECT count( empno ) AS 记录数 FROM emp;也是统计行首字段, 但是当行首字段为null时不统计
# 其实不加AS也行
SELECT
avg( sal ) AS 平均薪金,
max( sal ) AS 最高薪金,
min( sal ) AS 最低薪金,
sum( sal ) AS 薪金总和
FROM
emp;
group by/having
分组查询通常用于统计,一般和聚合函数配合使用
SELECT deptno, count( * ) FROM emp GROUP BY deptno;
# 分组的字段加上聚合函数, 不能再加上其他的聚合函数, 如(mysql中好像可以)select deptno, count(*), sal from emp group by deptno;
SELECT deptno, count( * ), sum( sal ) FROM emp GROUP BY deptno; # 后面加上聚合函数, 统计部门的总工资可以
SELECT deptno, count( * ), max( sal ) FROM emp GROUP BY deptno; # 统计部门内的最高工资
SELECT deptno, count( * ) cn FROM emp GROUP BY deptno HAVING cn > 3 ORDER BY cn DESC;
列出各部门信息以及部门人数。
SELECT
*,
( SELECT count( * ) FROM emp GROUP BY deptno HAVING deptno = dept.deptno ) total
FROM
dept;
使用函数把null显示为0
SELECT
*,
ifnull( ( SELECT count( * ) FROM emp GROUP BY deptno HAVING deptno = dept.deptno ), 0 ) total
FROM
dept;
查询出薪金成本最高的部门的部门号和部门名称
SELECT
dept.deptno,
dept.dname,
sum( sal )
FROM
dept,
emp
GROUP BY
dept.deptno,
dept.dname; # 没有连接条件, 没有分组
# 查出以部门与部门名称为分组查出总薪资
SELECT
dept.deptno,
dept.dname,
sum( sal )
FROM
dept,
emp
WHERE # 加上了连接条件
dept.deptno = emp.deptno
GROUP BY
dept.deptno,
dept.dname;
SELECT
dept.deptno,
dept.dname,
sum( sal )
FROM
dept,
emp
WHERE
dept.deptno = emp.deptno
GROUP BY
dept.deptno,
dept.dname
HAVING
sum( sal ) >= ALL ( SELECT sum( sal ) FROM emp GROUP BY deptno );
SELECT
dept.deptno,
dept.dname
FROM
dept,
emp
WHERE
dept.deptno = emp.deptno
GROUP BY
dept.deptno,
dept.dname
HAVING
sum( sal ) >= ( SELECT max( t.total ) FROM ( SELECT sum( sal ) total FROM emp GROUP BY deptno ) t )
MySQL函数
控制流程函数
字符串函数
数值函数
日期和时间函数
聚合函数
CASE value WHEN [compare-value] THEN result [WHEN [compare-value] THEN result ...] [ELSE result] END
CASE WHEN [condition] THEN result [WHEN [condition] THEN result ...] [ELSE result] END
SELECT
CASE
6
WHEN 1 THEN
'one'
WHEN 2 THEN
'two'
ELSE
'more'
END;
SELECT
CASE
WHEN 1 > 0 THEN
'true'
ELSE
'false'
END;
查询员工的薪金等级, 格式: 员工 薪金 等级
SELECT ename 员工, sal 薪资, grade
FROM emp, salgrade
WHERE sal BETWEEN losal and hisal;
select
ename 员工,
sal 薪金,
case grade when 1 then '一级'
when 2 then '二级'
when 3 then '三级'
when 4 then '四级'
when 5 then '五级'
end 等级
from emp, salgrade
where sal between losal and hisal; # 连接条件sal在最低工资与最高工资之间
SELECT ASCII('2a');
SELECT ASCII('a2'); 最左边饿ASCII码值
SELECT BIN(12); 转换成二进制字符串
SELECT BIT_LENGTH('text'); 位长度
SELECT CHAR(77,121,83,81,'76');
SELECT CHAR(77,121,83,81,76);
SELECT 3+5;
SELECT 3/5;
SELECT ABS(-32);
now
date_add/adddate
datediff
date_format
select now();
SELECT DATE_ADD('1998-02-02', INTERVAL 31 DAY);
SELECT DATE_ADD('1998-02-02', INTERVAL 28 DAY);
SELECT adddate('1998-02-02', INTERVAL 28 DAY);
SELECT adddate('1998-02-02', 28);
select DATEDIFF(now(),'2014-02-01');
select DATEDIFF('2014-02-01','2014-03-01');
select DATE_FORMAT(now(), '%H:%i:%s');
select DATE_FORMAT(now(), '%Y%M%D');
select DATE_FORMAT(now(), '%Y%m%d');
数据库设计三范式
学号 | 姓名 | 系 | 系主任 | 课程 | 成绩 |
---|---|---|---|---|---|
1 | 张三 | 计算机系 | 陈老师 | 数据结构 | 90 |
2 | 李四 | 经管系 | 李老师 | 国际金融 | 88 |
3 | 王五 | 计算机系 | 陈老师 | 数据结构 | 80 |
4 | 赵六 | 数学系 | 黄老师 | 基础数学 | 68 |
1NF,数据库表的每一列都是不可分割的基本数据项
学号 | 姓名 | 系 | 系主任 | 课程 | 成绩 |
---|---|---|---|---|---|
1 | 张三 | 计算机系 | 陈老师 | 数据结构 | 90 |
2 | 李四 | 经管系 | 李老师 | 国际金融 | 88 |
3 | 王五 | 计算机系 | 陈老师 | 数据结构 | 80 |
4 | 赵六 | 数学系 | 黄老师 | 基础数学 | 68 |
艺术系 | 刘老师 | ||||
1 | 张三 | 计算机 | 陈老师 | 操作系统 | 80 |
问题: |
- 插入异常. 新开一个艺术系没有学生, 系的字段插入艺术系, 其他字段没有数据, 插入异常
- 数据冗余. 如张三有两个课程
- 更新复杂. 如上表计算机系改成计算机科学系, 需要更改两条记录
- 删除异常. 如计算机系的学生都毕业了, 那么计算机系就被删除了
2NF, 首先满足1NF(属性不可分), 要求数据库表中的每个实体必须可以被唯一地区分(主关键字), 要求实体的非主属性完全依赖于主关键字
学号和课程是主属性, 姓名, 系, 成绩是非主属性
(学号, 课程) p-> 姓名, 依赖于学号, 部分依赖于主关键字
(学号, 课程) p-> 系, 依赖于课程, 部分依赖于主关键字
(学号, 课程) -> 成绩, 完全依赖于课程, 部分依赖于主关键字
存在这样的缺陷就会有上面的问题
解决方法: 投影
学号 姓名 系 系主任
学号 课程 成绩
学号->系, 系->系主任, 学号->系主任: 传递依赖
3NF, 首先满足2NF, 每一列数据都和主键直接相关, 而不能间接相关, 即不能传递依赖于主关键字
投影:
学号 姓名 系号
系号 系 系主任
学号 课程号 成绩
课程号 课程
学号 | 姓名 | 系 | 系主任 | 课程 | 成绩 |
---|---|---|---|---|---|
1 | 张三 | 计算机系 | 陈老师 | 数据结构 | 90 |
2 | 李四 | 经管系 | 李老师 | 国际金融 | 88 |
3 | 王五 | 计算机系 | 陈老师 | 数据结构 | 80 |
4 | 赵六 | 数学系 | 黄老师 | 基础数学 | 68 |
艺术系 | 刘老师 | ||||
1 | 张三 | 计算机 | 陈老师 | 操作系统 | 80 |
1NF, 属性不可分
2NF, 所有的非主属性完全依赖于码(不能部分依赖于码)
3NF, 所有的非主属性即不能部分依赖于码, 也不能传递依赖于码
减少数据冗余
易于扩展
易于编程
外键约束中级联更新与级联删除
级联更新: 课程名称改变课程表发生更新, 成绩表中课程号改变. ON DELETE CASCADE, 限制级联更新on delete restrict
级联删除: 课程表中课程删除, 课程表中成绩删除. ON UPDATE CASCADE, 限制级联删除on delete restrict
alter table TRANS add constraint FK_ACCOUNT_ID foreign key (ACCOUNT_ID)
references BANK_ACCOUNT (ACCOUNT_ID) on delete restrict on update restrict; # 不允许级联更新级联删除
alter table TRANS add constraint FK_ABSTRACT_ID foreign key (ABSTRACT_ID)
references ABSTRACT (ABSTRACT_ID) on delete restrict on update restrict;
alter table TRANS add constraint FK_OTHER_ACCOUNT_ID foreign key (OTHER_ACCOUNT_ID)
references BANK_ACCOUNT (ACCOUNT_ID) on delete restrict on update restrict;
数据
C++访问数据库
libmysql.lib 仅仅只包含libmysql.dll的一些符号信息, 并不包含实际的代码
libmysql.dll 实际的实现代码
// MYSQL结构代表一个连接句柄
MYSQL *mysql_init(MYSQL *mysql);
// 如果mysql是NULL指针,该函数将分配、初始化、并返回新对象。
// 否则,将初始化对象,并返回对象的地址。
// 如果mysql_init()分配了新的对象,当调用mysql_close()来关闭连接时。将释放该对象。
MYSQL *mysql_real_connect(
MYSQL *mysql,
const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long client_flag);
my_bool reconnect = true;
mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); // 设置重连
mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "gbk"); // 支持中文
int mysql_query(MYSQL *mysql, const char *q); // insert, update, delete不会返回结果集, 但会改变数据库的状态
my_ulonglong mysql_affected_rows(MYSQL *mysql); // 返回受影响的行数
MYSQL_RES* mysql_store_result(MYSQL *mysql); // 返回结果集
my_ulonglong mysql_num_rows(MYSQL_RES *res);
unsigned int mysql_num_fields(MYSQL_RES *res);
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
MYSQL_FIELD* mysql_fetch_field(MYSQL_RES *result);
void mysql_free_result(MYSQL_RES *result);
my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql); // 返回插入的ID, 用于字段自动增长, auto_increment
每个账号金额的变动, 都需要在trans表中增加交易流水
事务
存款, 取款, 转入, 转出涉及事务操作与MySQL封锁机制
四个基本特性(ACID):
- 原子性(Atomicity): 事务中包括的操作, 要么都做, 要么都不做
例如开户操作涉及两张表bank_account与trans, 首先在前插入一条记录然后在后者插入一条交易流水, 这两个操作要么都做要么都不做 - 一致性(Consistency): 事务执行的结果是使数据库从一个致性状态到另一个一致性状态, 失败则保持原有状态. 一致性是通过原子性来保证的, 因此一致性与原子性是密切相关的.
转账: A向B转100块, A余额减少100, B余额增加100. 如果A余额减少成功, 而B的余额增加失败, 那么就会使数据库处于不一致的状态 - 隔离性(Isolation): 一个事务的执行不能被其它事务干扰, 通过mysql封锁机制实现
- 持久性(Durability): 事务一旦提交,它对数据库中数据的改变是永久性的
事务的三个基本操作
- 开始
- 回滚. 事务的操作都是在缓存中进行的, 并没有更改到数据库中数据文件, 任何一个地方出错了都可以回滚.
- 提交. 一旦提交数据的改变就是永久的, 事务的日志中的数据更新到数据库文件中, 对数据起永久性改变
MySQL封锁机制
排它锁X: 又称为写锁
共享锁S: 又称为读锁
一个事务对数据库表或表中的某些行施加 排它锁, 那么另一个事务就不能再对它施加排它锁, 也不能对其施加共享锁
一个事务对数据库表或表中的某些行施加 共享锁, 那么另一个事务就不能再对它施加排它锁, 但可以施加共享锁
单纯的select语句通常情况下是不加锁的, 只有在隔离级别下才加锁
select … for update 称为IX锁, 意向排它锁, 又称为悲观锁, 用于存款, 取款, 转账(转入, 转出)
开户
客户端:
OpenAccountForm UI收集数据 -> BankSession -> CMD层中的OpenAccount Execute
服务端:
Thread Run -> BankSession Process -> CMD层中的OpenAccount Execute -> DAL层进行实际操作
开会操作涉及两张表, 事务操作, 要么两张表都做, 要么两者都不做
bank_account
trans