日期:2014-04-16  浏览次数:20601 次

简介

在本月的专栏中,我将讨论那些希望编写可以在多种语言之间移植的 Transact-SQL 代码的开发人员所面临的一些问题。在 SQL Server 7.0 中引入了 Unicode 数据类型、排序规则以及其他各种国际化和本地化功能,从而与以前的版本相比,使得编写可以在多种语言之间移植的 Transact-SQL 代码变得更加容易。SQL Server 2000 通过添加列级别的排序规则等内容增强了 SQL Server 7.0 中的国际化功能,因此可供开发人员使用的工具已经随着时间的推移而变得越来越好。

SQL Server 联机图书 (BOL) 包含一个简短的部分,内容是关于编写可以在多种语言之间移植的代码的,您应该首先阅读一下。如果您尚未阅读 BOL 主题“Writing International Transact-SQL Statements”(编写国际性 Transact-SQL 语句),我建议您首先阅读一下,然后再阅读本专栏。

日期

在开始讨论编写可移植代码时,让我们首先探讨一下以可移植的方式处理日期时所涉及到的问题。尽管人们通常将编写可移植的数据库代码与以不同方式处理常规字符串联系起来,但一开始编写可移植的日期处理代码是一种很好的方法,因为它基本上只需知道一些简单的规则和选项就可以了。

有关以可移植的方式在 Transact-SQL 中处理日期方面的内容,我想谈的第一点就是:避免就日期格式或一周的第一天进行假设。第一点似乎是很显然的 — 大多数人都知道全世界的日期格式是不一样的。然而,第二点可能不是那么明显。在不同的文化中,将哪一天视为一周的第一天是不同的。例如,请看以下代码:

set language us_english
select datepart (dw, '20060606')
set language british
select datepart (dw, '20060606')

它将返回:

Changed language setting to us_english.
-----------
3
Changed language setting to British.
-----------
2

这两个查询返回不同的结果,这是因为在美国和英国,将哪一天视为一周的第一天是不同的。您可以通过查询系统函数 @@datefirst 来核实这一点:

Changed language setting to us_english.
----
7
Changed language setting to British.
----
1

您可以通过 SET DATEFIRST 命令更改将哪一天视为一周的第一天(从而更改 @@datefirst 返回的值)。默认设置随文化的不同而异。

对于以日期不可知的方式编写使用 datepart(dw,...) 的代码,有两种简单方法。第一种方法是在任何过程的开始简单地调用 SET DATEFIRST,以便就一周的第一天进行假设。这将在相应过程中覆盖 DATEFIRST 的连接设置。第二种方法是规格化 @@datefirst 返回的值,以使其与语言无关。下面是一些说明如何做到这一点的代码。

declare @ndf_dw int
set language us_english
select @ndf_dw = (@@datefirst + datepart(dw, '20060606')) % 7
select @ndf_dw, datepart(dw, '20060606')
set language british
select @ndf_dw = (@@datefirst + datepart(dw, '20060606')) % 7
select @ndf_dw, datepart(dw, '20060606')

如果您运行上述代码,您将看到 datepart(dw,...) 返回的值随当前语言设置的不同而不同,但是为 @ndf_dw 返回的值是相同的。

日期名称

Transact-SQL 日期函数为月份和星期几返回的名称将随语言设置的不同而不同。正如联机图书中所指出的,这意味着您应该使用数值日期部分而不是名称字符串来进行月份和星期几的比较。例如:

select DATENAME(dw,'20060606')

如果默认语言被设置为美国英语,则返回:

------------------------------
Tuesday

但如果默认语言被设置为法语,则返回:

------------------------------
Mardi

很明显,如果您编写的代码依赖于返回的特定语言的日期字符串,则当默认语言被设置为其他某种语言时,返回的字符串将可能破坏您的代码。使用数值日期部分可以缓解这一问题。

显示日期值

当然,在提供数据以供用户使用或显示时,您将希望使用日期部分的名称,因为它们通常会更有意义。对于基本的两层应用程序,最简单的方法可能是让 SQL Server 为您返回这些名称,而不是通过客户端应用程序中的其他某些方式来转换这些名称。如果登录语言的设置不正确,这显然不能按预期方式工作。

对于涉及到中间层或连接池的更复杂环境,假设特定最终用户的登录语言设置将是正确的可能不切实际。在这些情况下,一种更好的方法是从 T-SQL 代码中返回明确的二进制日期/时间值,从而可以在客户端应用程序中将其转换为有意义的字符串。

比较和存储日期

当在 DML 或比较语句中指定日期时,请务必使用在所有语言中都具有相同含义的常量。在客户端应用程序中做到这一点的最简单且最安全的方法是,将此类语句作为 RPC 事件提交给服务器(例如,使用托管代码中的 SqlCommand 对象),并且使用显式参数传递日期。从客户端应用程序传递的并且以其内置格式编码的参数在本质上是明确的。

另一种选择是使用 ODBC 转义子句来表示在各种语言中一致的特定格式。应用程序不需要是 ODBC 应用程序,因为转义子句由服务器解释。前面提到过的联机图书主题“Writing International Transact-SQL Statements”(编写国际性 Transact-SQL 语句)详细介绍了受支持的转义子句。

应用程序还可以使用不带分隔符的、各个部分按重要性从高到低的顺序(即 yyyymmdd)排序的字符串。无论语言设置如何,SQL Server 都将正确地解释具有这种格式的日期。

另一种选择是使用 CONVERT() Transact-SQL 函数及其样式参数,以便显式指定转换日期的目标格式或源格式。这将消除日期/时间字符串的歧义,并且可以在各种语言之间移植。

Unicode

在编写可以在各种语言之间移植的 Transact-SQL 代码时,最简单最直接的方法是使用 Unicode 数据类型来表示和存储字符数据。这意味着您应该使用 nchar 来代替 char,使用 nvarchar 来代替 varchar,以及使用 ntext 来代替 text。尽管使用 Unicode 数据类型会在可存储的字符串的长度方面有所限制,并且可能比使用非 Unicode 类型稍微慢一些和麻烦一些,但它仍不失为处理语言可移植性问题的最简单方法,并且是唯一的、无须考虑其他编码注意事项就能行得通的解决方案。

在采用这一方法时,必须按照联机图书主题“Using Unicode Data”(使用 Unicode 数据)中所规定的那样,小心地在 Unicode 字符串常量前面加上一个大写字母 N 前缀。正如知识库文章 239530, INF:Unicode String Constants in SQL Server Require N Prefix 所指出的,不这样做将导致 SQL Server 在使用相应的字符串之前将其转换为当前数据库的非 Unicode 代码页。下面是一个示例:

-- Assumes the default code page