format () es una función de cadena incorporada no determinista ... ¿verdad?

10

Antes de publicar un elemento de conexión sobre la falta de documentación sobre esto, ¿alguien confirmará que no me estoy perdiendo algo aquí?

En la página de documentos donde formataparece como una función de cadena:

"Todas las funciones de cadena incorporadas son deterministas". - Funciones de cadena (Transact-SQL)

Tampoco se menciona formatque no sea determinista en páginas relacionadas:


Sin embargo, al intentar crear una columna calculada 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;

Devuelve el siguiente error:

La columna calculada 'date_formatted_01' en la tabla 't' no puede persistir porque la columna no es determinista.

La documentación indica que

Si no se proporciona el argumento de cultura, se usa el idioma de la sesión actual.

pero agregar un argumento cultural no cambia las cosas

Esto también falla

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

demo de rextester: http://rextester.com/ZMS22966

Demostración dbfiddle.uk: http://dbfiddle.uk/?rdbms=sqlserver_next&fiddle=7fc57d1916e901cb561b551af144aed6

SqlZim
fuente
1
Esto también falla: alter table #t add date_formatted_01 as CONVERT(VARCHAR(20), FORMAT(date_col, 'YYYY', 'en-US')) persisted;. No estoy seguro de por qué FORMATno es determinista, especialmente al especificar la cultura. La date_formattedcolumna puede ser VARCHAR(20)(aún persistente) y configurarse mediante Trigger usando FORMAT. O SQLCLR funciona. Usando la biblioteca SQL # SQLCLR (que escribí), puede hacerlo ALTER TABLE SQL#.t ADD date_formatted_03 AS SQL#.Date_Format(date_col, 'd', 'en-US') PERSISTED;(la tabla es propiedad de SQL # ya que el propietario de la tabla y la función deben ser los mismos).
Solomon Rutzky

Respuestas:

5

Una función no es necesariamente determinista o no determinista. Hay algunas funciones que pueden ser deterministas según cómo se usen :

Las siguientes funciones no siempre son deterministas, pero se pueden usar en vistas indexadas o índices en columnas calculadas cuando se especifican de manera determinista.

CASTy CONVERTson tales ejemplos. Según las pruebas que ha realizado hasta ahora, creo que es justo decir que FORMATno siempre es determinista, a pesar de ser una función de cadena. Si desea saber si a veces es determinista, la única técnica que se me ocurre es probar suficientes formas diferentes de llamarlo hasta que esté satisfecho. Por ejemplo, consideremos FORMATque se aplica a los números. Solo hay diez tipos de entrada numérica diferentes :

tipos de entrada numérica

También parece haber solo nueve formatos numéricos diferentes . Es posible intentar crear columnas persistentes para todas las combinaciones posibles. Algún código para hacerlo está a continuación:

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;

Aquí hay una muestra de la salida:

salida de código de prueba

No pude agregar ninguna de las columnas a la tabla para algunos valores de entrada y culturas. No probé exhaustivamente todas las culturas posibles porque no puedo encontrar una lista de ellas en SQL Server.

Como mínimo, parece seguro concluir que la documentación sobre el determinismo de FORMATes incorrecta, por lo que recomendaría enviar un elemento de conexión para ello.

Joe Obbish
fuente
1

No soy un usuario habitual de sqlserver, por lo que podría estar equivocado, pero supongo que el formato no es una función de cadena. De acuerdo con la documentación:

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

formato toma un tipo de fecha o un tipo numérico como argumento. Si todo lo que quiere hacer es tomar la parte del año de una fecha, ¿no puede usar la función de año?

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

si quieres una representación de cadena:

alter table t 
    add date_formatted_01 as cast(year(date_col) as char(4)) persisted;
Lennart
fuente
1
Está listado como una función de cadena en los documentos. i.stack.imgur.com/aj0T2.png
Martin Smith
1
@MartinSmith, interesante. Personalmente, lo encuentro contra intuitivo y también se vuelve lógicamente inconsistente con "Todas las funciones de cadena incorporadas son deterministas".
Lennart
@Lennart Agradezco la alternativa al ejemplo, pero el ejemplo no era importante.
SqlZim
1
@SqlZim, pensé que tu ejemplo era solo un ejemplo, pero agregué una alternativa por si acaso. No estoy seguro de cuál es su pregunta exactamente, ¿es si el formato es una función de cadena o no, si es determinista o no, o si todo eso está mal documentado?
Lennart