Non-dynamic way to change sql_variant collation and keep type and size

220 views Asked by At

SQL Server 2019. I am looking for a solution to change the collation of sql_variant values in a select statement, if there is one. The proplem is that the COLLATE operator cannot be applied to sql_variant data types. The select queries are cross-database and may be invoked using OPENQUERY. It is not allowed to inject extended stored procedures or CLR functions.

Currently I am forced to post-process results with a cursor.

SET NOCOUNT ON;
DECLARE @var sql_variant;
DECLARE @sql nvarchar(2000);
DECLARE varupdcur CURSOR GLOBAL FORWARD_ONLY FOR
    SELECT [_sqlvariant] FROM [dbo].[test] WHERE SQL_VARIANT_PROPERTY([_sqlvariant], 'Collation') = N'Latin1_General_CI_AS'
    FOR UPDATE OF [_sqlvariant]; OPEN varupdcur;
FETCH NEXT FROM varupdcur INTO @var;
WHILE @@FETCH_STATUS = 0 BEGIN
    SET @sql = N'UPDATE [dbo].[test] SET [_sqlvariant] = CAST(CAST([_sqlvariant] AS '
        + CAST(SQL_VARIANT_PROPERTY(@var, 'Basetype') AS nvarchar(16))
        + N'('
        + CAST(CAST(SQL_VARIANT_PROPERTY(@var, 'MaxLength') AS int) / IIF(SQL_VARIANT_PROPERTY(@var, 'Basetype') in (N'nchar', N'nvarchar'), 2, 1) AS nvarchar(16))
        + N')) COLLATE DATABASE_DEFAULT AS sql_variant) WHERE CURRENT OF varupdcur';
    EXECUTE sp_executesql @sql;
    FETCH NEXT FROM varupdcur INTO @var;
END
CLOSE varupdcur; DEALLOCATE varupdcur;
SET NOCOUNT OFF;

All my research led to this

WITH test(v, va) AS
(
    SELECT
        v,
        (CASE WHEN SQL_VARIANT_PROPERTY(v, 'Collation') = 'Cyrillic_General_CI_AS'

            -- Expression type sql_variant is invalid for COLLATE clause.
            --THEN v COLLATE DATABASE_DEFAULT

            -- Obviously makes MaxLength = 8000
            THEN CAST(CAST(v AS varchar(8000)) COLLATE DATABASE_DEFAULT AS sql_variant)

            -- Argument data type sql_variant is invalid for argument 1 of left function
            --THEN LEFT(v, SQL_VARIANT_PROPERTY(v, 'MaxLength')) COLLATE DATABASE_DEFAULT

            -- The constant length value works, but ???
            --THEN LEFT(CAST(v AS varchar(8000)) COLLATE DATABASE_DEFAULT, 777)

            -- Operand type clash: nvarchar(max) is incompatible with sql_variant
            --THEN sys.fn_sqlvarbasetostr(v) COLLATE DATABASE_DEFAULT

            -- The argument 2 of the XML data type method "value" must be a string literal
            --THEN (SELECT CAST(v AS varchar(8000)) COLLATE DATABASE_DEFAULT FOR XML PATH, TYPE).value('.[1]', N'varchar(' + CAST(SQL_VARIANT_PROPERTY(v, 'MaxLength') AS varchar(100)) + ')')

            ELSE v
        END) AS va
    FROM
    (   VALUES
        (CAST('some text' COLLATE Cyrillic_General_100_CI_AS AS sql_variant)),
        (34.56),
        ('some text some text some text some text some text some text' COLLATE Cyrillic_General_CI_AS),
        (0x0030),
        ('some text some text' COLLATE Cyrillic_General_CI_AS)
    ) AS test(v)
)
SELECT
    v,
    va,
    SQL_VARIANT_PROPERTY(v, 'MaxLength') maxlen,
    SQL_VARIANT_PROPERTY(va, 'MaxLength') va_maxlen,
    SQL_VARIANT_PROPERTY(v, 'Collation') collation,
    SQL_VARIANT_PROPERTY(va, 'Collation') va_collation
FROM
    test
0

There are 0 answers