sys.databases中某些列的排序如何处理?


21

我正在尝试UNPIVOTsys.databases2005年至2012年的各个版本的SQL Server中包含的各个列上运行。

UNPIVOT与以下错误消息失败:

消息8167,第16层,状态1,第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;

这旨在为给定数据库提供格式良好的数据库选项列表,类似于:

+----------+----------------------------+----------------------------+
| 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 |
+----------+----------------------------+----------------------------+

当我在带有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;

观察到的行为是,以下字段未遵循服务器排序规则或数据库排序规则;它们总是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;

结果:

在此处输入图片说明

为什么静态设置这些列的排序规则?

Answers:


17

微软官方的话:

某些包含预定义字符串的列(例如类型,系统描述和常量)始终固定为特定的排序规则– Latin1_General_CI_AS_KS_WS。这与实例/数据库排序规则无关。原因是这是系统元数据(不是用户元数据),基本上这些字符串不区分大小写(例如关键字,因此始终为拉丁文)。

系统表中包含用户元数据的其他列(例如对象名,列名,索引名,登录名等)采用实例或数据库排序规则。在实例排序规则的情况下,在安装SQL Server时,在数据库排序规则的情况下,在创建数据库时,将这些列排序为适当的排序规则。

您问(强调我的):

为什么静态设置这些列的排序规则?

静态设置某些列的原因是,查询无需担心服务器或数据库排序规则(更重要的是:CaSe SenSiTIviTy)才能正常工作。无论排序规则如何,此查询将始终有效:

SELECT * FROM sys.databases WHERE state_desc = N'ONLine';

如果服务器排序规则区分大小写,则上面的查询将返回0行,就像这样:

  SELECT * FROM sys.databases 
  WHERE state_desc COLLATE Albanian_BIN = N'ONLine';

例如,如果您使用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');

您将看到以下结果(或类似的结果,具体取决于您的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

现在,演示继承数据库排序规则而不是从主数据库继承服务器排序规则的元数据视图:

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

结果:

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

因此,您可以看到在这种情况下,几列继承了数据库排序规则,而其他列则固定于这种“通用” Latin1排序规则,这意味着它用于将某些名称和属性与大小写敏感性问题隔离开来。

如果您尝试执行UNION,例如:

SELECT name FROM albanian.sys.columns
UNION ALL
SELECT name FROM server_collation.sys.columns;

您收到此错误:

消息451,级别16,状态1
无法解决SELECT语句列1中出现的UNION ALL运算符中“ Albanian_BIN”和“ SQL_Estonian_CP1257_CS_AS”之间的排序规则冲突。

同样,如果您尝试执行PIVOTor或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;

唯一可能引起问题的是,如果强制排序规则但不包含相同的字符表示形式,或者如果使用排序并且强制排序规则使用的排序顺序与源排序顺序不同,则输出将令人困惑。


7

排序规则优先级的背景

关于系统目录视图中各个字段的排序规则,您看到的行为是每个字段的定义方式和排序规则优先级的结果。

查看时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]字段[mssqlsystemresource].[sys].[syspalvalues]

  • user_access_desc
  • snapshot_isolation_state_desc
  • recovery_model_desc
  • page_verify_option_desc
  • log_reuse_wait_desc
  • containment_desc

这些字段应始终具有的排序规则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)

这是开始排序规则优先级的地方。这里输出的两个选项都是系统函数:serverproperty()collationpropertyfromid()。该表达式的排序规则被认为是“强制默认”:

任何Transact-SQL字符串变量,参数,文字或目录内置函数的输出,或不接受字符串输入但产生字符串输出的内置函数。

如果在用户定义的函数,存储过程或触发器中声明了对象,则会为该对象分配在其中创建函数,存储过程或触发器的数据库的默认排序规则。如果以批处理形式声明对象,则会为该对象分配当前数据库用于连接的默认排序规则。

根据第二段,由于sys.databasesmaster数据库中存在的视图,因此它承担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

但是,此表达式的排序规则为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

返回(在使用归类设置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

在这里,我们看到引用该字段的两个查询[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将很难在内部进行自我管理(即,用于内部逻辑的排序和比较规则是否基于安装而发生了更改)。

如何亲自看这些细节

如果要在任何这些系统目录视图中查看任何字段的来源,只需执行以下操作:

  1. 在SSMS的查询标签中,启用“包括实际执行计划”(CTRL-M)的查询选项
  2. 执行一个查询,从一个系统目录视图中选择一个字段(我建议一次只选择一个字段,因为执行计划对于单个字段而言是非常大/复杂的,并且会包含对很多您不是的字段的引用。 t选择):

    SELECT recovery_model_desc FROM sys.databases;
  3. 转到“执行计划”标签
  4. 右键单击图形执行计划区域,然后选择“显示执行计划XML ...”。
  5. SSMS中的新标签将打开,标题类似于: Execution plan.xml
  6. 转到Execution plan.xml标签
  7. 查找首次出现的<OutputList>标签(通常应在第10和20行之间)
  8. 应该有一个<ColumnReference>标签。该标记中的属性应指向表中的特定字段,或者指向计划中稍后定义的表达式。
  9. 如果属性指向一个实字段,则说明已完成,因为它具有所有信息。以下是该recovery_model_desc字段显示的内容:

    <ColumnReference Database="[mssqlsystemresource]" Schema="[sys]"
                     Table="[syspalvalues]" Alias="[ro]" Column="name" />
  10. 如果属性指向表达式,例如您选择了该state_desc字段,则最初将发现:

    <ColumnReference Column="Expr1024" />
  11. 在这种情况下,您需要仔细查看计划的其余部分,以了解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]&amp;(128),0)=(1) THEN N'COPYING' ELSE CASE WHEN serverproperty('EngineEdition')=5 AND CONVERT(bit,[master].[sys].[sysdbreg].[status] as [d].[status]&amp;(256),0)=(1) THEN N'SUSPECT' ELSE [mssqlsystemresource].[sys].[syspalvalues].[name] as [st].[name] END END END">

也可以执行同样的操作来检查数据库级目录视图的来源。对这样的对象执行此操作sys.tables将显示该name字段来自[current_db].[sys].[sysschobjs](这就是为什么它的归类与数据库的归类匹配的原因),而该lock_escalation_desc字段来自于[mssqlsystemresource].[sys].[syspalvalues](这就是它的归类为的原因Latin1_General_CI_AS_KS_WS)。

Clippy说:“您似乎想执行UNPIVOT查询。”

现在我们知道了为什么排序规则优先级以及它是如何工作的,让我们将该知识应用于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 '---';

返回值:

---
Danish_Greenlandic_100_CI_AI
---
Upper_Sorbian_100_CI_AI
---

现在,让我们开始您的原始查询。我们将进行一些测试,以查看各种更改如何改变结果,然后我们将看到仅少数更改可以解决该问题。

  1. 第一个错误是关于CompatibilityLevel字段的,这是第二个不可透视的字段,恰好是包含所有字符串文字的表达式。如果没有字段引用,则将产生的归类视为“强制默认”)。强制默认值采用当前数据库的排序规则,例如SQL_Latin1_General_CP1_CI_AS。接下来的20个左右字段也只是字符串文字,因此也是强制默认值,因此它们不应冲突。但是,如果我们回顾第一个字段,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,则我们应该能够强制字段2 Latin1_General_CI_AS_KS_WS与字段1匹配,然后对于字段3(AutoClose)应该出现错误。

    将以下内容添加到该CompatibilityLevel行的末尾:
    COLLATE Latin1_General_CI_AS_KS_WS

    然后运行查询。可以肯定的是,该错误现在指出该AutoClose字段存在冲突。

  2. 对于我们的第二个测试,我们需要撤消刚刚所做的更改(即COLLATE从行尾删除该子句CompatibilityLevel

    现在,如果SQL Server真正按照指定字段的顺序进行评估,则我们应该能够删除字段1(RecoveryModel),这将导致当前字段2(CompatibilityLevel)成为设置主排序规则的字段。由此产生的联检组。并且该CompatibilityLevel字段是强制默认值,它承担数据库排序规则,因此第一个错误应该是该PageVerify字段,该字段是字段引用,它是保留原始排序规则的隐式排序规则,在这种情况下为Latin1_General_CI_AS_KS_WS,而不是当前数据库的整理。

    因此,继续注释掉, RecoveryModelSELECT(朝顶部)开头的行,然后注释掉下面子句中的RecoveryModel行,UNPIVOT并从下面的行中删除前导逗号,CompatibilityLevel以免出现语法错误。

    运行该查询。可以肯定的是,该错误现在指出该PageVerify字段存在冲突。

  3. 对于我们的第三个测试,我们需要撤消刚刚删除RecoveryModel字段所做的更改。因此,继续放回逗号,然后取消注释其他两行。

    现在我们可以通过强制排序来朝另一个方向发展。而不是更改强制默认排序规则字段(大多数)的排序规则,我们应该能够将隐式排序规则字段更改为当前数据库的排序规则,对吗?

    因此,就像我们的第一个测试一样,我们应该能够使用显式COLLATE子句强制字段1(RecoveryModel)的排序规则。但是,如果我们指定特定的排序规则,然后在具有不同排序规则的数据库中运行查询,则coercible-default排序规则字段将获取新的排序规则,这将与我们将第一个字段设置为的规则冲突。这似乎很痛苦。幸运的是,有一种动态的方法可以解决这个问题。有一个伪排序规则,它调用DATABASE_DEFAULT当前数据库的排序规则(就像coercible-default字段一样)。

    继续,将以下内容添加到该行的末尾,从顶部开始, RecoveryModelCOLLATE DATABASE_DEFAULT

    运行该查询。可以肯定的是,该错误再次表明PageVerify,存在冲突的字段。

  4. 对于最终测试,我们不需要撤消任何先前的更改。

    现在,解决此UNPIVOT查询所需要做的就是COLLATE DATABASE_DEFAULT在其余隐式排序规则字段的末尾添加PageVerifyRestrictedAccess。尽管该Collation字段也是隐式排序规则,但该字段来自master数据库,该数据库通常与“当前”数据库一致。但是,如果您希望安全起见,使其始终有效,那么也请在COLLATE DATABASE_DEFAULT该字段的末尾添加。

    运行该查询。果然没有错误。解决此查询COLLATE DATABASE_DEFAULT所需的全部工作是添加到3个字段的末尾(必填),可能还要再添加1个字段(可选)。

  5. 可选测试:现在,我们终于使UNPIVOT查询正常工作了,只更改以CONVERT(VARCHAR(50),to 开头的任何字段定义之一,改为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)至少要转换为-这是所有这些字段中最大的最大长度-保证将来对您或任何其他复制此代码的人都不会有问题,这些人的系统中可能已经有这些字符。


5

这是针对特定问题的解决方法,而不是问题的完整答案。您可以通过将转换为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;

我添加了三列,以获取有关列的基础类型的信息OptionValue

样品输出

如果客户端无法处理sql_variant数据,请在该unpvt.OptionValue列上进行最终(顶级)转换,例如nvarchar(256)


4

好吧,我看了一下

sp_helptext [sys.databases]

然后分解了列的来源。具有Latin1_General_CI_AS_KS_WS排序规则的数据都来自系统表sys.syspalvalues,该表似乎是通用的查找表(这是系统表,因此您必须通过DAC进行连接才能看到它)。

我的猜测是,它将设置Latin1_General_CI_AS_KS_WS为处理任何可能的查找值。我可以看到这会是多么烦人。

查看定义的另一种方法(最初由Max在注释中提供)是:

SELECT ObjectSchema = s.name
    , ObjectName = o.name
    , ObjectDefinition = sm.definition
FROM master.sys.all_sql_modules sm
    INNER JOIN master.sys.system_objects o ON sm.object_id = o.object_id
    INNER JOIN master.sys.schemas s ON o.schema_id = s.schema_id
WHERE s.name = 'sys' 
    AND o.name = 'databases';`
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.