Han*_*non 21 sql-server collation metadata
我试图UNPIVOT
在sys.databases
2005 年到 2012 年的各种版本的 SQL Server 中包含的各种列上运行。
在UNPIVOT
与以下错误消息失败:
Msg 8167, Level 16, State 1, Line 48
“CompatibilityLevel”列的类型与 UNPIVOT 列表中指定的其他列的类型冲突。
T-SQL:
DECLARE @dbname SYSNAME;
SET @dbname = DB_NAME();
SELECT [Database] = unpvt.DatabaseName
, [Configuration Item] = unpvt.OptionName
, [Configuration Value] = unpvt.OptionValue
FROM (
SELECT
DatabaseName = name
, RecoveryModel = CONVERT(VARCHAR(50), d.recovery_model_desc)
, CompatibilityLevel = CONVERT(VARCHAR(50), CASE d.[compatibility_level] WHEN 70 THEN 'SQL Server 7' WHEN 80 THEN 'SQL Server 2000' WHEN 90 THEN 'SQL Server 2005' WHEN 100 THEN 'SQL Server 2008' WHEN 110 THEN 'SQL Server 2012' WHEN 120 THEN 'SQL Server 2014' ELSE 'UNKNOWN' END)
, AutoClose = CONVERT(VARCHAR(50), CASE d.is_auto_close_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoCreateStatistics = CONVERT(VARCHAR(50), CASE d.is_auto_create_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoShrink = CONVERT(VARCHAR(50), CASE d.is_auto_shrink_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatistics = CONVERT(VARCHAR(50), CASE d.is_auto_update_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatisticsAsynch = CONVERT(VARCHAR(50), CASE d.is_auto_update_stats_async_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CloseCursorOnCommit = CONVERT(VARCHAR(50), CASE d.is_cursor_close_on_commit_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DefaultCursor = CONVERT(VARCHAR(50), CASE d.is_local_cursor_default WHEN 1 THEN 'LOCAL' ELSE 'GLOBAL' END)
, ANSINULL_Default = CONVERT(VARCHAR(50), CASE d.is_ansi_null_default_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSINULLS_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_nulls_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIPadding_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_padding_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIWarnings_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_warnings_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ArithmeticAbort_Enabled = CONVERT(VARCHAR(50), CASE d.is_arithabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ConcatNullYieldsNull = CONVERT(VARCHAR(50), CASE d.is_concat_null_yields_null_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CrossDBOwnerChain = CONVERT(VARCHAR(50), CASE d.is_db_chaining_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DateCorrelationOptimized = CONVERT(VARCHAR(50), CASE d.is_date_correlation_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, NumericRoundAbort = CONVERT(VARCHAR(50), CASE d.is_numeric_roundabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [Parameterization] = CONVERT(VARCHAR(50), CASE d.is_parameterization_forced WHEN 0 THEN 'SIMPLE' ELSE 'FORCED' END)
, QuotedIdentifiers_Enabled = CONVERT(VARCHAR(50), CASE d.is_quoted_identifier_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RecursiveTriggers_Enabled = CONVERT(VARCHAR(50), CASE d.is_recursive_triggers_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [TrustWorthy] = CONVERT(VARCHAR(50), CASE d.is_trustworthy_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, VARDECIMAL_Storage = CONVERT(VARCHAR(50), 'TRUE')
, PageVerify = CONVERT(VARCHAR(50), page_verify_option_desc )
, BrokerEnabled = CONVERT(VARCHAR(50), CASE d.is_broker_enabled WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DatabaseReadOnly = CONVERT(VARCHAR(50), CASE d.is_read_only WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, EncryptionEnabled = CONVERT(VARCHAR(50), CASE d.is_encrypted WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RestrictedAccess = CONVERT(VARCHAR(50), user_access_desc)
, Collation = CONVERT(VARCHAR(50), d.collation_name)
FROM sys.databases d
WHERE name = @dbname
OR @dbname IS NULL
) src
UNPIVOT
(
OptionValue FOR OptionName IN
(
RecoveryModel
, CompatibilityLevel
, AutoClose
, AutoCreateStatistics
, AutoShrink
, AutoUpdateStatistics
, AutoUpdateStatisticsAsynch
, CloseCursorOnCommit
, DefaultCursor
, ANSINULL_Default
, ANSINULLS_Enabled
, ANSIPadding_Enabled
, ANSIWarnings_Enabled
, ArithmeticAbort_Enabled
, ConcatNullYieldsNull
, CrossDBOwnerChain
, DateCorrelationOptimized
, NumericRoundAbort
, [Parameterization]
, QuotedIdentifiers_Enabled
, RecursiveTriggers_Enabled
, [TrustWorthy]
, VARDECIMAL_Storage
, PageVerify
, BrokerEnabled
, DatabaseReadOnly
, EncryptionEnabled
, RestrictedAccess
, Collation
)
) AS unpvt;
Run Code Online (Sandbox Code Playgroud)
这旨在为给定的数据库提供格式良好的数据库选项列表,类似于:
+----------+----------------------------+----------------------------+
| Database | Configuration Item | Value in Use |
+----------+----------------------------+----------------------------+
| master | RecoveryModel | SIMPLE |
| master | CompatibilityLevel | SQL Server 2008 |
| master | AutoClose | FALSE |
| master | AutoCreateStatistics | TRUE |
| master | AutoShrink | FALSE |
| master | AutoUpdateStatistics | TRUE |
| master | AutoUpdateStatisticsAsynch | FALSE |
| master | CloseCursorOnCommit | FALSE |
| master | DefaultCursor | GLOBAL |
| master | ANSINULL_Default | FALSE |
| master | ANSINULLS_Enabled | FALSE |
| master | ANSIPadding_Enabled | FALSE |
| master | ANSIWarnings_Enabled | FALSE |
| master | ArithmeticAbort_Enabled | FALSE |
| master | ConcatNullYieldsNull | FALSE |
| master | CrossDBOwnerChain | TRUE |
| master | DateCorrelationOptimized | FALSE |
| master | NumericRoundAbort | FALSE |
| master | Parameterization | SIMPLE |
| master | QuotedIdentifiers_Enabled | FALSE |
| master | RecursiveTriggers_Enabled | FALSE |
| master | TrustWorthy | TRUE |
| master | VARDECIMAL_Storage | TRUE |
| master | PageVerify | CHECKSUM |
| master | BrokerEnabled | FALSE |
| master | DatabaseReadOnly | FALSE |
| master | EncryptionEnabled | FALSE |
| master | RestrictedAccess | MULTI_USER |
| master | Collation | Latin1_General_CI_AS_KS_WS |
+----------+----------------------------+----------------------------+
Run Code Online (Sandbox Code Playgroud)
当我在带有Latin1_General_CI_AS_KS_WS
排序规则的服务器中运行它时,语句成功。如果我修改 T-SQL 以便某些字段具有COLLATE
子句,它将在具有其他排序规则的服务器上运行。
适用于除排序规则以外的服务器的代码Latin1_General_CI_AS_KS_WS
是:
DECLARE @dbname SYSNAME;
SET @dbname = DB_NAME();
SELECT [Database] = unpvt.DatabaseName
, [Configuration Item] = unpvt.OptionName
, [Configuration Value] = unpvt.OptionValue
FROM (
SELECT
DatabaseName = name
, RecoveryModel = CONVERT(VARCHAR(50), d.recovery_model_desc) COLLATE SQL_Latin1_General_CP1_CI_AS
, CompatibilityLevel = CONVERT(VARCHAR(50), CASE d.[compatibility_level] WHEN 70 THEN 'SQL Server 7' WHEN 80 THEN 'SQL Server 2000' WHEN 90 THEN 'SQL Server 2005' WHEN 100 THEN 'SQL Server 2008' WHEN 110 THEN 'SQL Server 2012' WHEN 120 THEN 'SQL Server 2014' ELSE 'UNKNOWN' END)
, AutoClose = CONVERT(VARCHAR(50), CASE d.is_auto_close_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoCreateStatistics = CONVERT(VARCHAR(50), CASE d.is_auto_create_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoShrink = CONVERT(VARCHAR(50), CASE d.is_auto_shrink_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatistics = CONVERT(VARCHAR(50), CASE d.is_auto_update_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatisticsAsynch = CONVERT(VARCHAR(50), CASE d.is_auto_update_stats_async_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CloseCursorOnCommit = CONVERT(VARCHAR(50), CASE d.is_cursor_close_on_commit_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DefaultCursor = CONVERT(VARCHAR(50), CASE d.is_local_cursor_default WHEN 1 THEN 'LOCAL' ELSE 'GLOBAL' END)
, ANSINULL_Default = CONVERT(VARCHAR(50), CASE d.is_ansi_null_default_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSINULLS_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_nulls_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIPadding_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_padding_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIWarnings_Enabled = CONVERT(VARCHAR(50), CASE d.is_ansi_warnings_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ArithmeticAbort_Enabled = CONVERT(VARCHAR(50), CASE d.is_arithabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ConcatNullYieldsNull = CONVERT(VARCHAR(50), CASE d.is_concat_null_yields_null_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CrossDBOwnerChain = CONVERT(VARCHAR(50), CASE d.is_db_chaining_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DateCorrelationOptimized = CONVERT(VARCHAR(50), CASE d.is_date_correlation_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, NumericRoundAbort = CONVERT(VARCHAR(50), CASE d.is_numeric_roundabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [Parameterization] = CONVERT(VARCHAR(50), CASE d.is_parameterization_forced WHEN 0 THEN 'SIMPLE' ELSE 'FORCED' END)
, QuotedIdentifiers_Enabled = CONVERT(VARCHAR(50), CASE d.is_quoted_identifier_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RecursiveTriggers_Enabled = CONVERT(VARCHAR(50), CASE d.is_recursive_triggers_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [TrustWorthy] = CONVERT(VARCHAR(50), CASE d.is_trustworthy_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, VARDECIMAL_Storage = CONVERT(VARCHAR(50), 'TRUE')
, PageVerify = CONVERT(VARCHAR(50), page_verify_option_desc ) COLLATE SQL_Latin1_General_CP1_CI_AS
, BrokerEnabled = CONVERT(VARCHAR(50), CASE d.is_broker_enabled WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DatabaseReadOnly = CONVERT(VARCHAR(50), CASE d.is_read_only WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, EncryptionEnabled = CONVERT(VARCHAR(50), CASE d.is_encrypted WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RestrictedAccess = CONVERT(VARCHAR(50), user_access_desc) COLLATE SQL_Latin1_General_CP1_CI_AS
, Collation = CONVERT(VARCHAR(50), d.collation_name)
FROM sys.databases d
WHERE name = @dbname
OR @dbname IS NULL
) src
UNPIVOT
(
OptionValue FOR OptionName IN
(
RecoveryModel
, CompatibilityLevel
, AutoClose
, AutoCreateStatistics
, AutoShrink
, AutoUpdateStatistics
, AutoUpdateStatisticsAsynch
, CloseCursorOnCommit
, DefaultCursor
, ANSINULL_Default
, ANSINULLS_Enabled
, ANSIPadding_Enabled
, ANSIWarnings_Enabled
, ArithmeticAbort_Enabled
, ConcatNullYieldsNull
, CrossDBOwnerChain
, DateCorrelationOptimized
, NumericRoundAbort
, [Parameterization]
, QuotedIdentifiers_Enabled
, RecursiveTriggers_Enabled
, [TrustWorthy]
, VARDECIMAL_Storage
, PageVerify
, BrokerEnabled
, DatabaseReadOnly
, EncryptionEnabled
, RestrictedAccess
, Collation
)
) AS unpvt;
Run Code Online (Sandbox Code Playgroud)
观察到的行为是以下字段不遵守服务器排序规则或数据库排序规则;它们总是以Latin1_General_CI_AS_KS_WS
整理的形式呈现。
在 SQL Server 2012 上,我们可以sys.sp_describe_first_result_set
轻松获取有关从特定查询返回的列的元数据。我使用以下内容来确定排序规则不匹配:
DECLARE @cmd NVARCHAR(MAX);
SET @cmd = '
SELECT
DatabaseName = CONVERT(VARCHAR(50), d.name)
, RecoveryModel = CONVERT(VARCHAR(50), d.recovery_model_desc)
, Collation = CONVERT(VARCHAR(50), d.collation_name)
FROM sys.databases d
WHERE name = DB_NAME();
';
EXEC sp_describe_first_result_set @command = @cmd;
Run Code Online (Sandbox Code Playgroud)
结果:
为什么这些列的排序规则是静态设置的?
Aar*_*and 17
微软官方说法:
一些包含预定义字符串(如类型、系统描述和常量)的列始终固定为特定的排序规则 –
Latin1_General_CI_AS_KS_WS
。这与实例/数据库整理无关。原因是这是系统元数据(不是用户元数据),基本上这些字符串不区分大小写(如关键字,所以总是拉丁文)。
系统表中包含用户元数据(如对象名称、列名称、索引名称、登录名称等)的其他列采用实例或数据库整理。在安装 SQL Server 时,如果进行实例整理,以及在创建数据库时进行数据库整理,这些列会进行整理以进行正确的整理。
你问(强调我的):
为什么这些列的排序规则是静态设置的?
静态设置某些列的原因是,查询无需担心服务器或数据库整理(更重要的是:案例敏感度)即可正常工作。无论排序如何,此查询将始终有效:
SELECT * FROM sys.databases WHERE state_desc = N'ONLine';
Run Code Online (Sandbox Code Playgroud)
而如果服务器排序规则区分大小写,上面的查询将返回 0 行,就像这样:
SELECT * FROM sys.databases
WHERE state_desc COLLATE Albanian_BIN = N'ONLine';
Run Code Online (Sandbox Code Playgroud)
例如,如果您使用SQL_Estonian_CP1257_CS_AS
排序规则安装 SQL Server 实例,则运行以下命令:
SELECT name, collation_name
FROM master.sys.all_columns
WHERE collation_name IS NOT NULL
AND [object_id] = OBJECT_ID(N'sys.databases');
Run Code Online (Sandbox Code Playgroud)
您将看到以下结果(或类似结果,具体取决于您的 SQL Server 版本):
name SQL_Estonian_CP1257_CS_AS
collation_name SQL_Estonian_CP1257_CS_AS
user_access_desc Latin1_General_CI_AS_KS_WS
state_desc Latin1_General_CI_AS_KS_WS
snapshot_isolation_state_desc Latin1_General_CI_AS_KS_WS
recovery_model_desc Latin1_General_CI_AS_KS_WS
page_verify_option_desc Latin1_General_CI_AS_KS_WS
log_reuse_wait_desc Latin1_General_CI_AS_KS_WS
default_language_name SQL_Estonian_CP1257_CS_AS
default_fulltext_language_name SQL_Estonian_CP1257_CS_AS
containment_desc Latin1_General_CI_AS_KS_WS
delayed_durability_desc SQL_Estonian_CP1257_CS_AS
Run Code Online (Sandbox Code Playgroud)
现在,演示继承数据库排序规则的元数据视图,而不是从 master 数据库继承服务器排序规则:
CREATE DATABASE server_collation;
GO
CREATE DATABASE albanian COLLATE Albanian_BIN;
GO
CREATE DATABASE hungarian COLLATE Hungarian_Technical_100_CS_AI;
GO
SELECT name, collation_name
FROM server_collation.sys.all_columns
WHERE collation_name IS NOT NULL
AND object_id = -391; -- sys.columns
SELECT name, collation_name
FROM albanian.sys.all_columns
WHERE collation_name IS NOT NULL
AND object_id = -391; -- sys.columns
SELECT name, collation_name
FROM hungarian.sys.all_columns
WHERE collation_name IS NOT NULL
AND object_id = -391; -- sys.columns
Run Code Online (Sandbox Code Playgroud)
结果:
server_collation
----------------
name SQL_Estonian_CP1257_CS_AS
collation_name SQL_Estonian_CP1257_CS_AS
generated_always_type_desc Latin1_General_CI_AS_KS_WS
encryption_type_desc Latin1_General_CI_AS_KS_WS
encryption_algorithm_name Latin1_General_CI_AS_KS_WS
column_encryption_key_database_name SQL_Estonian_CP1257_CS_AS
albanian
----------------
name Albanian_BIN
collation_name Albanian_BIN
generated_always_type_desc Latin1_General_CI_AS_KS_WS
encryption_type_desc Latin1_General_CI_AS_KS_WS
encryption_algorithm_name Latin1_General_CI_AS_KS_WS
column_encryption_key_database_name Albanian_BIN
hungarian
----------------
name Hungarian_Technical_100_CS_AI
collation_name Hungarian_Technical_100_CS_AI
generated_always_type_desc Latin1_General_CI_AS_KS_WS
encryption_type_desc Latin1_General_CI_AS_KS_WS
encryption_algorithm_name Latin1_General_CI_AS_KS_WS
column_encryption_key_database_name Hungarian_Technical_100_CS_AI
Run Code Online (Sandbox Code Playgroud)
因此您可以看到,在这种情况下,有几个列继承了数据库排序规则,而其他列则固定为这种“通用”Latin1 排序规则,这意味着它用于将某些名称和属性与上述区分大小写问题隔离开来。
如果您尝试执行 a UNION
,例如:
SELECT name FROM albanian.sys.columns
UNION ALL
SELECT name FROM server_collation.sys.columns;
Run Code Online (Sandbox Code Playgroud)
您收到此错误:
消息 451,级别 16,状态 1
无法解决出现在 SELECT 语句列 1 中的 UNION ALL 运算符中“Albanian_BIN”和“SQL_Estonian_CP1257_CS_AS”之间的排序规则冲突。
同样,如果您尝试执行PIVOT
或UNPIVOT
,规则更加严格(输出类型必须全部匹配准确,而不是仅仅是兼容的),但错误信息远不如有帮助的,甚至误导:
消息 8167,级别 16,状态 1
列“列名称”的类型与 UNPIVOT 列表中指定的其他列的类型冲突。
您需要COLLATE
在查询中使用显式子句来解决这些错误。例如,上面的联合可以是:
SELECT name COLLATE Latin1_General_CI_AS_KS_WS
FROM albanian.sys.columns
UNION ALL
SELECT name COLLATE Latin1_General_CI_AS_KS_WS
FROM server_collation.sys.columns;
Run Code Online (Sandbox Code Playgroud)
这可能导致问题的唯一一次是,如果强制排序但不包含相同的字符表示,或者如果使用排序并且强制排序使用与源不同的排序顺序,您将获得令人困惑的输出。
您在系统目录视图中看到的有关各个字段的排序规则的行为是每个字段的定义方式和排序规则优先级的结果。
在查看 时sys.databases
,重要的是要记住它不是一张桌子。虽然在过去(我认为以 SQL Server 2000 结束)这些是系统目录表,但它们现在是系统目录视图。因此,其中的信息来源不一定来自当前数据库上下文(或在处理完全限定的对象时,例如 指定数据库的上下文master.sys.databases
)。
特别处理sys.databases
,一些字段来自[master]
数据库(它是使用基于实例的默认排序规则的排序规则创建的——即服务器级排序规则),一些字段是表达式(即CASE
语句),有些字段是来自来自“隐藏”来源:[mssqlsystemresource]
数据库。并且[mssqlsystemresource]
数据库具有以下排序规则:Latin1_General_CI_AS_KS_WS
.
该name
字段源自 中的name
字段master.sys.sysdbreg
。所以这个字段应该总是在[master]
数据库的排序规则中,这将再次匹配服务器的排序规则。
但是,以下字段sys.databases
来自[name]
in 字段[mssqlsystemresource].[sys].[syspalvalues]
:
这些字段应始终具有Latin1_General_CI_AS_KS_WS
.
collation_name
但是,该字段来自以下表达式:
CONVERT(nvarchar(128),
CASE
WHEN serverproperty('EngineEdition')=5
AND [master].[sys].[sysdbreg].[id] as [d].[id]=(1)
THEN serverproperty('collation')
ELSE collationpropertyfromid(
CONVERT(int,
isnull([master].[sys].[sysobjvalues].[value] as [coll].[value],
CONVERT_IMPLICIT(sql_variant,DBPROP.[cid],0)
),
0),'name')
END,
0)
Run Code Online (Sandbox Code Playgroud)
这就是排序规则优先级开始出现的地方。这里的两个输出选项都是系统函数:serverproperty()
和collationpropertyfromid()
。此表达式的排序规则被视为“强制默认”:
任何 Transact-SQL 字符串变量、参数、文字或目录内置函数的输出,或不接受字符串输入但产生字符串输出的内置函数。
如果对象是在用户定义的函数、存储过程或触发器中声明的,则会为该对象分配在其中创建函数、存储过程或触发器的数据库的默认排序规则。如果对象是批量声明的,则对象被分配当前数据库的默认排序规则以进行连接。
根据第 2 段,由于sys.databases
是master
数据库中存在的视图,因此它采用master
数据库(不是当前数据库)的整理。
该state_desc
字段也是一个表达式:
CASE
WHEN serverproperty('EngineEdition')=5
AND [Expr1081]=(1)
THEN N'RESTORING'
ELSE
CASE
WHEN serverproperty('EngineEdition')=5
AND CONVERT(bit,
[master].[sys].[sysdbreg].[status] as [d].[status]&(128),
0)=(1)
THEN N'COPYING'
ELSE
CASE
WHEN serverproperty('EngineEdition')=5
AND CONVERT(bit,
[master].[sys].[sysdbreg].[status] as [d].[status]&(256),
0)=(1)
THEN N'SUSPECT'
ELSE [mssqlsystemresource].[sys].[syspalvalues].[name] as [st].[name]
END
END
END
Run Code Online (Sandbox Code Playgroud)
但是,这个表达式的排序规则是Latin1_General_CI_AS_KS_WS
。为什么?好吧,在这个表达式中引入了一些新的东西:对一个真实字段的引用:[mssqlsystemresource].[sys].[syspalvalues].[name]
在最后一个ELSE
子句中。列引用被视为“隐式”:
列引用。表达式的排序规则取自为表或视图中的列定义的排序规则。
当然,这带来了一个有趣的问题:这个表达式是否有可能根据CASE
计算方式返回不同的排序规则?文字将在定义此对象的数据库的排序规则中,但ELSE
条件返回应保留其原始排序规则的字段值。幸运的是,我们可以使用sys.dm_exec_describe_first_result_set动态管理函数来模拟一个测试:
-- Force ELSE condition
SELECT system_type_name, max_length, collation_name
FROM sys.dm_exec_describe_first_result_set(N'
DECLARE @A INT;
SET @A = -1;
SELECT CASE WHEN @A = 100 THEN N''All About the Benjamins''
ELSE [name]
END AS [Stuff]
FROM msdb.dbo.sysjobs
', NULL, NULL) rs
-- Force WHEN condition
SELECT system_type_name, max_length, collation_name
FROM sys.dm_exec_describe_first_result_set(N'
DECLARE @A INT;
SET @A = 100;
SELECT CASE WHEN @A = 100 THEN N''All About the Benjamins''
ELSE [name]
END AS [Stuff]
FROM msdb.dbo.sysjobs
', NULL, NULL) rs
-- Control test
SELECT system_type_name, max_length, collation_name
FROM sys.dm_exec_describe_first_result_set(N'
DECLARE @A INT;
SET @A = 100;
SELECT CASE WHEN @A = 100 THEN N''All About the Benjamins''
ELSE N''Whazzup, yo?!?!?''
END AS [Stuff]
', NULL, NULL) rs
Run Code Online (Sandbox Code Playgroud)
返回(在使用 的排序规则设置SQL_Latin1_General_CP1_CI_AS
但在具有排序规则的数据库中运行的实例上Japanese_Unicode_CI_AS
):
system_type_name max_length collation_name
---------------- ---------- --------------
nvarchar(128) 256 SQL_Latin1_General_CP1_CI_AS
nvarchar(128) 256 SQL_Latin1_General_CP1_CI_AS
nvarchar(23) 46 Japanese_Unicode_CI_AS
Run Code Online (Sandbox Code Playgroud)
在这里,我们看到引用字段的两个查询[msdb]
采用[msdb]
数据库的排序规则(作为系统 DB,由服务器排序规则确定)。
观察到的行为是以下字段不遵守服务器排序规则或数据库排序规则;它们总是以
Latin1_General_CI_AS_KS_WS
整理的形式呈现。
您的观察是准确的:这些字段的排序规则始终为Latin1_General_CI_AS_KS_WS
,无论服务器排序规则或数据库排序规则如何。原因是:整理优先。这些字段来自[mssqlsystemresource]
数据库中的一个表,除非被显式COLLATE
子句覆盖,否则将保留该初始排序规则,因为它具有最高优先级:
Explicit = 通过在表达式中使用 COLLATE 子句显式转换为特定排序规则的表达式。
显式优先于隐式。隐式优先于强制默认:
显式 > 隐式 > 强制默认
以及相关的问题:
为什么这些列的排序规则是静态设置的?
并不是它们是静态设置的,也不是其他字段在某种程度上是动态的。所有这些系统目录视图中的所有字段都按照相同的排序规则优先级规则运行。它们看起来比其他字段更“静态”的原因(即,即使您使用不同的默认排序规则安装 SQL Server,它们也不会更改,这反过来会使用该默认排序规则创建系统数据库)是[mssqlsystemresource]
数据库始终如一Latin1_General_CI_AS_KS_WS
在任何 SQL Server 安装中都有一个排序规则(或者它肯定会出现)。这是有道理的,因为否则 SQL Server 将难以在内部进行自我管理(即,如果用于内部逻辑的排序和比较规则根据安装而改变)。
如果您想在任何这些系统目录视图中查看任何字段的来源,只需执行以下操作:
执行查询从系统目录视图之一中选择一个字段(我建议一次只选择一个字段,因为即使对于单个字段,执行计划也非常大/复杂,并且将包含对许多字段的引用) t 选择):
SELECT recovery_model_desc FROM sys.databases;
Run Code Online (Sandbox Code Playgroud)Execution plan.xml
Execution plan.xml
选项卡<OutputList>
标签(通常应该在第 10 行和第 20 行之间)<ColumnReference>
标签。该标记中的属性应指向表中的特定字段,或指向计划中稍后定义的表达式。如果属性指向一个真实的字段,那么你就完成了,因为它拥有所有信息。以下是该recovery_model_desc
字段显示的内容:
<ColumnReference Database="[mssqlsystemresource]" Schema="[sys]"
Table="[syspalvalues]" Alias="[ro]" Column="name" />
Run Code Online (Sandbox Code Playgroud)如果属性指向一个表达式,例如如果您选择了该state_desc
字段,那么您最初会发现:
<ColumnReference Column="Expr1024" />
Run Code Online (Sandbox Code Playgroud)在这种情况下,您需要查看计划的其余部分,以了解Expr1024
它提出的定义或任何内容。请记住,可能有多个这样的引用,但定义不会在一个<OutputList>
块中。但是,它将有一个<ScalarOperator>
包含定义的兄弟元素。以下是该state_desc
字段显示的内容:
<ScalarOperator ScalarString="CASE WHEN serverproperty('EngineEdition')=5 AND [Expr1081]=(1) THEN N'RESTORING' ELSE CASE WHEN serverproperty('EngineEdition')=5 AND CONVERT(bit,[master].[sys].[sysdbreg].[status] as [d].[status]&(128),0)=(1) THEN N'COPYING' ELSE CASE WHEN serverproperty('EngineEdition')=5 AND CONVERT(bit,[master].[sys].[sysdbreg].[status] as [d].[status]&(256),0)=(1) THEN N'SUSPECT' ELSE [mssqlsystemresource].[sys].[syspalvalues].[name] as [st].[name] END END END">
Run Code Online (Sandbox Code Playgroud)也可以这样做来检查数据库级目录视图的源。对像这样的对象执行此操作sys.tables
将显示该name
字段来自[current_db].[sys].[sysschobjs]
(这就是它的排序规则与数据库的排序规则匹配的原因),而该lock_escalation_desc
字段来自[mssqlsystemresource].[sys].[syspalvalues]
(这就是它的排序规则为 的原因Latin1_General_CI_AS_KS_WS
)。
现在我们知道为什么 Collation Precedence 是什么以及它是如何工作的,让我们将这些知识应用于 UNPIVOT 查询。
对于UNPIVOT
操作,SQL Server 似乎真的更喜欢(意思是:要求)每个源字段的类型完全相同。通常“型”指的是基本类型(即VARCHAR
/ NVARCHAR
/ INT
/等),但这里的SQL Server也包括归类。考虑到排序规则控制的内容,这不应被视为不合理:VARCHAR 的字符集(即代码页),以及确定字符等价性和字符组合(即规范化)的语言规则。以下是关于什么是 Unicode“规范化”的 mimi-primer:
PRINT '---';
IF (N'aa' COLLATE Danish_Greenlandic_100_CI_AI = N'å' COLLATE Danish_Greenlandic_100_CI_AI)
PRINT 'Danish_Greenlandic_100_CI_AI';
IF (N'aa' COLLATE SQL_Latin1_General_CP1_CI_AI = N'å' COLLATE SQL_Latin1_General_CP1_CI_AI)
PRINT 'SQL_Latin1_General_CP1_CI_AI';
PRINT '---';
IF (N'of' COLLATE Upper_Sorbian_100_CI_AI = N'öf' COLLATE Upper_Sorbian_100_CI_AI)
PRINT 'Upper_Sorbian_100_CI_AI';
IF (N'of' COLLATE German_PhoneBook_CI_AI = N'öf' COLLATE German_PhoneBook_CI_AI)
PRINT 'German_PhoneBook_CI_AI';
PRINT '---';
Run Code Online (Sandbox Code Playgroud)
返回:
---
Danish_Greenlandic_100_CI_AI
---
Upper_Sorbian_100_CI_AI
---
Run Code Online (Sandbox Code Playgroud)
所以现在让我们开始您的原始查询。我们将进行一些测试,以了解各种更改如何改变结果,然后我们将了解仅进行一些更改即可解决问题。
第一个错误是关于CompatibilityLevel
字段的,这是第二个要取消透视的字段,恰好是一个包含所有字符串文字的表达式。如果没有字段引用,则生成的排序规则被视为“强制默认”)。Coercible-defaults 接受当前数据库的整理,比方说SQL_Latin1_General_CP1_CI_AS
. 接下来的 20 个左右的字段也只是字符串文字,因此也是 coercible-defaults,所以它们不应该发生冲突。但是,如果我们回顾一下第一场recovery_model_desc
,即直接从现场来了sys.databases
,这使得它的“隐含的”整理,这并没有采取对本地数据库的整理,而是保留它原本对照,这是Latin1_General_CI_AS_KS_WS
(因为它确实来自[mssqlsystemresource]
数据库)。
因此,如果字段 1 (RecoveryModel) 是Latin1_General_CI_AS_KS_WS
,而字段 2 (CompatibilityLevel) 是SQL_Latin1_General_CP1_CI_AS
,那么我们应该能够强制字段 2Latin1_General_CI_AS_KS_WS
匹配字段 1,然后字段 3 (AutoClose) 应该会出现错误。
将以下内容添加到CompatibilityLevel
行尾:
COLLATE Latin1_General_CI_AS_KS_WS
然后运行查询。果然,错误现在指出是该AutoClose
字段存在冲突。
对于我们的第二个测试,我们需要撤消刚刚所做的更改(即COLLATE
从行尾删除子句CompatibilityLevel
。
现在,如果 SQL Server 真正按照指定字段的顺序进行评估,我们应该能够删除字段 1(RecoveryModel),这将导致当前字段 2(CompatibilityLevel)成为设置主排序规则的字段由此产生的 UNPIVOT。该CompatibilityLevel
字段是一个 coercible-default,它采用数据库排序规则,所以第一个错误应该是PageVerify
字段,它是一个字段引用,它是一个保留原始排序规则的隐式排序规则,在这种情况下是Latin1_General_CI_AS_KS_WS
和不是当前数据库的整理。
因此,继续注释掉以, RecoveryModel
in SELECT
(朝顶部)开头的行,然后注释掉下面子句中的RecoveryModel
行,UNPIVOT
并从下一行中删除前导逗号,CompatibilityLevel
以免出现语法错误。
运行该查询。果然,错误现在指出是该PageVerify
字段存在冲突。
对于我们的第三个测试,我们需要撤消刚刚为删除RecoveryModel
字段所做的更改。因此,继续并放回逗号,并取消对另外两行的注释。
现在我们可以通过强制整理来走向另一个方向。我们应该能够将隐式排序规则字段更改为当前数据库的排序规则,而不是更改 coercible-default 排序规则字段的排序规则(这是大多数),对吗?
因此,就像我们的第一个测试一样,我们应该能够使用显式COLLATE
子句强制对字段 1 (RecoveryModel) 进行整理。但是,如果我们指定特定的排序规则,然后在具有不同排序规则的数据库中运行查询,则 coercible-default 排序规则字段将选择新的排序规则,然后将与我们将第一个字段设置为的内容冲突。这似乎是一种痛苦。幸运的是,有一种动态的方法可以解决这个问题。有一个伪排序规则称为DATABASE_DEFAULT
获取当前数据库排序规则(就像 coercible-default 字段所做的那样)。
继续将以下内容添加到行尾,朝向顶部,以 开头, RecoveryModel
:
COLLATE DATABASE_DEFAULT
运行该查询。果然,该错误再次指出,PageVerify
冲突是字段。
对于最终测试,我们不需要撤消任何先前的更改。
我们现在需要做的就是修复这个UNPIVOT
查询,就是将COLLATE DATABASE_DEFAULT
加到其余隐式排序规则字段的末尾:PageVerify
和RestrictedAccess
。虽然该Collation
字段也是隐式排序规则,但该字段来自master
数据库,通常与“当前”数据库一致。但是,如果您想确保安全以使其始终有效,那么继续将COLLATE DATABASE_DEFAULT
加到该字段的末尾。
运行该查询。果然,没有错误。修复此查询所需要的只是添加COLLATE DATABASE_DEFAULT
到 3 个字段的末尾(必需),可能还有 1 个(可选)。
可选测试:现在我们终于让 UNPIVOT 查询正常工作,只需更改以CONVERT(VARCHAR(50),
to开头的任何字段定义之一,而不是 be 51
,如:CONVERT(VARCHAR(51),
。
运行查询。您应该得到与The type of column "X" conflicts with the type of other columns specified in the UNPIVOT list.
只有不匹配的排序规则时得到的相同的错误。
对于数据类型和排序规则不匹配获得相同的错误并不足以具体到真正有用。所以那里肯定有改进的余地:)。
与查询相关的注意事项不仅仅是关于排序规则的特定问题:
由于所有源字段都属于数据类型NVARCHAR
,因此将CONVERT
所有输出字段NVARCHAR
改为VARCHAR
. 您目前可能没有处理具有任何非标准 ASCII 字符的数据,但系统元数据确实允许将它们转换为NVARCHAR(128)
- 这是任何这些字段的最大最大长度 - 至少保证您将来不会有问题,或者对于复制此代码的任何其他人可能已经在他们的系统中存在一些这些字符。
这是针对特定问题的解决方法,而不是问题的完整答案。您可以通过转换为sql_variant
而不是来避免错误varchar(50)
:
DECLARE @dbname SYSNAME;
SET @dbname = DB_NAME();
SELECT [Database] = unpvt.DatabaseName
, [Configuration Item] = unpvt.OptionName
, [Configuration Value] = unpvt.OptionValue
, [BaseType] = SQL_VARIANT_PROPERTY(unpvt.OptionValue, 'BaseType')
, [MaxLength] = SQL_VARIANT_PROPERTY(unpvt.OptionValue, 'MaxLength')
, [Collation] = SQL_VARIANT_PROPERTY(unpvt.OptionValue, 'Collation')
FROM (
SELECT
DatabaseName = name
, RecoveryModel = CONVERT(sql_variant, d.recovery_model_desc)
, CompatibilityLevel = CONVERT(sql_variant, CASE d.[compatibility_level] WHEN 70 THEN 'SQL Server 7' WHEN 80 THEN 'SQL Server 2000' WHEN 90 THEN 'SQL Server 2005' WHEN 100 THEN 'SQL Server 2008' WHEN 110 THEN 'SQL Server 2012' WHEN 120 THEN 'SQL Server 2014' ELSE 'UNKNOWN' END)
, AutoClose = CONVERT(sql_variant, CASE d.is_auto_close_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoCreateStatistics = CONVERT(sql_variant, CASE d.is_auto_create_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoShrink = CONVERT(sql_variant, CASE d.is_auto_shrink_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatistics = CONVERT(sql_variant, CASE d.is_auto_update_stats_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, AutoUpdateStatisticsAsynch = CONVERT(sql_variant, CASE d.is_auto_update_stats_async_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CloseCursorOnCommit = CONVERT(sql_variant, CASE d.is_cursor_close_on_commit_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DefaultCursor = CONVERT(sql_variant, CASE d.is_local_cursor_default WHEN 1 THEN 'LOCAL' ELSE 'GLOBAL' END)
, ANSINULL_Default = CONVERT(sql_variant, CASE d.is_ansi_null_default_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSINULLS_Enabled = CONVERT(sql_variant, CASE d.is_ansi_nulls_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIPadding_Enabled = CONVERT(sql_variant, CASE d.is_ansi_padding_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ANSIWarnings_Enabled = CONVERT(sql_variant, CASE d.is_ansi_warnings_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ArithmeticAbort_Enabled = CONVERT(sql_variant, CASE d.is_arithabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, ConcatNullYieldsNull = CONVERT(sql_variant, CASE d.is_concat_null_yields_null_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, CrossDBOwnerChain = CONVERT(sql_variant, CASE d.is_db_chaining_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DateCorrelationOptimized = CONVERT(sql_variant, CASE d.is_date_correlation_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, NumericRoundAbort = CONVERT(sql_variant, CASE d.is_numeric_roundabort_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [Parameterization] = CONVERT(sql_variant, CASE d.is_parameterization_forced WHEN 0 THEN 'SIMPLE' ELSE 'FORCED' END)
, QuotedIdentifiers_Enabled = CONVERT(sql_variant, CASE d.is_quoted_identifier_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RecursiveTriggers_Enabled = CONVERT(sql_variant, CASE d.is_recursive_triggers_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, [TrustWorthy] = CONVERT(sql_variant, CASE d.is_trustworthy_on WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, VARDECIMAL_Storage = CONVERT(sql_variant, 'TRUE')
, PageVerify = CONVERT(sql_variant, page_verify_option_desc )
, BrokerEnabled = CONVERT(sql_variant, CASE d.is_broker_enabled WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, DatabaseReadOnly = CONVERT(sql_variant, CASE d.is_read_only WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, EncryptionEnabled = CONVERT(sql_variant, CASE d.is_encrypted WHEN 0 THEN 'FALSE' ELSE 'TRUE' END)
, RestrictedAccess = CONVERT(sql_variant, user_access_desc)
, Collation = CONVERT(sql_variant, d.collation_name)
FROM sys.databases d
WHERE name = @dbname
OR @dbname IS NULL
) src
UNPIVOT
(
OptionValue FOR OptionName IN
(
RecoveryModel
, CompatibilityLevel
, AutoClose
, AutoCreateStatistics
, AutoShrink
, AutoUpdateStatistics
, AutoUpdateStatisticsAsynch
, CloseCursorOnCommit
, DefaultCursor
, ANSINULL_Default
, ANSINULLS_Enabled
, ANSIPadding_Enabled
, ANSIWarnings_Enabled
, ArithmeticAbort_Enabled
, ConcatNullYieldsNull
, CrossDBOwnerChain
, DateCorrelationOptimized
, NumericRoundAbort
, [Parameterization]
, QuotedIdentifiers_Enabled
, RecursiveTriggers_Enabled
, [TrustWorthy]
, VARDECIMAL_Storage
, PageVerify
, BrokerEnabled
, DatabaseReadOnly
, EncryptionEnabled
, RestrictedAccess
, Collation
)
) AS unpvt;
Run Code Online (Sandbox Code Playgroud)
我添加了三列以获取有关列的基础类型的信息OptionValue
。
如果客户端无法处理sql_variant
数据,请对unpvt.OptionValue
列进行最终(顶级)转换,例如nvarchar(256)
.
归档时间: |
|
查看次数: |
6134 次 |
最近记录: |