有時,需要存儲過程或函數才能多次使用樣本的結果。在這種情況下,我們經常使用臨時表。但是,值得考慮臨時表的一些優點和缺點。
好處:
臨時表是完整的表。因此,您可以爲它們創建索引和統計信息。這可以顯着加快他們的工作。
缺點:
填寫與數據移動相關的臨時表。雖然這是一個簡單的插入操作,但磁盤上仍然存在大量數據的負載;
存在查詢執行時間增加的風險。臨時表在tempdb數據庫中創建。而且這個基地的負荷很大。
考慮到使用臨時表的風險,使用通用表表達式看起來更具吸引力。
通用表表達式
公用表表達式(CTE)是一個帶有公用表的表達式,可以在查詢中多次使用。CTE不會保存數據,但會創建類似臨時視圖的內容。有人可能會說CTE是主查詢之前的子查詢。但這並不完全正確,因爲子查詢不能多次使用,但是,CTE可以。
在哪些情況下使用通用表表達式更好?
創建遞歸查詢,使用它可以以分層形式獲取數據;
在同一查詢中多次引用數據集;
爲了替換視圖,臨時表,表變量。
CTE的優點包括:遞歸,高速查詢,簡潔查詢。
缺點只能在有限的使用中。CTE只能用於它所屬的查詢。您不能在其他查詢中使用它。在這種情況下,您將不得不使用臨時表或表變量。 通用表表達式簡單且遞歸。 簡單的不包括對自己的引用,並且遞歸分別包括。 遞歸CTE用於返回分層數據。 考慮一個簡單CTE語句的示例:
WITH CTEQuery (Field1, Field2) AS ( SELECT (Field1, Field2) FROM TABLE ) SELECT * FROM CTEQuery
這裏CTEQuery是CTE的名稱;
Field1,Field2 - 請求的字段名稱;
Table - 從中選擇數據以在主查詢中使用的一些表。
在此示例中,可以而不是顯式指定選擇字段,因爲我們從TestTable表中選擇所有字段:
WITH CTEQuery AS ( SELECT * FROM Table ) SELECT * FROM CTEQuery
在CTE的幫助下,如果取出CTE中的部分邏輯,則可以優化主查詢。事實是,CTE允許您一次創建多個表達式(查詢)。因此,您可以使用CTE將複雜查詢拆分爲幾個初步“View”,然後將它們鏈接到一個公共查詢中:
WITH CTEQuery1 (Field1, Field2) AS ( SELECT Field1 AS ID, Field2 FROM Table1 WHERE Field2 >= 1000 ), CTEQuery2 (Field3, Field4) AS ( SELECT Field3 AS ID, Field4 FROM Table2 WHERE Field4 = 'Москва' ) SELECT * FROM CTEQuery1 INNER JOIN CTEQuery2 ON CTEQuery2.ID = CTEQuery1.ID
如上所述,CTE的主要目的是遞歸。遞歸的典型任務是樹遍歷。所以我們可以在“with”的幫助下構建一棵樹。遞歸查詢結構首先出現在SQL Server 2005中。 看一下WITH語句:
WITH RecursiveQuery AS ( {Anchor} UNION ALL {Joined TO RecursiveQuery} ) SELECT * FROM RecursiveQuery
{Anchor} - anchor,一個定義樹的初始元素的查詢(分層列表)。通常在錨中有一個WHERE子句,用於定義表的特定行。 在UNION ALL之後,目標表從JOIN跟隨到CTE表達式。 {加入RecursiveQuery} - 從目標表中選擇。這通常與錨點中使用的表相同。但是在這個查詢中,它連接到CTE表達式,形成遞歸。此連接的條件決定了父子關係。這取決於你是去樹的上層還是下層。 讓我們看一個返回組織單元列表的遞歸查詢。準備此請求的數據:
CREATE TABLE Department ( ID INT, ParentID INT, Name VARCHAR(50) ) INSERT INTO Department ( ID, ParentID, Name ) VALUES (1, 0, 'Finance Director') INSERT INTO Department ( ID, ParentID, Name ) VALUES (2, 1, 'Deputy Finance Director') INSERT INTO Department ( ID, ParentID, Name ) VALUES (3, 1, 'Assistance Finance Director') INSERT INTO Department ( ID, ParentID, Name ) VALUES (4, 3, 'Executive Bodget Office') INSERT INTO Department ( ID, ParentID, Name ) VALUES (5, 3, 'Comptroller') INSERT INTO Department ( ID, ParentID, Name ) VALUES (6, 3, 'Purchasing') INSERT INTO Department ( ID, ParentID, Name ) VALUES (7, 3, 'Debt Management') INSERT INTO Department ( ID, ParentID, Name ) VALUES (8, 3, 'Risk Management') INSERT INTO Department ( ID, ParentID, Name ) VALUES (9, 2, 'Public Relations') INSERT INTO Department ( ID, ParentID, Name ) VALUES (10, 2, 'Finance Personnel') INSERT INTO Department ( ID, ParentID, Name ) VALUES (11, 2, 'Finance Accounting') INSERT INTO Department ( ID, ParentID, Name ) VALUES (12, 2, 'Liasion to Boards and Commissions')
已經清楚的是,組織中的分支結構是分層的。我們的任務是獲得一份隸屬於財務總監助理的部門清單。如果我們在分層樹的上下文中進行討論,那麼我們必須找到一個分支及其葉子。 但首先,讓我們看看整個分部列表:
ID | ParentID | Name |
1 | 0 | Finance Director |
2 | 1 | Deputy Finance Director |
3 | 1 | Assistance Finance Director |
4 | 3 | Executive Bodget Office |
5 | 3 | Comptroller |
6 | 3 | Purchasing |
7 | 3 | Debt Management |
8 | 3 | Risk Management |
9 | 2 | Public Relations |
10 | 2 | Finance Personnel |
11 | 2 | Finance Accounting |
12 | 2 | Liasion to Boards and Commissions |
頭部有財務總監,副手和助理報表給他。他們每個人在其管轄範圍內都有一組單位。ParentID字段指示“主機”標識符。因此,我們有一個現成的主從連接。 讓我們用WITH編寫一個遞歸查詢。
WITH RecursiveQuery (ID, ParentID, Name) AS ( SELECT ID, ParentID, Name FROM Department dep WHERE dep.ID = 3 UNION ALL SELECT dep.ID, dep.ParentID, dep.Name FROM Department dep JOIN RecursiveQuery rec ON dep.ParentID = rec.ID ) SELECT ID, ParentID, Name FROM RecursiveQuery
在此示例中,清楚地指示了要在CTE中選擇的字段的名稱。但是,內部查詢具有相同的字段。因此,您只需刪除此列表以及括號即可。 在CTE內部,我們有兩個類似的查詢。第一個選擇我們正在構建的樹的根元素。第二個是所有後續的從屬元素,因爲它與CTE本身有關。SQL中的“遞歸”實際上不是遞歸,而是迭代。您需要以JOIN作爲循環提交查詢,然後一切都將立即清除。在每次迭代中,我們都知道前一個樣本的值並獲得從屬元素。在下一步中,我們獲得前一個樣本的從屬元素。也就是說,每次迭代都是向下或向上轉換,具體取決於通信條件。 上述查詢的結果是:
ID | ParentID | Name |
3 | 1 | Assistance Finance Director |
4 | 3 | Executive Bodget Office |
5 | 3 | Comptroller |
6 | 3 | Purchasing |
7 | 3 | Debt Management |
8 | 3 | Risk Management |
但是如果不使用CTE,這個查詢會是什麼樣子:
DECLARE @Department TABLE (ID INT, ParentID INT, Name VARCHAR(50), Status INT DEFAULT 0) -- First, we select the anchor in the table variable - the initial element from which we build the tree. INSERT @Department SELECT ID, ParentID, Name, 0 FROM Department dep WHERE dep.ID = 3 DECLARE @rowsAdded INT = @@ROWCOUNT -- We are going through a cycle until new departments are added in the previous step. WHILE @rowsAdded > 0 BEGIN -- Mark entries in a table variable as ready for processing UPDATE @Department SET Status = 1 WHERE Status = 0 -- Select child records for the previous record INSERT @Department SELECT dep.ID, dep.ParentID, dep.Name, 0 FROM Department dep JOIN @Department rec ON dep.ParentID = rec.ID AND rec.Status = 1 SET @rowsAdded = @@ROWCOUNT -- Mark entries found in the current step as processed UPDATE @Department SET Status = 2 WHERE Status = 1 END SELECT * FROM @Department
這樣的循環比CTE表達慢得多。此外,它需要創建一個表變量。並且代碼量增加了一倍。因此,CTE表達式是MS SQL中遞歸樹遍歷的最佳解決方案。