-- 方式一
SELECT salary
FROM employees
WHERE last_name ='Abel';
SELECT last_name, salary
FROM employees
WHERE salary>11000;
-- 方式二:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e1.last_name='Abel'
AND e1.salary <e2.salary;
-- 方式三:子查询
SELECT last_name,salary
FROM employees
WHERE salary>(
SELECT salary
FROM employees
WHERE last_name='Abel'
);
分类方式一:
按内查询的的结果返回一条还是多条记录,将子查询分为单行子查询、多行子查询。
子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。
同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式称为相关子查询。
SELECT last_name,job_id,salary
FROM employees
WHERE job_id =(
SELECT job_id
FROM employees
WHERE employee_id =141)
AND salary>(
SELECT salary
FROM employees
WHERE employee_id =143);
SELECT last_name,job_id,salary
FROM employees
WHERE salary = (
SELECT MIN(salary)
FROM employees);
-- 实现方式1:不成对比较
SELECT employee_id, manager_id,department_id
FROM employees
WHERE manager_id IN (
SELECT manager_id
FROM employees
WHERE employee_id IN (174,141))
AND department_id IN(
SELECT department_id
FROM employees
WHERE employees_id IN(141,174))
AND employee_id NOT IN(141,174);
-- 实现方式2:成对比较
SELECT employee_id,manager_id,department_id
FROM employees
WHERE (manager_id,department_id)IN(
SELECT manager_id,department_id
FROM employees
WHERE employee_id IN(141,174))
AND employee_id NOT IN(141,174);
SELECT department_id,MIN(salary)
FROM employees
GROUP BY department_id
HAVING MIN(salary)>(
SELECT MIN(salary)
FROM employees
WHERE department_id =50);
在CASE表达式中使用单列子查询
题目:显示员工的employee_id,last_name,和location。其中若员工的department_id与location_id为1800的department_id相同,则location为“Canada”,其余为“USA”。
SELECT employees_id,last_name,
(CASE department_id
WHEN
(SELECT department_id FROM departments
WHERE location_id =1800)
THEN 'Canada' ELSE 'USA' END)location
FROM employees;
SELECT last_name,job_id
FROM employees
WHERE job_id=(
SELECT job_id
FROM employees
WHERE last_name="Haas");
SELECT employee_id,last_name
FROM employees
WHERE salary=(
SELECT MIN(salary)
FROM employees
GROUP BY department_id);
-- 方式1:
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary)=(
SELECT MIN(avg_sal)
FROM (
SELECT AVG(salary) avg_sal
FROM employees
GROUP BY department_id
) dept_avg_sal
)
-- 方式2
SELECT department_id
FROM employees
GROUP BY department
HAVING AVG(salary) <= ALL(
SELECT AVG(salary) avg_sal
FROM employees
GROUP BY department_id);
SELECT last_name
FROM employees
WHERE employee_id NOT IN(
SELECT manager_id
FROM employees);
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称为关联子查询
题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
方式二:在FROM中使用子查询
SELECT last_name,salary,e1.department_id
FROM employees e1,(SELECT department_id,AVG(salary) dept_avg_sal FROM employees GROUP BY department_id) e2
WHERE e1.department_id = e2.department_id
AND e2.dept_avg_sal<e1.salary;
FROM 型的子查询:子查询是作为FROM的一部分,子查询要用()引起来,并且要给这个子查询取别名,把它当成一张临时的虚拟表来使用
在ORDER BY中使用子查询
题目:查询员工的id,salary,按照department_name排序
SELECT employee_id,salary
FROM employees e
ORDER BY(
SELECT department_name
FROM departments d
WHERE e.department_id=d.department_id);
SELECT e.employee_id, last_name,e.job_id
FROM employees e
WHERE 2 <= (
SELECT COUNT(*)
FROM job_history
WHERE employee_id = e.employee_id);
关联子查询通常也会和EXISTS操作符一起来使用,用来检查字查询中是否存在满足条件的行。
如果在子查询中不存在满足条件的行:
如果在子查询中存在满足条件的行:
NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。
题目:查询公司管理者的employee_id,last_name,job_id,department_id信息
方式一:
SELECT employee_id, last_name, job_id, department_id
FROM employees e1
WHERE EXISTS (
SELECT *
FROM employees e2
WHERE e2.manager_id = e1.employee_id);
SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id
FROM employees e1 JOIN employees e2
WHERE e1.employee_id = e2.manager_id;
SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN (
SELECT DISTINCT manager_id
FROM employees );
SELECT department_id, department_name
FROM departments d
WHERE NOT EXISTS (
SELECT 'X'
FROM employees
WHERE department_id = d.department_id);
UPDATE table1 alias1
SET column=(
SELECT expression
FROM table2 alias2
WHERE alias1.column=alias2.column);
-- 1.
ALTER TABLE employees
ADD(department_name VARCHAR2(14));
-- 2.
UPDATE employees e
SET department_name = (SELECT department_name
FROM departments d
WHERE e.department_id = d.department);
DELETE FROM table1 alias1
WHERE colunm operator(SELECT expression
FROM table2 alias2
WHERE alias1.column = alias2.column);
使用相关子查询依据一个表中的数据删除另一个表的数据
题目:删除表employees 中,与emp_history 表皆有的数据
DELETE FROM employees e
WHERE employee_id in(
SELECT employee_id
FROM emp_history
WHERE employee_id = e.employee_id);
问题:谁的工资必Abel的高?
解答:
-- 方式1:自连接
SELECT e2.last_name,e2.salary
FROM employees e1, employees e2
WHERE e1.last_name = 'Abel'
AND e1.salary<e2.sarary
-- 方式2:子查询
SELECT last_name, salary
FROM employees
WHERE salary>(
SELECT salary
FROM employeees
WHERE last_name='Abel');
问题:以上两种方式有好坏之分吗?
解答:自连接方式是好!
题目中可以使用子查询,也可以使用自连接。一般情况下建议使用自连接,因为在许多DBMS的处理过程中,对于自连接的处理速度要比子查询快很多。
可以这样理解:子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因为在大部分DBMS中都对自连接处理进行了优化。
CREATE DATABASE 数据库名;
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
CREATE DATABASE IF NOT EXISTS 数据库名;
如果MySQL中已经存在相应的数据库,则忽略创建语句,不再创建数据库。
SHOW DATABASES;# 有一个S,代表多个数据库
SELECT DATABAE(); # 使用一个MySQL中的全局函数。
SHOW TABLES FROM 数据库名;
SHOW CREATE DATABASE 数据库名;
或者
SHOW CREATE DATABASE 数据库名\G
USE 数据库名;
注意:要操作表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上数据库名
ALTER DATABASE 数据库名 CHARACTER SET 字符集; # 比如gbk,utf8等
DROP DATABASE 数据库名;
DROP DATABASE IF EXISTS 数据库名;
CREATE TABLE [IF NOT EXISTS]表名(
字段1,数据类型[约束条件][默认值],
字段2,数据类型[约束条件][默认值],
字段3,数据类型[约束条件][默认值],
……
[表约束条件]
);
如果加了IF NOT EXISTS 关键字,则表示:如果当前数据库中不存在要创建的数据包,则创建数据表;如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。
-- 创建表
CREATE TABLE emp(
-- int 类型
emp_id INT,
-- 最多保存20个中英文字符
emp_name VARCHAR(20),
-- 总位数不超过15位
salary DOUBLE,
-- 日期类
birthday DATE
);
DESC emp;
MySQL 在执行建表语句时,将id字段的类型设置为INT(11),这里的11实际上是int类型指定的显示宽度,默认的显示宽度为11,也可以在创建表的时候指定数据的显示宽度。
CREATE TABLE dept(
-- int类型,自增
deptno INT(2) AUTO_INCREMENT,
dname VARCHAR(14),
loc VARCHAR(13),
-- 主键
PRIMARY KEY(deptno)
);
DESCRIBE dept;
CREATE TABLE emp1 AS SELECT * FROM employees;
CREATE TABLE emp2 AS SELECT * FROM employees;
CREATE TABLE dept80
AS
SELECT employee_id,last_name,salary*12 ANNSAL,hire_date
FROM employes
WHERE department_id =80;
DESCRIBE dept80;
在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用 DESCRIBE/ DESC 语句查看数据表结构,也支持使用SHOW CREATE TABLE 语句查看数据表结构。
SHOW CREATE TABLE 表名\G
修改表指的是修改数据库中已经存在的数据表的结构。
ALTER TABLE 表名 ADD [COLUMN] 字段名 字段类型[FIRST|AFTER 字段名];
ALTER TABLE dept80
ADD job_id varchar(15);
ALTER TABLE 表名 MODIFY [COLUMN] 字段名1 字段类型[DEFAULT 默认值] [FIRDT|AFTER 字段名2];
ALTER TABLE dept80
MODIFY last_name VARCHAR(30);
ALTER TABLE dept80
MODIFY salary double(9,2) default 1000;
使用CHANGE old_column new_column dataType 子句重命名列。语法格式如下;
ALTER TABLE 表名 CHANGE 【column】 列名 新列名 新数据类型;
ALTER TABLE dept80
CHANGE department_name dept_name varchar(15);
ALTER TABLE 表名 DROP 【COLUMN】字段名
ALTER TABLE dept80 DROP COLUMN job_id;
RENAME TABLE emp
TO myemp
ALTER TABLE dept
RENAME[TO] detail_dept;-- [To] 可以省略
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n];
IF EXISTS:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
DROP TABLE dept80;
TRUNCATE TABLE detail_dept;
TRUNCATE 语句不能回滚,而是用DELETE语句删除数据,可以回滚
对比:
SET autocommit = FALSE;
DELECT FROM emp2;
# TRUNCATE TABLE emp2;
SELECT * FROM emp2;
ROLLBACK;
SELECT * FROM emp2;
拓展2:如何理解清空表,删除表等操作需谨慎!
表删除操作将把表的定义和表中的数据一起删除,并且MySQL在执行删除操作时,不会有任何的确认信息提示,因此执行删除操作时应当慎重。在删除表前,最好对表中的数据进行备份,这样当操作失误时可以对数据进行恢复,以避免造成无法挽回的结果。
同样的,在使用ALTER TABLE 进行表的基本修改操作是,在执行操作过程之前,也应该确保对数据进行完整的备份,因为数据库的改变时无法撤销的,如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下的所有数据都将会丢失。
拓展3:MySQL8 新特性-DDL的原子化
在MySQL8.0 版中,InNoDB表的DDL支持事务完整性,即DDL操作要么成功要么回滚。DDL操作回滚日志写入data dictionary数据字典表mysql.innodb_ddl_log(该表是隐藏的表,通过show tables无法看到)中,用于回滚操作。通过设置参数,可将DDL操作日志打印输出到MySQL错误日志中。
分别在MySQL 5.7 版本和MySQL 8.0版本中创建数据库和数据表,结果如下:
CREATE DATABASE mytest;
USE mytest;
CREATE TABLE book1(
book_id INT ,
book_name VARCHAR(255) );
SHOW TABLES;
mysql> DROP TABLE book1,book2;
ERROR
1051 (42S02): Unknown table 'mytest.book2'
再次查询数据库中的数据表名称,虽然删除时报错了,但是仍然删除了数据表book1。
在MySQL 8.0 版本中,测试步骤如下:删除数据表book1和数据表book2,结果如下:
mysql> DROP TABLE book1,book2;
ERROR 1051 (42S02): Unknown table 'mytest.book2'
使用这种语法一次只能向表中插入一条数据
INSERT INTO 表名
VALUES(value1,value2,...);
值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。
举例:
INSERT INTO departments
VALUES (70,'Pub',100,1700);
INSERT INTO departments
VALUES(100,'Finance',NULL,NULL);
INSERT INTO 表名(column1 [,column2,...,columnn])
VALUES (value1 [,value2,...,valuen]);
为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为定义时的默认值。
在INSET字句中随意列出列名,但是一旦列出,VALUES中要插入的值要与列一一对应。如果类型不同,将无法插入,并且MySQL会产生误会。
INSERT INTO departments(department_id,department_name)
VALUES(80,'IT');
INSERT INTO table_name
VALUES
(value1 [,value2,...,valuen]),
(value1 [,value2,...,valuen]),
.....
(value1 [,value2,...,valuen]);
-- 或者
INSERT INTO table_name(column1 [,column2,...,columnn])
VALUES
(value1 [,value2,...,valuen]),
(value1 [,value2,...,valuen]),
.....
(value1 [,value2,...,valuen]);
-- 举例
INSERT INTO emp(emp_id,emp_name)
VALUES(1001,'sahif'),
(1002,'falfdf'),
(1003,'dfajdg');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
使用INSERT 同时插入多条记录时,MySQL会返回一些在执行单行插入时没有的额外信息:Records:表明插入的记录条数。Duplicates:表明插入时被忽略的记录,原因可能是这些记录包含了重复的主键值。Warnings:表明有问题的数据值,例如发生数据类型转换。
一个同时插入多行记录的INSERT语句等同于多个单行插入的INSERT语句,但是多行的INSERT语句在处理过程中 效率更高 。因为MySQL执行单条INSERT语句插入多行数据比使用多条INSERT语句快,所以在插入多条记录时最好选择使用单条INSERT语句的方式插入。
小结
VALUES 也可以写成VALUE,但是VALUES是标准写法
字符和日期型数据应包含在单引号中。
INSERT INTO 目标表名 (tar_column1 [, tar_column2, …, tar_columnn])
SELECT
(src_column1 [, src_column2, …, src_columnn])
FROM 源表名
[WHERE condition]
举例:
INSERT INTO emp2
SELECT *
FROM employees
WHERE department_id = 90;
INSERT INTO sales_reps(id, name, salary, commission_pct)
SELECT employee_id, last_name, salary, commission_pct
FROM employees
WHERE job_id LIKE '%REP%';
UPDATE table_name
SET column1=value1, column2=value2, … , column=valuen
[WHERE condition]
UPDATE employees
SET department_id = 70
WHERE employee_id = 113;
UPDATE copy_emp
SET department_id = 110;
UPDATE employees
SET department_id = 55
WHERE department_id = 110;
DELETE FROM table_name [WHERE <condition>];
table_name 指定要执行删除操作的表,“[WHERE ]”为可选参数,指定删除的条件,如果没有WHERE子句,DELETE语句将删除表中的所有记录。
DELETE FROM departments
WHERE department_name ='Finance';
DELECT FROM copy_emp;
DELECT FROM departments
WHERE department_id =60;
什么叫计算列?简单来说就是某一列的值是通过别的列计算得来的,例如a列为1,b列为2,c列不需要手动插入,定义为a+b的结果为c,那么c就是计算列,是通过别的列计算来的。
在MySQL 8.0中, CREATE TABLE 和ALTER TABLE都支持增加计算列。下面以CREATE TABLE为例进行说明
CREATE TABLE tb1(
id INT,
a INT,
b INT,
c INT GENRATED ALWAYS AS (a+b) VIRTUSL
);
演示结果:
# 1、创建数据库test01_library
CREATE TABLE IF NOT EXISTS test01_library CHARACTER SET 'utf8';
USE test01_library;
# 2、创建表 books
CREATE TABLE books(
id INT,
name VARCHAR(50),
authors VARCHAR(100),
price FLOAT,
pubdate YEAR,
note VARCHAR(100),
num INT
);
# 3、向books表中插入记录
# 1)不指定字段名称,插入第一条记录
INSERT INTO books
VALUES(1,'Tal of AAA','Dickes',23,1995,'novel',111);
# 2)指定所有字段名称,插入第二记录
INSERT INTO books (id,name,`authors`,price,pubdate,note,num) VALUES(2,'EmmaT','Jane lura',35,1993,'Joke',22);
# 3)同时插入多条记录(剩下的所有记录)
INSERT INTO books (id,name,`authors`,price,pubdate,note,num)
VALUES (3,'Story of Jane','Jane Tim',40,2001,'novel',0),
(4,'Lovey Day','George Byron',20,2005,'novel',30),
(5,'Old land','Honore Blade',30,2010,'Law',0),
(6,'The Battle','Upton Sara',30,1999,'medicine',40),
(7,'Rose Hood','Richard haggard',28,2008,'cartoon',28);
# 4、将小说类型(novel)的书的价格都增加5。
UPDATE books SET price= price+5 WHERE note 'novel';
# 5、将名称为EmmaT的书的价格改为40,并将说明改为drama。
UPDATE nooks SET price=40, note='drame' WHERE name='EmmaT';
# 6、删除库存为0的记录
DELETE FROM books WHERE num=0;
# 7、统计书名中包含a字母的书
SELECT *FROM books WHERE name LIKE '%a%';
# 8、统计书名中包含a字母的书的数量和库存总量
SELECT COUNT(*),SUM(num) FROM books WHERE name LIKE '%a%';
# 9、找出“novel”类型的书,按照价格降序排列
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC;
# 10、查询图书信息,按照库存量降序排列,如果库存量相同的按照note升序排列
SELECT * FROM books ORDER BY num DESC,note ASC;
# 11、按照note分类统计书的数量
SELECT note,COUNT(*) FROM books GROUP BY note;
# 12、按照note分类统计书的库存量,显示库存量超过30本的
SELECT note,SUM(num) FROM books GROUP BY note HAVING SUM(num)>30;
# 13、查询所有图书,每页显示5本,显示第二页
SELECT * FROM books LIMIT 5,5;
# 14、按照note分类统计书的库存量,显示库存量最多的
SELECT note,SUM(num) sum_num
FROM books
GROUP BY note
ORDER BY sum_num DESC
LIMIT 0,1;
# 15、查询书名达到10个字符的书,不包括里面的空格
SELECT * FROM books WHERE CHAR_LENGTH(REPLACE(name,' ',''))>=10;
# 16、查询书名和类型,其中note值为novel显示小说,law显示法律,medicine显示医药,cartoon显示卡通, joke显示笑话
SELECT name AS "书名" ,note, CASE note
WHEN 'novel' THEN '小说'
WHEN 'law' THEN '法律'
WHEN 'medicine' THEN '医药'
WHEN 'cartoon' THEN '卡通'
WHEN 'joke' THEN '笑话'
END AS "类型"
FROM books;
# 17、查询书名、库存,其中num值超过30本的,显示滞销,大于0并低于10的,显示畅销,为0的显示需要无货
SELECT name,num,CASE
WHEN num>30 THEN '滞销'
WHEN num>0 AND num<10 THEN '畅销'
WHEN num=0 THEN '无货'
ELSE '正常'
END AS "库存状态"
FROM books;
# 18、统计每一种note的库存量,并合计总量
SELECT IFNULL(note,'合计总库存量') AS note,SUM(num) FROM books GROUP BY note WITH ROLLUP;
# 19、统计每一种note的数量,并合计总量
SELECT IFNULL(note,'合计总数') AS note,COUNT(*)
FROM books
GROUP BY note
WITH ROLLUP;
# 20、统计库存量前三名的图书
SELECT * FROM books ORDER BY num DESC LIMIT 0,3;
# 21、找出最早出版的一本书
SELECT * FROM books ORDER BY pubdate ASC LIMIT 0,1;
# 22、找出novel中价格最高的一本书
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC LIMIT 0,1;
# 23、找出书名中字数最多的一本书,不含空格
SELECT * FROM books ORDER BY CHAR_LENGTH(REPLACE(name,' ','')) DESC LIMIT 0,1;
整数类型一共有 5 种,包括 TINYINT、SMALLINT、MEDIUMINT、INT(INTEGER)和 BIGINT。
它们的区别如下表所示
显示宽度与类型可存储的值范围无关
。从MySQL 8.0开始,整数数据类型不推荐使用显示宽度属性。CREATE TABLE test_int1 (
x TINYINT,
y SMALLINT,
z MEDIUMINT,
m INT,
n BIGINT );
UNSIGNED:无符号类型(非负),所有的整数类型都有一个可选的UNSIGNED(无符号属性),无符号整数类型的最小取值为0。所以,如果需要在MySQL数据库中保存非负整数值时,可将整数类型设置为无符号类型。
int类型默认显示宽度为int(11),无符号int类型默认显示宽度为int(10)。
原来,在int(M)中,M的值根int(M)所占多少存储空间并无任何关系。int(3),int(4),int(8)在磁盘上都是占用4bytes的存储空间。也就是说,int(M),必须和UNSIGNED ZERROFILL 一起使用才有意义。如果整数超过M位,就按照实际位数存储。只是无需再用字符0进行填充。
浮点数和定点数类型的特点是可以处理小数,可以把整数看成小数的一个特例。因此,浮点数和定点数的使用场景,比正数大多了。MySQL 支持的浮点数类型,分别为FLOAT和DOUBLE,FEAL。
SET sql_mode="REAL_AS_FLOAT";
问题1: FLOAT和DOUBLE这两种类型的区别是啥?
FLOAT 占用字节数少,取值范围小;DOUBLE 占用字节数多,取值范围也大。
问题2: 为什么浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半,也就是只相当于有符号数取值范围大于等于零的部分呢?
MySQL 存储浮点数的格式为: 符号(S) 、 尾数(M) 和 阶码(E) 。因此,无论有没有符号,MySQL 的浮点数都会存储表示符号的部分。因此, 所谓的无符号数取值范围,其实就是有符号数取值范围大于等于零的部分。
对于浮点类型,在MySQL中单精度值使用 4 个字节,双精度值使用 8 个字节。
从MySQL 8.0.17开始,FLOAT(M,D) 和DOUBLE(M,D)用法在官方文档中已经明确不推荐使用,将来可能被移除。另外,关于浮点型FLOAT和DOUBLE的UNSIGNED也不推荐使用了,将来也可能被移除。
浮点数类型有个缺陷,就是不精准。下面重点解释一下为什么 MySQL 的浮点数不够精准。比如,设计一个表,有f1这个字段,插入值分别为0.47,0.44,0.19,期待的运行结果是:0.47 + 0.44 + 0.19 = 1.1。而使用sum之后查询:
DECIMAL
。MySQL 中的定点数类型只有DECIMAL一种类型
使用 DECIMAL(M,D) 的方式表示高精度小数。其中,M被称为精度,D被称为标度。0<=M<=65, 0<=D<=30,D
DECIMAL(M,D)的最大取值范围与DOUBLE类型一样,但是有效的数据范围是由M和D决定的。DECIMAL 的存储空间并不是固定的,由精度值M决定,总共占用的存储空间为M+2个字节。也就是说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数值范围可以更大一些。
定点数在MySQL内部是以 字符串 的形式进行存储,这就决定了它一定是精准的
当DECIMAL类型不指定精度和标度时,其默认为DECIMAL(10,0)。当数据的精度超出了定点数类型的精度范围时,则MySQL同样会进行四舍五入处理。
浮点数 vs 定点数
举例:
- 举例:
运行下面的语句,把test_double2表中字段“f1”的数据类型修改为 DECIMAL(5,2):
ALTER TABLE test_double2 MODIFY f1 DECIMAL(5,2);
BIT类型中存储的是二进制值,类似于010101。
BIT类型,如果没有指定(M),默认是1位。这个1位,表示只能存1位的二进制值。这里(M)是表示二进制的位数,位数最小值为1,最大值为64。
注意:在向BIT类型的字段中插入数据时,一定要确保插入的数据在BIT类型支持的范围内。
使用SELECT命令查询位字段时,可以用 BIN() 或 HEX() 函数进行读取。
可以看到,使用b+0查询数据时,可以直接查询出存储的十进制数据的值。
日期与时间是重要的信息,在我们的系统中,几乎所有的数据表都用得到。原因是客户需要知道数据的时间标签,从而进行数据查询、统计和处理。
MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR类型、TIME类型、DATE类型、DATETIME类型和TIMESTAMP类型。
YEAR类型用来表示年份,在所有的日期时间类型中所占用的存储空间最小,只需要 1个字节 的存储空间。
从MySQL5.5.27开始,2位格式的YEAR已经不推荐使用。YEAR默认格式就是“YYYY”,没必要写成YEAR(4), 从MySQL 8.0.19开始,不推荐使用指定显示宽度的YEAR(4)数据类型。
DATE类型表示日期,没有时间部分,格式为 YYYY-MM-DD ,其中,YYYY表示年份,MM表示月份,DD表示日期。需要 3个字节 的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。
创建数据表,表中只包含一个DATE类型的字段f1。
CREATE TABLE test_date1(
f1 DATE);
TIME类型用来表示时间,不包含日期部分。在MySQL中,需要 3个字节 的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。
在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。
(1)可以使用带有冒号的字符串,比如’ D HH:MM:SS’ 、’ HH:MM:SS ‘、’ HH:MM ‘、’ D HH:MM ‘、’ D HH ‘或’ SS '格式,都能被正确地插入TIME类型的字段中。其中D表示天,其最小值为0,最大值为34。如果使用带有D格式的字符串插入TIME类型的字段时,D会被转化为小时,计算格式为D*24+HH。当使用带有冒号并且不带D的字符串表示时间时,表示当天的时间,比如12:10表示12:10:00,而不是00:12:10。
(2)可以使用不带有冒号的字符串或者数字,格式为’ HHMMSS '或者 HHMMSS 。如果插入一个不合法的字符串或者数字,MySQL在存储数据时,会将其自动转化为00:00:00进行存储。比如1210,MySQL会将最右边的两位解析成秒,表示00:12:10,而不是12:10:00。
(3)使用 CURRENT_TIME() 或者 NOW() ,会插入当前系统的时间。
举例:
创建数据表,表中包含一个TIME类型的字段f1。
CREATE TABLE test_time1(
f1 TIME
);
INSERT INTO test_time1
VALUES('2 12:30:29'), ('12:35:29'), ('12:40'), ('2 12:40'),('1 05'), ('45');
INSERT INTO test_time1
VALUES ('123520'), (124011),(1210);
INSERT INTO test_time1
VALUES (NOW()), (CURRENT_TIME());
SELECT * FROM test_time1;
DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要 8 个字节的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为 YYYY-MM-DD HH:MM:SS ,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示时,MM表示分钟,SS表示秒。
在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件。
以 YYYY-MM-DD HH:MM:SS 格式或者 YYYYMMDDHHMMSS 格式的字符串插入DATETIME类型的字段时,最小值为1000-01-01 00:00:00,最大值为9999-12-03 23:59:59。
举例:
创建数据表,表中包含一个DATETIME类型的字段dt。
CREATE TABLE test_datetime1(
dt DATETIME
);
Query OK, 0 rows affected (0.02 sec)
插入数据
**INSERT INTO test_datetime1
VALUES ('2021-01-01 06:50:30'), ('20210101065030');
INSERT INTO test_datetime1
VALUES ('99-01-01 00:00:00'), ('990101000000'), ('20-01-01 00:00:00'), ('200101000000');
INSERT INTO test_datetime1
VALUES (20200101000000), (200101000000), (19990101000000), (990101000000);
INSERT INTO test_datetime1
VALUES (CURRENT_TIMESTAMP()), (NOW());**
TIMESTAMP类型也可以表示日期时间,其显示格式与DATETIME类型相同,都是 YYYY-MM-DD HH:MM:SS ,需要4个字节的存储空间。但是TIMESTAMP存储的时间范围比DATETIME要小很多,只能存储“1970-01-01 00:00:01 UTC”到“2038-01-19 03:14:07 UTC”之间的时间。其中,UTC表示世界统一时间,也叫作世界标准时间。
向TIMESTAMP类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。
如果向TIMESTAMP类型的字段插入的时间超出了TIMESTAMP类型的范围,则MySQL会抛出错误信息。
CREATE TABLE test_timestamp1(
ts TIMESTAMP
);
插入数据:
INSERT INTO test_timestamp1
VALUES ('1999-01-01 03:04:50'), ('19990101030405'), ('99-01-01 03:04:05'), ('990101030405');
INSERT INTO test_timestamp1
VALUES ('2020@01@01@00@00@00'), ('20@01@01@00@00@00');
INSERT INTO test_timestamp1
VALUES (CURRENT_TIMESTAMP()), (NOW());
#Incorrect datetime value
INSERT INTO test_timestamp1
VALUES ('2038-01-20 03:14:07');
用的最多的日期类型是DATETIME。虽然 MySQL 也支持 YEAR(年)、 TIME(时间)、DATE(日期),以及 TIMESTAMP 类型,但是在实际项目中,尽量用 DATETIME 类型。因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。毕竟,如果日期时间信息分散在好几个字段,很不容易记,而且查询的时候,SQL 语句也会更加复杂。
此外,一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用 时间戳 ,因为DATETIME虽然直观,但不便于计算。
在实际的项目中,我们还经常遇到一种数据,就是字符串数据。
CHAR和VARCHAR类型都可以存储比较短的字符串。
- CHAR 类型
- CHAR(M)类型一般需要预先定义字符串长度。如果不指定M,则表示默认1个字符。
- 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在右侧填充空格以达到指定的长度。当MYSQL检索CHAR类型的数据是,CHAR类型的字段会去除尾部的空格。
- 定义CHAR类型字段时,声明的字段长度即为CHAR乐西字段所占的存储空间的字节数。
CREATE TABLE test_varchar1(
NAME VARCHAR # 错误
)
# Column length too big for column ‘NAME’ (max =21845)
CREATE TABLE test_varchar2(
NAME VARCHAR(65535) #错误
);
**哪些情况下使用CHAR或VARCHAR更好 **
在MySQL中,TEXT用来保存文本类型的字符串,总共包含4种类型,分别为TINYTEXT、TEXT、 MEDIUMTEXT 和 LONGTEXT 类型。
在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和
VARCHAR类型相同。
每种TEXT类型保存的数据长度和所占用的存储空间不同,如下:
由于实际存储的长度不确定,MySQL 不允许 TEXT 类型的字段做主键。遇到这种情况,你只能采用
CHAR(M),或者 VARCHAR(M)。
举例:
说明在保存和查询数据时,并没有删除TEXT类型的数据尾部的空格。
开发中经验:
ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM
类型只允许从成员中选取单个值,不能一次选取多个值。
其所需要的存储空间由定义ENUM类型时指定的成员个数决定。
SET表示一个字符串对象,可以包含0个或多个成员,但成员个数的上限为 64 。设置字段值时,可以取
取值范围内的 0 个或多个值。
当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的,具体如下
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次
选择多个成员,这一点与ENUM类型不同。
MySQL中的二进制字符串类型主要存储一些二进制数据,比如可以存储图片、音频和视频等二进制数
据。
MySQL中支持的二进制字符串类型主要包括BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB类型。
BINARY与VARBINARY类型
BINARY和VARBINARY类似于CHAR和VARCHAR,只是它们存储的是二进制字符串。
BINARY (M)为固定长度的二进制字符串,M表示最多能存储的字节数,取值范围是0~255个字符。如果未指定(M),表示只能存储 1个字节 。例如BINARY (8),表示最多能存储8个字节,如果字段值不足(M)个字节,将在右边填充’\0’以补齐指定长度。
VARBINARY (M)为可变长度的二进制字符串,M表示最多能存储的字节数,总字节数不能超过行的字节长度限制65535,另外还要考虑额外字节开销,VARBINARY类型的数据除了存储数据本身外,还需要1或2个字节来存储数据的字节数。VARBINARY类型 必须指定(M) ,否则报错
MySQL中的BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB 4种类型,它们可容纳值的最大长度不同。可以存储一个二进制的大对象,比如 图片 、 音频 和 视频 等。
需要注意的是,在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图
片、音频和视频文件存储到 服务器的磁盘上 ,并将图片、音频和视频的访问路径存储到MySQL中。
JSON(JavaScript Object Notation)是一种轻量级的 数据交换格式 。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效
率。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻
松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效。 创建数据表,表中包含一个JSON类型的字段 js 。
MySQL 空间类型扩展支持地理特征的生成、存储和分析。这里的地理特征表示世界上具有位置的任何东
西,可以是一个实体,例如一座山;可以是空间,例如一座办公楼;也可以是一个可定义的位置,例如
一个十字路口等等。MySQL中使用 Geometry(几何) 来表示所有地理特征。Geometry指一个点或点的
集合,代表世界上任何具有位置的事物。
MySQL的空间数据类型(Spatial Data Type)对应于OpenGIS类,包括单值类型:GEOMETRY、POINT、 LINESTRING、POLYGON以及集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、 GEOMETRYCOLLECTION 。
在定义数据类型时,如果确定是 整数 ,就用 INT ; 如果是 小数 ,一定用定点数类型DECIMAL(M,D) ; 如果是日期与时间,就用DATETIME 。
这样做的好处是,首先确保你的系统不会因为数据类型定义出错。不过,凡事都是有两面的,可靠性好,并不意味着高效。比如,TEXT 虽然使用方便,但是效率不如 CHAR(M) 和 VARCHAR(M)。
关于字符串的选择,建议参考如下阿里巴巴的《Java开发手册》规范:
阿里巴巴《Java开发手册》之MySQL数据库:
【 强制 】如果存储的字符串长度几乎相等,使用 CHAR 定长字符串类型。
【 强制 】VARCHAR 是可变长字符串,不预先分配存储空间,长度不要超过 5000。如果存储长度大于此值,定义字段类型为 TEXT,独立出来一张表,用主键来对应,避免影响其它字段索引效率。