一 SQL查詢基本結構
SQL查詢是由三個基本語句構成,select
,from
和where
,更多的語句也是在這個基礎上進行拓展。from
后面添加模型關系,在這些關系上進行where
和select
語句中指定的運算,然后產生一個關系作為結果,查詢的結果是一個元組。
二 單關系模型查詢
我們使用如下結構構建我們的表結構和數據。
# 使用use + 庫名切換數據庫
use db1;
# 部門表
create table department
(
department_name varchar(20),
building varchar(15),
budget numeric(12, 0),
primary key (department_name)
);
# 產品表
create table product
(
# 用int類型 改成自動增長的主鍵
product_id int primary key auto_increment,
title varchar(20) not null,
credits numeric(8, 0),
department_name varchar(20),
foreign key (department_name) references department (department_name)
on update cascade
on delete cascade
);
# 人員表
create table staff
(
staff_id varchar(10),
name varchar(15) not null,
age int,
# 關聯的外鍵與被關聯外鍵數據類型應該一致
product_id int,
department_name varchar(20),
primary key (staff_id),
foreign key (product_id) references product (product_id),
foreign key (department_name) references department (department_name)
on update cascade
on delete cascade
);
insert into department(department_name, building, budget)
values ('市場部', '市場部大樓', 35000000),
('研發部', '研發部大樓', 20000000),
('人事部', '人事部大樓', 1000000);
insert into product(title, credits, department_name)
values ('1號產品', 888888, '研發部'),
('2號產品', 7777, '研發部'),
('3號產品', 999999, '研發部'),
('4號產品', 100000, '市場部'),
('5號產品', 5000000, '人事部');
insert into staff(staff_id, name, age, department_name,product_id)
values ('1', 'Albert', 18, '市場部',1),
('2', 'James', 35, '市場部',2),
('3', '劉德華', 50, '研發部',3),
('4', '張學友', 35, '人事部',4),
('5', '孫悟空', 500, '人事部',5);
單模型關系基本的查詢語句使用起來非常簡單,就像講話一樣方便。
# 1 找出所有的產品
select title
from product;
# 2 找出負責所有的產品的部分
select department_name
from product;
# 這樣看到的結果會有重復,保留重復元組在大部分的數據庫軟件中是默認的,如異,可以使用all指明不重復
select all department_name
from product;
# 我們希望看到的結果肯定是沒有重復的,在必要的地方使用distinct來完成基本去重
select distinct department_name
from product;
# 3 select中可以使用 + - * / 算數運算,這只是查詢結果的的改變,不會影響模型關系的任何變化
select budget + 10000000
from department;
select budget - 800000
from department;
select budget * 1.1
from department;
select budget * 2
from department;
# 4 where允許只選出滿足特定條件的元組
# (1) 查出年齡超過100的員工
select name
from staff
where age > 100;
# (2) 查出年齡超過30的市場部人員
select name
from staff
where age > 30
and department_name = '市場部';
三 多關系模型查詢
1. 多表查詢基本形式
依然是上面的表結構,這里面有三個模型關系,我們可以是使用非常簡單的查詢語句select * from 表名
查詢出每個表的數據如下:
department_name | building | budget |
---|---|---|
人事部 | 人事部大樓 | 1000000 |
市場部 | 市場部大樓 | 35000000 |
研發部 | 研發部大樓 | 20000000 |
product_id | title | credits | department_name |
---|---|---|---|
1 | 1號產品 | 888888 | 研發部 |
2 | 2號產品 | 7777 | 研發部 |
3 | 3號產品 | 999999 | 研發部 |
4 | 4號產品 | 100000 | 市場部 |
5 | 5號產品 | 5000000 | 人事部 |
staff_id | name | age | product_id | department_name |
---|---|---|---|---|
1 | Albert | 18 | 1 | 市場部 |
2 | James | 35 | 2 | 市場部 |
3 | 劉德華 | 50 | 3 | 研發部 |
4 | 張學友 | 35 | 4 | 人事部 |
5 | 孫悟空 | 500 | 5 | 人事部 |
如果我們需要查詢出所有的員工名字以及他們負責的產品和所在的部門,就需要找一個關聯關系進行跨表查詢。
select distinct name, title, staff.department_name
from department,
product,
staff
where staff.department_name = product.department_name;
2. 查詢語句的理解
再來看一下select
,from
和where
三個語句的作用:
- select語句用於列出查詢結果所需要的屬性
- from語句是查詢求值中需要訪問的關系模型
- where語句是作用在from語句關系之上的限制要求
where限制要求是可選項,如果沒有where語句,則限制要求默認為True。
盡管查詢語句必須以select
,from
,where
這樣的次序書寫,但理解查詢所代表的運算最容易的方式是按照運算的順序來看:首先是from,然后是where,最后是select。
3. 查詢過程說明
查詢過程是迭代循環,可能在這個過程中不同關系模型中會出現相同的屬性名,所以我們在屬性名前面加上關系名作為說明,表示該屬性來自於哪個關系。如果是單個關系模型,我們通常會去掉關系名,因為這樣不會造成任何混淆。查詢結果是求笛卡爾積,如果我們我們不指定條件,使用select * from department,product,staff
語句,那么即使是我們測試的數據量這么小,得出的結果也會使你眼花繚亂,而且得出的這些組合也是沒有意義的,where語句就是限制笛卡爾積所建立的組合,只留下那些對所需答案有意義的組合。
四 自然連接
1. 自然連接說明
在多關系模型查詢中我們往往需要多個表的組合信息,匹配條件是屬性名相同,為了簡化SQL開發者的工作,我們可以使用自然連接
作用在模型關系之上,自然連接會自動的將多關系模型中的所有元組進行連接,重要的是:自然連接只考慮那些在多關系模型中都出現的屬性相同的元組對。
# 查詢每個部門所負責的產品
# 不實用自然連接
select product.department_name, title
from department,
product
where department.department_name = product.department_name;
# 使用自然連接
select product.department_name, title
from department
natural join product;
2. 多關系自然連接
自然連接可以兩個關系連接,也可以多個關系連接。
# 查出一個人負責一個產品的人,產品和所在部門,然后給他漲工資
select name, title, department_name
from staff
natural join department
natural join product;
自然連接的結果依然是關系,所以在自然連接之后的結果依然可以像模型關系那樣使用在from語句中。
select name, title
from staff
natural join department,
product;
3. 自然連接揚長避短
自然連接會只考慮那些在多關系模型中都出現的屬性相同的元組對,也就是說會考慮多關系模型中所有屬性相同的元組對,而在有些時候,我們並不需要所有的元組對。為了發揚自然連接的有點,同時避免不必要的相等的屬性帶來的限制,SQL提供了一種自然連接的構造形式,允許用書來指定需要哪些屬性相等,我們使用using
語句來完成,這時是限制性連接而不是自然連接,所以natural
關鍵字就不再需要了。
select name, title
from staff
natural join product
join department using (department_name);
五 SQL附加運算
1. 更名運算
數據庫設計者為了查詢方便,有些字段名會使用一樣的,對於調用者在復雜查詢中就可能會混淆。而有些時候這個字段名又會很長,調用者使用不方便,這時我們可以使用as對屬性名做更名。
select department_name as Dname
from department;
as可以使用在select中也可以使用在from中
select D.department_name as Dname, title as T
from department as D,
product as P
where D.department_name = P.department_name;
需要注意的是:在一些SQL版本,尤其是Oracle中,不允許在from中使用as
,但是也有同樣的功能,所以這並不影響大局,Oracle中會把select department_name as Dname
語句寫成select department_name Dname
,幸運的是,在MySQL中as
也可以省略,所以影響不大(把以上語句所有as都去掉依然可以正常執行)。
2. 字符串運算
(1) SQL標准
在SQL標准中,字符串的相等運算是大小寫敏感的,所以在表達式staff.name = staff.Name
的結果是假,然而在一些特定的數據庫,如MySQL
和SQL Server
中,默認並不區分大小寫,所以返回結果可能為真,但是這種默認方式是可以在數據庫級或者特定屬性級做修改的。SQL中還允許在字符串上有變種函數,如upper(s)
,lower(s)
和trim(s)去除空格
。
(2) like匹配
在字符串上可以使用like
操作符來實現模式匹配。
- 百分號
%
:匹配任意字符串 - 下划線
_
:匹配任意一個字符,模式匹配中大小寫是敏感的,例如:Pro%
匹配任何以‘Pro’開頭的字符串,% Pro %
匹配任何包含‘Pro’的字符串。 - 三個下划線
___
:匹配只包含三個字符的字符串 ___%
:匹配至少包含三個字符的字符串
為了使模式中能夠包含特殊模式的字符(百分號或者下划線),SQL中允許定義轉義字符,使用的時候轉義字符直接放在特殊字符前面,在like
比較運算中使用escape
關鍵字來定義轉義字符。
# \ 為定義的轉義字符,
# 轉義第一個%為字符串,匹配所有以abc%d開頭的字符串
like 'abc\%d %' escape '\'
# 第一個\為轉義字符,匹配所有以abc\d開頭的字符串
like 'abc\\d %' escape '\'
SQL中還允許使用not like
比較運算搜索不匹配項。
# 匹配所有不是以abc%d開頭的字符串
not like 'abc\%d %' escape '\'
(3) 正則匹配
SQL可以支持使用REGEXP
來進行正則匹配,用法和Python,PHP等語言非常類似,這里我們給出幾個簡單的示例。
select *
from staff
where name REGEXP 'A.';
select *
from staff
where name REGEXP '^A';
select *
from staff
where name REGEXP 't$';
select *
from staff
where name REGEXP '[abc]';
select *
from staff
where name REGEXP '[^abc]';
select *
from staff
where name REGEXP '[a|b|c]';
3. 序列運算
(1) 基本排序
SQL為用戶提供了一種對關系中元組顯示次序的控制,使用order by
語句可以時候查詢結果中的元組按照排列順序顯示。
# 按照年齡大小排序,默認是升序
select name
from staff
order by age;
# 添加desc改為降序
select name
from staff
order by age desc;
# 按照字符編碼表排序,默認是從前往后,asc可以省略
select name
from staff
order by name asc;
# 從后往前排序
select name
from staff
order by name desc;
(2) 時間排序
除此之外,我們常用的還有按照時間排序,這些我們先介紹以下用於存儲時間的數據類型。
- date:日期,包括年月日
- time:一天當中的時間,包括時分秒
- timestamp:data與time的組合,包括年月日和時分秒
除此之外,SQL中定義了一些函數獲取當前的時間和日期。
- current_date:當前日期
- current_time:當前時間(帶有時區,時區可以在配置文件中設置,也可手動加減)
- localtime:當前本地時間,與
select now()
結果一樣 - current_timestamp:帶有時區的時間戳(時間戳指的是年月日和時分秒的組合,從Unix元年1970年開始計時)
- localtimestamp:本地時間戳
create table time1
(
name char(10),
data_time date
);
create table time2
(
name char(10),
time_time time
);
create table time3
(
name char(10),
timestamp_time timestamp
);
insert into time1 (name, data_time)
values ('1', '2001-04-25'),
('2', '2021-04-25'),
('3', '2011-01-25'),
('4', '2001-12-02'),
('5', '2001-04-05'),
('6', '2001-04-11');
# 插入數據的時候可以隨意一些,但是存儲的時候還是按照規范2001-04-11存儲
insert into time1 (name, data_time)
values ('1', '2001-04-5'),
('2', '2001-4-11');
insert into time2 (name, time_time)
values ('1', '09:30:00'),
('2', '21:4:11'),
('3', '00:4:11'),
('4', '21:14:11'),
('5', '21:42:11'),
('6', '21:47:11');
insert into time3 (name, timestamp_time)
values ('1', '2001-04-5 09:30:00'),
('2', '2011-04-5 09:30:00'),
('4', '2011-04-5 01:30:00'),
('5', '2011-04-5 10:30:00'),
('6', '2011-04-5 01:30:01');
select *
from time1
order by data_time;
select *
from time1
order by data_time desc;
select *
from time2
order by time_time;
select *
from time2
order by time_time desc;
select *
from time3
order by timestamp_time;
select *
from time3
order by timestamp_time desc;
select current_date;
select current_time;
select localtime;
select now();
select current_timestamp;
select localtimestamp;
4. 比較運算
(1) between比較
為了簡化where語句,SQL提供了between
比較運算符來說明一個值小於等於某個值,同時打不等於另外一個值。
use db1;
# 不支持 30 <= age <= 100
select name
from staff
where age <= 100
and age >= 30;
select name
from staff
where age between 30 and 100;
同理,我們還是可以使用not between
來做排除篩選
select name
from staff
where age not between 30 and 100;
(2) 分量比較
在SQL中使用(v1,v2,v3,...,vn)來表示一個分量的值分別為v1,v2,v3,vn的n維元組,在元組上可以使用比較運算,按照字典的順序進行一對一比較,比如(a1,a2) <= (b1,b2)
,則代表a1<=b1
並且a2<=b2
,同理,比較相當的時候也是一樣的。
# 查出研發部負責3號產品的人
select name
from staff,
product
where (product.title, staff.department_name) = ('3號產品', '研發部');
5. 集合運算
(1) 集合運算說明
SQL中有語句union
,intersect
和except
分別對應數學集合中並,交,差運算。
(2) 並運算
與select語句不同的是,union語句會自動去除重復。
select name
from staff
where staff.product_id = 1;
(select name
from staff,
product
where staff.product_id = 1)
union
(select name
from staff,
product
where staff.product_id = 2);
如果我們希望保留重復,需使用union all
代替union
。
(select name
from staff,
product
where staff.product_id = 1)
union all
(select name
from staff,
product
where staff.product_id = 2);
(3) 交運算
非常遺憾的是MySQL中沒有交運算的instersect
語句,但是這樣的語句在Oracle,PostgreSQL和SQL Server中都是有的。
# 適用於Oracle,PostgreSQL和SQL Server
(select name
from staff
where staff.department_name = '市場部');
instersect
(select name
from staff
where staff.product_id = 2);
# instersect 語句會自動去重,如果希望顯示全部結果,在后面添加一個all即可
# MySQL中等價用法可以用分量比較
select name
from staff
where (staff.product_id, staff.department_name) = (2, '市場部');
(4) 差運算
MySQL中也沒有差運算的except
,但是在PostgreSQL和SQL Server中有,Oracle中使用MINUS
語句來實現同樣的功能,並運算和交運算都很好理解,差運算這里說明一下:指的是前一個結果關系中有而后一個結果關系中沒有,也就是前一個比后一個多的部分,即前一個減去后一個,這也就是差的來源。
# PostgreSQL和SQL Server
(select name
from staff
where staff.department_name = '市場部');
except
(select name
from staff
where staff.product_id = 2);
# 同理,自動去重,可以加all顯示全部
MySQL中類似於差運算的語句將會在我們下一章節的內容中講解。
6. 空值運算
在生產環境中,數據庫出現空值是很常見的,空值運算也給我們帶來了特殊的問題。如果在比較運算中出現1 > null
這樣的語句,由於我們不知道空值代表的是什么,所以也發評定結果是為真還是為假,但是根據我們的邏輯語句來推理,如果1 > null
為真,那么not (1 > null)
就應該為假,反之,亦然。但是這卻並沒有什么意義,數據庫設計者為了程序的健壯性,將涉及空值的任何比較運算的結果視為unknown
,這是除了true
和false
之外的第三個邏輯。由於where語句可以使用and
,or
和not
做邏輯運算,所以unknown
也被擴展到這三個關鍵字上面。
- and:
ture and unknown
的結果是unknown
,false and unknown
的結果是false,unknown and unknown
的結果是unknown
。 - or:
ture or unknown
的結果是true,false or unknown
的結果是unknown
,unknown or unknown
的結果是unknown
。 - not:
not unknown
的結果是unknown
。
如果是在where語句中對一個元組計算出false或者unknown,那么該元組自然不能被加入到結果中。在where語句中可以使用特殊關鍵字null
來測試空值,也允許使用is unknown
來測試一個表達式的結果是否為unknown,它們兩種的用法前面添加not
自然也是可以使用的。
# 插入空值
insert into staff(staff_id, name, product_id)
values ('6', '小龍女', 1);
select *
from staff;
# 找空值
select *
from staff
where age is null;
select *
from staff
where age is not null;
# 條件為unknown
select *
from staff
where name = '小龍女'
and age > 15;
# 認為條件正確,取其結果
select *
from staff
where (name = '小龍女'
and age > 15) is unknown;
# unknown is True 自然是false,無結果
select *
from staff
where (name = '小龍女'
and age > 15) is true;
# 認為條件不正確,取反
select *
from staff
where (name = '小龍女'
and age > 15) is FALSE;