高級T-SQL第1級的階梯:使用交叉連接來引入高級T-SQL
高級T-SQL第1級的階梯:使用交叉連接來引入高級T-SQL
格雷戈裏·拉森(Gregory Larsen),2016/02/19(第一次出版:2014 /12/17)
原文來自:http://www.sqlservercentral.com/articles/Stairway+Series/119933/
該系列
本文是進階系列的一部分:通向高級的T-SQL
這個樓梯將包含一系列的文章,這些文章將擴展到您在前面的兩個T-SQL stairways中學習的T-SQL基礎上,以及在基礎之上的T-SQL DML和T-SQL的進階。這應該幫助讀者準備通過微軟認證考試70 - 461:查詢微軟SQL Server 2012
這是一個新的進階系列的第一篇文章,它將探索Transact SQL(TSQL)的更高級特性。這個進階將包含一系列的文章,這些文章將擴展到您在之前的兩個TSQL stairways中學習的TSQL基礎:
l T-SQL DML的階梯
l T-SQL的進階:超越基礎
這個“高級Transact SQL”的進階將涵蓋以下TSQL主題:
l 使用交叉連接操作符
l 使用應用操作符
l 理解公共表表達式(CTE)
l 使用Transact SQL遊標記錄級別的處理
l 用支點將數據轉到側邊
l 使用UNPIVOT將列轉換成行
l 使用排序函數排序數據
l 使用函數管理日期和時間
l 了解條款的變化
這個進階的讀者應該已經很好地理解了如何從SQL Server表查詢、更新、插入和刪除數據。此外,他們應該有一個工作知識,這些方法可以用來控制他們的TSQL代碼的流程,以及能夠測試和操作數據。
這個進階應該幫助讀者準備通過微軟認證考試70 - 461:查詢微軟SQL Server 2012。
在這個新的進階系列的第一期中,我將討論CROSS JOIN操作符。
CROSS JOIN操作符介紹
交叉連接操作符可以將一個數據集的所有記錄合並到另一個數據集中的所有記錄。通過使用兩組記錄之間的交叉連接操作符,您創建了一個稱為笛卡爾乘積的東西。
這裏有一個簡單的例子,使用CROSS JOIN操作符來連接兩個表
註意,當使用交叉連接操作符時,沒有連接子句連接兩個表,就像在兩個表之間執行內部和外部連接操作時使用的連接子句。
需要註意的是,使用交叉連接可以生成一個大的記錄集。為了研究這種行為,我們來看看兩個不同的例子,說明這個結果集的大小將來自於交叉連接操作。對於第一個示例,假設您是交叉連接兩個表,其中表A有10行,表B有3行。一個交叉連接的結果集將是10乘以3或30行。對於第二個示例,假設表A有1000萬行,表B有300萬行。在表a和B之間的交叉連接結果中有多少行?那將是一個巨大的30000億行。這是大量的行,需要大量的時間和大量的資源來創建這個結果集。因此,在大型記錄集上使用交叉連接操作符時需要非常小心。
讓我們仔細研究一下使用CROSS JOIN操作符的一些例子。
使用交叉連接的基本示例
在前面的幾個例子中,我們將會連接兩個示例表。清單1中的代碼將用於創建這兩個示例表。確保在用戶數據數據庫中運行這些腳本,而不是在master中。
清單1:交叉連接的示例表
對於第一個交叉連接示例,我將運行清單2中的代碼。
清單2:簡單的交叉連接示例
當我在一個SQL Server Management Studio窗口中運行清單2中的代碼時,通過我的會話設置輸出結果的文本,我得到了報告1中的輸出:
報告1:運行清單2的結果
如果你回顧報告1的結果,你可以看到有15個不同的記錄。這些前5個記錄包含從產品表的第一行與SalesItem表中5個不同的行連接的列值。同樣適用於產品表的2秒和3行。返回的行數是Product表中的行數乘以SalesItem表中的行數,即15行。
創建Cartesian產品可能有用的一個原因是生成測試數據。假設我想在我的產品和SalesItem表中使用日期生成一些不同的產品。我可以使用一個交叉連接來實現,如清單3所示:
清單3:簡單的交叉連接示例
當我運行清單3中的代碼時,我得到了報告2中的輸出。
報告2:運行清單3的結果
通過查看清單3中的代碼,您可以看到,我生成了一些列,其中包含與產品表中的數據類似的數據。通過使用ROW_NUMBER函數,我可以在每行上生成唯一的ID列。此外,我使用SalesItem表中的ID列創建惟一的ProductName和成本列值。產生的行數等於產品表中的行數乘以SalesItem表中的行數。
到目前為止,本節中的示例只執行了跨兩個表的交叉連接。可以使用CROSS JOIN操作符跨多個表執行交叉連接操作。清單4中的示例在三個表中創建了一個Cartesian產品。
清單4:使用CROSS JOIN操作符創建三個表的Cartesian產品
運行清單4的輸出有兩個不同的CROSS_JOIN操作。由該代碼創建的Cartesian產品將產生一個結果集,其總行數等於sys中的行數。表乘以sys中的行數。對象乘以sysusers中的行數。
當交叉連接執行類似於內部連接時
在前面的部分中,我提到過,當使用交叉連接運算符時,它會產生一個笛卡爾積。這不是真的。當您使用WHERE子句約束連接到跨連接操作SQL Server的表時,不會創建笛卡爾產品。相反,它的功能類似於普通的連接操作。為了演示這種行為,請查看清單5中的代碼。
清單5:兩個等價的SELECT語句。
清單5中的代碼包含兩個SELECT語句。第一個SELECT語句使用CROSS JOIN操作符,然後使用WHERE子句定義如何連接到交叉連接操作中的兩個表。第二個SELECT語句使用一個正常的內部連接操作符,並使用一個ON子句來連接這兩個表。SQL Server的查詢優化器足夠聰明,可以知道清單5中的第一個SELECT語句可以作為內部連接重新編寫。優化器知道,當使用交叉連接操作時,它可以重新編寫查詢,與在交叉連接中涉及的兩個表之間提供連接謂詞的WHERE子句一起使用。因此,SQL Server引擎為清單5中的SELECT語句生成相同的執行計劃。當您不提供一個約束SQL服務器不知道如何連接跨連接操作的兩個表時,它會在與交叉連接操作相關聯的兩個集合之間創建一個Cartesian產品。
使用交叉連接查找未銷售的產品
在前面的小節中找到的示例是為了幫助您理解CROSS JOIN操作符以及如何使用它。使用CROSS JOIN操作符的一個功能是使用它來幫助在一個表中查找與另一個表中沒有匹配記錄的項。例如,假設我想要在我的產品表中每一個產品被售出的每一個日期,報告我的產品表中每個產品名稱的總數量和總銷售額。因為在我的例子中,每一個產品的名字都不是每天都有銷售,我的報告要求是我需要顯示一個0的數量和總的銷售額的0美元,因為這些產品在某一天沒有銷售。這是交叉連接操作符與左外JOIN操作的結合,它將幫助我識別那些在給定的一天中沒有被出售的項目。滿足這些報告需求的代碼如清單6所示:
清單6:查找不使用交叉連接銷售的產品
讓我帶你走過這段代碼。我創建了一個子查詢,它選擇所有不同的SalesDate值。這個子查詢提供了所有的日期,其中有一個銷售。然後我將它與我的產品表連接起來。這允許我在每個銷售日期和每個產品行之間創建一個Cartesian產品。從交叉連接返回的集合將具有在最終結果集中所需要的所有值,除了每個產品的Qty和TotalSalesAmt的總和。為了獲得這些匯總值,我在SalesItem表上執行一個左外連接,並與通過CROSS JOIN操作創建的Cartesian產品連接。我基於ProductID和SalesDate列執行了此連接。通過使用我的Cartesian產品中的左外聯接來返回,如果有一個與ProductID和SalesDate相匹配的SalesDate記錄,那麽Qty和TotalSalesAmt值將與相應的行相關聯。這個查詢的最後一件事是使用GROUP BY子句來總結基於SalesDate和ProductName的Qty和TotalSalesAmount。
性能考慮
產生笛卡爾積的交叉連接運算符有一些性能方面需要考慮。因為SQL引擎需要在一個集合中加入每一行,而在另一個集合中,結果集可以相當大。如果我做一個交叉連接一個表有1,000,000行和另一個表有100,000行那麽我的結果集就會有1,000,000 X 10萬行,或者說100,000,000,000行。這是一個很大的結果集,它將花很多時間來創建它。
交叉連接操作符可以是一個很好的解決方案,可以在所有可能的組合中確定一個結果集,就像所有客戶的每個月的所有銷售,即使在幾個月的時間裏,一些客戶沒有銷售。在使用CROSS JOIN操作符時,如果希望優化性能,應該盡量減少交叉聯接的大小。例如,假設我有一個表,其中包含過去兩個月的銷售數據。如果我想要生成一個報告,顯示一個月沒有銷售的客戶,那麽確定一個月的天數的方法可以極大地改變我的查詢的性能。為了證明這一點,我首先為1000名客戶創造了一個為期兩個月的銷售記錄。我將使用清單7中的代碼來實現這一點。
清單7:TSQL為性能測試創建示例數據
清單7中的代碼為1000個不同的客戶創建了兩個月的數據。這段代碼沒有為每7個客戶增加銷售數據。這段代碼產生了1000個Cust表記錄和52,338個銷售表記錄。
為了演示如何使用交叉連接操作符執行不同的操作,這取決於跨連接輸入集中使用的集合的大小,讓我來運行清單8和清單9中的代碼。對於每個測試,我將記錄返回結果所需的時間。
清單8:與所有銷售記錄交叉連接
清單9:與不同的銷售日期列表交叉連接
在清單8中,CROSS JOIN操作符加入了1000個Cust記錄,其中有52,338個銷售記錄,生成一個創紀錄的52338000行的記錄集,然後用來確定一個月銷售為零的客戶。在清單9中,我將選擇標準從Sales表中更改為只返回一組不同的SalesDate值。這個獨特的集合只產生了61個不同的銷售日期值,因此清單9中的CROSS JOIN操作的結果只產生了61,000條記錄。通過減少交叉連接操作的結果集,清單9中的查詢運行不到1秒,而清單8中的代碼在我的機器上運行了19秒。這種性能差異的主要原因是記錄SQL Server需要處理每個查詢執行的不同操作的數量。如果您查看兩個清單的執行計劃,您將看到計劃略有不同。但是,如果您看一下嵌套循環(Inner Join)操作所生成的記錄的數量,在圖形化計劃的右側,您將看到清單8估計有52338000條記錄,而清單9中的操作僅估計有61,000條記錄。這個巨大的記錄集,清單8的查詢計劃從交叉連接嵌套循環操作中生成,然後再傳遞到幾個額外的操作。因為清單8中的所有操作都必須處理5200萬的記錄。清單8比清單9慢得多。
正如您所看到的,交叉連接操作中使用的記錄數可以極大地影響查詢運行的時間長度。因此,如果您可以編寫您的查詢來最小化交叉連接操作中涉及的記錄的數量,那麽您的查詢將執行得更有效率。
結論
交叉連接運算符在兩個記錄集之間產生一個笛卡爾積。這個操作符有助於識別一個表中沒有與另一個表中匹配的記錄的項。應註意盡量減少與交叉連接操作符使用的記錄集的大小。通過確保交叉連接的結果集盡可能小,您將確保代碼盡可能快地運行。
問題和答案
在本節中,您可以通過回答下列問題,來回顧您使用CROSS JOIN操作符理解的程度。
問題1:
交叉連接操作符根據on子句中指定的列匹配兩個記錄集創建一個結果集。
l 真
l 假
問題2:
可以使用哪個公式來標識將從兩個表A和B之間不受約束的交叉連接返回的行數,當表A和B包含重復的行時?
l 表A中的行數乘以表B中的行數
l 表A中的行數乘以表B中唯一行的數目
l 表A中唯一行數乘以表B中的行數
l 表A中唯一行數乘以表B中唯一行數
問題3:
哪一種方法提供了減少交叉連接操作產生的笛卡爾乘積的最大機會?
l 確保連接的兩個集合有盡可能多的行
l 確保連接的兩個集合有盡可能少的行
l 確保在交叉聯接操作的左邊設置的行數越少越好
l 確保在交叉聯接操作的右側設置的行數越少越好
答案:
問題1:
正確的答案是b . CROSS JOIN操作符不使用ON子句來執行交叉連接操作。它將一個表中的每一行連接到另一個表中的每一行。當它連接兩個集合時,交叉聯接創建一個笛卡爾乘積。
問題2:
正確的答案是A . b、c和d是不正確的,因為如果在表A或b中有重復的行,那麽在創建CROSS join操作的Cartesian產品時,將會連接到每個重復的行。
問題3:
正確答案是b。通過減少交叉連接操作中所涉及的兩個集合的大小,可以最小化CROSS JOI操作創建的最終集的大小。c和d還有助於減少交叉連接操作創建的最終集的大小,但不像確保交叉連接操作中涉及的兩個集都可以有最少的行一樣理想。
這篇文章是通往高級T-SQL進階的一部分
註冊到我們的RSS頻道,一旦我們在進階上發布一個新的級別,就會得到通知!
高級T-SQL第1級的階梯:使用交叉連接來引入高級T-SQL