SQL Server 2008

新数据类型

Kelly Wilson

 

概览:

  • 新日期和时间数据类型
  • 代表在层次结构中的位置
  • 用于处理空间数据的两种模型

在全球经济环境下开展业务这一趋势越来越要求各公司使用新型的数据、应用程序以及复杂的计算。SQL Server 2008 内置的七种新数据类型

提供了处理和简化更复杂数据管理的方法。

日期和时间

使用旧的 datetime 数据类型时,SQL Server® 用户无法分别处理日期和时间信息。四种新数据类型(date、time、datetime2 和 datetimeoffset)则改变了这一状况,从而简化了日期和时间数据的处理,并且提供了更大的日期范围、小数秒精度以及时区支持。新数据库应用程序应使用这些新数据类型,而非原来的 datetime。让我们进一步了解一下这些新类型。

Date 数据类型仅存储日期,不存储时间。范围是从 1000 年 1 月 1 日到 9999 年 12 月 31 日(0001-01-01 到 9999-12-31)。每个日期变量都需要 3 个存储字节,且精度为 10 位。Date 类型的准确性仅限于单天。

看一看图 1,它显示了如何在 T-SQL 脚本中创建和初始化 Date 变量。变量 @myDate1 初始化为 'MM/DD/YYYY' 格式的字符串。变量 @myDate2 则未初始化,它的值将为 NULL。变量 @myDate3 初始化为本地计算机系统的日期。可随时使用 SELECT 或 SET 语句来更改变量的值,示例中更改了 @myDate2 的值。也可在表格中创建日期列。图 2 展示了如何创建包含三个日期列的表格。

Figure 2 Create a table with three date columns

USE TempDB
GO

CREATE TABLE myTable
(
    myDate1 date,myDate2 date,myDate3 date
)
GO

INSERT INTO myTable
VALUES('01/22/2005',
       '2007-05-08 12:35:29.1234567 +12:15',
       GetDate())

SELECT * FROM myTable

--Results
--myDate1    myDate2    myDate3
------------ ---------- ----------
--2005-01-22 2007-05-08 2007-11-20

Figure 1 Create and initialize date variables in T-SQL scripts

DECLARE @myDate1 date = '01/22/2005'
DECLARE @myDate2 date
DECLARE @myDate3 date = GetDate()

SELECT @myDate2 = '2007-05-08 12:35:29.1234567 +12:15'

SELECT @myDate1 AS '@myDate1',
       @myDate2 AS '@myDate2',
       @myDate3 AS '@myDate3'

--Results
--@myDate1   @myDate2   @myDate3
------------ ---------- ----------
--2005-01-22 2007-05-08 2007-11-20

Time 数据类型仅存储一天中的时间,不存储日期。它使用的是 24 小时时钟,因此支持的范围是 00:00:00.0000000 到 23:59:59.9999999(小时、分钟、秒和小数秒)。可在创建数据类型时指定小数秒的精度。默认精度是 7 位,准确度是 100 毫微秒。精度影响着所需的存储空间大小,范围包括最多 2 位的 3 个字节、3 或 4 位的 4 个字节以及 5 到 7 位的 5 个字节。

图 3 中的 T-SQL 脚本展示了字符串初始化值的隐式转换将如何影响变量的精度。T-SQL 代码首先创建八个单独的时间变量,并将其初始化为相同值。每个变量的小数精度等于其名称。例如,@myTime3 的小数精度为 3 位。结果显示每个时间数据类型的精度均等于声明的小数精度。超出的位数将被截断。

Figure 3 Display time data type's variable precision

DECLARE @myTime  time = '01:01:01.1234567 +01:01'
DECLARE @myTime1 time(1) = '01:01:01.1234567 +01:01'
DECLARE @myTime2 time(2) = '01:01:01.1234567 +01:01'
DECLARE @myTime3 time(3) = '01:01:01.1234567 +01:01'
DECLARE @myTime4 time(4) = '01:01:01.1234567 +01:01'
DECLARE @myTime5 time(5) = '01:01:01.1234567 +01:01'
DECLARE @myTime6 time(6) = '01:01:01.1234567 +01:01'
DECLARE @myTime7 time(7) = '01:01:01.1234567 +01:01'

SELECT @myTime  AS '@myTime',
       @myTime1 AS '@myTime1',
       @myTime2 AS '@myTime2',
       @myTime3 AS '@myTime3',
       @myTime4 AS '@myTime4',
       @myTime5 AS '@myTime5',
       @myTime6 AS '@myTime6',
       @myTime7 AS '@myTime7'

--Results
--@myTime          @myTime1   @myTime2    @myTime3     @myTime4      
------------------ ---------- ----------- ------------ -------------
--01:01:01.1234567 01:01:01.1 01:01:01.12 01:01:01.123 01:01:01.1235
--
--@myTime5       @myTime6        @myTime7
---------------- --------------- ----------------
--01:01:01.12346 01:01:01.123457 01:01:01.1234567

DROP TABLE myTable

可将 time 数据类型创建为表格中的一列。图 4 中的 DROP TABLE myTable T-SQL 脚本创建了一个名为 myTable1 的表格并向表格添加了三个时间列。然后,使用 SELECT 语句来将记录插入到表格中并且显示表格的内容。

Figure 4 Create myTable1

USE TempDB
GO

CREATE TABLE myTable1
(
    myTime1 time(1),
    myTime2 time(2),
    myTime3 time(3)
)
GO

INSERT INTO myTable1
VALUES('01:30:01.1234567',
       '02:34:01.1234567',
       '03:01:59.1234567')

SELECT * from myTable1

--Results
--myTime1    myTime2     myTime3
------------ ----------- ------------
--01:30:01.1000000 02:34:15.1200000 03:01:59.1230000

DROP TABLE myTable1

Datetimeoffset 和 Datetime2

Datetimeoffset 数据类型提供了时区信息。time 数据类型不包含时区,因此仅适用于当地时间。然而,在全球经济形势下,常常需要知道某个地区的时间与另一地区的时间之间的关系。时区偏移值表示为 + 或 - hh:mm。

以下代码创建了一个 datetimeoffset 变量并将其初始化为时间值 8:52 A.M.太平洋时间:

DECLARE @date DATETIMEOFFSET = '2007-11-26T08:52:00.1234567-08:00'
PRINT @date
--Results
--2007-11-26 08:52:00.1234567 -08:00

初始化 datetimeoffset 变量(脚本中的 @date)的字符串为特殊格式,从最重要的元素到最不重要的元素。单个大写字母 T 分隔了日期和时间元素。减号将时间元素与时区分隔开来。减号与时间或时区元素之间并无空格。该格式是 datetimeoffset 数据类型所支持的两种 ISO 8601 格式中的一种(ISO 8601 是日期和时间值表示的国际标准)。

时间组件的精度指定为与 time 数据类型一样,并且如果未指定则默认为同样的七位。支持的范围也相同。

Datetime2 数据类型是原始 datetime 类型的扩展。它支持更大的日期范围以及更细的小数秒精度,同时可使用它来指定精度。datetime2 类型的日期范围是 0001 年 1 月 1 日到 9999 年 12 月 31 日(原始 datetime 的范围则是 1753 年 1 月 1 日到 9999 年 12 月 31 日)。与 time 类型一样,提供了七位小数秒精度。原始 datetime 类型提供了三位精度,且时间范围为 00:00:00 到 23:59:59.999。以下代码显示了如何创建一个 datetime2 变量并将其初始化为本地服务器日期和时间:

DECLARE @datetime2 DATETIME2 = GetDate();
PRINT @datetime2

--Results
--2007-11-26 09:39:04.1370000

接下来看看新的 hierarchyid 数据类型。该数据类型用于处理表中数据元素之间的关系,而非具体的日期或时间数据。

Hierarchyid 数据类型

Hierarchyid 数据类型可用于构建表中数据元素之间的关系,专门代表在层次结构中的位置。为研究该数据类型,首先通过使用图 5 中的脚本来创建 MyCompany 数据库并在其中填写员工数据。

Figure 5 Create and populate the MyCompany database

USE MASTER
GO

CREATE DATABASE MyCompany
GO
USE MyCompany
GO

--Create a table called employee that will store
--the data for the employees for MyCompany.
    
CREATE TABLE employee
(
    EmployeeID int NOT NULL,
    EmpName    varchar(20) NOT NULL,
    Title      varchar(20) NULL,
    Salary     decimal(18, 2) NOT NULL,
    hireDate   datetimeoffset(0) NOT NULL,
)
GO

--These statements will insert the data for the employees of MyCompany.

INSERT INTO employee
VALUES(6,   'David',  'CEO', 35900.00, '2000-05-23T08:30:00-08:00')

INSERT INTO employee
VALUES(46,  'Sariya', 'Specialist', 14000.00, '2002-05-23T09:00:00-08:00')

INSERT INTO employee
VALUES(271, 'John',   'Specialist', 14000.00, '2002-05-23T09:00:00-08:00')

INSERT INTO employee
VALUES(119, 'Jill',   'Specialist', 14000.00, '2007-05-23T09:00:00-08:00')

INSERT INTO employee
VALUES(269, 'Wanida', 'Assistant', 8000.00, '2003-05-23T09:00:00-08:00')

INSERT INTO employee
VALUES(272, 'Mary',   'Assistant', 8000.00, '2004-05-23T09:00:00-08:00')
GO
--Results
--EmployeeID  EmpName Title      Salary   hireDate
------------- ------- ---------- -------- --------------------------
--6           David   CEO        35900.00 2000-05-23 08:30:00 -08:00
--46          Sariya  Specialist 14000.00 2002-05-23 09:00:00 -08:00
--271         John    Specialist 14000.00 2002-05-23 09:00:00 -08:00
--119         Jill    Specialist 14000.00 2007-05-23 09:00:00 -08:00
--269         Wanida  Assistant  8000.00  2003-05-23 09:00:00 -08:00
--272         Mary    Assistant  8000.00  2004-05-23 09:00:00 -08:00

图 6 显示了生成的简单数据库,仅包含一个员工表。MyCompany 数据库中的该员工表不包含任何强加结构。这对于关系型数据库而言非常正常,因为结构是由应用程序通过其查询和处理代码来动态强加的。

Figure 6 MyCompany 员工表

Figure 6** MyCompany 员工表 **

但是,企业数据通常都具有内在结构。例如,每个企业都具有报告结构,如图 7 中所示的 MyCompany 的报告结构。MyCompany 的所有员工都向 CEO David 报告。某些员工为直接报告(如 Jill)。其他人(如 Mary)则通过中间人报告。在编程术语中,MyCompany 的报告结构称为树,因为其形状非常像树。最上方的 David 无需向任何人报告,他是父级或祖先。其下为向 David 报告的员工。此类节点称为子级或后代。David 可拥有任意多个后代,以代表其直接下属。

Figure 7 MyCompany 的组织结构

Figure 7** MyCompany 的组织结构 **(单击该图像获得较大视图)

图 8 中的脚本使用 hierarchyid 数据类型重构了 MyCompany 数据库,构建了一个符合 MyCompany 报告结构的关系。先使用 ALTER TABLE 语句添加一个 hierarchyid 列。然后使用 hierarchyid 的 GetRoot 方法插入 David 的节点。接着使用 GetDescendant 方法将 David 的直接下属添加到树中。

Figure 8 Rebuild the database using hierarchyid

DELETE employee
GO
ALTER TABLE employee ADD OrgNode hierarchyid NOT NULL
GO

DECLARE @child hierarchyid,
@Manager hierarchyid = hierarchyid::GetRoot()

--The first step is to add the node at the top of the
--tree. Since David is the CEO his node will be the
--root node.

INSERT INTO employee
VALUES(6,   'David',  'CEO', 35900.00,
       '2000-05-23T08:30:00-08:00', @Manager)

--The next step is to insert the records for
--the employees that report directly to David.

SELECT @child = @Manager.GetDescendant(NULL, NULL)

INSERT INTO employee
VALUES(46,  'Sariya', 'Specialist', 14000.00,
       '2002-05-23T09:00:00-08:00', @child)

SELECT @child = @Manager.GetDescendant(@child, NULL)
INSERT INTO employee
VALUES(271, ‚John',   ‚Specialist', 14000.00,
       '2002-05-23T09:00:00-08:00', @child)

SELECT @child = @Manager.GetDescendant(@child, NULL)
INSERT INTO employee
VALUES(119, ‚Jill',   ‚Specialist', 14000.00,
       ‚2007-05-23T09:00:00-08:00', @child)

--We can now insert the employee that reports to
--Sariya.
SELECT @manager = OrgNode.GetDescendant(NULL, NULL)
FROM employee WHERE EmployeeID = 46

INSERT INTO employee
VALUES(269, ‚Wanida', ‚Assistant', 8000.00,
       ‚2003-05-23T09:00:00-08:00', @manager)

--Next insert the employee that report to John.
SELECT @manager = OrgNode.GetDescendant(NULL, NULL)
FROM employee WHERE EmployeeID = 271

INSERT INTO employee
VALUES(272, ‚Mary',   ‚Assistant', 8000.00,
       ‚2004-05-23T09:00:00-08:00', @manager)
GO

添加数据库记录且构建好层次结构后,就可使用类似以下的查询来显示员工表的内容:

SELECT EmpName, Title, Salary, OrgNode.ToString() AS OrgNode
FROM employee ORDER BY OrgNode
GO
--Results
--EmpName  Title      Salary    OrgNode
---------- ---------- --------- -------
--David    CEO        35900.00  /
--Sariya   Specialist 14000.00  /1/
--Wanida   Assistant  8000.00   /1/1/
--John     Specialist 14000.00  /2/
--Mary     Assistant  8000.00   /2/1/
--Jill     Specialist 14000.00  /3/

OrgNode 是 hierarchyid 列。结果中的每个斜线 / 字符表示层次结构树中的一个节点。David 位于根节点,因此显示一个斜线。Sariya、John 和 Jill 向 David 报告,因此有两个斜线,表示他们是层次结构中的第二层节点。数字 1、2 或 3 显示各子节点的顺序。该系统非常灵活。可根据需要删除、插入或添加子节点。例如,如果在 John 和 Jill 之间添加一名员工,该员工在结果集中将列为:/2.1/.

要回答问题(如“谁向 Sariya 报告?”),可创建以下 T-SQL 代码中所示的查询:

DECLARE @Sariya hierarchyid

SELECT @Sariya = OrgNode
FROM employee WHERE EmployeeID = 46

SELECT EmpName, Title, Salary, OrgNode.ToString() AS 'OrgNode'
FROM employee
WHERE OrgNode.GetAncestor(1) = @Sariya
GO
--Results
--EmpName Title     Salary  OrgNode
--------- --------- ------- -------
--Wanida  Assistant 8000.00 /1/1/

此查询使用 hierarchyid 的 GetAncestor 方法,它会返回当前 hierarchyid 节点的父项。在之前的代码中,将变量 @Sariya 设为 Sariya 的层次结构节点。因为 Sariya 是向她报告的所有员工的直接前辈。因此,要编写一个返回直接向 Sariya 报告的员工的查询,需从树中检索 Sariya 的节点,然后选择前辈节点为 Sariya 节点的所有员工。

Hierarchyid 列应非常紧凑,因为代表树中节点所需的位数取决于节点的平均子项数(通常称为节点的扇出)。例如,拥有 100,000 名员工且平均扇出为六级的组织层次结构中的新节点需五个存储字节。

Hierarchyid 数据类型提供了多个便于处理层次数据的方法。图 9 概要显示了这些方法。有关所有方法的详细信息,请参阅 SQL Server 联机丛书 (msdn2.microsoft.com/ms130214)。

Figure 9 Methods provided by the hierarchyid data type

方法 说明
GetAncestor 返回代表该 hierarchyid 节点第 n 代前辈的 hierarchyid。
GetDescendant 返回该 hierarchyid 节点的子节点。
GetLevel 返回一个整数,代表该 hierarchyid 节点在整个层次结构中的深度。
GetRoot 返回该层次结构树的根 hierarchyid 节点。静态。
IsDescendant 如果传入的子节点是该 hierarchyid 节点的后代,则返回 true。
Parse 将层次结构的字符串表示转换成 hierarchyid 值。静态。
Reparent 将层次结构中的某个节点移动另一个位置。
ToString 返回包含该 hierarchyid 逻辑表示的字符串。

空间数据类型

空间数据是用于确定地理位置和形状(主要是在地球上)的数据。它们可以是界标、道路甚至企业所在地。SQL Server 2008 提供了 geography 和 geometry 数据类型来处理此类数据。

Geography 数据类型处理圆地信息。圆地模型在计算时考虑了地球的曲面。位置信息是由经度和纬度组成。该模型极其适合越洋运输、军事规划等应用程序以及涉及地球表面的短程应用程序。如果数据是按经度和纬度存储的,则使用此模型。

Geometry 数据类型处理平地或平面模型。在此模型中,将地球当作从已知点起的平面投影。平地模型不考虑地球的弯曲,因此主要用于描述较短的距离,如映射建筑物内部的数据库应用程序。

Geography 和 geometry 类型从矢量对象构建,格式为 Well-Known Text (WKT) 或 Well-Known Binary (WKB)。它们是开放地理空间联盟 (OGC) SQL 简单特征规范描述的空间数据传输格式。图 10 列出了 SQL Server 2008 支持的七种矢量对象类型。

Figure 10 Vector objects supported by SQL Server 2008

对象 说明
Point 一个位置。
MultiPoint 一系列点。
LineString 由直线连接的零个或多个点。
MultiLineString 一组 linestring。
Polygon 一组封闭 linestring 形成的相连区域。
MultiPolygon 一组多边形。
GeometryCollection geometry 类型集合。

要使用一个或多个矢量对象构建一个 geography 类型,首先在 T-SQL 脚本中声明 geography 类型(如图 11 所示)。然后调用图 12 中列出的一个方法,并传入矢量对象字符串和空间参照 ID (SRID)。SRID 是欧洲石油调查组定义的空间参照标识系统。它是针对绘图、调查和测量数据存储制定的一组标准的组成部分。每个 SRID 标识地理计算中使用的一个具体椭圆体类型。因为地球并非标准的球体,所以这是必须的。SQL Server 2008 仅能对相同 SRID 执行计算。

Figure 12 Construct objects for geography and geometry

方法 说明
STGeomFromText 根据输入文本构建任意类型的 geography 实例。
STPointFromText 根据输入文本构建一个 geography 的 Point 实例。
STMPointFromText 根据输入文本构建一个 geography 的 MultiPoint 实例。
STLineFromText 根据输入文本构建一个 geography 的 LineString 实例。
STMLineFromText 根据输入文本构建一个 geography 的 MultiLineString 实例。
STPolyFromText 根据输入文本构建一个 geography 的 Polygon 实例。
STMPolyFromText 根据输入文本构建一个 geography 的 MultiPolygon 实例。
STGeomCollFromText 根据输入文本构建一个 geography 的 GeometryCollection 实例。

Figure 11 Create points, lines, and polygon geometry

DECLARE @geo1 geometry
SELECT @geo1 = geometry::STGeomFromText('POINT (3 4)', 0)
PRINT @geo1.ToString()

DECLARE @geo2 geometry
SELECT @geo2 = geometry::Parse('POINT(3 4 7 2.5)')
PRINT @geo2.STX;
PRINT @geo2.STY;
PRINT @geo2.Z;
PRINT @geo2.M;

DECLARE @geo3 geography;
SELECT @geo3 = geography::STGeomFromText(
    'LINESTRING(47.656 -122.360, 47.656 -122.343)', 4326);
SELECT @geo3.ToString();

--Results
--POINT (3 4)
--3
--4
--7
--2.5

DECLARE @gx geometry; 
SET @gx = geometry::STPolyFromText(
    'POLYGON ((5 5, 10 5, 10 10, 5 5))', 0);
PRINT @gx.ToString();
--Results
--POLYGON ((5 5, 10 5, 10 10, 5 5))

Geography 和 Geometry 之间的差别

Geography 和 geometry 数据类型专用于处理不同类型的数据,因此必须了解一些关键差别。使用 geometry 数据类型时,距离和面积的度量单位需与实例的坐标一致。例如,点 (0,0) 和点 (6,8) 之间的距离将始终为 10 个单位。使用 geography 类型时则不一样,它使用的是以经度和纬度表示的椭圆体坐标。

当坐标以经度纬度对表示时,GEOMETRY 数据类型返回的是不同的结果。以下 T-SQL 代码计算点 (90 0) 和点 (90 180) 之间的距离。这两个点均代表北极,因此它们之间的距离应为 0。在使用 GEOMETRY 时,计算后的距离为 180。

DECLARE @g1 GEOMETRY, @g2 GEOMETRY, @g3 GEOGRAPHY, @g4 GEOGRAPHY
SELECT @g1 = GEOMETRY::STGeomFromText('POINT (90 0)', 0)
SELECT @g2 = GEOMETRY::STGeomFromText('POINT (90 180)', 0)

SELECT @g3 = GEOGRAPHY::STGeomFromText('POINT (90 0)', 4326)
SELECT @g4 = GEOGRAPHY::STGeomFromText('POINT (90 180)', 4326)
SELECT @g2.STDistance(@g1) AS 'GEOMETRY',
       @g4.STDistance(@g3) AS 'GEOGRAPHY';

--Results
--GEOMETRY               GEOGRAPHY
------------------------ ----------------------
--180                    0

两种数据类型的空间数据方向也不同。在 geometry 数据类型所使用的平面系统中,多边形的方向并非重要因素。例如,坐标为 ((0, 0), (10, 0), (0, 20), (0, 0)) 的多边形被视为等同于多边形 ((0, 0), (0, 20), (10, 0), (0, 0))。但是,在 geography 数据类型所使用的数据模型中,如果没有指定方向,则并不能确定多边形。例如,假定有个环绕赤道的环。那么,该环所形成的多边形代表的是北半球还是南半球?因此,在使用 geography 数据时,必须准确指出方向和位置。

SQL Server 2008 对 geography 数据类型还设有一些限制。例如,每个 geography 实例均必须位于单个半球内。不允许更大的空间对象,否则会抛出 ArgumentException。如果方法的结果并不位于单个半球内,则需要两个输入的 Geography 数据类型会返回 NULL。

SQL Server 提供了多个可针对 geography 和 geometry 实例执行操作的方法。图 13 显示了使用 SQL Server 2008 提供的方法来处理空间数据的一些示例。鉴于篇幅有限,在此就不再详述该主题,SQL Server 联机丛书中提供有完整说明。

Figure 13 Working with spatial data

DECLARE @gm geometry;
DECLARE @gg geography;
DECLARE @h geography;

SET @gm = geometry::STGeomFromText('POLYGON((0 0, 13 0, 3 3, 0 13, 0 0),(2 2, 2 1, 1 1, 1 2, 2 2))', 0);
SELECT @gm.STArea();

--Results
--38

SET @gg = geography::STGeomFromText('LINESTRING(0 0, 5 5)', 4326);
--Calculate the distance to a point slightly offset from the LINESTRING.
SET @h = geography::STGeomFromText('POINT(4 4)', 4326);
SELECT @gg.STDistance(@h);

--Results
-- 430.182777043046

--Calculate the distance to a point on the LINESTRING.
SET @h = geography::STGeomFromText('POINT(5 5)', 4326);
SELECT @gg.STDistance(@h);

--Results
-- 0

DECLARE @temp table ([name] varchar(10), [geom] geography);

INSERT INTO @temp values ('Point', geography::STGeomFromText('POINT(
5 10)', 4326));
INSERT INTO @temp values ('LineString', geography::STGeomFromText(
'LINESTRING(13 5, 50 25)', 4326));
--Calculate the distance to a point on the LINESTRING.
--Display the number of dimensions for a geography object stored in a --table variable.
INSERT INTO @temp values ('Polygon', geography::STGeomFromText(
'POLYGON((47.653 -122.358, 47.649 -122.348, 47.658 -122.348, 47.658 -122.358, 47.653 -122.358))', 4326));

SELECT [name], [geom].STDimension() as [dim]
FROM @temp;

--Results
--name       dim
------------ -----------
--Point      0
--LineString 1
--Polygon    2

希望在此介绍的 SQL Server 2008 中的七种新数据类型能对您有所帮助。

Kelly Wilson 从事软件工程方面的工作已有 20 余年。他擅长的是 SQL Server、3D 图形、游戏以及颜色科学方面的应用程序。Kelly 目前是 Microsoft SQL Server 组的程序员。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.