if exists select * from sys.systypes t where t.name = ‘ytdsales_tabletype’ and t.uid = USER_ID‘dbo’ drop type ytdsales_tabletype go CREATE TYPE ytdsales_tabletype AS TABLE title_id char6
Trang 1CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008
@time3 as ‘time3’
select
@datetime2 as ‘datetime2’,
@datetimeoffset as ‘datetimeoffset’,
@utcdatetime as ‘utcdatetime’
select SYSDATETIMEOFFSET() as sysdatetimeoffset,
SYSDATETIME() as sysdatetime
go
datetime date time time3
- - -
-2010-03-28 23:18:30.490 -2010-03-28 23:18:30.4904294 23:18:30.492
datetime2 datetimeoffset utcdatetime
-
-2010-03-28 23:18:30.49 -2010-03-28 23:18:30.4924295 -04:00 2010-03-29 03:18:30.49
sysdatetimeoffset sysdatetime
-
-2010-03-28 23:24:10.7485902 -04:00 -2010-03-28 23:24:10.74
Be aware that retrieving the value from getdate()orsysdatetime()into a
datetimeoffsetvariable or column does not capture the offset from UTC, even if you
store the returned value in a column or variable defined with the datetimeoffsetdata
type To do so, you need to use the SYSDATETIMEOFFSET()function:
declare @datetimeoffset1 datetimeoffset,
@datetimeoffset2 datetimeoffset
select
@datetimeoffset1 = SYSDATETIME(),
@datetimeoffset2 = SYSDATETIMEOFFSET()
select @datetimeoffset1, @datetimeoffset2
go
-2010-03-28 23:36:39.7271831 +00:00 -2010-03-28 23:36:39.7271831 -04:00
Note that in the output, SQL Server Management Studio (SSMS) trims the time values
down to two decimal places when it displays the results in the Text Results tab However,
this is just for display purposes (and applies only with text results; grid results display the
full decimal precision) The actual value does store the precision down to the specified
number of decimal places, which can be seen if you convert the datetime2value to a
string format that displays all the decimal places:
Trang 2select SYSDATETIME() as datetime2_trim,
convert(varchar(30), SYSDATETIME(), 121) as datetime2_full
go
datetime2_trim datetime2_full
-
-2010-03-30 23:52:30.68 -2010-03-30 23:52:30.6851262
TheSWITCHOFFSET()function can be used to convert a datetimeoffsetvalue into a
differ-ent time zone offset value:
select SYSDATETIMEOFFSET(), SWITCHOFFSET ( SYSDATETIMEOFFSET(), ‘-07:00’ )
go
-2010-03-29 00:07:21.1335738 -04:00 2010-03-28 21:07:21.1335738 -07:00
When you are specifying a time zone value for the SWITCHOFFSETorTODATETIMEOFFSET
offset functions, the value can be specified as an integer value representing the number of
minutes of offset or as a time value in hh:mm format The range of allowed values is +14
hours to -13 hours
select TODATETIMEOFFSET ( SYSDATETIME(), -300 )
select TODATETIMEOFFSET ( SYSDATETIME(), ‘-05:00’ )
go
-2010-03-29 00:23:05.5773288 -05:00
-2010-03-29 00:23:05.5773288 -05:00
Date and Time Conversions
If an existing CONVERTstyle includes the time part, and the conversion is from
datetimeoffsetto a string, the time zone offset (except for style 127) is included If you
do not want the time zone offset, you need to use cast or convert the datetimeoffset
value to datetime2first and then to a string:
select convert(varchar(35), SYSDATETIMEOFFSET(), 121) as datetime_offset,
CONVERT(varchar(30), cast(SYSDATETIMEOFFSET() as datetime2),121) as datetime2
go
datetime_offset datetime2
-
-2010-03-30 23:57:36.1015950 -04:00 -2010-03-30 23:57:36.1015950
Trang 3CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008
When you convert from datetime2ordatetimeoffsettodate, there is no rounding and
the date part is extracted explicitly For any implicit conversion from datetimeoffsetto
date,time,datetime2,datetime, or smalldatetime, conversion is based on the local date
and time value (to the persistent time zone offset) For example, when the
datetimeoffset(3)value,2006-10-21 12:20:20.999 -8:00, is converted to time(3), the
result is 12:20:20.999,not20:20:20.999(UTC)
If you convert from a higher-precision time value to a lower-precision value, the conversion
is permitted, and the higher-precision values are truncated to fit the lower precision type
If you are converting a time(n),datetime2(n),ordatetimeoffset(n)value to a string,
the number of digits depends on the type specification If you want a specific precision in
the resulting string, convert to a data type with the appropriate precision first and then to
a string, as follows:
select
convert(varchar(35), sysdatetime(), 121) as datetime_offset,
CONVERT(varchar(30), cast(sysdatetime() as datetime2(3)), 121) as datetime2
go
datetime_offset datetime2
-
-2010-03-31 00:04:37.3306880 -2010-03-31 00:04:37.331
If you attempt to cast a string literal with a fractional seconds precision that is more than
that allowed for smalldatetimeordatetime, Error 241 is raised:
declare @datetime datetime
select @datetime = ‘2010-03-31 00:04:37.3306880’
go
Msg 241, Level 16, State 1, Line 2
Conversion failed when converting date and/or time from character string.
Table-Valued Parameters
In previous versions of SQL Server, it was not possible to share the contents of table
vari-ables between stored procedures SQL Server 2008 changes that with the introduction of
table-valued parameters, which allow you to pass table variables to stored procedures as
input parameters Table-valued parameters provide more flexibility and, in many cases,
better performance than temporary tables as a means to pass result sets between stored
procedures
Trang 4To create and use table-valued parameters, you must first create a user-defined table type
as a TABLEdata type and define the table structure This is done using the CREATE TYPE
command, as shown in Listing 42.10
if exists (select * from sys.systypes t where t.name = ‘ytdsales_tabletype’
and t.uid = USER_ID(‘dbo’)) drop type ytdsales_tabletype
go
CREATE TYPE ytdsales_tabletype AS TABLE
(title_id char(6),
title varchar(50),
pubdate date,
ytd_sales int)
go
After creating the user-defined table data type, you can use it for declaring local table
vari-ables and for stored procedure parameters To use the table-valued parameter in a
proce-dure, you create a procedure to receive and access data through a table-valued parameter,
as shown in Listing 42.11
/* Create a procedure to receive data for the table-valued parameter */
if OBJECT_ID(‘tab_parm_test’) is not null
drop proc tab_parm_test
go
create proc tab_parm_test
@pubdate datetime = null,
@sales_minimum int = 0,
@ytd_sales_tab ytdsales_tabletype READONLY
as
set nocount on
if @pubdate is null
if no date is specified, set date to last year
set @pubdate = dateadd(month, -12, getdate())
select * from @ytd_sales_tab
where pubdate > @pubdate
and ytd_sales >= @sales_minimum
return
go
Trang 5CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008
Then, when calling that stored procedure, you declare a local table variable using the table
data type defined previously, populate the table variable with data, and then pass the table
variable to the stored procedure (see Listing 42.12)
/* Declare a variable that references the table type */
declare @ytd_sales_tab ytdsales_tabletype
/* Add data to the table variable */
insert @ytd_sales_tab
select title_id, convert(varchar(50), title), pubdate, ytd_sales
from titles
/* Pass the table variable populated with data to a stored procedure */
exec tab_parm_test ‘6/1/2001’, 10000, @ytd_sales_tab
go
title_id title ytd_sales
- -
-BU2075 You Can Combat Computer Stress! 18722
MC3021 The Gourmet Microwave 22246
TC4203 Fifty Years in Buckingham Palace Kitchens 15096
The scope of a table-valued parameter is limited to only the stored procedure to which it is
passed To access the contents of a table-valued parameter in a procedure called by
another procedure that contains a valued parameter, you need to pass the
table-valued parameter to the subprocedure Listing 42.13 provides an example of a
subproce-dure and alters the procesubproce-dure created in Listing 42.6 to call the subprocesubproce-dure
/* Create the sub-procedure */
create proc tab_parm_subproc
@pubdate datetime = null,
@sales_minimum int = 0,
@ytd_sales_tab ytdsales_tabletype READONLY
as
select * from @ytd_sales_tab
where ytd_sales <= @sales_minimum
and ytd_sales <> 0
Trang 6go
/* modify the tab_part_test proc to call the sub-procedure */
alter proc tab_parm_test
@pubdate datetime = null,
@sales_minimum int = 0,
@ytd_sales_tab ytdsales_tabletype READONLY
as
set nocount on
if @pubdate is null
if no date is specified, set date to last year
set @pubdate = dateadd(month, -12, getdate())
select * from @ytd_sales_tab
where pubdate > @pubdate
and ytd_sales >= @sales_minimum
exec tab_parm_subproc @pubdate,
@sales_minimum,
@ytd_sales_tab return
go
/* Declare a variable that references the type */
declare @ytd_sales_tab ytdsales_tabletype
/* Add data to the table variable */
insert @ytd_sales_tab
select title_id, convert(varchar(50), title), pubdate, ytd_sales
from titles
where type = ‘business’
/* Pass the table variable populated with data to a stored procedure */
exec tab_parm_test ‘6/1/2001’, 10000, @ytd_sales_tab
go
title_id title pubdate ytd_sales
- - -
-BU2075 You Can Combat Computer Stress! 2004-06-30 18722
title_id title pubdate ytd_sales
- - -
-BU1032 The Busy Executive’s Database Guide 2004-06-12 4095
Trang 7CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008
BU1111 Cooking with Computers: Surreptitious Balance Shee 2004-06-09 3876
BU7832 Straight Talk About Computers 2004-06-22 4095
Table-Valued Parameters Versus Temporary Tables
Table-valued parameters offer more flexibility and in some cases better performance than
temporary tables or other ways to pass a list of values to a stored procedure One benefit is
table-valued parameters do not acquire locks for the initial population of data from a
client Also, table-valued parameters are memory resident and do not incur physical I/O
unless they grow too large to remain in cache memory
However, table-valued parameters do have some restrictions:
SQL Server does not create or maintain statistics on columns of table-valued
parame-ters
Table-valued parameters can be passed only as READONLYinput parameters to T-SQL
routines You cannot perform UPDATE,DELETE, or INSERToperations on a table-valued
parameter within the body of the stored procedure to which it is passed
Like table variables, a table-valued parameter cannot be specified as the target of a
SELECT INTOorINSERT EXECstatement They can only be populated using an
INSERTstatement
Hierarchyid Data Type
TheHierarchyiddata type introduced in SQL Server 2008 is actually a system-supplied
common language runtime (CLR) user-defined type (UDT) that can be used for storing and
manipulating hierarchical structures (for example, parent-child relationships) in a
rela-tional database The Hierarchyidtype is stored as a varbinaryvalue that represents the
position of the current node in the hierarchy (both in terms of parent-child position and
position among siblings) You can perform manipulations on the type in Transact-SQL by
invoking methods exposed by the type
Creating a Hierarchy
First, let’s define a hierarchy in a table using the Hierarchyiddata type For example, this
section uses the Partstable example used in Chapter 28, “Creating and Managing Stored
Procedures,” to demonstrate how a stored procedure could be used to traverse a hierarchy
stored in a table There is also an example in Chapter 52 using a recursive common table
expression (CTE) to perform a similar action Let’s see how to implement an alternative
solution by adding a Hierarchyidcolumn to the Parts table First, you create a version of
thePartstable using the Hierarchyiddata type (see Listing 42.14)
Trang 8Use bigpubs2008
Go
CREATE TABLE PARTS_hierarchy(
partid int NOT NULL,
hid hierarchyid not null,
lvl as hid.GetLevel() persisted,
partname varchar(30) NOT NULL,
PRIMARY KEY NONCLUSTERED (partid),
UNIQUE NONCLUSTERED (partname)
)
Note the hidcolumn defined with the Hierarchyiddata type Notice also how the lvl
column is defined as a compute column using the GetLevelmethod of the hidcolumn to
define the persisted computed column level The GetLevelmethod returns the level of the
current node in the hierarchy
TheHierarchyiddata type provides topological sorting, meaning that a child’s sort value is
guaranteed to be greater than the parent’s sort value This guarantees that a node’s sort value
will be higher than all its ancestors You can take advantage of this feature by creating an index
on the Hierarchyidcolumn because the index will sort the data in a depth-first manner This
ensures that all members of the same subtree are close to each other in the leaf level of the
index, which makes the index useful as an efficient mechanism for returning all descendents of
a node To take advantage of this, you can create a clustered index on the hidcolumn:
CREATE UNIQUE CLUSTERED INDEX idx_hid_first ON Parts_hierarchy (hid);
You can also use another indexing strategy called breadth-first, in which you organize all
nodes from the same level close to each other in the leaf level of the index This is done by
building the index such that the leading column is level in the hierarchy Queries that need
to get all nodes from the same level in the hierarchy can benefit from this type of index:
CREATE UNIQUE INDEX idx_lvl_first ON Parts_hierarchy(lvl, hid);
Populating the Hierarchy
Now that you’ve created the hierarchy table, the next step is to populate it To insert a
new node into the hierarchy, you must first produce a new Hierarchyidvalue that
repre-sents the correct position in the hierarchy There are two methods available with the
Hierarchyiddata type to do this: the HIERARCHYID::GetRoot()method and
GetDescendantmethod You use the HIERARCHYID::GetRoot()method to produce the
value for the root node of the hierarchy This method simply produces a Hierarchyid
value that is internally an empty binary string representing the root of the tree
You can use the GetDescendantmethod to produce a value below a given parent The
GetDescendantmethod accepts two optional Hierarchyidinput values that represent the
two nodes between which you want to position the new node If both values are not NULL,
the method produces a new value positioned between the two nodes If the first parameter
Trang 9CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008
is not NULLand the second parameter is NULL, the method produces a value greater than
the first parameter Finally, if the first parameter is NULLand the second parameter is not
NULL, the method produces a value smaller than the second parameter If both parameters
areNULL, the method produces a value simply below the given parent
NOTE
TheGetDescendantmethod does not guarantee that Hierarchyidvalues are unique
To enforce uniqueness, you must define either a primary key, unique constraint, or
unique index on the Hierarchyidcolumn
The code in Listing 42.15 uses a cursor to loop through the rows currently in the Parts
table and populates the Parts_hierarchytable If the part is the first node in the
hierar-chy, the procedure uses the HIERARCHYID::GetRoot()method to assign the hidvalue for
the root node of the hierarchy Otherwise, the code in the cursor looks for the last child
hidvalue of the new part’s parent part and uses theGetDescendantmethod to produce a
value that positions the new node after the last child of that parent part
NOTE
Listing 42.15 also makes use of a recursive common table expression to traverse the
existingPartstable in hierarchical order to add in the rows at the proper level, starting
with the top-most parent part If you are unfamiliar with CTEs (which were introduced in
SQL Server 2005), you may want to review the “In Case you Missed it…” section in
Chapter 43
DECLARE
@hid AS HIERARCHYID,
@parent_hid AS HIERARCHYID,
@last_child_hid AS HIERARCHYID,
@partid int,
@partname varchar(30),
@parentpartid int
declare parts_cur cursor for
WITH PartsCTE(partid, partname, parentpartid, lvl)
AS
(
SELECT partid, partname, parentpartid, 0
FROM PARTS
WHERE parentpartid is null
Trang 10UNION ALL
SELECT P.partid, P.partname, P.parentpartid, PP.lvl+1
FROM Parts as P
JOIN PartsCTE as PP
ON P.parentpartid = PP.Partid
)
SELECT PartID, Partname, ParentPartid
FROM PartsCTE
order by lvl
open parts_cur
fetch parts_cur into @partid, @partname, @parentpartid
while @@FETCH_STATUS = 0
begin
if @parentpartid is null
set @hid = HIERARCHYID::GetRoot()
else
begin
select @parent_hid = hid from PARTS_hierarchy
where partid = @parentpartid
select @last_child_hid = MAX(hid) from PARTS_hierarchy
where hid.GetAncestor(1) = @parent_hid
select @hid = @parent_hid.GetDescendant(@last_child_hid, NULL)
end
insert PARTS_hierarchy (partid, hid, partname)
values (@partid, @hid, @partname)
fetch parts_cur into @partid, @partname, @parentpartid
end
close parts_cur
deallocate parts_cur
go
Querying the Hierarchy
Now that you’ve populated the hierarchy, you should query it to view the data and verify
the hierarchy was populated correctly However, If you query the hidvalue directly, you
see only its binary representation, which is not very meaningful To view the Hierarchyid
value in a more useful manner, you can use the ToStringmethod, which returns a logical
string representation of the Hierarchyid This string representation is shown as a path