format () - это недетерминированная встроенная строковая функция… верно?

10

Перед тем, как я отправлю сообщение о нехватке документации по этому вопросу, подтвердит ли кто-то, что я здесь не что-то упускаю?

На странице документов, где formatуказана строковая функция:

«Все встроенные строковые функции являются детерминированными». - Строковые функции (Transact-SQL)

Также нет упоминания о formatнедетерминированности на связанных страницах:


Однако при попытке создать постоянный вычисляемый столбец:

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

Возвращает следующую ошибку:

Вычисляемый столбец «date_formatted_01» в таблице «t» не может быть сохранен, поскольку столбец недетерминирован.

В документации говорится, что

Если аргумент культуры не указан, используется язык текущего сеанса.

но добавление аргумента культуры ничего не меняет

Это также не удается

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

демонстрационный ролик: http://rextester.com/ZMS22966

Демоверсия dbfiddle.uk: http://dbfiddle.uk/?rdbms=sqlserver_next&fiddle=7fc57d1916e901cb561b551af144aed6

SqlZim
источник
1
Это также не: alter table #t add date_formatted_01 as CONVERT(VARCHAR(20), FORMAT(date_col, 'YYYY', 'en-US')) persisted;. Не уверен, почему FORMATне является детерминированным, особенно при определении культуры. date_formattedКолонка может быть VARCHAR(20)( по- прежнему сохраняется) и установить с помощью триггера использования FORMAT. Или SQLCLR работает. Используя библиотеку SQL # SQLCLR (которую я написал), вы можете сделать это ALTER TABLE SQL#.t ADD date_formatted_03 AS SQL#.Date_Format(date_col, 'd', 'en-US') PERSISTED;(таблица принадлежит SQL #, поскольку владелец таблицы и функции должен совпадать).
Соломон Руцки

Ответы:

5

Функция не обязательно является детерминированной или недетерминированной. Есть некоторые функции, которые могут быть детерминированными в зависимости от того, как они используются :

Следующие функции не всегда являются детерминированными, но их можно использовать в индексированных представлениях или индексах для вычисляемых столбцов, если они определены детерминистическим образом.

CASTи CONVERTтакие примеры. На основании проведенного вами тестирования, я думаю, что будет справедливо сказать, что FORMATэто не всегда детерминировано, несмотря на то, что это строковая функция. Если вы хотите знать, является ли это порой детерминированным, единственная техника, о которой я могу подумать, - это попробовать достаточно разных способов назвать ее, пока вы не будете удовлетворены. Например, давайте рассмотрим FORMATприменительно к числам. Существует только десять различных типов ввода чисел :

числовые типы ввода

Также существует всего девять различных числовых форматов . Можно попытаться создать постоянные столбцы для всех возможных комбинаций. Некоторый код для этого ниже:

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;

Вот пример вывода:

вывод тестового кода

Я не смог получить ни один из столбцов для добавления в таблицу для нескольких входных значений и культур. Я не исчерпывающе попробовал все возможные культуры, потому что не могу найти их список в SQL Server.

Как минимум, можно с уверенностью сделать вывод, что документация, касающаяся детерминизма, FORMATневерна, поэтому я бы порекомендовал представить для нее элемент подключения.

Джо Оббиш
источник
1

Я не обычный пользователь sqlserver, поэтому я могу ошибаться, но я предполагаю, что формат не является строковой функцией. Согласно документации:

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

Формат принимает в качестве аргумента либо тип даты, либо числовой тип. Если все, что вы хотите сделать, это захватить годичную часть даты, можете ли вы использовать функцию year?

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

если вы хотите строковое представление:

alter table t 
    add date_formatted_01 as cast(year(date_col) as char(4)) persisted;
Леннарт
источник
1
Это перечислено как строковая функция в документах. i.stack.imgur.com/aj0T2.png
Мартин Смит
1
@MartinSmith, интересно. Лично я считаю, что это противоречит интуитивно, и это также становится логически несовместимым с «Все встроенные строковые функции являются детерминированными».
Леннарт
@ Леннарт Я ценю альтернативу примеру, но пример не был важен.
SqlZim
1
@SqlZim, я подумал, что твой пример был просто примером, но я добавил альтернативу на всякий случай. Я не уверен, в чем конкретно заключается ваш вопрос: является ли формат строковой функцией или нет, детерминирован ли она или нет, или все это плохо документировано?
Леннарт