O que há com o agrupamento de algumas colunas nos sys.databases?

21

Estou tentando executar um UNPIVOTem várias colunas contidas emsys.databases várias versões do SQL Server, variando de 2005 a 2012.

O UNPIVOTestá a falhar com a seguinte mensagem de erro:

Msg 8167, nível 16, estado 1, linha 48

O tipo de coluna "CompatibilityLevel" entra em conflito com o tipo de outras colunas especificadas na lista UNPIVOT.

O 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;

Isso foi projetado para fornecer uma lista bem formatada de opções de banco de dados para o banco de dados especificado, semelhante a:

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

Quando eu executo isso em um servidor com Latin1_General_CI_AS_KS_WSagrupamento, a instrução é bem-sucedida. Se eu modificar o T-SQL para que certos campos tenham uma COLLATEcláusula, ele será executado em servidores que possuem outros agrupamentos.

O código que funciona em servidores com agrupamentos diferentes de 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;

O comportamento observado é que os seguintes campos não observam o agrupamento do servidor ou o agrupamento do banco de dados; eles são sempre apresentados em Latin1_General_CI_AS_KS_WSagrupamento.

No SQL Server 2012, podemos usar sys.sp_describe_first_result_setpara obter facilmente metadados sobre as colunas retornadas de uma consulta específica. Usei o seguinte para determinar a incompatibilidade de agrupamento:

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;

Os resultados:

insira a descrição da imagem aqui

Por que o agrupamento dessas colunas está definido estaticamente?

Max Vernon
fonte

Respostas:

17

A palavra oficial da Microsoft:

Algumas das colunas que contêm seqüências de caracteres predefinidas (como tipos, descrições de sistema e constantes) são sempre fixadas em um agrupamento específico - Latin1_General_CI_AS_KS_WS. Isso é independente do agrupamento da instância / banco de dados. O motivo é que esses são metadados do sistema (não metadados do usuário) e, basicamente, essas strings são tratadas sem distinção entre maiúsculas e minúsculas (como palavras-chave, portanto sempre em latim).

Outras colunas nas tabelas do sistema que contêm metadados do usuário, como nomes de objetos, nomes de colunas, nomes de índices, nomes de logon, etc. levam a instância ou agrupamento de banco de dados. As colunas são agrupadas para agrupamento adequado no momento da instalação do SQL Server no caso de agrupamento de instância e no momento da criação do banco de dados no caso de agrupamento de banco de dados.

Você perguntou (ênfase minha):

Por que o agrupamento dessas colunas está definido estaticamente?

O motivo pelo qual algumas colunas são definidas estaticamente é para que as consultas não precisem se preocupar com o agrupamento de servidores ou bancos de dados (mais importante: CaSe SenSiTIviTy) para funcionar corretamente. Esta consulta sempre funcionará independentemente do agrupamento:

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

Considerando que, se o agrupamento do servidor diferencia maiúsculas de minúsculas, a consulta acima retornará 0 linhas, assim:

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

Por exemplo, se você instalar uma instância do SQL Server com SQL_Estonian_CP1257_CS_ASagrupamento, execute o seguinte:

SELECT name, collation_name 
FROM master.sys.all_columns
WHERE collation_name IS NOT NULL
AND [object_id] = OBJECT_ID(N'sys.databases');

Você verá estes resultados (ou algo semelhante, dependendo da sua versão do 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

Agora, para demonstrar visualizações de metadados que herdam o agrupamento de banco de dados, em vez de herdar o agrupamento de servidores do banco de dados mestre:

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

Resultados:

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

Portanto, você pode ver que, neste caso, várias colunas herdam o agrupamento do banco de dados, enquanto outras são fixadas nesse agrupamento Latin1 "genérico", o que significa que é usado para isolar certos nomes e propriedades dos problemas de diferenciação de maiúsculas e minúsculas, conforme descrito acima.

Se você tentar executar um UNION, por exemplo:

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

Você recebe este erro:

Mensagem 451, Nível 16, Estado 1
Não é possível resolver o conflito de agrupamento entre "Albanian_BIN" e "SQL_Estonian_CP1257_CS_AS" no operador UNION ALL que ocorre na coluna 1 da instrução SELECT.

Da mesma forma, se você tentar executar um PIVOTou UNPIVOT, as regras serão ainda mais rígidas (os tipos de saída devem corresponder exatamente ao invés de apenas serem compatíveis), mas a mensagem de erro é muito menos útil e até enganosa:

Mensagem 8167, nível 16, estado 1
O tipo de coluna "nome da coluna" está em conflito com o tipo de outras colunas especificadas na lista UNPIVOT.

Você precisa solucionar esses erros usando COLLATEcláusulas explícitas em suas consultas. Por exemplo, a união acima poderia ser:

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;

O único momento em que isso pode causar problemas é que você obterá resultados confusos se um agrupamento for forçado, mas não contiver a mesma representação de caractere, ou se a classificação for usada e o agrupamento forçado usar uma ordem de classificação diferente da origem.

Aaron Bertrand
fonte
7

Antecedentes da Precedência de Agrupamento

O comportamento que você está vendo com relação ao agrupamento de vários campos nas visualizações do catálogo do sistema é o resultado de como cada campo é definido e a precedência do agrupamento.

Ao olhar sys.databases, é importante ter em mente que não é uma mesa. Enquanto no passado (acho que terminando no SQL Server 2000) essas eram tabelas de catálogo do sistema , agora são visualizações de catálogo do sistema . Portanto, a fonte das informações nelas não é necessariamente proveniente do contexto atual do banco de dados (ou do contexto do banco de dados especificado ao lidar com um objeto totalmente qualificado, comomaster.sys.databases ).

Lidando especificamente com sys.databases, alguns dos campos são provenientes do [master]banco de dados (que foi criado com um agrupamento com base no agrupamento padrão da instância - ou seja, no nível do servidor), alguns dos campos são expressões (ou seja, CASEinstruções) e outros estão vindo de uma fonte "oculta": o [mssqlsystemresource]banco de dados. E o [mssqlsystemresource]banco de dados tem um agrupamento de: Latin1_General_CI_AS_KS_WS.

O namecampo é originário do namecampo em master.sys.sysdbreg. Portanto, esse campo deve sempre estar no agrupamento do [master]banco de dados, que novamente corresponderá ao agrupamento do servidor.

MAS, os seguintes campos sys.databasesvêm do [name]campo em [mssqlsystemresource].[sys].[syspalvalues]:

  • user_access_desc
  • snapshot_isolation_state_desc
  • recovery_model_desc
  • page_verify_option_desc
  • log_reuse_wait_desc
  • containsment_desc

Esses campos devem sempre ter um agrupamento de Latin1_General_CI_AS_KS_WS.

O collation_namecampo, no entanto, vem da seguinte expressão:

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)

É aqui que a Precedência de intercalação começa a aparecer. As duas opções de saída aqui são funções do sistema: serverproperty()e collationpropertyfromid(). O agrupamento dessa expressão é considerado um "padrão coercível":

Qualquer variável da cadeia de caracteres Transact-SQL, parâmetro, literal ou a saída de uma função interna do catálogo ou uma função interna que não aceita entradas de cadeia, mas produz uma saída de cadeia.

Se o objeto for declarado em uma função definida pelo usuário, procedimento armazenado ou gatilho, o objeto receberá o agrupamento padrão do banco de dados no qual a função, procedimento armazenado ou gatilho será criado. Se o objeto for declarado em um lote, será atribuído ao objeto o agrupamento padrão do banco de dados atual para a conexão.

À luz desse segundo parágrafo, como sys.databasesé uma visão existente no masterbanco de dados, ele assume o agrupamento do masterbanco de dados (não o banco de dados atual).

O state_desccampo também é uma expressão:

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

Mas, o agrupamento nessa expressão é Latin1_General_CI_AS_KS_WS. Por quê? Bem, algo novo é introduzido nesta expressão: uma referência a um campo real: [mssqlsystemresource].[sys].[syspalvalues].[name]nessa ELSEcláusula final . As referências de coluna são consideradas "implícitas":

Uma referência de coluna. O agrupamento da expressão é obtido do agrupamento definido para a coluna na tabela ou exibição.

Obviamente, isso abre uma questão interessante: é possível que essa expressão retorne um agrupamento diferente dependendo de como a CASEavaliação é avaliada? Os literais estarão no agrupamento do banco de dados em que este objeto está definido, mas a ELSEcondição retorna um valor de campo que deve manter seu agrupamento original. Felizmente, podemos simular um teste usando a Função de Gerenciamento Dinâmico 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

Retorna (em uma instância configurada com um agrupamento SQL_Latin1_General_CP1_CI_ASmas em execução em um banco de dados com um agrupamento de 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

Aqui vemos que as duas consultas que referenciam o campo [msdb]assumem o agrupamento do[msdb] banco dados (que, sendo um banco de dados do sistema, foi determinado pelo agrupamento do servidor).

Voltar à pergunta original

O comportamento observado é que os seguintes campos não observam o agrupamento do servidor ou o agrupamento do banco de dados; eles são sempre apresentados em Latin1_General_CI_AS_KS_WSagrupamento.

Sua observação é direta: esses campos sempre têm um agrupamento Latin1_General_CI_AS_KS_WS, independentemente do agrupamento do servidor ou do banco de dados. E o motivo é: Precedência de agrupamento. Esses campos vêm de uma tabela no [mssqlsystemresource]banco de dados e reterão esse agrupamento inicial, a menos que sejam substituídos por uma COLLATEcláusula explícita, uma vez que possui a maior precedência:

Explícito = Uma expressão que é convertida explicitamente em um agrupamento específico usando uma cláusula COLLATE na expressão.

Explícito tem precedência sobre implícito. Implícito tem precedência sobre Coercible-default:
Explicit> Implicit> Coercible-default

E a pergunta relacionada:

Por que o agrupamento dessas colunas está definido estaticamente?

Não é que eles sejam definidos estaticamente, nem que os outros campos sejam dinâmicos. Todos os campos em todas essas visualizações de catálogo do sistema estão operando nas mesmas regras da Precedência de agrupamento. O motivo pelo qual eles parecem ser mais "estáticos" do que os outros campos (ou seja, eles não são alterados, mesmo que você instale o SQL Server com um agrupamento padrão diferente, que por sua vez cria os bancos de dados do sistema com esse agrupamento padrão) é que o [mssqlsystemresource]banco de dados é consistente possui um agrupamento Latin1_General_CI_AS_KS_WSem qualquer instalação do SQL Server (ou pelo menos é o que parece). E isso faz sentido porque, caso contrário, seria difícil para o SQL Server gerenciar-se internamente (ou seja, se as regras de classificação e comparação usadas para a lógica interna fossem alteradas com base na instalação).

Como ver você mesmo essas especificidades

Se você deseja ver a origem de qualquer campo em qualquer uma dessas visualizações de catálogo do sistema, faça o seguinte:

  1. Em uma guia de consulta no SSMS, ative a opção de consulta "Incluir plano de execução real" ( CTRL-M)
  2. Execute uma consulta selecionando um campo em uma das visualizações do catálogo do sistema (eu recomendo selecionar apenas um campo por vez, pois o plano de execução é ridiculamente grande / complexo para apenas um único campo e incluirá referências a muitos campos que você não tiver ' t selecionando):

    SELECT recovery_model_desc FROM sys.databases;
  3. Vá para a guia "Plano de execução"
  4. Clique com o botão direito do mouse na área do plano de Execução gráfica e selecione "Mostrar XML do Plano de Execução ..."
  5. Uma nova guia no SSMS será aberta com um título semelhante a: Execution plan.xml
  6. Vá para a Execution plan.xmlguia
  7. Procure a primeira ocorrência de uma <OutputList>tag (ela deve estar entre as linhas 10 e 20 geralmente)
  8. Deve haver uma <ColumnReference>tag. Os atributos nessa tag devem apontar para um campo específico em uma tabela ou apontar para uma expressão definida posteriormente no plano.
  9. Se os atributos apontarem para um campo real, você estará pronto, pois ele possui todas as informações. A seguir, é apresentado o recovery_model_desccampo:

    <ColumnReference Database="[mssqlsystemresource]" Schema="[sys]"
                     Table="[syspalvalues]" Alias="[ro]" Column="name" />
  10. Se os atributos apontarem para uma expressão, como se você selecionasse o state_desccampo, você encontrará inicialmente:

    <ColumnReference Column="Expr1024" />
  11. Nesse caso, você precisa examinar o restante do plano para a definição Expr1024ou o que quer que seja # que for apresentado. Lembre-se de que pode haver várias dessas referências, mas a definição não estará em um <OutputList>bloco. No entanto, ele terá um <ScalarOperator>elemento irmão que contém a definição. A seguir, é apresentado o state_desccampo:

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

O mesmo pode ser feito para verificar a fonte das visualizações de catálogo no nível do banco de dados. Fazer isso para um objeto como sys.tablesirá mostrar que o namecampo é proveniente [current_db].[sys].[sysschobjs](é por isso que possui um agrupamento correspondente ao agrupamento do banco de dados), enquanto o lock_escalation_desccampo é proveniente [mssqlsystemresource].[sys].[syspalvalues](é por isso que possui um agrupamento Latin1_General_CI_AS_KS_WS).

Clippy diz: "Parece que você deseja fazer uma consulta UNPIVOT".

Agora que sabemos por que o que é Precedência de Agrupamento e como funciona, vamos aplicar esse conhecimento à consulta UNPIVOT.

Para uma UNPIVOToperação, o SQL Server parece realmente preferir (o que significa: requer) que cada campo de origem seja exatamente do mesmo tipo . Normalmente, "type" refere-se ao tipo base (por exemplo, VARCHAR/ NVARCHAR/ INT/ etc), mas aqui o SQL Server também inclui o COLLATION. Isso não deve ser considerado irracional, considerando o que os Collations controlam: o conjunto de caracteres (por exemplo, Página de Código) do VARCHAR e as regras linguísticas que determinam a equivalência de caracteres e combinações de caracteres (por exemplo, normalização). A seguir, é apresentado um mimi-primer sobre o que é "normalização" Unicode:

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 '---';

Devoluções:

---
Danish_Greenlandic_100_CI_AI
---
Upper_Sorbian_100_CI_AI
---

Então agora vamos começar sua consulta original. Faremos alguns testes para ver como várias alterações alteram o resultado e, em seguida, veremos como apenas algumas alterações podem corrigi-lo.

  1. O primeiro erro é sobre o CompatibilityLevelcampo, que é o segundo campo a ser não dinâmico, e acontece de ser uma expressão que contém todos os literais de string. Sem referências de campo, o agrupamento resultante é considerado um "padrão coercível"). Padrões coercíveis assumem o agrupamento do banco de dados atual, digamos SQL_Latin1_General_CP1_CI_AS. Os próximos 20 campos também são apenas literais de seqüência de caracteres e, portanto, também são padrões coercíveis, portanto, não devem estar em conflito. Mas se olharmos para o primeiro campo, recovery_model_descque está vindo diretamente de um campo em sys.databases, o que o torna um agrupamento "implícito", e isso não assume o agrupamento do banco de dados local, mas mantém o agrupamento original, que é Latin1_General_CI_AS_KS_WS( porque é realmente proveniente do [mssqlsystemresource]DB).

    Portanto, se o campo 1 (RecoveryModel) for Latin1_General_CI_AS_KS_WSe o campo 2 (CompatibilityLevel) for SQL_Latin1_General_CP1_CI_AS, poderemos forçar o campo 2 Latin1_General_CI_AS_KS_WSa corresponder ao campo 1 e, em seguida, o erro deverá aparecer no campo 3 (AutoClose).

    Adicione o seguinte ao final da CompatibilityLevellinha:
    COLLATE Latin1_General_CI_AS_KS_WS

    E, em seguida, execute a consulta. Com certeza, o erro agora afirma que é o AutoClosecampo que está com o conflito.

  2. Para o nosso segundo teste, precisamos desfazer a alteração que acabamos de fazer (por exemplo, remover a COLLATEcláusula do final da CompatibilityLevellinha.

    Agora, se o SQL Server estiver realmente avaliando na ordem em que os campos são especificados, poderemos remover o campo 1 (RecoveryModel), que fará com que o campo atual 2 (CompatibilityLevel) seja o campo que define o agrupamento principal de o UNPIVOT resultante. E como o CompatibilityLevelcampo é um padrão coercível que aceita o agrupamento do banco de dados, o primeiro erro deve ser o PageVerifycampo, que é uma referência de campo, que é um agrupamento implícito que mantém o agrupamento original, que nesse caso é Latin1_General_CI_AS_KS_WSe qual não é o agrupamento do banco de dados atual.

    Então vá em frente e comente a linha que começa com , RecoveryModelno SELECT(no topo) e depois comente a RecoveryModellinha na UNPIVOTcláusula abaixo e remover a vírgula que conduz da seguinte linha de CompatibilityLevelmodo que você não obter um erro de sintaxe.

    Execute essa consulta. Com certeza, o erro agora afirma que é o PageVerifycampo que está com o conflito.

  3. Para o nosso terceiro teste, precisamos desfazer as alterações que acabamos de fazer para remover o RecoveryModelcampo. Então vá em frente, coloque de volta a vírgula e remova o comentário dessas duas outras linhas.

    Agora podemos ir na outra direção forçando um agrupamento. Em vez de alterar o agrupamento dos campos de agrupamento padrão coercível (que é a maioria deles), devemos poder alterar os campos de agrupamento implícito para o do banco de dados atual, certo?

    Portanto, assim como nosso primeiro teste, poderemos forçar o agrupamento do campo 1 (RecoveryModel) com uma COLLATEcláusula explícita . Porém, se especificarmos um agrupamento específico e, em seguida, executarmos a consulta em um banco de dados com um agrupamento diferente, os campos de agrupamento padrão coercível selecionarão o novo agrupamento, que entrará em conflito com o que estamos definindo neste primeiro campo. Isso parece uma dor. Felizmente, existe uma maneira dinâmica de lidar com isso. Existe um pseudo agrupamento chamado DATABASE_DEFAULTque coleta o agrupamento atual dos bancos de dados (assim como os campos padrão coercível).

    Vá em frente e adicione o seguinte ao final da linha, em direção ao topo, que começa com , RecoveryModel: COLLATE DATABASE_DEFAULT

    Execute essa consulta. Com certeza, o erro afirma, mais uma vez, que é o PageVerifycampo que está em conflito.

  4. Para o teste final, não precisamos desfazer nenhuma das alterações anteriores.

    Tudo o que precisamos fazer agora para corrigir essa UNPIVOTconsulta é adicionar COLLATE DATABASE_DEFAULTo final dos campos de intercalação implícita restantes: PageVerifye RestrictedAccess. Embora o Collationcampo também seja um agrupamento implícito, esse campo vem do masterbanco de dados, que normalmente está alinhado com o banco de dados "atual". Mas, se você quiser estar seguro para que isso sempre funcione, adicione COLLATE DATABASE_DEFAULTo final desse campo também.

    Execute essa consulta. Com certeza, sem erros. Tudo o que foi necessário para corrigir essa consulta foi adicionar COLLATE DATABASE_DEFAULTao final de 3 campos (obrigatório) e talvez mais 1 (opcional).

  5. Teste Opcional: Agora que finalmente temos a consulta UNPIVOT funcionando corretamente, a mudança apenas um de qualquer uma das definições de campo começando com CONVERT(VARCHAR(50),a ser em vez 51, como em: CONVERT(VARCHAR(51),.

    Execute a consulta. Você deve obter o mesmo The type of column "X" conflicts with the type of other columns specified in the UNPIVOT list.erro que ocorreu quando apenas o agrupamento não correspondia.

    Obter o mesmo erro para incompatibilidades de tipo de dados e agrupamento não é específico o suficiente para ser realmente útil. Portanto, definitivamente há espaço para melhorias lá :).


Nota relacionada à consulta mais do que à pergunta específica sobre agrupamento:

Uma vez que todos os campos de origem são do tipo de dados NVARCHAR, que seria mais seguro para CONVERTtodos os campos de saída para NVARCHAR, em vez de VARCHAR. Você pode não estar lidando com dados no momento que possuam caracteres ASCII fora do padrão, mas os metadados do sistema permitem a conversão para, NVARCHAR(128)pelo menos, o maior tamanho máximo de qualquer um desses campos, pelo menos garante que não haverá um problema para você no futuro ou para qualquer outra pessoa que copie esse código que já possa ter alguns desses caracteres em seu sistema.

Solomon Rutzky
fonte
5

Esta é uma solução alternativa para o problema específico, em vez de uma resposta completa para a pergunta. Você pode evitar o erro convertendo para em sql_variantvez de 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;

Eu adicionei três colunas para obter informações sobre o tipo subjacente da OptionValuecoluna.

Saída de amostra

Se o cliente não puder manipular sql_variantdados, faça uma conversão final (nível superior) na unpvt.OptionValuecoluna para, por exemplo nvarchar(256).

Paul White diz que a GoFundMonica
fonte
4

Ok, então eu dei uma olhada

sp_helptext [sys.databases]

depois quebrou de onde vinham as colunas. Os que estão no Latin1_General_CI_AS_KS_WSagrupamento são todos provenientes da tabela do sistema, sys.syspalvaluesque parece ser uma tabela de pesquisa genérica (é uma tabela do sistema, portanto, você precisará se conectar via DAC para vê-la.).

Meu palpite é que ele está configurado Latin1_General_CI_AS_KS_WSpara lidar com quaisquer possíveis valores de pesquisa. Eu posso ver como seria irritante embora.

Outra maneira de ver a definição (originalmente fornecida por Max em um comentário) é:

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';`
Kenneth Fisher
fonte