format () é uma função de string interna não determinística ... certo?

10

Antes de publicar um item de conexão com relação à falta de documentação sobre isso, alguém confirmará que não estou simplesmente perdendo algo aqui?

Na página de documentos, onde formatestá listada como uma função de sequência:

"Todas as funções internas de string são determinísticas." - Funções de sequência (Transact-SQL)

Também não há menção de formatser não determinístico em páginas relacionadas:


No entanto, ao tentar criar uma coluna computada persistente:

create table t (date_col date); 
insert into t values (getdate());
alter table t add date_formatted_01 as format(date_col,'YYYY') persisted;

Retorna o seguinte erro:

A coluna computada 'date_formatted_01' na tabela 't' não pode ser mantida porque a coluna é não determinística.

A documentação afirma que

Se o argumento de cultura não for fornecido, o idioma da sessão atual será usado.

mas adicionar um argumento de cultura não muda as coisas

Isso também falha

alter table t add date_formatted_02 as format(date_col, 'd', 'en-US' ) persisted

demonstração do rextester: http://rextester.com/ZMS22966

Demonstração do dbfiddle.uk: http://dbfiddle.uk/?rdbms=sqlserver_next&fiddle=7fc57d1916e901cb561b551af144aed6

SqlZim
fonte
11
Isso também falhar: alter table #t add date_formatted_01 as CONVERT(VARCHAR(20), FORMAT(date_col, 'YYYY', 'en-US')) persisted;. Não sei por que FORMATnão é determinista, especialmente ao especificar a cultura. A date_formattedcoluna pode ser VARCHAR(20)(ainda persistida) e configurada via Trigger usando FORMAT. Ou o SQLCLR funciona. Usando a biblioteca SQL # SQLCLR (que eu escrevi), você pode fazer ALTER TABLE SQL#.t ADD date_formatted_03 AS SQL#.Date_Format(date_col, 'd', 'en-US') PERSISTED;(a tabela pertence ao SQL #, pois o proprietário da tabela e da função precisa ser o mesmo).
Solomon Rutzky

Respostas:

5

Uma função não é necessariamente determinística ou não determinística. Existem algumas funções que podem ser determinísticas, dependendo de como são usadas :

As funções a seguir nem sempre são determinísticas, mas podem ser usadas em visualizações indexadas ou índices em colunas computadas quando especificadas de maneira determinística.

CASTe CONVERTsão esses exemplos. Com base nos testes que você fez até agora, acho justo dizer que FORMATnem sempre é determinístico, apesar de ser uma função de string. Se você quiser saber se às vezes é determinístico, a única técnica em que posso pensar é tentar maneiras diferentes de chamá-lo até que você esteja satisfeito. Por exemplo, vamos considerar FORMATcomo aplicado a números. Existem apenas dez tipos de entrada numéricos diferentes :

tipos de entrada numéricos

Também parece haver apenas nove formatos numéricos diferentes . É possível tentar criar colunas persistentes para todas as combinações possíveis. Alguns códigos para fazer isso estão abaixo:

DECLARE @FormatValue INT = 76767; -- change this if you want
DECLARE @FormatCulture VARCHAR(10) = 'en-US'; -- change this if you want
DECLARE @Format VARCHAR(1);
DECLARE @FormatType VARCHAR(10);
DECLARE @SQLForColumn VARCHAR(200);
DECLARE @TestNumber INT = 0;

BEGIN

    DROP TABLE IF EXISTS dbo.TargetTable;
    CREATE TABLE dbo.TargetTable (ID INT);

    DROP TABLE IF EXISTS #ColumnAddResults;
    CREATE TABLE #ColumnAddResults (
    FormatType VARCHAR(10),
    [Format] VARCHAR(1), 
    Succeeded VARCHAR(1), 
    ErrorMessage VARCHAR(1000)
    );

    drop table if exists #Types;
    create table #Types (FormatType VARCHAR(10));

    INSERT INTO #Types VALUES
    ('bigint'), ('int'), ('smallint'), ('tinyint'), ('decimal')
    , ('numeric'), ('float'), ('real'), ('smallmoney'), ('money');

    drop table if exists #Formats;
    create table #Formats ([Format] VARCHAR(1));

    INSERT INTO #Formats VALUES 
    ('C'), ('D'), ('E'), ('F'), ('G'), ('N'), ('P'), ('R'), ('X');

    DECLARE format_statements CURSOR LOCAL FAST_FORWARD FOR 
    SELECT #Types.FormatType, #Formats.[Format]
    FROM #Formats
    CROSS JOIN #Types;

    OPEN format_statements;

    FETCH NEXT FROM format_statements   
    INTO @FormatType, @Format;  

    WHILE @@FETCH_STATUS = 0  
    BEGIN
        SET @TestNumber = @TestNumber + 1;
        SET @SQLForColumn = 'alter table dbo.TargetTable add NewColumn' + CAST(@TestNumber AS VARCHAR(10))
        + ' as FORMAT(CAST(' +  CAST(@FormatValue AS VARCHAR(10)) + ' AS ' + @FormatType + '), '
        + '''' + @Format + ''', ''' + @FormatCulture + ''') persisted';

        BEGIN TRY
            EXEC (@SQLForColumn);
            INSERT INTO #ColumnAddResults VALUES (@FormatType, @Format, 'Y', NULL);
        END TRY
        BEGIN CATCH
            INSERT INTO #ColumnAddResults VALUES (@FormatType, @Format, 'N', ERROR_MESSAGE());
        END CATCH;

        PRINT @SQLForColumn;

        FETCH NEXT FROM format_statements   
        INTO @FormatType, @Format;  
    END;

    CLOSE format_statements;  
    DEALLOCATE format_statements;  

    SELECT * FROM dbo.TargetTable;
    SELECT * FROM #ColumnAddResults;
    DROP TABLE #ColumnAddResults;

END;

Aqui está uma amostra da saída:

saída do código de teste

Não consegui adicionar nenhuma coluna à tabela para alguns valores e culturas de entrada. Não tentei exaustivamente todas as culturas possíveis porque não consigo encontrar uma lista delas no SQL Server.

No mínimo, parece seguro concluir que a documentação referente ao determinismo de FORMATestá incorreta; portanto, eu recomendaria enviar um item de conexão para ele.

Joe Obbish
fonte
1

Como não sou usuário regular do sqlserver, posso estar enganado, mas meu palpite é que o formato não é uma função de string. De acordo com a documentação:

https://docs.microsoft.com/en-us/sql/t-sql/functions/format-transact-sql

O formato aceita um tipo de data ou numérico como argumento. Se tudo o que você deseja fazer é pegar a parte do ano de uma data, você não pode usar a função ano?

alter table t 
    add date_formatted_01 as year(date_col) persisted;

se você deseja uma representação de string:

alter table t 
    add date_formatted_01 as cast(year(date_col) as char(4)) persisted;
Lennart
fonte
11
Ele está listado como uma função de string nos documentos. i.stack.imgur.com/aj0T2.png
Martin Smith
11
@ MartinSmith, interessante. Pessoalmente, acho isso contra-intuitivo e também se torna logicamente inconsistente com "Todas as funções internas de string são determinísticas".
Lennart
@Lennart Agradeço a alternativa ao exemplo, mas o exemplo não foi importante.
SqlZim
11
@SqlZim, achei que seu exemplo era apenas um exemplo, mas adicionei uma alternativa por precaução. Não sei exatamente qual é a sua pergunta: se o formato é uma função de string ou não, se é determinista ou não, ou se tudo isso está mal documentado?
Lennart