《SQL入门经典》学习笔记

第一部分  SQL概念综述

第1章  欢迎来到SQL世界
1.4.1 表命名标准
表的名称以_TBL作为后缀,表的索引以_INX为后缀
1.4.3 表的构成
字段是表里的一列,记录是表里的一行

第二部分  建立数据库

第2章  定义数据结构
2.2.1 定长字符串
CHARACTER(n): n为最大字符数,通常用空格来填充数量不足的字符
2.2.2 变长字符串
CHARACTER VARYING(n): n为最大字符数
2.2.5 小数类型
DECIMAL(p,s): p为有效位数,s表示标度,即小数点后面的位数
2.2.7 浮点数
REAL为单精度浮点数,有效位数为1~21,DOUBLE PRECISION为双精度浮点数,有效位数为22~53
2.2.9 直义字符串
直义字符串为用户或程序明确指定的一系列字符,一般来说,字符型字符串需要使用单引号
2.2.10 NULL数据类型
NULL表示没有值
2.2.12 自定义类型
CREATE TYPE PERSON AS OBJECT
(NAME VARCHAR (30),
SSN   VARCHAR (9)); # 创建PERSON类型
2.2.13 域
CREATE DOMAIN MONEY_D AS NUMBER(8,2); # 创建域MONEY_D, 域类似于自定义类型,但可以向域添加约束
ALTER MONEY_D
ADD CONSTRAINT MONEY_CON1
CHECK (VALUE > 5);

第3章  管理数据库对象
3.2 什么是规划
规划是与数据库某个用户名相关联的数据库对象的集合
3.3.3 CREATE TABLE语句
CREATE TABLE EMPLOYEE_TBL
(EMP_ID      CHAR(9)      NOT NULL,
EMP_NAME     VARCHAR(40)  NOT NULL,
EMP_ST_ADDR  VARCHAR(20)  NOT NULL,
EMP_CITY     VARCHAR(15)  NOT NULL,
EMP_ST       CHAR(2)      NOT NULL,
EMP_ZIP      INTEGER(5)   NOT NULL,
EMP_PHONE    INTEGER(10)  NULL,
EMP_PAGER    INTEGER(10)  NULL); # 创建表EMPLOYEE_TBL, NOT NULL表示不能为空,NULL表示可以为空
3.3.5 ALTER TABLE命令
ALTER TABLE EMPLOYEE_TBL MODIFY
EMP_ID VARCHAR(10); # 将EMP_ID的类型修改为VARCHAR(10)
如果表已经包含数据,这时添加的列就不能定义为NUT NULL,这是一条基本规则
CREATE TABLE TEST_INCREMENT(
ID         SERIAL,
TEST_NAME  VARCHAR(20)); # SERIAL为自动增加的数值,
INSERT INTO TEST_INCREMENT(TEST_NAME)
VALUES ('FRED'), ('JOE'), ('MIKE'), ('TED'); # 向表中插入记录,而不用为自动增加的列指定值
3.3.6 从现有表新建另一个表
CREATE TABLE PRODUCTS_TMP AS
SELECT * FROM PRODUCTS_TBL; # 从表PRODUCTS_TBL新建表PRODUCTS_TMP
3.3.7 删除表
DROP TABLE PRODUCTS_TMP; # 删除表PRODUCTS_TMP, 如果使用了RESTRICT选项,且表被视图或约束所引用,就会返回一个错误,如果使用了CASCADE选项,则全部引用视图和约束都被删除
3.4.1 主键约束
主键是表里一个或多个用于实现记录唯一性的字段
CREATE TABLE EMPLOYEE_TBL
(EMP_ID      CHAR(9)      NOT NULL    PRIMARY KEY,
EMP_NAME     VARCHAR(40)  NOT NULL,
EMP_ST_ADDR  VARCHAR(20)  NOT NULL,
EMP_CITY     VARCHAR(15)  NOT NULL,
EMP_ST       CHAR(2)      NOT NULL,
EMP_ZIP      INTEGER(5)   NOT NULL,
EMP_PHONE    INTEGER(10)  NULL,
EMP_PAGER    INTEGER(10)  NULL); # 创建表EMPLOYEE_TBL时指定EMP_ID为主键
3.4.2 唯一性约束
唯一性约束要求表里某个字段的值在每条记录里都是唯一的,与主键类似
CREATE TABLE EMPLOYEE_TBL
(EMP_ID      CHAR(9)      NOT NULL    PRIMARY KEY,
EMP_NAME     VARCHAR(40)  NOT NULL,
EMP_ST_ADDR  VARCHAR(20)  NOT NULL,
EMP_CITY     VARCHAR(15)  NOT NULL,
EMP_ST       CHAR(2)      NOT NULL,
EMP_ZIP      INTEGER(5)   NOT NULL,
EMP_PHONE    INTEGER(10)  NULL        UNIQUE,
EMP_PAGER    INTEGER(10)  NULL); # 创建表EMPLOYEE_TBL时指定EMP_PHONE为UNIQUE,表示任意两个雇员不能有相同的电话
3.4.3 外键约束
外键是子表里的一个字段,引用父表里的主键
CREATE TABLE EMPLOYEE_PAY_TBL
(EMP_ID         CHAR(9)      NOT NULL,
POSITION        VARCHAR2(15) NOT NULL,
DATE_HIRE       DATE         NULL,
PAY_RATE        NUMBER(4,2)  NOT NULL,
DATE_LAST_RAISE DATE         NULL,
CONSTRAINT EMP_ID_FK FOREIGN KEY (EMP_ID) REFERENCES EMPLOYEE_TBL (EMP_ID)); # EMP_ID_FK为外键约束名,将EMP_ID定义为外键,且引用表EMPLOYEE_TBL里的EMP_ID字段
3.4.5 检查约束
CREATE TABLE EMPLOYEE_PAY_TBL
(EMP_ID         CHAR(9)      NOT NULL,
POSITION        VARCHAR2(15) NOT NULL,
DATE_HIRE       DATE         NULL,
PAY_RATE        NUMBER(4,2)  NOT NULL,
DATE_LAST_RAISE DATE         NULL,
CONSTRAINT EMP_ID_FK FOREIGN KEY (EMP_ID) REFERENCES EMPLOYEE_TBL (EMP_ID),
CONSTRAINT CHK_PAY CHECK ( PAY_RATE > 12.50)); # CHK_PAY为检查约束名,表示PAY_RATE不得低于12.50
3.4.6 去除约束
ALTER TABLE EMPLOYEES
DROP CONSTRAINT EMPLOYEES_PK; # 去除表EMPLOYEES中的约束EMPLOYEE_PK
3.7.2 练习
$ mysql -h localhost -u username -ppassword # -p与密码之间没有空格
CREATE DATABASE learnsql; # 建立数据库learnsql
SHOW DATABASES; # 显示所有数据库
USE learnsql; # 使用learnsql数据库
SHOW TABLES; # 显示所有的表
DESCRIBE EMPLOYEE_TBL; # 列出表EMPLOYEE_TBL的所有字段和它们的属性
DROP TABLE ORDER_TBL; # 删除表ORDER_TBL

第4章  规格化过程
规格化是把原始数据分解为由关联数据形成的多个表
4.1.3 规格形式
第一规格形式的目标是把原始数据分解到表中
第二规格形式的目标是提取对主键仅有部分依赖的数据,把它们保存到另一个表里
第三规格形式的目标是删除表里不依赖主键的数据

第5章  操作数据
5.2.1 把数据插入到表
INSERT INTO PRODUCTS_TBL
VALUES ('7725', 'LEATHER GLOVES', 24.99); # 把一条新记录插入到表PRODUCTS_TBL里
5.2.2 给表里指定列插入数据
INSERT INTO ORDERS_TBL (ORD_NUM, CUST_ID, PROD_ID, QTY)
VALUES ('23A16', '109', '7725', 2); # 向表ORDER_TBL里的某些列插入数据
5.2.3 从另一个表插入数据
INSERT INTO PRODUCTS_TMP
SELECT * FROM PRODUCTS_TBL; # 将PRODUCTS_TBL中的数据插入到PRODUCTS_TMP中
5.2.4 插入NULL值
INSERT INTO ORDERS_TBL (ORD_NUM, CUST_IN, PROD_ID, QTY, ORD_DATE)
VALUES ('23A16', '109', '7725', 2, NULL); # ORD_DATE字段被插入NULL
5.3.1 更新一个字段的数据
UPDATE ORDERS_TBL
SET QTY = 1
WHERE ORD_NUM = '23A16'; # 把表ORDERS_TBL里ORD_NUM值为'23A16'的记录中QTY更新为1
5.3.2 更新一条或多条记录里的多个字段
UPDATE ORDERS_TBL
SET QTY = 1, CUST_ID = '221'
WHERE ORD_NUM = '23A16'; # 把表ORDERS_TBL里ORD_NUM值为'23A16'的记录中QTY更新为1,CUST_ID更新为221
5.4 从表里删除数据
DELETE FROM ORDERS_TBL
WHERE ORD_NUM = '23A16'; # 从表ORDERS_TBL中删除ORD_NUM为'23A16'的一行记录
# 作为一条规则,DELETE语句应该总是使用WHERE子句,另外,还应该首先使用SELECT语句对WHERE子句进行测试

第6章  管理数据库事务
6.1 什么是事务
事务是对数据库执行的一个操作单位,可以是一个或多个DML(数据操作语言)语句
START TRANSACTION; # mariadb数据库中开始事务
6.2.1 COMMIT命令
COMMIT; # 把修改保存到数据库,完成这个事务
6.2.2 ROLLBACK命令
ROLLBACK; # 撤销还没有被保存到数据库的命令,只用于撤销上一个COMMIT或ROOLBACK命令之后的事务
6.2.3 SAVEPOINT命令
SAVEPOINT sp1; # 创建保存点sp1,保存点是事务过程中的一个逻辑点,我们可以把事务回退到这个点,而不必回退整个事务
6.2.4 ROOLBACK TO SAVEPOINT命令
ROOLBACK TO sp1; # 回退到名为sp1的保存点
6.2.5 RELEASE SAVEPOINT命令
RELEASE SAVEPOINT sp1; # 删除保存点sp1
6.2.6 SET TRANSACTION命令
SET TRANSACTION READ WRITE; # 初始化数据库事务,进行查询和操作
SET TRANSACTION READ ONLY; # 初始化数据库事务,只进行查询,适合生成报告,能够提高事务完成的速度

第三部分  从查询中获得有效的结果

第7章 数据库查询
7.2.1 SELECT 语句
SELECT * FROM PRODUCTS_TBL; # 对表PRODUCTS_TBL进行查询,*表示表里的全部字段
SELECT PROD_DESC FROM CANDY_TBL; # 显示表CANDY_TBL里的PROD_DESC字段
SELECT DISTINCT PROD_DESC
FROM CANDY_TBL; # DISTINCT选项指定显示中去除重复记录
SELECT DISTINCT (PROD_DESC)
FROM CANDY_TBL; # 用圆括号以提高代码的可读性
7.2.3 WHERE子句
SELECT * FROM PRODUCTS_TBL
WHERE COST < 5; # 显示价格小于5的记录
7.2.4 ORDER BY子句
SELECT PROD_DESC, PROD_ID, COST
FROM PRODUCTS_TBL
WHERE COST < 20
ORDER BY PROD_DESC ASC; # 以字段PROD_DESC按升序排列,ASC为升序,DESC为降序,默认为升序
SELECT PROD_DESC, RPOD_ID, COST
FROM PRODUCTS_TBL
WHERE COST < 20
ORDER BY 1; # 1代表字段PROD_DESC, 2代表字段PROD_ID, 3代表字段COST
7.2.5 大小写敏感性
一般来说,SQL命令和关键字不区分大小写,而数据的大小写敏感性则取决于所用的数据库
7.3.1 统计表里的记录数量
SELECT COUNT (*) FROM PRODUCTS_TBL; # 统计表PRODUCTS_TBL里的记录数量
SELECT COUNT (PROD_ID) FROM PRODUCTS_TBL; # 统计表PRODUCTS_TBL里字段PROD_ID的值的数量
SELECT COUNT (DISTINCT PROD_ID) FROM PRODUCTS_TBL; # 统计表PRODUCTS_TBL里字段PROD_ID中不同值的种类数
7.3.2 从另一个用户表里选择数据
SELECT EMP_ID
FROM SCHEMA.EMPLOYEE_TBL; # 对用户SCHEMA的表EMPLOYEE_TBL进行查询
7.3.3 使用字段别名
SELECT PROD_DESC PRODUCT
FROM PRODUCTS_TBL; # 给字段PROD_DESC定义别名为PRODUCT

第8章  使用操作符对数据进行分类
操作符用于在SELECT命令的WHERE子句中为返回的数据指定更明确的条件
8.2.1 相等
SELECT *
FROM PEODUCTS_TBL
WHERE PROD_ID = '2345'; # 返回PROD_ID等于2345的记录
8.2.2 不等于
SELECT *
FROM PRODUCTS_TBL
WHERE PROD_ID <> '2345'; # 返回PROD_ID不等于2345的记录
8.2.3 小于和大于
SELECT *
FROM PRODUCTS_TBL
WHERE COST > 20; # 返回COST大于20的记录
8.3.1 IS NULL
SELECT *
FROM EMPLOYEE_TBL
WHERE PAGER IS NULL; # 返回PAGER为NULL的记录,注意单词'NULL'与NULL值是不同的
8.3.2 BETWEEN
SELECT *
FROM PRODUCTS_TBL
WHERE COST BETWEEN 5.59 AND 14.5; # 返回COST在5.95与14.5之间的记录
8.3.3 IN
SELECT *
FROM PRODUCTS_TBL
WHERE PROD_ID IN ('13', '9', '87', '119'); # 返回PROD_ID在指定范围内的记录
8.3.4 LIKE
SELECT PROD_DESC
FROM PRODUCTS_TBL
WHERE PROD_DESC LIKE '_S%'; # 返回PROD_DESC的第二个字符是S的记录,%代表零个、一个或多个字符,下划线_代表一个字符
8.3.5 EXISTS
SELECT COST
FROM PRODUCTS_TBL
WHERE EXISTS ( SELECT COST
               FROM PRODUCTS_TBL
               WHERE COST < 100 ); # EXISTS用于搜索指定表里是否存在满足特定条件的记录
8.3.6 ALL、SOME和ANY操作符
SELECT *
FROM PRODUCTS_TBL
WHERE COST > ALL ( SELECT COST
                   FROM PRODUCTS_TBL
                   WHERE COST < 10 ); # ALL用于把一个值与另一个集合里的全部值进行比较,即要求比其中所有值都大
SELECT *
FROM PRODUCTS_TBL
WHERE COST > ANY ( SELECT COST
                   FROM PRODUCTS_TBL
                   WHERE COST < 10 ); # ANY用于把一个值与另一个集合里的任意值进行比较,即只要求比其中任意一个大即可
SOME是ANY的别名,它们可以互换
8.4.1 AND
SELECT *
FROM PRODUCTS_TBL
WHERE COST > 10
AND COST < 30; # 返回10<COST<30的记录
8.4.2 OR
SELECT *
FROM PRODUCTS_TBL
WHERE PROD_ID = '90'
OR PROD_ID = '2345'; # 返回PROD_ID等于90或2345的记录
8.5.1 不相等
WHERE SALARY <> '20000'; # 不等于
WHERE SALARY != '20000'; # 不等于,两者等价
8.5.2 NOT BETWEEN
SELECT *
FROM PRODUCTS_TBL
WHERE COST NOT BETWEEN 5.95 AND 14.5; # 返回COST不在5.95和14.5之间且不包含5.95和14.5的记录
8.5.3 NOT IN
SELECT *
FROM PRODUCTS_TBL
WHERE PROD_ID NOT IN (119, 13, 87, 9); # 返回PROD_ID不在列表里的记录
8.5.4 NOT LIKE
SELECT *
FROM PRODUCTS_TBL
WHERE PROD_DESC NOT LIKE 'L%'; # 返回PROD_DESC不是以L开头的记录
8.5.5 IS NOT NULL
SELECT *
FROM EMPLOYEE_TBL
WHERE PAGER IS NOT NULL; # 返回PAGER不是空的记录
8.5.6 NOT EXISTS
SELECT MAX(COST)
FROM PRODUCTS_TBL
WHERE NOT EXISTS ( SELECT COST
                   FROM PRODUCTS_TBL
                   WHERE COST > 100 ); # 返回COST不是大于100的最大值
8.6.1 加法
SELECT SALARY + BONUS
FROM EMPLOYEE_PAY_TBL;
8.6.2 减法
SELECT SALARY - BONUS
FROM EMPLOYEE_PAY_TBL;
8.6.3 乘法
SELECT EMP_ID, PAY_RATE, PAY_RATE * 1.1
FROM EMPLOYEE_PAY_TBL
WHERE PAY_RATE IS NOT NULL;
8.6.4 除法
SELECT SALARY / 10 FROM EMPLOYEE_PAY_TBL;

第9章  汇总查询得到的数据
9.1.1 COUNT函数
SELECT COUNT(EMPLOYEE_ID) FROM EMPLOYEE_PAY_ID; # 统计EMPLOYEE_ID字段,不包括NULL
SELECT COUNT(DISTINCT DALARY) FROM EMPLOYEE_PAY_TBL; # 只统计不相同的行
SELECT COUNT(*) FROM EMPLOYEE_TBL; # 统计表里的全部记录,包括重复项和NULL
9.1.2 SUM函数
SELECT SUM(SALARY) FROM EMPLOYEE_PAY_TBL; # 统计SALARY字段的总和
9.1.3 AVG函数
SELECT AVG(SALARY) FROM EMPLOYEE_PAY_TBL; # 返回SALARY字段的平均值
9.1.4 MAX函数
SELECT MAX(SALARY) FROM EMPLOYEE_PAY_TBL; # 返回SALARY字段的最大值,NULL不在统计之内,字符数据按排序规则统计最大值
9.1.5 MIN函数
SELECT MIN(SALARY) FROM EMPLOYEE_PAY_TBL; # 返回SALARY字段的最小值,NULL不在统计之内

第10章  数据排序与分组
10.2.3 创建分组和使用汇总函数
SELECT CITY, COUNT(*)
FROM EMPLOYEE_TBL
GROUP BY CITY; # 以CITY分组,统计每个CITY的记录数量
10.4 CUBE和ROLLUP语句
SELECT CITY, ZIP, AVG(PAY_RATE), AVG(SALARY)
FROM EMPLOYEE_TBL E INNER JOIN EMPLOYEE_PAY_TBL P
ON E.EMP_ID = P.EMP_ID
GROUP BY CITY, ZIP WITH ROLLUP; # 使用ROLLUP语句来获得小计(mysql语法)
SELECT CITY, ZIP, AVG(PAY_RATE), AVG(SALARY)
FROM EMPLOYEE_TBL E INNER JOIN EMPLOYEE_PAY_TBL P
ON E.EMP_ID = P.EMP_ID
GROUP BY CUBE(CITY, ZIP); # CUBE对分组列表中的所有字段进行排列组合,并根据每一种组合结果,分别进行统计汇总
10.5 HAVING子句
SELECT CITY, AVG(PAY_RATE), AVG(SALARY)
FROM EMPLOYEE_PAY_TBL
WHERE CITY <> 'GREENWOOD'
GROUP BY CITY
HAVING AVG(SALARY) > 20000
ORDER BY 3; # 只显示AVG(SALARY)>20000的分组

第11章  调整数据的外观
11.2.1 串接函数
SELECT CONCAT(FIRST_NAME, ' ', LAST_NAME) NAME
FROM EMPLOYEE_TBL; # 将FIRST_NAME和LAST_NAME串接在一起(mysql语法)
11.2.2 TRANSLATE函数
SELECT CITY, TRANSLATE(CITY, 'IND', 'ABC')
FROM EMPLOYEE_TBL; # 将CITY中的I替换为A,将N替换为B,将D替换为C
11.2.3 REPLACE函数
SELECT CITY, REPLACE(CITY, 'I', 'Z')
FROM EMPLOYEE_TBL; # 把CITY中的I替换为Z,REPLACE与TRANSLATE类似,只是它替换的是一个字符或字符串
11.2.4 UPPER函数
SELECT UPPER(CITY)
FROM EMPLOYEE_TBL; # 将CITY中的所有字符都转化为大写
11.2.5 LOWER函数
SELECT LOWER(CITY)
FROM EMPLOYEE_TBL; # 将CITY中的所有字符都转化为小写
11.2.6 SUBSTR函数
SELECT EMP_ID, SUBSTRING(EMP_ID, 1, 3)
FROM EMPLOYEE_TBL; # 返回EMP_ID的前1个字符
11.2.7 INSTR
SELECT PROD_DESC, INSTR(PROD_DESC, 'A', 1, 1)
FROM PRODUCTS_TBL; # 在PROD_DESC中从第1个字符开始查找A第1次出现的位置
11.2.8 LTRIM函数
SELECT POSITION, LTRIM(POSITION, 'SALES')
FROM EMPLOYEE_PAY_TBL; # LTRIM从左侧剪除POSITION中的SALES,例SHIPPER->HIPPER, SALE被忽略
11.2.9 RTRIM函数
同上
11.2.9 DECODE函数
SELECT CITY, DECODE(CITY, 'INDIANAPOLIS', 'INDY', 'GREENWOOD', 'GREEN', 'OTHER')
FROM EMPLOYEE_TBL; # 将CITY中的INDIANAPOLIS显示为INDY,GREENWOOD显示为GREEN,其他显示为OTHER
11.3.1 LENGTH函数
SELECT PROD_DESC, LENGTH(PROD_DESC)
FROM PRODUCTS_TBL; # 返回PROD_DESC的字符长度
11.3.2 IFNULL函数
SELECT PAGER, IFNULL(PAGER, 999999999)
FROM EMPLOYEE_TBL; # 若PAGER为NULL则用999999999代替
11.3.3 COALESCE函数
SELECT EMP_ID, COALESCE(BONUS, SALARY, PAY_RATE)
FROM EMPLOYEE_PAY_TBL; # 返回BONUS, SALARY, PAY_RATE字段里第一个非NULL值
11.3.4 LPAD函数
SELECT LPAD(PROD_DESC, 30, '.') PRODUCT
FROM PRODUCTS_TBL; # 在PROD_DESC左侧添加句点,使其总长度达到30个字符
11.3.5 RPAD函数
同上
11.3.6 ASCII函数
ASCII('A')返回65;
ASCII('a')返回95
11.4 算术函数
ABS:绝对值
ROUND:舍入
SQRT:平方根
SIGN:符号
POWER:幂
CEIL:上限
FLOOR:下限
EXP:指数
SIN、COS、TAN:三角函数
11.5.1 字符串转换为数字
SELECT EMP_ID, TO_NUMBER(EMP_ID)
FROM EMPLOYEE_TBL; # 在输出结果里,数值是右对齐的,字符串是左对齐的
11.5.2 数字转换为字符串
SELECT PAY_RATE, TO_CHAR(PAY_RATE)
FROM EMPLOYEE_PAY_TBL
WHERE PAY_RATE IS NOT NULL;

第12章  日期和时间
12.1.2 DATETIME元素
DATETIME元素  有效范围
YEAR          0001-9999
MONTH         01-12
DAY           01-31
HOUR          00-23
MINUTE        00-59
SECOND        00.000...-61.999...
12.2.1 当前日期
SELECT NOW(); # 返回当前日期和时间(mysql语法)
SELECT CURRENT_DATE; # 返回当前日期
12.2.3 时间与日期相加
SELECT DATE_HIRE, DATE_ADD(DATE_HIRE, INTERVAL 1 DAY), DATE_HIRE + 1
FROM EMPLOYEE_PAY_TBL; # 日期加1天,DATE_HIRE+1则会把日期转换为整数后再加1(mysql语法)
12.2.4 其他日期函数
日期函数         用途
DAYNAME(date)    显示星期几
DAYOFMONTH(date) 显示几日
DAYOFWEEK(date)  显示星期几
DAYOFYEAR(date)  显示一年中的第几天
12.3.1 日期描述
语法          日期元素
SECOND        秒
MINUTE        分钟
HOUR          小时
DAY           天
MONTH         月
YEAR          年
MINUTE_SECOND 分和秒
HOUR_MINUTE   小时和分
DAY_HOUR      天和小时
YEAR_MONTH    年和月
HOUR_SECOND   小时、分和秒
DAY_MINUTE    天和分钟
DAY_SECOND    天和秒
SELECT EXTRACT(YEAR FROM DATE_HIRE)
FROM EMPLOYEE_PAY_TBL; # 提取DATE_HIRE中的年
12.3.2 日期转换为字符串
SELECT DATE_HIRE, TO_CHAR(DATE_HIRE, 'Month dd, yyyy') HIRE
FROM EMPLOYEE_PAY_TBL; # 将日期转换为字符串(Oracle语法)
12.3.3 字符串转换为日期
SELECT STR_TO_DATE('01/01/2010 12:00:00 AM', '%m/%d/%Y %h:%i:%s %p') AS FORMAT_DATE
FROM EMPLOYEE_PAT_TBL; # 将字符串转换为日期格式(mysql语法)

第四部分  建立复杂的数据库查询

第13章  在查询里结合表
13.2.2 等值结合
SELECT EMPLOYEE_TBL.EMP_ID,
       EMPLOYEE_TBL.LAST_NAME,
       EMPLOYEE_PAY_TBL.POSITION
FROM EMPLOYEE_TBL,
     EMPLOYEE_PAY_TBL
WHERE EMPLOYEE_TBL.EMP_ID = EMPLOYEE_PAY_TBL.EMP_ID; # 等值结合
SELECT EMPLOYEE_TBL.EMP_ID,
       EMPLOYEE_PAY_TBL.DATE_HIRE
FROM EMPLOYEE_TBL INNER JOIN EMPLOYEE_PAY_TBL
ON EMPLOYEE_TBL.EMP_ID = EMPLOYEE_PAY_TBL.EMP_ID; # INNER JOIN ON语法与WHERE不同,但结果相同
13.2.3 使用表的别名
SELECT E.EMP_ID, EP.SALARY, EP.DATE_HIRE, E.LAST_NAME
FROM EMPLOYEE_TBL E,
     EMPLOYEE_PAY_TBL EP
WHERE E.EMP_ID = EP.EMP_ID
      AND EP.SALARY > 20000; # EMPLOYEE_TBL被重命名为E, EMPLOYEE_PAY_TBL被重命名为EP,别名能减少输入,减少输入错误
13.2.4 不等值结合
SELECT E.EMP_ID, E.LAST_NAME, P.POSITION
FROM EMPLOYEE_TBL E,
     EMPLOYEE_PAT_TBL EP
WEHRE E.EMP_ID <> P.EMP_ID;# 每个表6行记录,不等值结合返回30行记录
13.2.5 外部结合
SELECT P.PROD_DESC, O.QTY
FROM PRODUCTS_TBL P,
     ORDERS_TBL O
WHERE P.PROD_ID = O.PROD_ID(+); # 外部结合,显示P.PROD_DESC字段的全部记录,不管O.QTY字段是否为空
SELECT P.PROD_DESC, Q.QTY
FROM PRODUCTS_TBL P LEFT OUTER JOIN ORDERS_TBL O
ON P.PROD_ID = O.PROD_ID; # LEFT OUTER JOIN 左外部结合,语法同+不同,但结果相同
13.2.6 自结合
SELECT E1.NAME, E2.NAME
FROM EMP E1, EMP E2
WHERE E1.MGR_ID = E2.ID; # 自结合,将一个表等同两个不同的表进行比较
13.2.7 结合多个主键
SELECT P.PRODUCT_NAME, O.ORD_DATE, O.QUANTITY
FROM PROD P, ORD O
WHERE P.SERIAL_NUMBER = O.SERIAL_NUMBER
AND P.VENDOR_NUMBER = O.VENDOR_NUMBER; # 结合主键SERIAL_NUMBER和VENDOR_NUMBER
或者使用INNER JOIN ON
SELECT P.PRODUCT_NAME, O.ORD_DATE, O.QUANTITY
FROM PROD P, INNER JOIN ORD O
ON P.SERIAL_NUMBER = O.SERIAL_NUMBER
AND P.VENDOR_NUMBER = O.VENDOR_NUMBER;
13.3.1 使用基表
基表:如果需要从两个表里获取数据,但它们又没有公用字段,我们就必须结合另一个表,这个表与前两个表都有公用字段,这个表就被称为基表
SELECT C.CUST_NAME, P.PROD_DESC
FROM CUSTOMER_TBL C,
     PRODUCTS_TBL P,
     ORDERS_TBL O
WHERE C.CUST_ID = O.CUST_ID
  AND P.PROD_ID = O.PROD_ID; # ORDERS_TBL为基表
13.3.2 笛卡尔积
笛卡尔积是笛卡尔结合或“无结合”的结果,如果从两个或多个没有结合的表里获取数据,输出结果就是所有被选表里的全部记录,笛卡尔积通常也被称为交叉结合
SELECT E.EMP_ID, E.LAST_NAME, P.POSITION
FROM EMPLOYEE_TBL E,
     EMPLOYEE_PAY_TBL P; # 可怕的笛卡尔积

第14章  使用子查询定义未确定数据
14.1 什么是子查询
子查询也被称为嵌套查询,是位于另一个查询的WHERE子句里的查询,它返回的数据通常在主查询里作为一个条件,从而进一步限制数据库返回的数据
SELECT E.EMP_ID, E.LAST_NAME, E.FIRST_NAME, EP.PAY_RATE
FROM EMPLOYEE_TBL E, EMPLOYEE_PAY_TBL TP
WHERE E.EMP_ID = EP.EMP_ID
AND EP.PAY_RATE < (SELECT PAY_RATE
                   FROM EMPLOYEE_PAY_TBL
                   WHERE EMP_ID = '443679012'); # 子查询
14.1.2 子查询与INSERT语句
INSERT INTO RICH_EMPLOYEES
SELECT E.EMP_ID, E.LAST_NAME, E.FIRST_NAME, EP.PAY_RATE
FROM EMPLOYEE_TBL E, EMPLOYEE_PAY_TBL EP
WHERE E.EMP_ID = EP.EMP_ID
  AND EP.PAY_RATE > (SELECT PAY_RATE
                     FROM EMPLOYEE_PAY_TBL
                     WHERE EMP_ID = '220984332'); # 在INSERT语句中使用子查询
14.1.3 子查询与UPDATE语句
UPDATE FROM EMPLOYEE_PAY_TBL
SET PAY_RATE = PAY_RATE * 1.1
WHERE EMP_ID IN (SELECT EMP_ID
                 FROM EMPLOYEE_TBL
                 WHERE CITY = 'INDIANAPOLIS'); # UPDATE语句中使用子查询
14.1.4 子查询与DELETE语句
DELETE FROM EMPLOYEE_PAY_TBL
WHERE EMP_ID = (SELECT EMP_ID
                FROM EMPLOYEE_TBL
                WHERE LAST_NAME = 'GLASS'
                AND FIRST_NAME = 'BRANDON'); # DELETE语句中使用子查询
14.2 嵌套的子查询
SELECT CUST_ID, CUST_NAME
FROM CUSTOMER_TBL
WHERE CUST_ID IN (SELECT O.CUST_ID
                  FROM ORDERS_TBL O, PRODUCTS_TBL P
                  WHERE O.PROD_ID = P.PROD_ID
                    AND O.QTY * P.COST > (SELECT SUM(COST)
                                          FROM PRODUCTS_TBL)); # 嵌套的子查询、
14.3 关联子查询
关联子查询是依赖主查询里的信息的子查询
SELECT C.CUST_NAME
FROM CUSTOMER_TBL C
WHERE 10 < (SELECT SUM(O.QTY)
            FROM ORDERS_TBL O
            WHERE O.CUST_ID = C.CUST_ID); # 关联子查询

第15章  组合多个查询
15.1 单查询与组合查询
单查询是一个SELECT语句,而组合查询具有两个或多个SELECT语句
SELECT EMP_ID, SALARY
FROM EMPLOYEE_PAY_TBL
WHERE SALARY IS NOT NULL
UNION
SELECT EMP_ID, PAY_RATE
FROM EMPLOYEE_PAY_TBL
WHERE PAY_RATE IS NOT NULL; # 使用操作符UNION结合两个查询
15.2.1 UNION
UNION操作符可以组合两个或多个SELECT语句的结果,不包含重复的记录,列标题来自于第一个查询的字段名称
SELECT PROD_DESC FROM PRODUCTS_TBL
UNION
SELECT LAST_NAME FROM EMPLOYEE_TBL; # 使用UNION操作符组合两个不相关的查询
15.2.2 UNION ALL
UNION ALL操作符可以组合两个SELECT语句的结果,并且包含重复的结果
SELECT PROD_DESC FROM PRODUCTS_TBL
UNION ALL
SELECT PROD_DESC FROM PRODUCTS_TMP;
15.2.3 INTERSECT
INTERSECT可以组合两个SELECT语句,但只返回第一个SELECT语句里与第二个SELECT语句里一样的记录
SELECT CUST_ID FROM CUSTOMER_TBL
INTERSECT
SELECT CUST_ID FROM ORDERS_TBL; # 返回具有订单的顾客的ID
15.2.4 EXCEPT
EXCEPT操作符组合两个SELECT语句,返回第一个SELECT语句里有但第二个SELECT语句里没有的记录
SELECT PROD_DESC FROM PRODUCTS_TBL
EXCEPT
SELECT PROD_DESC FROM PRODUCTS_TMP;
15.3 组合查询里使用ORDER BY
SELECT EMP_ID FROM EMPLOYEE_TBL
UNION
SELECT EMP_ID FROM EMPLOYEE_PAY_TBL
ORDER BY 1; # ORDER BY用于组合查询时只能对全部查询结果排序,且组合查询里只能有一个ORDER BY子句,而且它只能以别名或数字来引用字段
15.4 组合查询里使用GROUP BY
SELECT 'CUSTOMERS' TYPE, COUNT(*)
FROM CUSTOMER_TBL
UNION
SELECT 'EMPLOYEES' TYPE, COUNT(*)
FROM EMPLOYEE_TBL
UNION
SELECT 'PRODUCTS' TYPE, COUNT(*)
FROM PRODUCTS_TBL
GROUP BY 1; # 利用一个字符串代表顾客记录、雇员记录、产品记录,GROUP BY子句用于把整个结果根据第一个字段进行分组

第五部分  SQL性能调整
第16章  利用索引改善性能
16.1 什么是索引
简单来说,索引就是一个指针,指向表里的数据
16.4.1 单字段索引
CREATE INDEX NAME_IDX
ON EMPLOYEE_TBL (LAST_NAME); # 对表EMPLOYEE_TBL中LAST_NAME字段创建单字段索引
16.4.2 唯一索引
唯一索引不允许表里具有重复值,除此之外,它与普通索引的功能一样
CREATE UNIQUE INDEX NAME_IDX
ON EMPLOYEE_TBL (LAST_NAME); # 对表EMPLOYEE_TBL中LAST_NAME字段创建唯一索引
16.4.3 组合索引
组合索引是基于一个表里两个或多个字段单索引
CREATE INDEX ORD_IDX
ON ORDERS_TBL (CUST_ID, PROD_ID); # 基于表ORDERS_TBL中的两个字段CUST_ID和PROD_ID创建组合索引
16.4.4 隐含索引
隐含索引是数据库服务程序在创建对象时自动创建的,比如,数据库会为主键约束和唯一性约束自动创建索引
16.5 何时考虑使用索引
外键经常用于与父表的结合,所以适合设置索引,一般来说,大多数用于表结合的字段都应该设置索引
经常在ORDER BY和GROUP BY里引用的字段也应该考虑设置索引
具有大量唯一值的字段,或是在WHERE子句里会返回很小部分记录的字段,都可以考虑设置索引
16.6 何时应该避免使用索引
索引不应该用于小规模的表
当字段用于WHERE子句作为过滤器会返回表里大部分记录时,该字段就不适合设置索引
经常会被批量更新单表可以具有索引,但批量操作的性能会由于索引而降低
不应该对包含大量NULL值的字段设置索引
经常被操作的字段不应该设置索引,因为对索引的维护会变得很繁重
16.7 修改索引
ALTER INDEX INDEX_NAME
16.8 删除索引
DROP INDEX INDEX_NAME ON TABLE_NAME # mysql语法

第17章  改善数据库性能
17.1 什么是SQL语句调整
SQL语句调整主要涉及调整语句的FROM和WHERE子句,因为数据库服务程序主要根据这两个子句执行查询
17.2 数据库调整与SQL语句调整
数据库调整是调整实际数据库的过程,包括分配内存、磁盘、CPU、I/O和底层数据库进程,还涉及数据库结构本身的管理与操作,比如表和索引的设计与布局
17.3.1 为提高可读性格式化SQL语句
每个子句都以新行开始
当子句里的参数超过一行长度需要换行时,利用制表符或空格来形成缩进
以一致的方式使用制表符和空格
当语句里使用多个表时,使用表的别名
如果SQL实现里允许使用注释,应该在语句里有节制地使用
如果在SELECT语句里要使用多个字段,就让每个字段都从新行开始
如果在FROM子句里要使用多个表,就让每个表名都从新行开始
让WHERE子句里每个条件都以新行开始
17.3.2 FROM子句里的表
把较小的表列在前面,把较大的表列在后面,就会获得更好的性能
17.3.3 结合条件的次序
在WHERE子句里,来自基表的字段一般放在结合操作的右侧,要被结合的表通常按照从小到大的次序排列
如果没有基表,那表就应该从小到大排列,让最大的表位于WHERE子句里结合操作的右侧
结合条件应该位于WHERE子句的最前面,其后才是过滤条件
17.3.4 最严格条件
最严格条件是WHERE子句里返回最少记录的条件
应该让SQL优化器首先计算最严格条件,因为它会返回最小但数据子集,从而减小查询的开销。最严格条件的位置取决于优化器的工作方式,若优化器从WHERE子句的底部开始读取,则需要把最严格条件放到WHERE子句的末尾
17.4 全表扫描
在没有使用索引时,或是SQL语句所使用的表没有索引时,就会发生全表扫描
下面是应该被索引的数据:
作为主键的字段
作为外键的字段
在结合表里经常使用的字段
经常在查询里作为条件的字段
大部分值是唯一值的字段
17.5.1 使用LIKE操作符和通配符
17.5.2 避免使用OR操作符
在SQL语句里用谓词IN代替OR操作符能够提高数据检索速度
17.5.3 避免使用HAVING子句
HAVING子句会让SQL优化器进行额外的工作,也就需要额外的时间,在可能的情况下,尽量不要在SQL语句中使用HAVING子句
17.5.4 避免大规模排序操作
大规模排序操作意味着使用ORDER BY、GROUP BY、HAVING子句,由于大规模排序操作不是总可以避免的,所以最好把大规模排序在批处理过程里,在数据库使用的非繁忙期运行,从而避免影响大多数用户进程的性能
17.5.5 使用存储过程
所谓存储过程就是经过编译的、以可执行格式永久保存在数据库里的SQL语句
17.5.6 在批加载时关闭索引
批加载可能包含数百、数千或数百万操作语句或事务,最好在批加载前删除索引,批加载结束后再重建索引
17.6 基于成本的优化
资源消耗量:
总计资源消耗 = 衡量方法 * 执行次数

第六部分  使用SQL管理用户和安全
第18章  管理数据库用户
18.2.1 创建用户
CREATE USER user [IDENTIFIED BY [PASSWORD] 'password'] # mysql语法,创建用户
GRANT priv_type [(column_list)] [, priv_type [(column_list)]]...
ON [object_type] {tbl_name | * | *.* | db_name.* | db_name.routine_name}
TO user # mysql语法,分配用户权限
CREATE USER JOHN; # 创建JOHN用户
GRANT SELECT ON TABLE EMPLOYEE_TBL TO JOHN; # 分配权限
DROP USER JOHN CASCADE; # 删除用户
18.2.2 创建规划
CREATE SCHEMA [SCHEMA_NAME] [USER_ID]
              [DEFAULT CHARACTER SET CHARACTER_SET]
              [PATH SCHEMA NAME [, SCHEMA NAME]]
              [SCHEMA_ELEMENT_LIST] # 创建规划,mysql不支持CREATE SCHEMA,在mysql中规划被看作一个数据库,可以用CREATE DATABASE来创建规划
18.2.3 删除规划
DROP SCHEMA SCHEMA_NAME { RESTRICT | CASCADE } # 删除规划,若规划里有对象,则必须指定CASCADE选项
18.2.4 调整用户
UPDATE mysql.user SET password=PASSWORD('new password')
WHERE user='username'; # mysql中重置用户密码
RENAME USER old_username TO new_username; # mysql中改变用户名
18.2.5 用户会话
CONNECT TO DEFAULT | STRING1 [AS STRING2] [USER STRING3]
DISCONNECT DEFAULT | CURRENT | ALL | STRING
SET CONNECTION DEFAULT | STRING # 连接和断开数据库,从而开始和结束SQL会话
18.2.6 禁止用户访问
DROP USER USER_ID [CASCADE] # 删除数据库里的用户
REVOKE PRIV1 [, PRIV2, ...] FROM USERNAME # 取消已经分配给用户的权限

第19章  管理数据库安全
19.3.1 GRANT命令
GRANT SELECT ON EMPLOYEE_TBL TO USER1; # 向用户授予权限
GRANT SELECT ON EMPLOYEE_TBL TO USER1 WITH GRANT OPTION; # 当对象所有者利用GRANT OPTION把自己对象的权限授予另一个用户时,这个用户还可以把这个对象的权限授予其他用户
GRANT CREATE TABLE TO USER1 WITH ADMIN OPTION; # 当一个用户用ADMIN OPTION向另一个用户授予系统权限之后,后者还可以把系统权限授予其他用户
19.3.2 REVOKE命令
REVOKE INSERT ON EMPLOYEE_TBL FROM USER1; # 撤销权限,RESTRICT选项只有当REVOKE命令里指定的权限撤销之后不会导致其他用户产生报废权限时,REVOKE才能顺利完成,而CASCADE选项会撤销权限,不会遗留其他用户的权限
19.3.3 控制对单独字段的访问
GRANT UPDATE (NAME) ON EMPLOYEES TO PUBLIC; # 分配表里指定字段的权限
19.3.4 数据库账户PUBLIC
GRANT SELECT ON EMPLOYEE_TBL TO PUBLIC; # 数据库账户PUBLIC是个代表数据库里全体用户的账户,所有用户都属于PUBLIC账户
19.3.5 权限组
19.4 通过角色控制权限
19.4.1 CREATE ROLE语句
CREATE ROLE RECORDS_CLERK; # 创建角色
GRANT SELECT, INSERT, UPDATE, DELETE ON EMPLOYEE_PAY TO RECORDS_CLERK; # 授予角色权限
GRANT RECORDS_CLERK TO USER1; # 授予用户角色
19.4.2 DROP ROLE语句
DROP ROLE RECORDS_CLERK; # 删除角色
19.4.3 SET ROLE语句
SET ROLE RECORDS_CLERK; # 为用户的SQL会话设置角色

第七部分  摘要数据结构
第20章  创建和使用视图及异名
20.1 什么是视图
视图是一个虚拟表,视图可以包含表的全部或部分记录,可以由一个或多个表创建,表与视图的主要区别在于表包含实际的数据、占据物理存储空间,而视图不包含数据,而且只需要保存视图定义(即查询语句)。
20.2.1 从一个表创建视图
CREATE VIEW EMP_VIEW AS
SELECT LAST_NAME, FIRST_NAME, MIDDLE_NAME
FROM EMPLOYEE_TBL; # 选择指定的字段创建视图
20.2.2 从多个表创建视图
CREATE VIEW EMPLOYEE_SUMMARY AS
SELECT E.EMP_ID, E.LAST_NAME, P.POSITION, P.DATE_HIRE, P.PAY_RATE
FROM EMPLOYEE_TBL E,
     EMPLOYEE_PAY_TBL P
WHERE E.EMP_ID = P.EMP_ID; # 从多个表创建视图
20.2.3 从视图创建视图
CREATE VIEW2 AS
SELECT * FROM VIEW1; # 从VIEW1创建VIEW2
20.3 WITH CHECK OPTION
CREATE VIEW EMPLOYEE_PAGERS AS
SELECT LAST_NAME, FIRST_NAME, PAGER
FROM EMPLOYEE_TBL
WHERE PAGER IS NOT NULL
WITH CHECK OPTION; # WITH CHECK OPTION会确保视图的PAGER字段里不包含NULL值
20.4 从视图创建表
CREATE TABLE CUSTOMER_ROSTER_TBL AS
SELECT CUST_ID, CUST_NAME
FROM ACTIVE_CUSTOMERS; # 从视图ACTIVE_CUSTOMERS创建表CUSTOMER_ROSTER_TBL
20.5 视图与GROUP BY子句
CREATE VIEW NAMES2 AS
SELECT LAST_NAME || ', ' || FIRST_NAME || ' ' ||MIDDLE_NAME NAME
FROM EMPLOYEE_TBL
GROUP BY LAST_NAME || ', ' || FIRST_NAME || ' ' || MIDDLE_NAME; # CREATE VIEW语句里不能包含ORDER BY子句,但是GROUP BY子句用于CREATE VIEW语句时,可以起到类似ORDER BY子句的作用
20.7 删除视图
DROP VIEW NAMES2; # 删除视图,若使用RESTRICT选项,当其他视图在约束里有所引用,删除操作就会出错,若使用CASCADE选项,则底层视图或约束也会被删除
20.9 什么是异名
异名就是表或视图的另一个名称,mysql不支持异名,但可以使用视图来实现同样的功能
20.9.1 创建异名
CREATE SYNONYM CUST FOR CUSTOMER_TBL; # 创建CUSTOMER_TBL的异名CUST
20.9.2 删除异名
DROP SYNONYM CUST; # 删除异名CUST

第21章  使用系统目录
21.1 系统目录
系统目录是一些表和视图的集合,它们包含了关于数据库的信息,在mysql里系统目录位于mysql数据库里

第八部分  在实际工作中应用SQL知识
第22章  高级SQL主题
22.1 光标
光标被用于通过以记录为单位的操作,来获得数据库中数据的子集
DECLARE CURSOR_NAME CURSOR
FOR SELECT_STATEMENT; # mysql里对光标的声明语法
22.1.1 打开光标
OPEN CURSOR_NAME; # mysql里打开光标语法
22.1.2 从光标获取数据
FETCH CURSOR_NAME INTO VARIABLE_NAME, [VARIABLE_NAME] ... # mysql里从光标获取数据放到变量中
mysql光标处理过程:
BEGIN
     DECLARE done INT DEFAULT 0;
     DECLARE custname VARCHAR(30);
     DECLARE namecursor CURSOR FOR SELECT CUST_NAME FROM TBL_CUSTOMER;

     OPEN namecursor;
     read_loop: LOOP
         FETCH namecursor INTO custname;
         IF done THEN
             LEAVE read_loop;
         END IF;
         -- Do something with the variable
     END LOOP;
     CLOSE namecursor;
END;
22.1.3 关闭光标
CLOSE CURSOR_NAME; # mysql中关闭光标语法
22.2 存储过程和函数
存储过程是保存在数据库里的一组SQL语句或函数,它们被编译,随时可以被数据库用户使用
mysql创建存储过程的语法:
CREATE [OR REPLACE] PROCEDURE PROCEDURE_NAME
[(ARGUMENT [{IN | OUT | IN OUT}] TYPE,
ARGUMENT [{IN | OUT | IN OUT}] TYPE)] {AS}
PROCEDURE_BODY
例如:
CREATE PROCEDURE NEW_PRODUCT
(PROD_ID IN VARCHAR2, PROD_DESC IN VARCHAR2, COST IN NUMBER)
AS
BEGIN
    INSERT INTO PRODUCTS_TBL
    VALUES (PROD_ID, PROD_DESC, COST);
    COMMIT;
END; # 存储过程,在表PRODUCTS_TBL里插入一行新记录
CALL NEW_PRODUCT('9999', 'INDIAN CORN', 1.99); # 执行上面创建的过程
22.3 触发器
触发器是存储过程的一种,在特定DML行为作用于表格时被执行
22.3.1 CREATE TRIGGER语句
CREATE [DEFINER={user | CURRENT_USER}]
TRIGGER TRIGGER_NAME
{BEFORE | AFTER}
{INSERT | UPDATE | DELETE [, ..]}
ON TABLE_NAME
AS
SQL_STATEMENTS
22.3.2 DROP TRIGGER语句
DROP TRIGGER TRIGGER_NAME; # 删除触发器
22.3.3 FOR EACH ROW语句
CREATE TRIGGER TRIGGER_NAME
ON TABLE_NAME
FOR EACH ROW
SQL_STATEMENT; # FOR EACH ROW在SQL语句影响每条记录时都触发
22.7 直接SQL与嵌入SQL
{HOST PROGRAMMING COMMANDS}
EXEC SQL {SQL STATEMENT};
{MORE HOST PROGRAMMING COMMANDS} # 在主机程序(比如ANSI C)里嵌入SQL
22.9 使用XML
EXTRACTVALUE([XML Fragment], [locator string]) # 从XML文档或片段里获取信息
例如:
SELECT EXTRACTVALUE('<a>Red</a><b>Blue</b>', '/a') AS ColorValue; # 从节点a里提取值

第23章  SQL扩展到企业、互联网和内部网

第24章  标准SQL的扩展
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值