format () est une fonction de chaîne intégrée non déterministe… non?

10

Avant de publier un élément de connexion concernant le manque de documentation à ce sujet, quelqu'un confirmera-t-il que je ne manque pas simplement quelque chose ici?

Sur la page de documents où formatest répertoriée en tant que fonction de chaîne:

"Toutes les fonctions de chaîne intégrées sont déterministes." - Fonctions de chaîne (Transact-SQL)

Il n'y a également aucune mention d' formatêtre non déterministe sur les pages connexes:


Cependant, lorsque vous tentez de créer une colonne calculée persistante:

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

Renvoie l'erreur suivante:

La colonne calculée 'date_formatted_01' dans la table 't' ne peut pas être conservée car la colonne n'est pas déterministe.

La documentation indique que

Si l'argument culture n'est pas fourni, la langue de la session en cours est utilisée.

mais l'ajout d'un argument de culture ne change rien

Cela échoue également

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

démo rextester: http://rextester.com/ZMS22966

Démo dbfiddle.uk: http://dbfiddle.uk/?rdbms=sqlserver_next&fiddle=7fc57d1916e901cb561b551af144aed6

SqlZim
la source
1
Cela échoue aussi: alter table #t add date_formatted_01 as CONVERT(VARCHAR(20), FORMAT(date_col, 'YYYY', 'en-US')) persisted;. Je ne sais pas pourquoi FORMATn'est pas déterministe, surtout quand on spécifie la culture. La date_formattedcolonne peut être VARCHAR(20)(toujours persistante) et définie via Trigger à l'aide de FORMAT. Ou SQLCLR fonctionne. En utilisant la bibliothèque SQL # SQLCLR (que j'ai écrite), vous pouvez le faire ALTER TABLE SQL#.t ADD date_formatted_03 AS SQL#.Date_Format(date_col, 'd', 'en-US') PERSISTED;(la table appartient à SQL # car le propriétaire de la table et de la fonction doit être le même).
Solomon Rutzky

Réponses:

5

Une fonction n'est pas nécessairement déterministe ou non déterministe. Certaines fonctions peuvent être déterministes en fonction de leur utilisation :

Les fonctions suivantes ne sont pas toujours déterministes, mais peuvent être utilisées dans des vues indexées ou des index sur des colonnes calculées lorsqu'elles sont spécifiées de manière déterministe.

CASTet CONVERTsont de tels exemples. Sur la base des tests que vous avez effectués jusqu'à présent, je pense qu'il est juste de dire que ce FORMATn'est pas toujours déterministe, bien qu'il s'agisse d'une fonction de chaîne. Si vous voulez savoir si c'est parfois déterministe, la seule technique à laquelle je peux penser est d'essayer suffisamment de façons différentes de l'appeler jusqu'à ce que vous soyez satisfait. Par exemple, considérons FORMATcomme appliqué aux nombres. Il n'y a que dix types d'entrées numériques différents :

types de saisie numérique

Il semble également qu'il n'y ait que neuf formats numériques différents . Il est possible d'essayer de créer des colonnes persistantes pour toutes les combinaisons possibles. Un code pour le faire est ci-dessous:

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;

Voici un échantillon de la sortie:

sortie du code de test

Je n'ai pu obtenir aucune des colonnes à ajouter au tableau pour quelques valeurs d'entrée et cultures. Je n'ai pas essayé de façon exhaustive toutes les cultures possibles car je ne trouve pas de liste d'entre elles dans SQL Server.

Au minimum, il semble sûr de conclure que la documentation concernant le déterminisme de FORMATest incorrecte, donc je recommanderais de lui soumettre un élément de connexion.

Joe Obbish
la source
1

Je ne suis pas un utilisateur régulier de sqlserver, donc je peux me tromper, mais je suppose que le format n'est pas une fonction de chaîne. Selon la documentation:

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

format prend soit un type de date soit un type numérique comme argument. Si tout ce que vous voulez faire est de saisir la partie année d'une date, ne pouvez-vous pas utiliser la fonction année?

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

si vous voulez une représentation sous forme de chaîne:

alter table t 
    add date_formatted_01 as cast(year(date_col) as char(4)) persisted;
Lennart
la source
1
Il est répertorié comme une fonction de chaîne dans les documents. i.stack.imgur.com/aj0T2.png
Martin Smith
1
@MartinSmith, intéressant. Personnellement, je trouve cela contre-intuitif et cela devient logiquement incompatible avec "Toutes les fonctions de chaîne intégrées sont déterministes."
Lennart
@Lennart J'apprécie l'alternative à l'exemple, mais l'exemple n'était pas important.
SqlZim
1
@SqlZim, j'ai pensé que votre exemple n'était qu'un exemple mais j'ai ajouté une alternative juste au cas où. Je ne sais pas exactement quelle est votre question, est-ce que le format est une fonction de chaîne ou non, qu'il soit déterministe ou non, ou si tout cela est mal documenté?
Lennart