LISTING 43.32 Implementing a Purge/Archive Scenario, Using the OUTPUT Clause declare @purge_date datetime, @rowcount int -- find the oldest month in the sales_big table select @purge_dat
Trang 1only in a trigger The insertedanddeletedtables provide access to the new/old images of
the modified rows; this is similar to how they provide the information in triggers In an
INSERTstatement, you are allowed to access only the insertedtable In a DELETE
state-ment, you are allowed to access only the deletedtable In an UPDATEstatement, you are
allowed to access both the insertedanddeletedtables
Following is the general syntax of the OUTPUTclause:
UPDATE [ TOP ( expression ) [ PERCENT ] ] tablename
SET { column_name = { expression | DEFAULT | NULL }
| @variable = expression
| @variable = column = expression [ , n ]
} [ , n ]
OUTPUT
{ DELETED | INSERTED | from_table_name}.{* | column_name} | scalar_expression
[ INTO { @table_variable | output_table } [ ( column_list ) ] ] }
[ FROM { table_name } [ , n ] ]
[ WHERE search_conditions ]
DELETE [ TOP ( expression ) [ PERCENT ] ] tablename
OUTPUT { DELETED | from_table_name}.{* | column_name} | scalar_expression
[ INTO { @table_variable | output_table } [ ( column_list ) ] ] }
[ FROM ] table_name
[ FROM table_name [ , n ] ]
[ WHERE search_conditions ]
INSERT [ TOP ( expression ) [ PERCENT ] ] [ INTO ] tablename
{
[ ( column_list ) ]
[ OUTPUT { INSERTED | from_table_name}.{* | column_name} | scalar_expression
[ INTO { @table_variable | output_table } [ ( column_list ) ] ] ] }
{ VALUES ( { DEFAULT | NULL | expression } [ , n ] )
| SELECT_statement
}
}
The output table (output_table ) may be a table variable, permanent table, or temporary
table Ifcolumn_listis not specified, the output table must have the same number of
columns as theOUTPUTresult set If column_listis specified, any omitted columns must
either allowNULLvalues or have default values assigned to them Any identity or
computed columns in the output table must be skipped In addition, output_tablecannot
have any enabled triggers defined on it, participate on either side of a foreign key
constraint, or have any check constraints or enabled rules
Trang 2One use of the OUTPUTclause is to verify the rows being deleted, updated, or inserted:
begin tran
delete from sales_big output deleted.*
where sales_id in (select top 10 sales_id from sales_big order by ord_date)
rollback
go
sales_id stor_id ord_num ord_date qty payterms title_id
- - - -
-168745 7067 P2121 2005-06-15 00:00:00.000 40 Net 30 TC3218
168746 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC4203
168747 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC7777
20 7067 P2121 2005-06-15 00:00:00.000 40 Net 30 TC3218
21 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC4203
22 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC7777
337470 7067 P2121 2005-06-15 00:00:00.000 40 Net 30 TC3218
337471 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC4203
337472 7067 P2121 2005-06-15 00:00:00.000 20 Net 30 TC7777
506195 7067 P2121 2005-06-15 00:00:00.000 40 Net 30 TC3218
Another possible use of theOUTPUTclause is as a purge/archive solution Suppose you want to
periodically purge historic data from thesales_bigtable but also want to copy the purged
data into an archive table calledsales_big_archive Rather than writing a process that has
to select the rows to be archived before deleting them, or putting a delete trigger on the
table, you could use theOUTPUTclause to insert the deleted rows into the archive table
On approach would be to implement a loop to delete historic data (for example, delete
rows for the oldest month in the sales_bigtable) in chunks, using the TOPclause to
specify the chunk size The OUTPUTclause can be specified to copy the deleted rows into
thesales_big_archivetable, as shown in Listing 43.32
LISTING 43.32 Implementing a Purge/Archive Scenario, Using the OUTPUT Clause
declare @purge_date datetime,
@rowcount int
find the oldest month in the sales_big table
select @purge_date = dateadd(day, - (datepart(day, min(ord_date))) + 1,
dateadd(month, 1, min(ord_date))),
@rowcount = 1000
from sales_big
while @rowcount = 1000
begin
delete top (1000) sales_big
output deleted.* into sales_big_archive
where ord_date < @purge_date
set @rowcount = @@rowcount
end
Trang 3In addition to referencing columns in the table being modified by using theINSERTEDor
DELETEDqualifier, you can also retrieve information from another table included in the
FROMclause of aDELETEor anUPDATEstatement used to specify the rows to update or delete:
begin tran
delete top (5) sales
output t.title_id
from sales s
join titles t on t.title_id = s.title_id
where t.pub_id = ‘9906’
rollback
go
title_id
-FI9620
CH2080
BI7178
CH8924
FI2680
When used with anUPDATEcommand,OUTPUTproduces both adeletedand aninserted
table Thedeletedtable contains the values before theUPDATEcommand, and the
insertedtable has the values after theUPDATEcommand TheOUTPUTclause is also useful
for retrieving the value of identity or computed columns after anINSERTor anUPDATE
operation Listing 43.33 shows an example ofOUTPUTbeing used to capture the computed
column as the result of anUPDATE.
LISTING 43.33 UsingOUTPUT to Capture a Computed Column
create table UpdateOutputTest
(col1 tinyint,
col2 tinyint,
computed_col3 as convert(float, col2/convert(float, col1)))
go
insert UpdateOutputTest (col1, col2)
output inserted.computed_col3
values (10, 20)
insert UpdateOutputTest (col1, col2)
output inserted.computed_col3
values (10, 25)
go
computed_col3
Trang 4
-2
computed_col3
-2.5
declare @output_table TABLE (del_col1 int, ins_col1 int,
del_col2 int, ins_col2 int, del_computed_col3 float, ins_computed_col3 float, mod_date datetime)
update UpdateOutputTest
set col2 = col2/5.0
output deleted.col1, inserted.col1,
deleted.col2, inserted.col2,
deleted.computed_col3, inserted.computed_col3,
getdate()
into @output_table
output deleted.computed_col3,
inserted.computed_col3,
getdate() as mod_date
select del_col1, ins_col1, del_col2, ins_col2,
del_computed_col3 as del_col3, ins_computed_col3 as ins_col3,
mod_date from @output_table
go
computed_col3 computed_col3 mod_date
- -
-2 0.4 -2010-0 -2 -28 19:48:34. -240
2.5 0.5 2010-02-28 19:48:34.240
del_col1 ins_col1 del_col2 ins_col2 del_col3 ins_col3 mod_date
- - - - - -
-10 -10 20 4 2 0.4 2010-02-28 19:48:34.240
10 10 25 5 2.5 0.5 2010-02-28 19:48:34.240
TheUPDATEstatement in Listing 43.33 also demonstrates the capability to use OUTPUTto
both insert values into a table and return values to the caller
Note that the OUTPUTclause is not supported in DML statements that reference local
parti-tioned views, distributed partiparti-tioned views, remote tables, or INSERTstatements that
contain an execute_statement Columns returned from OUTPUTreflect the data as it is
after the INSERT,UPDATE, or DELETEstatement has completed but before any triggers on
the target table are executed
Trang 5Common Table Expressions
A common table expression (CTE) is an ANSI SQL-99 expression that produces a table that
is referred to by name within the context of a single query The general syntax for a CTE is
as follows:
WITH expression_name [ ( column_name [ , n ] ) ]
AS ( CTE_query_definition )
TheWITHclause, in effect, defines a table and its columns Note that the syntax of the
WITHclause is similar to that of a view You can think of a CTE as a temporary view that
lasts only for the life of the query that defines the CTE Listing 43.34 shows an example of
a simple CTE This CTE is used to return the average and maximum sales quantities for
each store The CTE is then joined to the salestable to return the average and maximum
sales quantity for the store, along with sales records for a specific title_id
LISTING 43.34 An Example of a Simple CTE
with sales_avg (stor_id, avg_qty, max_qty)
as (select stor_id, avg(qty), max(qty)
from sales
group by stor_id)
select top 5 s.stor_id, s.ord_num,
convert(varchar(10), ord_date, 101) as ord_date,
qty, title_id, avg_qty, max_qty
from sales s
join sales_avg a on s.stor_id = a.stor_id
where s.title_id = ‘DR8514’
go
stor_id ord_num ord_date qty title_id avg_qty max_qty
- - - -
-A004 ONGGGGGGGGGGGGGGG 09/13/2006 1224 DR8514 1008 1716
A068 ONEEEEEEEEEEE 09/02/2007 1572 DR8514 961 1572
A071 ONWWWWWWWWWWWWWWWWWW 08/20/2006 1728 DR8514 948 1728
A161 ONDDDDDDDDDDDD 05/25/2006 624 DR8514 829 1668
A203 ONGGGGGGGGGGGGGGGGGG 11/16/2006 1572 DR8514 1056 1692
NOTE
If theWITHclause for a CTE is not the first statement in the batch, you should delimit
it from the preceding statement by placing a semicolon (;) in front of it The semicolon
is used to avoid ambiguity with other uses of theWITHclause (for example, for table
hints) Including a semicolon is not necessary in all cases, but it is recommended that
you use it consistently to avoid problems
Trang 6It is also possible to define multiple CTEs in a single query, with each CTE delimited by a
comma Each CTE is able to refer to previously defined CTEs Listing 43.35 shows an
example of a nested CTE that calculates the minimum, maximum, and difference of
counts of store orders
LISTING 43.35 An Example of Multiple CTEs in a Single Query
WITH store_orders(stor_id, cnt)
AS ( SELECT stor_id, COUNT(*) FROM sales
GROUP BY stor_id ),
MinMaxCTE(MN, MX, Diff)
AS ( SELECT MIN(Cnt), MAX(Cnt), MAX(Cnt)-MIN(Cnt)
FROM store_orders )
SELECT * FROM MinMaxCTE
go
MN MX Diff
-1 22 2 -1
A CTE must be followed by a single SELECT,INSERT, UPDATE, or DELETEstatement that
references some or all of the CTE columns A CTE can also be specified in a CREATE VIEW
statement as part of the defining SELECTstatement of the view
Listing 43.36 shows an example of a CTE used in a DELETEstatement
LISTING 43.36 An Example of a CTE in a DELETE
with oldest_sales (stor_id, ord_num, ord_date)
as (select top 100 stor_id, ord_num, ord_date from sales_big order by ord_date)
delete sales_big from sales_big s, oldest_sales o
where s.stor_id = o.stor_id
and s.ord_num = o.ord_num
and s.ord_date = o.ord_date
go
Most valid SELECTstatement constructs are allowed in a CTE, except the following:
COMPUTEorCOMPUTE BY
ORDER BY(except when a TOPclause is specified)
INTO
OPTIONclause with query hints
Trang 7FOR XML
FOR BROWSE
Recursive Queries with CTEs
Nonrecursive CTEs are ANSI SQL-99 compliant expressions that provide T-SQL coding
flex-ibility However, for each nonrecursive CTE, there is usually another T-SQL construct that
can be used to achieve the same results (for example, derived tables) The real power and
capability of CTEs is revealed when you use them to create recursive queries
A recursive CTE can help simplify the code required to run a recursive query within a
SELECT, INSERT, UPDATE, DELETE, or CREATE VIEWstatement Recursive queries are often
useful for expanding a hierarchy stored in a relational table (for example, displaying
employees in an organizational chart) In previous versions of SQL Server, a recursive
query usually required using temporary tables, cursors, and logic to control the flow of the
recursive steps
A CTE is considered recursive when it refers to itself within the CTE definition Recursive
CTEs are constructed from at least two queries One is a nonrecursive query, also referred
to as the anchor member (AM) The other is the recursive query, also referred to as the
recursive member (RM) The queries are combined using the UNION ALLoperator
The following pseudocode defines the basic structure of a recursive CTE:
WITH cte_name ( column_name [, n] )
AS
( CTE_query_definition1 Anchor member (AM) is defined.
UNION ALL
CTE_query_definition2 Recursive member (RM) is referencing cte_name.
)
Statement using the CTE
SELECT col_list FROM cte_name
Logically, you can think of the algorithm implementing the recursive CTE as follows:
1 The anchor member is activated, and the initial result set (R) is generated
2 The recursive member is activated, using the initial result set (Rn) as input and
generating result set Rn+1.
3 The logic of step 2 is run repeatedly, incrementing the step number (n) until an
empty set is returned
4 The outer query is executed, getting the cumulative (UNION ALL) result of all the
pre-vious steps when referring to the recursive CTE
You can have more than two members in a recursive CTE, but only the UNION ALL
opera-tor is allowed between a recursive member and another recursive or nonrecursive member
Other operators, such as UNION, are allowed only between nonrecursive members
Trang 8Recursive CTEs also require an exact match of the columns in all members, including the
same data type, length, and precision
Listing 43.37 shows a simple recursive CTE that generates a list of sequential numbers
Note that the AM generates the base result, and the RM following the UNION ALLcontrols
the recursion It is important in this example that a valid endpoint be defined to avoid
infinite recursion
LISTING 43.37 An Example of a Simple Recursive CTE
with numlist (val)
as (select 1
union all
select val + 1
from numlist
where val < 10)
select * from numlist
go
val
-1
2
3
4
5
6
7
8
9
10
The following sections present some examples and uses of recursive CTEs
Using Recursive CTEs for Expanding a Hierarchy
For this hierarchy example, we use the partstable in the bigpubs2008database This table
contains a simplified hierarchy of car parts, as shown in Figure 43.1
In thepartstable, any part that is a subpart of another part has the parent part ID stored
in theparentpartidcolumn Theparentpartidcolumn is a foreign key that references
thepartidcolumn Therefore, theparentpartidmust either correspond to a valid
partidwithin the table or beNULL For example, the car itself has NULLin the
parentpartidcolumn
Following are some common requests that might be run on the partstable:
Return all the parts for the engine
Trang 9Car
Engine Transmission Driveshaft Axle
Flywheel
Clutch
Gearbox
Reverse Gear
First Gear
Second Gear
Third Gear
Fourth Gear
Radiator
Intake Manifold
Exhaust Manifold
Carburetor
Piston
Crankshaft
Piston Rings Float Valve
FIGURE 43.1 Theparts table hierarchy.
The first request is probably the most common one: returning a part (for example, the
engine, which has partid = 2) and all subparts The recursive CTE shown in Listing 43.38
provides a solution to this request
LISTING 43.38 A Recursive CTE to Return a Part and All Subparts
WITH PartsCTE(partid, partname, parentpartid, lvl)
AS
(
SELECT partid, partname, parentpartid, 0
FROM PARTS
WHERE partid = 2 Engine
UNION ALL
SELECT P.partid, P.partname, P.parentpartid, PP.lvl+1
FROM Parts as P
JOIN PartsCTE as PP
ON P.parentpartid = PP.Partid
)
Show me all parts that are two levels below the drivetrain
Show me all the parts in such a way that it is easy to see their hierarchical
depen-dencies
Trang 10SELECT PartID, Partname, ParentPartid, lvl
FROM PartsCTE
go
PartID Partname ParentPartid lvl
- -
-2 Engine 1 0
5 Radiator 2 1
6 Intake Manifold 2 1
7 Exhaust Manifold 2 1
8 Carburetor 2 1
13 Piston 2 1
14 Crankshaft 2 1
21 Piston Rings 13 2
11 Float Valve 8 2
Notice that the lvlvalue is repeatedly incremented with each recursive invocation of the CTE You can use this level counter to limit the number of iterations in the recursion For example, Listing 43.39 shows an example of a CTE that returns all parts two levels below the drivetrain LISTING 43.39 A Recursive CTE to Return All Subparts Two Levels Below a Part WITH PartsCTE(partid, partname, parentpartid, lvl) AS ( SELECT partid, partname, parentpartid, 0 FROM PARTS WHERE partid = 1 Drivetrain UNION ALL SELECT P.partid, P.partname, P.parentpartid, PP.lvl+1 FROM Parts as P JOIN PartsCTE as PP ON P.parentpartid = PP.Partid where lvl < 2 ) SELECT PartID, Partname, ParentPartid, lvl FROM PartsCTE where lvl = 2 go PartID Partname ParentPartid lvl - -
-9 Flywheel 3 2