从一个前端视角初识sql

262 阅读8分钟

本文将从一个前端开发者的视角快速入门sql相关命令,主要参考资料如下

本文有如下约定

  • sql包含的关键词一律大写
  • 注释: -- 是单行注释,/* */是多行注释
  • []表示这些可有可不有,比如column_name1 VARCHAR(25) [constraints]表示 constraints可以不写
  • ;是语句结束的分割符,,一般是用来分开column的

需要注意,这里写的是较为通用的sql命令或者是基础概念类的命令,在各sql实现比如mysql postgresql里可能会有细微出入,但总体概念是没问题的

常用命令

数据表创建等操作

这部分主要包含创建或删除数据表、新增或删除column、修改column名称或数据类型

-- 创建table
CREATE TABLE [IF NOT EXISTS] table_name
  (
  column_name1 dataType [constraints],
  column_name2 VARCHAR(4),
  );


-- 删除table
DROP TABLE table_name [IF EXISTS];


-- 修改table name, 有两种写法
RENAME table_name1 TO table_name2;
ALTER table_name1 RENAME TO table_name2;


/* 修改column的相关操作
- ADD 新增column,约束、index
- DROP 删除column,约束、index
- MODIFY 修改数据类型
- CHANGE 修改名称和数据类型
*/
ALTER TABLE table_name
ADD column_name dataType [constraints];

ALTER TABLE table_name
DROP column_name;

ALTER TABLE table_name
MODIFY column_name dataType [constraints];

ALTER TABLE table_name
CHANGE column_name1 column_name2 dataType [constraints];

数据增删改查相关操作

本部分主要介绍crud相关操作

基础的crud命令

SELECT column_name1, column_name2 FROM table_name;

INSERT INTO table_name
  (column_name1, column_name2)
  VALUES (column_value1, column_value2);

UPDATE table_name
  SET column_name1='name', column_name2='name2'
  WHERE id='id-1';

DELETE FROM table_name WHERE id='id-1';

复杂一点的查询命令

常用过滤数据关键词
名称作用
GROUP BY按照column,将同value的行统计在一起进行操作,一般是需要使用COUNT等函数对数据进行计算是使用
HAVING跟在GROUP BY后面使用,用来对GROUP BY的结果进行过滤操作
ORDER BY顾名思义,进行排序
WHERE过滤不需要的数据
LIMIT展示的数据量
OFFSET忽视前n个数据,从n+1开始
DISTINCT去除重复值,只留下唯一的数据
LIKE用来过滤字符串类型数据,常用'%NAME%'对应'xxxNAMExxx','NAME'对应'xNAMEx',"%"对应一个或多个字符,"_"对应一个字符

下面的例子就是获取拥有大于1家商店的店主名称,取拥有商店数量最多的5位展示

SELECT owner, COUNT(name) AS owned_store_number
FROM stores
  GROUP BY owner
  HAVING COUNT(name) > 1
  ORDER BY owned_store_number DESC LIMIT 5;

多表联查

有时候查询需要多表联查,这时候就分为了inner join和outer join,分别对应的常用关键字是INNER JOINCROSS JOINLEFT JOINRIGHT JOINFULL JOIN

通过集合运算的图来理解一下区别

名称含义
INNER JOIN取交集
CROSS JOIN将A中的每一项都和B中的每一项链接一遍产生的结果
LEFT JOIN包含全部A和A与B的交集,即便某些数据只在A有,不在B内
RIGHT JOIN包含全部B和A与B的交集,即便某些数据只在B有,不在A内
FULL JOIN并集

join

下面是一个简单的INNER JOIN的语句

mysql> SELECT f.film_id, f.title, i.inventory_id
    -> FROM film f
    ->   INNER JOIN inventory i
    ->   ON f.film_id = i.film_id
    -> WHERE f.film_id BETWEEN 13 AND 15;
+---------+--------------+--------------+
| film_id | title        | inventory_id |
+---------+--------------+--------------+
|      13 | ALI FOREVER  |           67 |
|      13 | ALI FOREVER  |           68 |
|      15 | ALIEN CENTER |           75 |
|      15 | ALIEN CENTER |           76 |
+---------+--------------+--------------+
subquery 子查询

一般来说,join是可以替代subquery的,且join的性能可能比subquery要好,所以在业务开发中不是很推荐使用

subquery是指在一个query语句里再嵌套一个query语句的场景,有时候在实现多表联查的时候比join写起来更简单

subquery有两种类型

  • noncorrelated subqueries: subquery和父query无关
  • correlated subqueries: subquery需要用到父query里的数据作为判定条件

下面是两种subquery的例子

# noncorrelated subqueries
mysql> SELECT city_id, city
    -> FROM city
    -> WHERE country_id <>
    ->  (SELECT country_id FROM country WHERE country = 'India');
    +---------+----------------------------+
    | city_id | city                       |
    +---------+----------------------------+
    |       1 | A Corua (La Corua)         |
    |       2 | Abha                       |
    |       3 | Abu Dhabi                  |
    |       4 | Acua                       |
    |       5 | Adana                      |
    +---------+----------------------------+


# correlated subqueries
mysql> SELECT c.first_name, c.last_name
    -> FROM customer c
    -> WHERE 20 =
    ->  (SELECT count(*) FROM rental r
    ->   WHERE r.customer_id = c.customer_id);
+------------+-------------+
| first_name | last_name   |
+------------+-------------+
| LAUREN     | HUDSON      |
| JEANETTE   | GREENE      |
| TARA       | RYAN        |
+------------+-------------+

其他特性介绍

这部分主要包含比如index,transaction等偏向于概念类内容的介绍

index索引

index的作用是生成一个索引,可以加快查询速度;数据的存储一般是无序的,在查询的时候如果遍历会很慢,设置index之后数据库会把设置了index对应的column进行排序存储,在之后搜索对应column的速度就会快很多,index的存储方式有b tree等等方式,这里就不做介绍了,详细内容可以看 how-indexing-works

index可以在创建table的时候就设置好,或者是在后续再新增

-- 创建table时创建index
CREATE TABLE customer (
  customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  store_id TINYINT UNSIGNED NOT NULL,
  last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  -- 此处是index的创建
  PRIMARY KEY  (customer_id),
  KEY idx_fk_store_id (store_id),
);


-- 后续创建index的两种方法
CREATE INDEX idx_name ON table_name (column_name);

ALTER TABLE table_name
ADD INDEX idx_name (column_name);

constraints约束

约束主要是用来控制数据是否正确的,比如数据是否唯一、是否满足CHECK、是否满足外键依赖等等; CHECK可以用来校验某个column是否满足对应的条件,对输入内容进行约束;

创建方式和index比较像

CREATE TABLE customer (
  customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (customer_id),
  CONSTRAINT fk_customer_address FOREIGN KEY (address_id)
    REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE,
)

ALTER TABLE table_name
ADD CONSTRAINT constraint_name PRIMARY KEY (column_name)
ADD CONSTRAINT CHK_PersonAge CHECK (Age>=18);

transaction事务

在实际生产中一般要开启事务再进行数据的更新,这样可以万一在更新数据的时候出现问题,可以回滚到开始事务时或者指定的SAVEPOINT

START TRANSACTION;

UPDATE product
SET date_retired = CURRENT_TIMESTAMP()
WHERE product_cd = 'XYZ';

SAVEPOINT before_close_accounts;

UPDATE account
SET status = 'CLOSED', close_date = CURRENT_TIMESTAMP(),
  last_activity_date = CURRENT_TIMESTAMP()
WHERE product_cd = 'XYZ';

ROLLBACK TO SAVEPOINT before_close_accounts;
COMMIT;

lock锁

在数据库的更新过程中可能会出现concurrency并发问题,需要加锁来避免这种情况导致不可预料的问题

分为lock table和lock row两个级别,但是在sql标准里没有lock的定义,是各数据库自行实现,所以此处不给出具体的使用语句,请自行搜索

view 视图

view可以理解为一个query语句的别名,在某些时候可以把view当table使用;不但可以从view读取数据,有时也可以直接使用view更新数据,但是需要注意:

  • 不可以更新派生出来的数据
  • 如果view包含多个表格的数据,那一次只可以更新一个表格的数据
CREATE VIEW low_budget_locs_view AS
(
  SELECT city, country FROM locations
  WHERE budget <= 1500
);

SELECT * FROM low_budget_locs_view;

temporary table 临时表

临时表的主要作用是保存获取到的数据,可能读取一次的数据量大或者性能不好,需要临时存放,以供后续使用;一般临时表会在一次session里生效,关闭链接后会自动清除,但具体要看各数据库的实现

CREATE TEMPORARY TABLE hot_location AS (
  SELECT * FROM locations
  WHERE weather_id  = ( SELECT weather_id
        FROM weather
        WHERE weather = "hot"
        )
);

-- DROP TABLE也可以用来删除临时表
DROP [TEMPORARY] TABLE hot_location'

CTE-Common Table Expression,with语句

CTE其实就是with语句,就我个人的理解,CTE的主要作用是将数据的前置,简化查询,可读性提升,在非递归cte时可以提升性能

WITH avg_salary AS (
    SELECT department, AVG(salary) avg_salary
    FROM employees
    GROUP BY department
)
SELECT *
FROM avg_salary;

需要注意的一些事项

NULL

NULL不等于任何值,也即 NULL != NULL,可以使用IS NULL来判断是否是NULL

VARCHAR

VARCHAR 需要多占用一个byte用来存prefix; VARCHAR(4) needs anywhere from 1 to 5 bytes

条件语句

case语句可以用来对选中的数据进行二次计算,格式如下

CASE
  WHEN C1 THEN E1
  WHEN C2 THEN E2
  ...
  WHEN CN THEN EN
  [ELSE ED]
END

mysql的例子如下

mysql> SELECT c.first_name, c.last_name,
    ->   CASE
    ->     WHEN active = 0 THEN 0
    ->     ELSE
    ->      (SELECT count(*) FROM rental r
    ->       WHERE r.customer_id = c.customer_id)
    ->   END num_rentals
    -> FROM customer c;
+-------------+--------------+-------------+
| first_name  | last_name    | num_rentals |
+-------------+--------------+-------------+
| MARY        | SMITH        |          32 |
| PATRICIA    | JOHNSON      |          27 |
| AUSTIN      | CINTRON      |          19 |
+-------------+--------------+-------------+

一对一,一对多,多对多; one-to-many, many-to-many

one-to-one一般可以存在一个表里,但是有时候会拆开来,有如下情况:

  • 拆开可能可以加速数据的query
  • 更细粒度的控制数据的访问权限
  • 某些column的数据量很大,拆分到另一个表可能更好
  • 把在新增时可能不知道的数据放入另一个表,有助于避免null值

many-to-many 的某些使用场景: 学生参加课程,课程表有学生的信息,学生需要有其参加的课程的信息; 一般需要一个关系表来专门存储学生id和课程id,作为两个表的联系

如何拆分表格

  • 每一行数据都时原子化的数据,比如旅行社的路线活动表,某个城市可以进行多个活动,则这些活动需要放在其他表中
  • 每一行数据都要有primary key
  • 如果primary key是由多个column组成的,那不能有某个非key的column依赖于部分组成primary key的column,比如名字+性别组成primary key,名字的拼音首字母就不可以放在此表格里,因为和名字相关联,但是和性别无关——no partial functional deps
  • 非key的column依赖于另一个非key的column——no transitive deps

拆分原则