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

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

dbfiddle.uk demo: http://dbfiddle.uk/?rdbms=sqlserver_next&fiddle=7fc57d1916e901cb561b551af144aed6


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це не завжди детерміновано, незважаючи на те, що це функція string. Якщо ви хочете знати, чи це іноді детермінований єдиний прийом, про який я можу придумати, - спробувати достатньо різних способів викликати це, поки ви не задоволитесь. Наприклад, розглянемо, 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

Формат приймає або тип дати, або числовий тип як аргумент. Якщо все, що ви хочете зробити, це захопити частину року дати, не можете використовувати функцію року?

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, цікаво. Особисто мені здається, що це протилежно інтуїтивно, і воно також логічно не відповідає "Усі вбудовані рядкові функції детерміновані".
Леннарт

@Lennart Я ціную альтернативу прикладу, але приклад не був важливим.
SqlZim

1
@SqlZim, я подумав, що твій приклад є лише прикладом, але я додав альтернативу на всякий випадок. Я не впевнений, яке саме ваше питання, чи це формат - це функція рядка чи ні, детермінований він чи ні, чи все це погано задокументовано?
Леннарт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.