《SQL Server溫故系列》之增刪改查,CRUD,查詢語句,SELECT。顧名思義,SELECT 語句的作用就是從表中查詢數據。查詢語句一次可以從一個或多個表中檢索一個或多個欄位的一行或多行。SELECT 是 SQL 中最常用的一個語句,完整的 SELECT 語法是非常複雜的,本文將先對簡單查詢... ...
1、查詢語句 SELECT
顧名思義,SELECT 語句的作用就是從表中查詢數據。查詢語句一次可以從一個或多個表中檢索一個或多個欄位的一行或多行。SELECT 是 SQL 中最常用的一個語句,完整的 SELECT 語法是非常複雜的,本文將先對簡單查詢做個回顧,更高級的用法將會在本系列的後續博文中陸續介紹。基本語法如下:
SELECT TOP(top-expressions) column-expressions
FROM source-table-name
WHERE search-conditions
ORDER BY order-by-expressions;
1.1、查詢語句的 SELECT 子句
SELECT 子句的作用是指定查詢返回的欄位,而如果只是查詢常量、變數或算術表達式等(非表的)數據,就不需要 FROM 子句了,示例如下:
SELECT 'A','1','A1'; -- 查詢 SQL 字面量
SELECT GETDATE(); -- 調用系統函數,獲得當前系統時間
SELECT 5+1,7-2,2*2,9/3; -- 做加減乘除四則運算
通過 FROM 子句來查詢表數據,示例如下:
SELECT t.Code AS 學號,t.Name AS 姓名 FROM T_Students t; -- 查詢所有學生的學號和姓名(且給表和欄位指定了別名)
通過 * 查詢 FROM 子句中的所有表中的全部列,示例如下:
SELECT * FROM T_Students; -- 查詢學生表中所有欄位
SELECT * FROM T_Students,T_Students; -- 查詢學生表和學生表中所有欄位(僅為演示,無實際意義)
通過 DISTINCT 參數過濾重覆數據,示例如下:
SELECT DISTINCT t.* FROM T_Students t; -- 查詢所有學生的信息
SELECT DISTINCT t.ClassId FROM T_Students t; -- 查詢“有效的”班級 Id
註意:DISTINCT 參數的作用在於確保結果集中只包含唯一的行。然而有意思的是,儘管在 SQL Server 中理論上不存在兩個相等的 NULL,但對 DISTINCT 來說 NULL 值卻是相等的。所以如果表中有多個 NULL 值,DISTINCT 之後就只會返回一個。另外,與 DISTINCT 對應的還有一個 ALL,表示可以包含重覆行,也就是預設返回,該參數總是可以省略不寫。
通過 TOP 參數限制查詢的返回行數,示例如下:
SELECT TOP(5) t.* FROM T_Students t; -- 查詢隨機的前 5 個學生
SELECT TOP(3+2) t.* FROM T_Students t; -- 結果集同上(支持運算表達式)
SELECT TOP(SELECT 5) t.* FROM T_Students t; -- 結果集同上(支持子查詢)
SELECT TOP(25) PERCENT t.* FROM T_Students t; -- 查詢隨機的前 25% 的學生
註意:PERCENT 用於指示返回結果集中前 expression% 的行,如果行數的計算結果為小數,則小數部分的值向上舍入到下一個整數值。當指定了 PERCENT 時,TOP 的表達式會被隱式轉換為 float 值;否則,就會被轉換為 bigint;轉換失敗則返回錯誤。
在實際開發中,TOP 參數一般要跟 ORDER BY 子句結合起來使用才更有意義,示例如下:
SELECT TOP(5) t.* FROM T_Students t ORDER BY t.Birthday; -- 查詢年齡最大的前 5 個學生
SELECT TOP(5) PERCENT t.* FROM T_Students t ORDER BY t.Birthday; -- 查詢年齡最大的前 5% 的學生
假如現在要查詢最近一次英語考試排名前三的學生,但實際上第三名和第四名的分數相同,如果只是 TOP(3) 就會漏掉一個第三名,這時候在 TOP 的最後面加上 WITH TIES 就會返回所有前三名的學生。示例如下:
SELECT TOP(3) WITH TIES t1.Code,t1.Name,t3.Name,t2.Counts,t2.Scores
FROM T_Students t1,T_ExamResults t2,T_Courses t3
WHERE t1.Id = t2.StudentId AND t2.CourseId = t3.Id AND t3.Name = '英語' AND t2.Counts = 8
ORDER BY t2.Scores DESC;
查詢結果如下:
Code Name Name Counts Scores
--------------- --------------- ------------- ----------- -------------------
S1032007 劉艷 英語 10 100.0
S1032009 宋梅 英語 10 99.0
S1032003 宋陽 英語 10 98.0
S1033005 馬鵬飛 英語 10 98.0
再來看一個綜合點的案例,按 ClassId 升序排序,查詢前 30% 的學生信息,示例如下:
SELECT TOP(30) PERCENT WITH TIES t.* FROM T_Students t ORDER BY t.ClassId;
讓我們來分析一下:因為學生總數 30 人,所以 30% 就是 30×0.3 等於 9 人;然後按 ClassId 升序排序,那麼前 9 個人就都是 1 班的,然而 1 班有 12 個人,也就是說從第 9 個到第 12 個的 ClassId 都是 1,所以最終會返回 12 行(整個 1 班的學生信息都會被返回)。
1.2、查詢語句的 FROM 子句
FROM 子句的作用是在增刪改查中指定要被影響或查詢的目標表。可以給查詢語句的 FROM 子句中的表取別名,別名可帶來使用上的方便,也可用於區分自連接或子查詢中的表。如果出現重覆的表名或別名,SQL Server 則會返回錯誤。如果連接中的多個表中存在同名的欄位,則需要使用表名或別名來限定欄位名。如果定義了別名,就不能再使用表名限定了。示例如下(查詢學生和班級信息):
SELECT t2.Name,t1.Code,t1.Name,t1.Gender,t1.Birthday
FROM T_Students t1
JOIN T_Classes t2 ON t1.ClassId = t2.Id;
如果表在同一 SQL Server 實例的另一個資料庫中,則需要按照 database.schema.table-name 的形式來訪問。示例如下:
SELECT t.* FROM AnchorEdu.dbo.T_LearnCard t; -- 查詢 AnchorEdu 庫中 T_LearnCard 表的信息
如果表不在同一 SQL Server 的實例中,則需要按照 linked-server.catalog.schema.object 的形式來訪問。示例如下:
SELECT t.* FROM [192.168.0.16].ShopDB.dbo.T_User t; -- 查詢 ShopDB 庫中 T_User 表的信息
在設計資料庫的時候,我們一般會根據資料庫範式,將現實中的數據拆分為多個實體後分別存儲到不同的表中,以避免過度的數據冗餘。隨之而來的問題是為了滿足實際業務需要,我們往往需要同時獲取多個表中的數據,這時候連結查詢就派上用場了。
簡單來講,聯合多個表來獲取數據的查詢就是連結查詢。連結查詢也是關係型資料庫中最重要的查詢,主要分為內連接、外鏈接和交叉連接。連結查詢中通過 JOIN 來指定哪些表參與連接,而 ON 則用來指定連接所基於的匹配條件。示例如下:
SELECT t1.* FROM T_Students t1 JOIN T_Classes t2 ON t1.ClassId=t2.Id; -- 查詢班級有效的學生信息
-- 查詢考過 100 分的學生信息
SELECT DISTINCT t1.* FROM T_Students t1
JOIN T_ExamResults t2 ON t1.Id = t2.StudentId AND t2.Scores = 100;
註意:在 ON 中做欄位運算時,欄位不必具有相同的數據類型。當數據類型不相同時,運算符兩邊的欄位類型要麼相互相容,要麼是 SQL Server 能夠隱式轉換的類型。如果不能隱式轉換,就必須使用類型轉換函數顯式的轉換數據類型才行。
1.2.1、內連接查詢 INNER JOIN
內連接查詢會返回所有匹配上的行,放棄不匹配的行。如果未指定任何連接類型,則預設未內連接。示例如下:
SELECT t2.Name,t1.Code,t1.Name,t4.Name,t3.Counts,t3.Scores
FROM T_Students t1
INNER JOIN T_Classes t2 ON t1.ClassId=t2.Id
INNER JOIN T_ExamResults t3 ON t1.Id=t3.StudentId
INNER JOIN T_Courses t4 ON t3.CourseId=t4.Id
ORDER BY t2.Id,t1.Id,t4.Id,t3.Counts;
1.2.2、外連接查詢 OUTER JOIN
外連接又分為左外連接 LEFT OUTER、右外連接 RIGHT OUTER 和 全外連接 FULL OUTER,其中 OUTER 關鍵字是可以省略的。左外連接會根據左表數據,在右表中查出符合條件的記錄與之匹配,並將未找到匹配行的右表輸出列設置為 NULL。右外連接的查詢步驟正好與左外連接相反。而全外連接相當於是把左外連接的操作和右外連接的操作都做一遍,然後取並集。示例如下:
SELECT t2.Name,t1.Code,t1.Name,t4.Name,t3.Counts,t3.Scores
FROM T_Students t1
LEFT OUTER JOIN T_Classes t2 ON t1.ClassId=t2.Id
RIGHT OUTER JOIN T_ExamResults t3 ON t1.Id=t3.StudentId
FULL OUTER JOIN T_Courses t4 ON t3.CourseId=t4.Id
ORDER BY t2.Id,t1.Id,t4.Id,t3.Counts;
1.2.3、交叉連接查詢 CROSS JOIN
交叉連接查詢會返回被連接表的笛卡爾積,返回行數等於兩個表的行數乘積。交叉連接不需要 ON 條件,可以同時省略 CROSS JOIN。示例如下:
SELECT * FROM T_Classes CROSS JOIN T_Courses;
註意:ON 中的條件一般也可以用在 WHERE 子句中,雖然這種條件位置的不同對 INNER 連接查詢結果不會有影響,但對 OUTER 連接來說就可能會導致出現不同的結果。這是因為 ON 子句中的條件在應用於連接之前先應用於表,而 WHERE 子句是在語義上應用於聯接結果。
1.3、查詢語句的 WHERE 子句
WHERE 子句的作用是定義要返回的行應滿足的條件。示例如下:
SELECT t.* FROM T_Students t WHERE t.Birthday >= '2000-01-01'; -- 查詢 00 後學生
SELECT t.* FROM T_Students t WHERE t.Id IN(1,3,5,7,9); -- 查詢 Id 為 1,3,5,7,9 的學生
SELECT t.* FROM T_Students t WHERE t.Id NOT IN(1,3,5,7,9); -- 查詢 Id 不為 1,3,5,7,9 的學生
SELECT t.* FROM T_Students t WHERE t.Id BETWEEN 11 AND 20; -- 查詢 Id 在 11~20 之間的學生
SELECT t.* FROM T_Students t WHERE t.Id NOT BETWEEN 11 AND 20; -- 查詢 Id 不在 11~20 之間的學生
邏輯函數 AND 和 OR 的作用都是在 WHERE 子語句中把兩個或多個條件結合起來。不同的是,AND 是當兩邊的條件都為 TRUE 時才返回 TRUE,否則就返回 FALSE;而 OR 是當兩邊有一個為 TRUE 時就返回 TRUE,否則就返回 FALSE。而且 AND 的優先順序比 OR 的優先順序要高,不過可以通過使用括弧來改變求值順序。示例如下:
SELECT t.* FROM T_Students t WHERE t.ClassId = 1 AND t.Gender = 0; -- 同時滿足兩個條件
SELECT t.* FROM T_Students t WHERE t.ClassId = 1 OR t.ClassId = 3; -- 滿足兩個條件之一
SELECT t.* FROM T_Students t WHERE t.Id >= 1 AND t.ClassId = 1 AND t.Gender = 0; -- 同時滿足多個條件
SELECT t.* FROM T_Students t WHERE t.Id >= 1 OR t.ClassId = 1 OR t.ClassId = 1; -- 滿足多個條件之一
註意:AND 和 OR 其實都還有返回 UNKNOWN 的情況,只是本人沒遇到過,也不知道要怎麼模擬,有興趣的讀者可以看官方文檔自行瞭解。
1.4、查詢語句的 ORDER BY 子句
ORDER BY 子句的作用對查詢返回的數據進行排序。ORDER BY 指定的欄位必須是選擇列表中定義的欄位或 FROM 子句中指定的表中定義的欄位。指定欄位時可以用欄位名,也可以用欄位的別名,還可以用表示欄位在選擇列表中所處位置的非負整數。示例如下:
SELECT t.* FROM T_Students t ORDER BY t.Birthday; -- 按出生日期升序排序
SELECT t.* FROM T_Students t ORDER BY t.Birthday DESC; -- 按選擇列表中的欄位降序排序
SELECT t.Code,t.Name FROM T_Students t ORDER BY t.Birthday DESC; -- 按 FROM 中表的欄位降序排序
SELECT t.Code AS 學號,t.Name 姓名 FROM T_Students t ORDER BY 學號; -- 按欄位別名升序排序
註意:DESC 按從最大值到最小值的順序進行排序,即降序。ASC 表示按從最小值到最大值的順序進行排序,即升序。ASC 是 ORDER BY 子句的預設參數,可不寫。在 SQL Server 的排序中,NULL 值總是被視為最小的值。
1.4.1、指定多個排序欄位時,將先按第 1 個欄位對結果集進行排序,然後按第 2 個欄位對結果集進行排序,依此類推。示例如下:
SELECT t.* FROM T_Students t ORDER BY t.ClassId ASC,t.Gender,t.Birthday DESC;
1.4.2、指定表達式為排序條件(大數據量時性能會很差)示例如下:
SELECT t.* FROM T_Students t ORDER BY DATEPART(month, t.Birthday); -- 按出生月份升序排序
SELECT t.* FROM T_Students t ORDER BY SUBSTRING(t.Code,5,3) DESC; -- 按學號後 3 位降序排序
1.4.3、有條件的指定排序條件示例如下:
SELECT t.* FROM T_Students t
ORDER BY CASE t.Gender WHEN 1 THEN t.Birthday END DESC -- 男生按年齡降序排列
,CASE t.Gender WHEN 0 THEN t.Birthday END ASC; -- 女生按年齡升序排列
SELECT t.* FROM T_Students t
ORDER BY CASE WHEN t.ClassId = 1 THEN t.Name -- 1 班的學生按姓名降序排序
ELSE t.Code END DESC; -- 其它班的學生按學號降序排序
1.4.4、限制查詢返回的行數從 SQL Server 2012 開始,支持在 ORDER BY 子句中使用 OFFSET 和 FETCH 來限制查詢返回的行數,其中,OFFSET 用於指定將要跳過的行數(即偏移量),而 FETCH 用於指定最終要返回的行數(即提取數)。示例如下:
SELECT * FROM T_Students ORDER BY Id OFFSET 10 ROWS; -- 返回第 10 條以後的全部數據
SELECT * FROM T_Students ORDER BY Id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY; -- 返回第 11~15 條數據
註意:OFFSET 和 FETCH 必須在 ORDER BY 子句中才能使用,且不能與 TOP 在同一個查詢中共存。FETCH 必須結合 OFFSET 才能使用,但 OFFSET 卻可以單獨使用。
2、插入子句 INTO
2.1、查詢語句的 INTO 子句
SELECT 語句中的 INTO 子句能夠在預設文件組中創建一個新表,並將來自查詢的結果行插入到新表中,且新表中欄位的名稱、數據類型、是否為 NULL 性和值都與查詢選擇列表中的相應欄位或表達式的相同。換句話說,SELECT INTO 的作用相當於是把一個表的表結構和表數據拷貝到一個新表中,但源表中的約束和索引是不會拷貝到新表中的。
SELECT INTO 語句常用於創建表的備份或用於對記錄進行存檔。如要創建一個女學生表並填充相應信息,示例如下:
SELECT t.* INTO T_GirlStudents FROM T_Students t WHERE t.Gender = 0;
2.1.1、通過多個源表來創建一個新表 如要創建女生歷次考試數學成績表,並填充相應信息,示例如下:
SELECT t1.Code,t1.Name,t2.Counts,t2.Scores
INTO T_GirlExamResults
FROM T_Students t1
JOIN T_ExamResults t2 ON t1.Id = t2.StudentId
JOIN T_Courses t3 ON t2.CourseId = t3.Id
WHERE t1.Gender = 0 AND t3.Name = '語文'
ORDER BY t1.Code,t2.Counts;
2.1.2、以最小的日誌記錄創建一個新表 這樣比先 CREATE 表再 INSERT 數據的效率要高,如要創建最近一次考試的成績表,示例如下:
ALTER DATABASE MicroEDU SET RECOVERY BULK_LOGGED; -- 修改恢復模式為大容量日誌模式
SELECT t.Id,t.StudentId,t.CourseId,t.Scores
INTO T_LastExamResults
FROM T_ExamResults t
WHERE t.Counts = (SELECT MAX(Counts) FROM T_ExamResults);
ALTER DATABASE MicroEDU SET RECOVERY FULL; -- 修改恢復模式為完整日誌模式
2.1.3、在同一實例上的另一個資料庫中創建表 如要再創建一個資料庫 MiniEDU,然後把學生表拷貝過去,示例如下:
CREATE DATABASE MicroEDU2;
GO
SELECT t.* INTO MicroEDU2.dbo.T_Students FROM MicroEDU.dbo.T_Students t;
2.1.4、使用 IDENTITY 函數創建標識列 IDENTITY 函數可在有 INTO 子句的 SELECT 語句中將標識列插入到新表中,語法如下:
IDENTITY(data-type [,seed, increment]) AS column-name
其中 data-type 是標識列的數據類型,也是函數最終的返回類型,可以是任何整數類型,也可以是 decimal 數據類型。seed 是要分配給表中第一行的整數值,也就是標識種子。如果既沒有指定 seed,也沒有指定 increment,那麼它們都預設為 1。increment 是要加到表中後續行的 seed 值上的整數值,也就是標識增量。column-name 是將要插入到新表中的欄位名稱。示例如下:
SELECT IDENTITY(INT,100,1) Id,t.Name INTO T_PartStudents FROM T_Students t WHERE t.ClassId = 1;
註意:上例中由的 IDENTITY 函數指定的標識列與源表沒有任何關係。
2.2、INSERT INTO SELECT 與 SELECT INTO
通俗來講,INSERT INTO SELECT 和 SELECT INTO 兩個語句的作用都是複製表,因為都是從一個表中查詢出數據然後插入到另一個表中,被查詢的表可以是派生表。二者的區別在於:INSERT INTO SELECT 只複製表數據,不複製表結構,也不創建新表;而 SELECT INTO 不僅複製表數據,還複製表結構,而且會創建新表。
從另一個角度來說,INSERT INTO SELECT 要插入的表必須是已經存在的,因此需要考慮查詢表與插入表的數據相容性,而 SELECT INTO 則會把數據插入到尚不存在的新表中。
還有一個角度就是,INSERT INTO SELECT 要求必須有目標(插入)表,但可以沒有(數據)源表,臨時組裝的數據即可;而 SELECT INTO 恰恰相反,可以(也必須)沒有目標表,但必須要有源表。
Oracle(PL/SQL) 中也有複製表的語句,相較於 SQL Server(T-SQL) 的語法略有不同,其中 INSERT INTO SELECT 二者是相同的,而 SELECT INTO 在 Oracle 中對應的語法是 CREATE TABLE SELECT。
對開發者而言,複製表的功能並不常用,但也可能會遇到急需要用的時候。一般我們都會上網去查一下,這本身很正常,但遺憾的是網上很多帖子的作者可能也沒搞清楚,只是模糊的寫了個 SQL。例如我當初急找 Oracle 複製表時就多次找到了 SELECT INTO,因此浪費了不少時間。但願本文的讀者看到這裡的對比和總結後,不再走我當初的彎路了吧!
3、本文小結
本文主要講述了 T-SQL 語句中的 SELECT 語句及其子句的簡單用法,查詢功能也是關係型資料庫中最重要、最常用的一個功能。
本文參考鏈接:
- 1、SQL Server 2016 SELECT
- 2、SQL Server 2016 FROM
- 3、SQL Server 2016 WHERE
- 4、SQL Server 2016 LIKE
- 5、SQL Server 2016 INTO
去導航目錄篇下載創建本系列博文通用庫表及數據的 SQL 語句
本文鏈接:http://www.cnblogs.com/hanzongze/p/tsql-select.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!個人博客,能力有限,若有不當之處,敬請批評指正,謝謝!