1. 程式人生 > >四巨頭第九周作業翻譯

四巨頭第九周作業翻譯

product group 用戶數據 考試 cts lar 等於 into 8.0

這是將要探索Transact SQL (TSQL)更高級特性的新階梯系列的第一篇文章。這個階梯將包含一系列的文章,這些文章將擴展到你在前兩個TSQL stairways中學習的TSQL的基礎上:

l t - sql DML的階梯

l 通往T-SQL的階梯:超越基礎

這個“高級Transact SQL”階梯將涵蓋以下TSQL主題:

l 使用交叉連接操作符

l 使用應用操作符

l 理解公共表表達式(CTE)

l 使用Transact-SQL遊標記錄級別處理

l 使用PIVOT將數據轉向其一側

l 使用UNPIVOT將列變為行

l 使用排序函數排序數據

l 使用函數管理日期和時間

l 了解以上條款的變化

這個階梯的讀者應該已經很好地理解了如何從SQL Server表中查詢、更新、插入和刪除數據。此外,他們還應該掌握可以用來控制TSQL代碼流的方法的工作知識,並且能夠測試和操作數據。

這個階梯應該幫助讀者準備通過微軟認證考試70-461:查詢微軟SQL Server 2012。

對於這個新的階梯系列的第一部分,我將要討論交叉連接操作符。

交叉聯接操作符的介紹

交叉連接操作符可以將一個數據集中的所有記錄合並到另一個數據集中的所有記錄中。這裏有一個使用交叉連接操作符來連接兩個表a和B的簡單示例:

SELECT * FROM A CROSS JOIN B

註意,當使用交叉連接操作符時,沒有連接子句來連接兩個表,就像在兩個表之間執行內部和外部連接操作時使用的連接子句。

你需要註意的是,使用交叉連接可以生成一個大型記錄集。為了探究這種行為,讓我們看看兩個不同的示例,看看結果集的大小如何來自於交叉連接操作。對於第一個示例,假設你要交叉連接兩個表,其中表A有10行,表B有3行。交叉連接的結果集將是10乘以3或30行。對於第二個示例,假設表A有1,000萬行,表B有300萬行。在表a和B之間的交叉連接結果中有多少行?那將是一個巨大的30萬億的行。這是很多行,需要很多時間和大量的資源來創建這個結果集,所以在大型記錄集上使用交叉連接操作符時需要非常小心。

讓我們通過幾個例子來進一步了解使用交叉連接操作符。

使用交叉連接的基本示例

在前面的幾個例子中,我們將連接兩個示例表。清單1中的代碼將用於創建這兩個示例表。確保在用戶數據數據庫中運行這些腳本,而不是在master中運行。

CREATE TABLE Product (ID int,

ProductName varchar(100),

Cost money);

CREATE TABLE SalesItem (ID int,

SalesDate datetime,

ProductID int,

Qty int,

TotalSalesAmt money);

INSERT INTO Product

VALUES (1,‘Widget‘,21.99),

(2,‘Thingamajig‘,5.38),

(3,‘Watchamacallit‘,1.96);

INSERT INTO SalesItem

VALUES (1,‘2014-10-1‘,1,1,21.99),

(2,‘2014-10-2‘,3,1,1.96),

(3,‘2014-10-3‘,3,10,19.60),

(4,‘2014-10-3‘,1,2,43.98),

(5,‘2014-10-3‘,1,2,43.98);

清單1:交叉連接的示例表。

對於第一個交叉連接示例,我將運行清單2中的代碼。

SELECT * FROM 
Product CROSS JOIN SalesItem;

清單2:簡單的交叉連接示例。

當我在SQL Server Management Studio窗口中運行清單2中的代碼時,使用會話設置來輸出結果的文本,我得到了報告1中的輸出:

ID  ProductName           Cost     ID   SalesDate               ProductID Qty  TotalSalesAmt
--- --------------------- -------- ---- ----------------------- --------- ---- ---------------
1    Widget               21.99    1    2014-10-01 00:00:00.000 1         1    21.99
1    Widget               21.99    2    2014-10-02 00:00:00.000 3         1    1.96
1    Widget               21.99    3    2014-10-03 00:00:00.000 3         10   19.60
1    Widget               21.99    4    2014-10-03 00:00:00.000 1         2    43.98
1    Widget               21.99    5    2014-10-03 00:00:00.000 1         2    43.98
2    Thingamajig          5.38     1    2014-10-01 00:00:00.000 1         1    21.99
2    Thingamajig          5.38     2    2014-10-02 00:00:00.000 3         1    1.96
2    Thingamajig          5.38     3    2014-10-03 00:00:00.000 3         10   19.60
2    Thingamajig          5.38     4    2014-10-03 00:00:00.000 1         2    43.98
2    Thingamajig          5.38     5    2014-10-03 00:00:00.000 1         2    43.98
3    Watchamacallit       1.96     1    2014-10-01 00:00:00.000 1         1    21.99
3    Watchamacallit       1.96     2    2014-10-02 00:00:00.000 3         1    1.96
3    Watchamacallit       1.96     3    2014-10-03 00:00:00.000 3         10   19.60
3    Watchamacallit       1.96     4    2014-10-03 00:00:00.000 1         2    43.98
3    Watchamacallit       1.96     5    2014-10-03 00:00:00.000 1         2    43.98

報告1:運行清單2的結果。

如果您查看報告1中的結果,您可以看到有15個不同的記錄。這些前5個記錄包含產品表的第一行與SalesItem表中的5個不同行連接的列值。產品表的2秒和3行也是如此。返回的行數是Product表中的行數乘以SalesItem表中的行數,即15行。

創建Cartesian產品可能有用的一個原因是生成測試數據。假設我想在我的產品和SalesItem表中使用日期生成一些不同的產品。我可以使用交叉連接來實現,如清單3所示。

SELECT ROW_NUMBER() OVER(ORDER BY ProductName DESC) AS ID,
       Product.ProductName 
                  + CAST(SalesItem.ID as varchar(2)) AS ProductName, 
       (Product.Cost / SalesItem.ID) * 100 AS Cost
FROM Product CROSS JOIN SalesItem;

清單3:簡單的交叉連接示例

當我運行清單3中的代碼時,我得到了報告2中的輸出。

ID    ProductName                                                 Cost
----- ----------------------------------------------------------- ---------------------
1     Widget1                                                     2199.00
2     Widget2                                                     1099.50
3     Widget3                                                     733.00
4     Widget4                                                     549.75
5     Widget5                                                     439.80
6     Watchamacallit1                                             196.00
7     Watchamacallit2                                             98.00
8     Watchamacallit3                                             65.33
9     Watchamacallit4                                             49.00
10    Watchamacallit5                                             39.20
11    Thingamajig1                                                538.00
12    Thingamajig2                                                269.00
13    Thingamajig3                                                179.33
14    Thingamajig4                                                134.50
15    Thingamajig5                                                107.60

報告2:運行清單3的結果。

通過查看清單3中的代碼,您可以看到,我生成了許多行,其中包含與產品表中的數據類似的數據。通過使用ROW_NUMBER函數,我可以在每一行上生成唯一的ID列。另外,我使用SalesItem表中的ID列來創建惟一的ProductName和成本列值。產生的行數等於產品表中的行數乘以SalesItem表中的行數。

到目前為止,本節中的示例只執行了跨兩個表的交叉連接。可以使用交叉連接操作符跨多個表執行交叉連接操作。清單4中的示例在三個表之間創建了一個Cartesian產品。

SELECT * FROM sys.tables 
CROSS JOIN sys.objects
CROSS JOIN sys.sysusers;

清單4:使用交叉連接操作符創建三個表的Cartesian產品。

運行清單4的輸出有兩個不同的CROSS_JOIN操作。由該代碼創建的Cartesian產品將產生一個結果集,該結果集將具有與sys中的行數相等的總行數。表乘以sys中的行數。對象乘以sysusers中的行數。

當交叉連接執行時,就像內部連接一樣。

在上一節中,我提到過,當您使用一個交叉連接操作符時,它會產生一個笛卡爾積。這不是真的。當您使用WHERE子句約束連接到交叉連接操作中的表時,SQL Server不會創建Cartesian產品。相反,它的功能類似於正常的連接操作。為了演示此行為,請查看清單5中的代碼。

SELECT * FROM Product P CROSS JOIN SalesItem S
WHERE P.ID = S.ProductID;
 
SELECT * FROM Product P INNER JOIN SalesItem S
ON P.ID = S.ProductID;

清單5:兩個相同的SELECT語句

清單5中的代碼包含兩個SELECT語句。第一個SELECT語句使用交叉連接操作符,然後使用WHERE子句定義如何連接到交叉連接操作中的兩個表。第二個SELECT語句使用一個正常的內部連接操作符和一個ON子句來連接兩個表。SQL Server的查詢優化器足夠聰明,可以知道清單5中的第一個SELECT語句可以重寫為內部連接。優化器知道,當一個交叉連接操作與一個WHERE子句一起使用時,它可以重新編寫查詢。WHERE子句提供了包含在交叉連接中的兩個表之間的連接謂詞。因此,SQL Server引擎為清單5中的SELECT語句生成相同的執行計劃。當您不提供一個約束SQL Server不知道如何連接兩個包含交叉連接操作的表時,它將在與交叉連接操作關聯的兩個集合之間創建一個Cartesian產品。

使用交叉連接查找未售出的產品。

前面幾節中的示例將幫助您理解交叉連接操作符以及如何使用它。使用交叉連接操作符的一個功能是使用它來幫助在另一個表中查找沒有匹配記錄的表。例如,假設我想要在我的產品表中每一個產品的每一個產品被出售的日期,在我的產品表中報告每一個產品名稱的總數量和總銷售額。因為在我的例子中,每一個產品的名字都不是每天都有銷售的,我的報告要求是指我需要顯示一個0的數量,以及在某一天沒有銷售的產品的總銷售額為0。這是交叉連接操作符與左邊的外部連接操作一起,將幫助我識別那些沒有被賣出一天的東西。滿足這些報告需求的代碼如清單6所示:

SELECT S1.SalesDate, ProductName
     , ISNULL(Sum(S2.Qty),0) AS TotalQty
                , ISNULL(SUM(S2.TotalSalesAmt),0) AS TotalSales
FROM Product P
CROSS JOIN  
(
SELECT DISTINCT SalesDate FROM SalesItem
  ) S1
LEFT OUTER JOIN 
SalesItem S2
ON P.ID = S2.ProductID
AND S1.SalesDate = S2.SalesDate
GROUP BY S1.SalesDate, P.ProductName
ORDER BY S1.SalesDate;

清單6:查找不使用交叉連接銷售的產品。

讓我帶你看看這段代碼。我創建一個子查詢來選擇所有不同的SalesDate值。這個子查詢給出了銷售的所有日期。然後我將它與我的產品表連接起來。這允許我在每個SalesDate和每個產品行之間創建一個Cartesian產品。從交叉連接返回的集合將具有在最終結果集中所需要的所有值,除了每個產品的Qty和TotalSalesAmt之和。為了得到這些匯總值,我在SalesItem表上執行了一個左外連接,並將其與通過交叉連接操作創建的Cartesian產品連接起來。我基於ProductID和SalesDate列執行了這個連接。通過使用左外連接,我將返回笛卡爾產品中的每一行,如果有一個與ProductID和SalesDate相匹配的SalesDate記錄,那麽Qty和TotalSalesAmt值將與適當的行相關聯。這個查詢最後做的事情是使用GROUP BY子句來總結基於SalesDate和ProductName的Qty和TotalSalesAmount。

性能考慮

產生笛卡爾積的交叉連接運算符有一些性能方面需要考慮。因為SQL引擎需要將每一行與另一個集合中的每一行連接起來,因此結果集可以相當大。如果我做一個交叉連接,一個有1,000,000行的表,另一個表有100,000行,那麽我的結果集將有1,000,000 X 100,000行,或100,000,000,000行。這是一個很大的結果集,它將花費大量的時間來創建它。

交叉連接操作符可以是一個很好的解決方案,可以在所有可能的組合中確定一個結果集,比如每個月的所有客戶的所有銷售,即使在幾個月的時間內一些客戶沒有銷售。在使用交叉連接操作符時,如果希望優化性能,應該盡量減少交叉連接的大小。例如,假設我有一個表,其中包含了過去兩個月的銷售數據。如果我想要生成一個報告,顯示一個月沒有任何銷售的客戶,那麽確定一個月的天數的方法可以極大地改變我的查詢的性能。為了證明這一點,我首先為1000名顧客創造了一個為期兩個月的銷售記錄。我將使用清單7中的代碼來實現這一點。

CREATE TABLE Cust (Id int, CustName varchar(20));
CREATE TABLE Sales (Id int identity
                    ,CustID int
                                   ,SaleDate date
                                   ,SalesAmt money);
SET NOCOUNT ON;
DECLARE @I int = 0;
DECLARE @Date date;
WHILE @I < 1000
BEGIN  
       SET @I = @I + 1;
       SET @Date = DATEADD(mm, -2, ‘2014-11-01‘);
       INSERT INTO Cust 
       VALUES (@I, 
               ‘Customer #‘ + right(cast(@I+100000 as varchar(6)),5));
       WHILE @Date < ‘2014-11-01‘ 
       BEGIN
              IF @I%7 > 0
                     INSERT INTO Sales (CustID, SaleDate, SalesAmt) 
                     VALUES (@I, @Date, 10.00);
              SET @Date = DATEADD(DD, 1, @Date);
       END
END

清單7:TSQL為性能測試創建樣本數據

清單7中的代碼為1000個不同的客戶創建了兩個月的數據。這段代碼沒有為每7個客戶增加銷售數據。此代碼生成1000個Cust表記錄和52338個Sales表記錄。

為了演示如何使用交叉連接操作符執行不同的操作,這取決於在交叉連接輸入集中使用的集合的大小,讓我來運行清單8和清單9中的代碼。對於每個測試,我將記錄返回結果所需的時間。

SELECT CONVERT(CHAR(6),S1.SaleDate,112) AS SalesMonth, C.CustName, 
       ISNULL(SUM(S2.SalesAmt),0) AS TotalSales
FROM Cust C
CROSS JOIN  
(
SELECT SaleDate FROM Sales 
) AS S1
LEFT OUTER JOIN 
Sales  S2
ON C.ID = S2.CustID
AND S1.SaleDate = S2.SaleDate
GROUP BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName
HAVING ISNULL(SUM(S2.SalesAmt),0) = 0
ORDER BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName

清單8:對所有銷售記錄的交叉聯接

SELECT CONVERT(CHAR(6),S1.SaleDate,112) AS SalesMonth, C.CustName, 
       ISNULL(SUM(S2.SalesAmt),0) AS TotalSales
FROM Cust C
CROSS JOIN  
(
SELECT DISTINCT SaleDate FROM Sales 
) AS S1
LEFT OUTER JOIN 
Sales  S2
ON C.ID = S2.CustID
AND S1.SaleDate = S2.SaleDate
GROUP BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName
HAVING ISNULL(SUM(S2.SalesAmt),0) = 0
ORDER BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName

清單9:交叉連接到不同的銷售日期列表。

在清單8中,CROSS JOIN操作符連接1000個Cust記錄,其中有52,338個Sales記錄,生成一個記錄集,記錄為52,338,000個行,然後用來確定一個月銷售為零的客戶。在清單9中,我從Sales表中更改了選擇標準,只返回一組不同的SalesDate值。這個不同的集合只產生了61個不同的SalesDate值,因此清單9中的CROSS JOIN操作的結果只產生了61,000條記錄。通過減少交叉連接操作的結果集,清單9中的查詢運行不到1秒,而清單8中的代碼在我的機器上運行19秒。這種性能差異的主要原因是記錄SQL Server需要為每個查詢執行的不同操作的數量。如果您查看兩個清單的執行計劃,您將看到計劃略有不同。但是,如果查看由嵌套循環(內連接)操作生成的記錄數量,在圖形化計劃的右側,您將看到清單8估計有52,338000條記錄,而清單9中的相同操作只估計了61,000條記錄。清單8的查詢計劃從交叉連接嵌套循環操作生成的這個大記錄集,然後傳遞給多個額外的操作。因為清單8中的所有操作都必須處理5200萬條記錄。清單8比清單9慢得多。

正如您所看到的,交叉連接操作中使用的記錄數可以極大地影響查詢運行的時間長度。因此,如果您可以編寫查詢來最小化交叉連接操作中涉及的記錄的數量,那麽您的查詢將執行得更有效率。

結論

交叉連接運算符在兩個記錄集之間產生一個笛卡爾積。這個操作符有助於在另一個表中不具有匹配記錄的表中識別項。應註意盡量減小與交叉連接操作符使用的記錄集的大小。通過確保交叉連接的結果集盡可能小,您將確保代碼盡可能快地運行。

問題和答案

在本節中,您可以通過回答下列問題來回顧您如何使用交叉連接操作符理解您的理解。

問題1:

交叉連接操作符根據on子句中指定的列匹配兩個記錄集,從而創建一個結果集。(真或假)?

問題2:

當表A和B包含重復行時,可以使用哪個公式來識別從兩個表A和B之間不受約束的交叉連接返回的行數?

表A中的行數乘以表B中的行數。

表A中的行數乘以表B中唯一行數。

表A中唯一行數乘以表B中的行數。

表A中唯一行數乘以表B中唯一行數。

問題3:

哪種方法提供了減少交叉連接操作產生的笛卡爾積的最大機會?

確保連接的兩個集合有盡可能多的行。

確保連接的兩個集合盡可能少。

確保在交叉聯接操作的左側設置的行數盡可能少。

確保在交叉連接操作的右側設置的行數越少越好。

答案:

問題1:

正確的答案是b。交叉連接操作符不使用ON子句來執行交叉連接操作。它將一個表中的每一行連接到另一個表中的每一行。當它連接兩個集合時,交叉連接創建一個笛卡爾積。

問題2:

正確的答案是A . b、c和d是不正確的,因為如果表A或b中有重復的行,那麽在創建交叉連接操作的Cartesian產品時,將會連接到每個重復的行。

問題3:

正確的答案是b。通過減少交叉連接操作中所涉及的兩個集合的大小,可以最小化由CROSS JOI操作創建的最終集的大小。c和d還有助於減小交叉連接操作所創建的最終集的大小,但不像確保交叉連接操作中涉及的兩個集合具有最少的行數一樣最優。

四巨頭第九周作業翻譯