SQL Prompt根据数据库的对象名称、语法和代码片段自动进行检索,为用户提供合适的代码选择。自动脚本设置使代码简单易读--当开发者不大熟悉脚本时尤其有用。SQL Prompt安装即可使用,能大幅提高编码效率。此外,用户还可根据需要进行自定义,使之以预想的方式工作。
本教程说明了SQL_VARIANT数据类型的“怪癖”,以及为什么最好调查SQL Prompt何时提醒您使用它。如果在使用之前将其显式转换为真实类型,那么将数据存储为SQL_VARIANT才是唯一安全的。
sql_variant数据类型来自几个不同数据类型的值,并由SQL Server在内部使用。它不是SQL标准的一部分,在关系数据库中的用途有限。需要小心处理它,因为它的误用会导致难以追踪的性能问题和bug。sql_variant不能直接传递给某些SQL运算符和函数,例如LIKE、SUM()或者AVG(),并且在比较或表达式中使用时会产生误导性结果。除二进制数据外,它不能通过ODBC返回到应用程序。
SQL Server是一种强类型语言,这样做是为了确保数据完整性、高效存储和有效检索。由于这个原因,使用sql_variant有点奇怪,因此通过不明智地使用它会无意中造成问题也就不足为奇了。出于这些原因,SQL Prompt强制执行“最佳实践”代码分析规则(BP024),该规则将提醒您使用sql_variant数据类型。
与许多“最佳实践”规则一样,这些建议有时听起来像是告诉人们在拿着剪刀时不要跑。在这种情况下,只有在使用数据sql_variant之前将其显式转换为真实类型,才能将数据存储为安全。
为什么有sql_variant?
sql_variant数据类型是在微软从Sybase开发的SQL Server时首次引入的。他们需要能够从微软首次进入数据库市场的Microsoft Access将数据库导入SQL Server,该市场支持变体数据类型。它仍然在SQL Server内部用于系统存储过程的参数以及扩展属性等数据。
sql_variant倾向于作为用户定义函数返回的列、变量、参数或值的catch-all数据类型。它最多可以容纳8000个字节,并且可以存储基本数据类型,如整数、小数、字符串和日期。它不能存储其他一些数据类型,例如(MAX)数据类型、CLR数据类型或XML。
有时,sql_variant可能是一个有用的工具,例如在处理不一致或未指定的数据类型时,这通常是因为数据库支持允许用户定义数据的应用程序。
它存储所包含的值的基本数据类型,因此当它用作中介时,强制执行数据类型之间的所有转换规则。您可以使用数据类型函数检索此基本数据类型sql_variant_property():
DECLARE @MyVariant SQL_VARIANT = '2.3657'
SELECT SQL_VARIANT_PROPERTY(@MyVariant,'BaseType')在这种情况下返回varchar。这里还有一些其他有用的属性:Precision、Scale、TotalBytes、Collation和MaxLength。如果要从sql_variant生成主键,则TotalBytes参数使该函数可用作初步检查,因为主键(或索引)的总大小限制为900字节。
顺便提一句,您可以在任何数据类型上使用此函数。例如:
SELECT SQL_VARIANT_PROPERTY(N'Béoáed mac Ocláin','collation')聚合
让我们看看如果我们尝试聚合sql_variant列会发生什么。为了简单起见,我们将从派生表中执行此操作。
SELECT Sum(ValueAsVariant)
FROM
(
VALUES (Convert(SQL_VARIANT, 'one'), 1, Convert(SQL_VARIANT, 1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f (ValueAsString, ValueAsInt, ValueAsVariant);我们看到一个错误:
Msg 8117,Level 16,State 1,Line 3操作数数据类型sql_variant对sum运算符无效。
而如果我们先显式地转换为数字(int、numeric等等),它工作正常。
SELECT Sum(Convert(NUMERIC(9,4), ValueAsVariant))
-- try sum, avg, stdev, stdevp, var, varp, or string_agg
FROM
(
VALUES (Convert(SQL_VARIANT, 'one'), 1, Convert(SQL_VARIANT, 1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f (ValueAsString, ValueAsInt, ValueAsVariant);在max()和min()聚合函数似乎很好地工作的sql_variant数据类型,所以不可能有技术问题阻止其他函数工作。
比较
您不能用LIKE过滤sql_variant列,因为LIKE它不支持sql_variant参数。
SELECT f.ValueAsVariant, f.ValueAsInt, f.ValueAsString
FROM
(
VALUES (Convert(SQL_VARIANT,'one'), 1, Convert(VARCHAR(5),1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f(ValueAsVariant, ValueAsInt, ValueAsString)
WHERE ValueAsVariant like 't%'错误时候这样的:
Msg 8116,Level 16,State 1,Line 4
参数数据类型sql_variant对于LIKE函数的参数1无效。
实际上,没有任何字符串函数接受sql_variant,并且不会尝试对字符串进行隐式转换。相反,他们只是拒绝参数。相反,如果我们声明它到底是什么类型的数据类型,它的工作原理如下:
SELECT f.ValueAsVariant, f.ValueAsInt, f.ValueAsString
FROM
(
VALUES (Convert(SQL_VARIANT,'one'), 1, Convert(NVARCHAR(5),1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f(ValueAsVariant, ValueAsInt, ValueAsString)
WHERE Convert(VARCHAR(20),ValueAsVariant) like 't%'除非您sql_variant在WHERE子句中显式转换数据类型,否则在隐藏在漫长且曲折的过程中时,可能会得到不正确的结果,其原因很难检测到。例如,这只返回第4行和第5行,这是您所期望的:
DECLARE @ParameterAsINT INT
SELECT @ParameterAsINT = 3
SELECT f.ValueAsString, f.ValueAsInt, f.ValueAsVariant
FROM
(
VALUES ('one', 1, Convert(SQL_VARIANT, 1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f (ValueAsString, ValueAsInt, ValueAsVariant)
WHERE ValueAsVariant > @ParameterAsInt但是,如果我们将参数更改为a sql_variant并为其提供字符串值,会发生什么?
DECLARE @ParameterAsVariant sql_variant
SELECT @ParameterAsVariant ='3'
SELECT f.ValueAsString, f.ValueAsInt, f.ValueAsVariant
FROM
(
VALUES ('one', 1, Convert(SQL_VARIANT, 1)),
('two', 2, 2),
('three', 3, 3),
('four', 4, 4),
('five', 5, 5)
) AS f (ValueAsString, ValueAsInt, ValueAsVariant)
WHERE ValueAsVariant > @ParameterAsVariant现在它返回所有你可能不会想到的行。这里的问题是,为了评估表达式,SQL Server检查它的基类型或类型族,并将其与我们的变量类型进行比较。sql_variant的基类型系列可以是Unicode、精确数字、近似数字、日期和时间、二进制或唯一标识符,我们的ValueAsVariant列包含精确数字。
在第一个仅返回第4行和第5行的示例中,我们的参数类型与ValueAsVariant列的类型属于同一族。SQL Server执行隐式转换,代码可以正常工作。但是,在第二个示例中,我们使用sql_variant带有字符串值的参数,其中@ParameterAsVariant包含Unicode。而不是将Unicode类型隐式转换为精确数字(即“高级”数据类型),SQL Server判断高级数据类型为“更大”,因此我们的搜索条件对每一行的计算结果为true。
这显然是sql_variant的一个怪癖。如果我们比较完全相同的基本数据类型的两个sql_variant值,它将“工作”。如果我们将sql_variant与同一系列中的另一种数据类型进行比较,隐式转换将允许它工作。除此之外,一切都不可能了。
ODBC支持
ODBC不完全支持sql_variant。当使用与包含sql_variant类型的表的连接时,您会注意到这一点,因为sql_variant当您使用Microsoft OLE DB Provider for ODBC(MSDASQL)时,列中的数据将作为二进制数据(例如0x32303931)返回。
限制在索引中使用sql_variant
sql_variant仅当索引的总长度小于900字节的最大值时,才可以在索引中包含列。这意味着如果值的长度超过900个字节,则索引sql_variant列上的插入操作将失败。如果我们创建表或表变量:
DECLARE @MyTableVariable TABLE (MyProperty sql_Variant PRIMARY KEY)我们得到一个警告:
警告!聚簇索引的最大密钥长度为900字节。索引“PK __#B2961DC__8E45D1198BEEA325”的最大长度为8016字节。对于某些大值组合,插入或更新操作将失败。
如果我们忽略警告......
DECLARE @MyTableVariable TABLE (MyProperty sql_Variant PRIMARY KEY)
INSERT INTO @MyTableVariable (MyProperty)
VALUES (N'Abbán moccu Corbmaic'),
(N'Abel of Reims'),
(N'Buíte [Boetius] mac Brónaig'),
(N'Buriana'),
(Replicate(N'Caillín [Caillén] mac Niataig Crom mac Feradaig, Comgall mac Sétnai, Comgán mac Dá Cherda, Commán mac Fáelchon, Mo ChommócCrónán of Balla, see Mo Chua mac Bécáin',3))我们得到错误......
Ms 1946,Level 16,State 3,Line 45
操作失败。索引“PK __#B72883F__8E45D1191C112AAE”的长度为980字节的索引条目超过了聚簇索引的最大长度900字节。
结论
sql_variant在用户表中使用数据类型是一种代码味道,因为它将非类型化数据类型引入强类型语言,并且需要进行调查,就像您在家闻到烧焦的味道一样。它可能只是烧烤,但它可能更令人担忧。
sql_variant 具有合法用途,但总有一种风险,即尽管您可能确切知道如何使用它们,但是其他必须维护或调试代码的人可能不知道,并且如果您除了纯粹使用它们之外做任何其他事情,则最有可能导致问题用于存储。
你绝不能依赖sql_variant的隐式转换,因为它经常失败,要么是因为它没有实现,要么是因为它是奇怪的。相反,在进行比较、表达式或聚合之前,将它们显式转换为SQL数据类型。如果你不完全确定你理解了最后一句话,那么最好永远不要使用sql_variant。