在當今這個多種不同資料庫混用,各種不同語言不同框架融合的年代(一切為了降低成本並高效的提供服務),知識點多如牛毛。雖然大部分SQL腳本可以使用標準SQL來寫,但在實際中,效率就是一切,因而每種不同廠商的SQL新特性有時還是會用到,這部分內容更是讓人抓瞎,常常會由於一些很簡單的問題花很久來搜索準確答案
在當今這個多種不同資料庫混用,各種不同語言不同框架融合的年代(一切為了降低成本並高效的提供服務),知識點多如牛毛。雖然大部分SQL腳本可以使用標準SQL來寫,但在實際中,效率就是一切,因而每種不同廠商的SQL新特性有時還是會用到,這部分內容更是讓人抓瞎,常常會由於一些很簡單的問題花很久來搜索準確答案。趕腳俺弱小的智力已經完全無法記清楚常見的命令了,即使是用的最熟悉的T-SQL(SQL Server)。因此將最常見的T-SQL操作做個簡單的總結,包括一些容易忽視的知識點和常見的開發樣例。實話實說,現在開發中較少直接寫SQL了,但有時需要給測試團隊提供一些便利還是需要的。
本系列包含上中下三篇,內容比較駁雜,望大家耐心閱讀:
那些年我們寫過的T-SQL(上):上篇介紹查詢的基礎,包括基本查詢的邏輯順序、聯接和子查詢
那些年我們寫過的T-SQL(中):中篇介紹表表達式、集合運算符和開窗函數(編輯中)
那些年我們寫過的T-SQL(下):下篇介紹數據修改、事務&併發和可編程對象(編輯中)
預祝大家新年快樂,萬事如意! I believe: 萬丈高樓平地起,大海無邊百川融。
這部分中重要的概念就是要弄清楚SQL語句具體的執行順序,記得在南京做一個短期培訓講師期間,就發現這部分是一個很容易被忽視的基礎,一旦弄清這部分內容,基本的標準SQL的編寫基本上就沒有很大問題了。此外關於SQL的一個非常關鍵的概念是,儘可能的使SQL語句進行的是整體的集合操作,而不是類似游標的迴圈迭代操作,這一點也是SQL優化的一個核心概念。
語句 | 執行順序 |
SELECT empid, YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders WHERE custid = 71 GROUP BY empid, YEAR(orderdate) HAVING COUNT(*) > 1 ORDER BY empid, orderyear [ASC, DESC] |
|
不知道這兒的執行順序和你心中的是否相同,記得瞭解到這部分知識時,自己也花了很久去理解, 不過從形式上可以看到實際的執行順序很像LINQ,有木有?SQL只所以語句順序和實際執行順序不同是因為SQL設計師將該高級語言作為聲明式語言來定義的,"可以按照類似英語的方法提供自己的請求"。之所以說這部分重要,不知道大家遇到過自己給欄位起的別名在where中不能使用的情況沒有,那是因而where執行時,select還未執行,那麼select中給欄位其的別名還不存在好,但在order by字句中就可以正常使用。接下來,補充說明一下以上六個字句中的相關知識。
FROM字句:在From字句中的對象中需要附加上schema架構限定,如dbo.Sales, hr.Employee等。
WHERE字句:該字句中欄位的選擇對於查詢性能影響很大,如果符合索引(包括組合索引,需要正確的順序)條件,那麼查詢就會通過索引而不是全表掃描。例如建立的組合索引為(name, time),那麼如果查詢中使用where time =xx and name = xx會造成索引不起作用,而造成全表掃描,當然由於內置查詢優化器的存在,實際的查詢可能與教科書上說的不同, 一定要實際查看執行計劃,一起以事實說話。在實際項目中,資料庫的設計需要保證基本不犯明顯的錯誤即可,其他的到出現性能問題時通過查詢計劃和查詢統計信息才去優化,不用過度設計,因為數據量沒變化一個量級可能調優的方式就會出現不同。才外,需要記住,在TSQL中使用三值謂詞邏輯,邏輯表達式可以計算為TRUE、FALSE和UNKNOWN,而如果數據欄位為空,需要使用IS [NOT] NULL判斷。
GROUP BY字句:當涉及分組時,其後續的所有操作都是對組的操作而不是對單個行的操作,每組均是一個單個行,這些操作中表達式需要保證返回一個標量。不參與到group by中的欄位僅允許作為一個聚合函數的輸入,如COUNT、SUM等。註意,除了Count(*)外,所有的聚合函數忽略NULL標記,DISTINCT可以包含在聚合函數中,針對不重覆且有值的項。
HAVING字句:可以指定一個謂詞來篩選組而不是單個的行,比如使用集合函數count(*)>1表示篩選組成員大於1的組。
SELECT字句:指定返回到查詢結果表中列的地方,可以包含表達式,推薦給表達式創建一個易懂的別名,比如Year(orderdate) AS OrderYear,尤其是新增一些與列無關的表達式,如current_timestamp()函數,使關係模型變得完善。這人再次提及SELECT字句中別名的使用範圍,只能是SELECT字句執行之後的部分,也就是Order by字句。此外,有一點曾經困擾了我很久,就是如果我在where字句中使用YEAR(orderdate),還在select中使用YEAR(orderdate),那樣不是重覆計算了?其實,SQL SERVER能夠識別查詢中重覆使用的相同表達式,也就是說在一個查詢,出現多次相同的表達式,實際上只會運算一次,簡直贊贊噠。不過在同層中使用別名還是沒有被支持的,例如,
SELECT YEAR(orderdata)AS orderYear, orderYear + 1 AS nextyear是不正確的。常見的,我們在一般的查詢中,比如檢驗數據等,是推薦使用SELECT *,包括加上top 1000的,但在項目代碼中,是嚴禁這樣的操作的。
補充一點關係代數的知識,我們知道在關係模型中,所有操作均基於關係代數,並且操作結果是一個關係集合,但實際上我們返回的結果集還是會出現重覆行的情況,不過可以通過DISTINCT關鍵字刪除重覆行。
ORDER BY字句:按序輸出行,需要理解的是,在SQL中,表中沒有確定的順序,表假定為一個集合,集合是沒有順序(這個觀念如果是半路出家,需要很久才能真正理會的到)。因此,Order by之後的有序結果,其實失去表資格,一般將這種結果稱之為游標,"一個具有確定行順序的非關係型結果",這部分概念在之後的還會有介紹。此外,該字句中可以使用不在SELECT列表中的欄位排序,但如果使用了DISTINCT關鍵字,則必須使用SELECT列表中的列,否則由於單個結果行可能代表多個原行,造成排序的不清晰。
這兒有點需要補充的是,在同樣的ORDER BY條件下,可能會得到不一樣結果的問題,這個其實和數據結構中排序的概念一樣。在某個條件(比如order by日期)下,有多個符合條件的記錄時,這幾個結果集的順序是不一定的(已實際訪問的物理記錄行的順序為準),屬於不穩定排序。常見排序演算法中,快速排序、希爾排序、堆排序、直接選擇排序不是穩定的排序演算法,而基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序是穩定的排序演算法。那麼有沒有穩定的情況呢,那麼就需要排序條件中的每一項都是獨一無二的,比如是主鍵列,唯一列,這種屬性也稱之為排序的決勝屬性(tiebreaker)。
此外註意在SQL的關鍵字和系統函數名使用大寫,涉及多表查詢時需要給表起別名方便理解。以上是最核心的部分,接下來以列表的形式闡述與基本查詢相關的SQL關鍵字。
關鍵字 |
解釋與示例 |
TOP |
T-SQL特有功能,用於限制查詢返回的行數或行的百分比 獲取前5行記錄:SELECT TOP 5 userid FROM HR.Employee ORDER BY userid 獲取前5%的記錄:SELECT TOP 5 PERCENT userid FROM HR.Employee ORDER BY userid 這兒其實隱含了一個問題,就是這兒TOP返回的結果是表結果還是游標,因為之前有提到,使用order by之後返回的游標,這個問問的解釋會放到表表達式部分。 |
OFFSET-FETCH |
這是標準SQL的選取行數的語法,並且支持跳過功能,免得我們需要使用開窗函數或者兩個TOP取交來實現該功能,等價於C#中 XXX.Skip(m).Take(n) 查詢第51到75條記錄:SELECT userid FROM HR.Employee ORDER BY userid OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY Tip:ROW和ROWS關鍵字等價,該關鍵字不支持PERCENT和WITH TIES選項 |
ROW_NUMBER |
是一種開窗函數,理解起來有點困難,其實就是對分組中的數據做更加細粒度的操作,方便實現複雜的查詢,尤其是為報告服務(SSAS報表分析服務)。為了之後能更好的理解該知識點,提前拿出來給大家見見面,本文下篇還會具體介紹 開窗函數的定義:對於一個查詢中的每一行,按行的視窗組進行運算,並計算一個標量結果值,行的視窗使用OVER字句定義 SELECT userid, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY name) AS rownum 這兒的PARTITION分區其實就是分組條件,ROW_NUMBER函數實際用於對分組後小組內成員標上行號,同時OVER窗中的ORDER BY是組內的排序,規則和正常排序一致 |
- 謂詞、常見運算符和系統函數
謂詞(Predicate,這個委托熟悉不?)主要出現在WHERE、HAVING查詢篩選中,包括TRUE、FALSE和UNKNOWN中邏輯結果,這兒一定不能忘記UNKNOWN未知結果這種情況,比如說讓兩個NULL作比較等,接下來用一個簡單的表格展示。
關鍵字 |
解釋與示例 |
BETWEEN, IN, LIKE |
BETWEEN AND表示屬於什麼之間;IN表示在枚舉出來的幾個值中;LIKE可以使用%作為萬能替代符,主要註意的LIKE中預設使用的Unicode的字元類型,並且在使用LIKE關鍵字時一定要謹慎,會造成很大的查詢消耗,如果實在需要大量字元串的查詢,考慮使用全文檢索或選用其他類型資料庫等解決方案 |
NOT, AND, OR |
分別表示非、與、或的邏輯,優先順序依次遞減 |
% |
取餘操作符或是之前介紹的萬能占位符 |
CAST(col1 AS NUMERIC(12, 2)) |
在數值運算時,如果出現兩個整型相除,需要修改其類型避免丟失小數點後位數 |
CASE |
CASE是一個標量表達式,返回一個基於條件邏輯的值,需要註意CASE不是語句不能用於控制邏輯(比如IF ELSE),實際中,CASE的使用場景還是很多的,比如行列轉換等,才外,ORANGE有一個叫做的decade的簡化版操作符。 尤其需要註意的是,CASE具有"簡單"和"搜索"兩種格式,後者非常的靈活 簡單格式: SELECT studentid, CASE score WHEN 59 THEN 'Fail' WHEN 60 THEN 'Alive' FROM dbo.testScore 搜索格式: SELECT studentid, CASE WHEN score < 60 THEN 'Bad' WHEN score > 60 THEN 'Good'ELSE 'UNKNOWN' FROM dbo.testScore 此外,在SQL SERVER2012中還增加幾個簡化操作符, COALESCE , CHOOSE,ISNULL等,比如ISNULL(col1, '')表示Nullable<T> ?? '',若col1不為空就取其值,為空就是''空字元串,不過均不推薦使用。 |
NULL |
NULL標記的理解在SQL中非常重要,很多細微的SQL錯誤都來之於此。其根源仍然是之前提到的3值邏輯,NULL標記表示不知道是什麼值(在現實生活中,就像登記時缺失了),它與除了IS [NOT] NULL邏輯操作以外的邏輯運算結果均是UNKNOWN。 為了更好的理解應用這個概念,來看看接下來的例子 SELECT COUNT(*) FROM Address WHERE region <> 'Beijing', 假設該表中100條記錄,10條的region是Beijing,20條的region為NULL,70為其他城市,那麼這個查詢的結果將是70,而不是我們想要的90,修改查詢如下即可。 SELECT COUNT(*) FROM Address WHERE region <> 'Beijing' OR region IS NULL |
- 同時操作
同時操作(all-at-once operations)表示出現在同一邏輯處理階段的所有表達式在同一時間點進行邏輯運算,即並行運算,這個概念是之前查詢順序概念的補充,之前講的縱向的順序,這兒講的是橫向的並行。是不是覺得很拗口,其實重點就是SQL在同一層中運算的順序不固定,所以之後運算一定不能依賴於之前的運算,並且不支持C#等常見語言中的短路: if(result != null && result == true),前者失敗後者不運算,接下舉兩個錯誤的例子。
SELECT orderID, YEAR(orderdate) AS orderyear, orderyear + 1 AS nextyear FROM [order] |
SELECT num1, num2 FROM dbo.tableB WHERE num1 <> 0 AND num2/num1 > 5 |
那麼不禁要問,SQL中能提供同層次的邏輯運算的解決方案麽?其實是有的,一種技巧性的,一種是通過合理的規劃。
技巧性 | 通過使用CASE表達式來實現,形式上有一些奇怪 SELECT num1, num2 FROM dbo.tableB CASE WHEN num1 = 0 THEN 0 WHEN num1/num2 > 2 THEN 1 ELSE 0 END = 1。 其中1表示true,0 表示false |
合理規劃 | SELECT num1, num2 FROM dbo.tableB WHERE (num1 < 0 AND num2/num1 > 5) OR (num1 > 0 AND num2/num1 > 5) |
- 字元數據類型及其函數
最基礎的字元類型包括 ASCII(American Standard Code for Information Interchange,這單詞還是第一次認真看)和Unicode類型,在T-SQL中就是(CHAR, VARCHAR)和(NCHAR, NVARCHAR)。ASCII每個字元用一個位元組存儲,用''單引號括起來,Unicode用2個位元組存儲,用N''包起來。CHAR字元類型是固定大小的,效率高但空間浪費率高,VARCHAR靈活節省空間,有2個位元組偏移數據,但在欄位值變長時,可能出現行擴展導致分頁等,更新效率較低。字元類型預設最長(max)為8000個字元,若超過使用LOB存儲,放在行外,此外數據在壓縮(Data Compression)是會有一些變化。
介紹一個不常見的知識點,排序規則,知道以下概念即可
獲取資料庫支持的排序規則:SELECT * FROM sys.fn_helpcollations() |
篩選條件區分大小寫; SELECT * FROM user WHERE name COLLATE Latin1_General_CS_AS = N'xionger' |
接下來是最重要的字元函數使用示例列表
關鍵字 |
解釋與示例 |
+, CONCAT |
連接字元串, SELECT firstname + lastname AS fullname FROM user,需要註意的是null與任何字元串連接操作的結果還是NULL |
SUBSTRING |
獲取world子串:SELECT SUBSTRING('hello world', 7, 5),註意index從1開始,不是0 |
LEFT, RIGHT |
SUBSTRING的簡化形式,獲取字元串左邊/右邊指定的字元數,有個一個很經典的應用場景如下 對ID值補0操作:SELECT RIGHT('0000000000' + CAST(1973 AS char(10)), 10), 1973可以為任何的類似ID的變數 |
LEN, DATALENGTH |
前者返回字元長度,後者返回位元組長度,SELECT LEN('abcde'), LEN(N'abcde'), DATALENGTH(N'abcde')的結果為: 5, 5, 10 |
CHARINDEX, PATINDEX |
前者返回子串第一次出現的位置,後者返回匹配的子串第一次出現的位置,SELECT CHARINDEX(' ', 'xiong er 1'), PATINDEX('%[1-9]', 'xiong er 1'),結果6, 10 |
REPLACE, REPLICATE, STUFF |
分別是替換、複製和刪除後新增,SELECT REPLACE('1e11', 'e', '1'), REPLICATE('0', 10), STUFF('1e11', 2, 2, '222'),結果為 1111, 0000000000, 12221 |
LIKE |
包括幾個常見的通配符,% 表示任意大小字元串 ,_ 表示單個字元,以及其他常見正則表達式,如[ABC]、[A-Z]、[^1-9] |
- 時間日期數據類型及其函數
在T-SQL中,常見的時間類型僅僅包含DATETIME,其實記住這個基本上足夠用了。其他的都是一些更高精度和便捷的選擇,包括SMALLDATETIME,DATE, TIME, DATETIME2,DATETIMEOFFSET等,精度達到了ns納秒級,需要時再查閱即可。需要提及的一點時,在SQL中經常使用字元串常量格式的日期實際上最終是通過一個隱式轉化為變為DATETIME類型的,如ordedate = '20160203'等價於orderdate = CAST('20160203'AS DATETIME),這兒的轉化是基於當前會話的語言格式的,在實際中為了相容,推薦使用與語言無關的常量格式: YYYYMMDD hh:mm:ss.nnn或YYYT-MM-DD。
接下來,將一個很容易忽視的知識點,篩選時間範圍,涉及查詢優化,比如我們想選擇今年的全部訂單,很自然的會想到如下SQL語句。
錯誤的方式 | SELECT * FROM [order] WHERE YEAR(orderdate) = 2016 |
正確的方式 | SELECT * FROM [order] WHERE orderdate >= '20160101' AND orderdate < '20170101' |
對於所有的查詢條件,儘可能的不要在其上使用表達式,這樣查詢優化器更可能通過索引的方式查找,此外想說的是,查詢的條件的順序也很重要哦,其需要和你所建立組合索引的順序一致。
時間日期函數看起來比較簡單,但在實際的使用中,由於不同的時間格式,往往會讓人非常的困擾,畢竟那麼多的API使用起來選擇比較多,這兒將最常見的羅列了出來。
關鍵字 |
解釋與示例 |
GETDATE(), CURRENT_TIMESTAMP |
均是獲得當前時間,後者遵循ANSI SQL規範 |
CAST, CONVERT, PARSE |
將輸入值轉換為目標類型,CAST(value AS datatype)最簡單,CONVERT(datatype, value [,style_number])足夠完美,PARSE(value AS datatype [USING culture]) 在SQLSERVER2008之後版本獲取時間、日期方式:CAST(GETDATE() AS DATE), CAST(GETDATE() AS TIME) 老版本相容方式:SELECT CONVERT(CHAR(8),GETDATE(),112), SELECT CONVERT(CHAR(12),GETDATE(),114) 此外如果想的到今天的午夜時間可以用:SELECT CAST(CONVERT(CHAR(8),GETDATE(),112) AS DATETIME) |
DATEADD |
增加一年, SELECT DATEADD(year, 1, CURRENT_TIMESTAMP), month, day,second, quarter季度, week星期等類似 |
DATEDIFF |
返回兩個日期間的差值,SELECT DATEDIFF(day, '20160101', CURRENT_TIMESTAMP) 常見的,獲得本月(年)的開始:SELECT DATEADD(MONTH, DATEDIFF(month, '19000101', CURRENT_TIMESTAMP), '19000101') 獲得本月(年)的結束:SELECT DATEADD(day, -1, DATEADD(MONTH, DATEDIFF(month, '19000101', CURRENT_TIMESTAMP) + 1, '19000101')) 這部分如果和之前的篩選時間日期的知識點結合在一起就能寫出非常靈活的SQL代碼 |
DATEPART, YEAR, MONTH, DAY |
獲取時間日期中的部分整數,DATEPART(month, CURRENT_TIMESTAMP), 後面的3個函數是前面的簡化版 |
- 基本元數據查詢
對於.NET程式員來說,元數據這個概念一點也不陌生,這兒指的是資料庫本身以及其中對象的結構信息,接下來介紹最簡單的幾種元數據的查詢。
元數據查詢類型 |
解釋與示例 |
目錄視圖 |
獲取表信息:SELECT SCHEMA_NAME(schema_id) AS table_schema_name, name AS table_name FROM sys.tables 獲取列信息:SELECT name AS columnName, TYPE_NAME(system_type_id) AS columnType FROM SYS.columns WHERE object_id = object_id(N'dbo.tableA') 此外還有很多的目錄信息都在sys這個schema之下,這兒只選擇最常見的表和列 |
信息架構視圖(推薦使用) |
這其實是前面目錄視圖的標準化版本,功能基本類似,相對更加的簡潔,推薦使用這種方式查詢,相關視圖均在INFORMATION_SCHEMA這一schema下 表信息: SELECT TABLE_CATALOG, TABLE_NAME, TABLE_SCHEMA, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES 列信息:SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tableA' AND TABLE_SCHEMA = 'dbo' |
系統存儲過程和函數 |
還可以通過存儲過程來查詢相關信息,比如EXEC sys.sp_tables; EXEC sys.sp_columns; EXEC sys.sp_help @objname = N'dbo.tableA'等,這種方式不是特別推薦,但sp_help在俺的SQL生涯中確實出現了很多次哦。此外SELECT SERVERPROPERTY('productlevel')可以查詢當前資料庫實例的版本,這不是重點,想說的是本機的查詢結果是RTM,這表示Release to Manufacturing發佈到生產製造,即正式版,有時遇到RTM、SP1等是註意理解。 |
DBCC命令 |
查看資料庫的隔離級別的信息:DBCC USEROPTIONS |
在介紹聯接前先引出一個概念--表運算符,我們知道FROM字句是第一個被邏輯處理的字句,其中包含表信息,那麼對錶進行操作的運算符就是表運算符,其中本節要介紹的JOIN是最重要的,很多時候,工作中可能僅僅使用它就足夠,此外還是APPLY、PIVOT、UNPIVOT等操作符,之後會介紹。其中JOIN操作符對兩個輸入表進行操作,類型包括交叉聯接、內部聯接和外部聯接,它們之間的差別在於其邏輯查詢處理階段,這是本節的最需要理解的概念,是真正理解聯接操作的基礎,通過一個表格來做一個初步的瞭解(這部分初學時,很容易忽視,但非常重要)。
邏輯查詢階段 | 笛卡爾乘積 | 篩選 | 添加外部行 | 示例 | |
聯接類型 | |||||
交叉聯接 | Y | N | N | SELECT u.userid, s.studentid FROM user AS u CROSS JOIN student AS s | |
內部聯接 | Y | Y | N | SELECT u.userid, s.studentid FROM user AS u INNER JOIN student AS s ON u.name = s.name | |
外部聯接 | Y | Y | Y | SELECT u.userid, s.studentid FROM user AS u INNER JOIN student AS s LEFT JOIN student AS s ON u.name = s.name |
之前一直強調的邏輯查詢階段其實相對應與物理查詢階段的,由於資料庫查詢分析器的存在,有時看起來有性能問題的聯接也能運行的很好,所以當遇到查詢性能問題時,查看執行計劃和分析統計數據非常的重要。
交叉聯接:只包含笛卡爾乘積階段,比如一張表A有m行,表B有n行,其結果集有m*n行記錄。該類型使用場景非常少,但其中有2個場景還是需要知道的。
自交叉聯接 | SELECT e1.empid, e2.empid FROM hr.employee AS e1 CROSS JOIN hr.employee AS e2 |
生成數字表(有點像數學的輔助函數) | 首先在DB中創建一張包含1到10的數字表,之後通過這張表來構建1到1000的數字表 SELECT d3.digit * 100 + d2.digit * 10 + d1.digit + 1 AS n FROM dbo.digits AS d1 CROSS JOIN dbo.digits AS d2 CROSS JOIN dbo.digits AS d3 ORDER BY n 這兒介紹這個的原因是,在實際工作中,為處理異構數據或者按指定格式呈現時,可能需要構建輔助表,埋下這樣一個種子就好 |
內部聯接:最常見和基礎的聯接方式,包含笛卡爾乘積和篩選兩個步驟,相對複雜的情形包括複合聯接、不等聯接和多聯接查詢,如下表所示。
情形 |
解釋與示例 |
複合聯接 |
一般在查流水、履歷時會遇到這樣的場景,因為這時並沒有一個唯一的主鍵標識,需要組合的候選鍵來查詢 SELECT dbo.tableA AS t1 JOIN dbo.tableB AS t2 ON t1.col1 = t2.col1 AND t1.col2 = t2.col2 |
不等聯接 |
用到不等聯接的場景不算太多,一種比較有意思婚配的婚配場景,找到一組人中所有婚配組合(不重覆,不並剔除自己和自己配對) SELECT e1.empid, e2.empid FROM hr.employee AS e1 INNER JOIN hr.employee AS e2 ON e1.empid < e2.empid |
多連接查詢 |
SELECT * FROM sales.[order] AS o INNER JOIN sales.orderdetail od ON o.orderid = od.orderid INNER JOIN sales.product p ON od.productid = p.id |
自聯結 |
有一個比較典型的例子,就是在員工表找到自己的直屬領導 SELECT e1.*, e2.name AS manageName FROM hr.employee AS e1 INNER JOIN hr.employee AS e2 ON e1.manageid = e2.empid |
外部聯接:除了包含內聯接的兩個邏輯處理階段,還包含一個"添加外部行"的第三個階段。外聯接包含LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN三種類型,分別表示左側表為保留表、右側表為保留表和兩側表均為保留表。這兒的保留表也就是我們常說的基準表,即其中的每一條記錄實際上都會在最終的結果中顯示出來