1. 程式人生 > >SQL Server溫故系列(2):SQL 資料操作 CRUD 之簡單查詢

SQL Server溫故系列(2):SQL 資料操作 CRUD 之簡單查詢

  • 1、查詢語句 SELECT
    • 1.1、查詢語句的 SELECT 子句
    • 1.2、查詢語句的 FROM 子句
      • 1.2.1、內連線查詢 INNER JOIN
      • 1.2.2、外連線查詢 OUTER JOIN
      • 1.2.3、交叉連線查詢 CROSS JOIN
    • 1.3、查詢語句的 WHERE 子句
    • 1.4、查詢語句的 ORDER BY 子句
  • 2、插入子句 INTO
    • 2.1、查詢語句的 INTO 子句
    • 2.2、INSERT INTO SELECT 與 SELECT INTO
  • 3、本文小結

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

本文連結:http://www.cnblogs.com/hanzongze/p/tsql-select.html
版權宣告:本文為部落格園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文連結!個人部落格,能力有限,若有不當之處,敬請批評指正,謝謝!