Создание пользовательской функции CLR с переменным числом параметров

Я хотел, чтобы функция находила наибольшее из списка переданных значений String.

Я хочу вызвать его как Select Greatest ('Abcd', 'Efgh', 'Zxy', 'EAD') с сервера sql. Он должен вернуть Zxy. Количество параметров является переменным. Между прочим, это очень похоже на функцию ORACLE GREATEST. Поэтому я написал очень простую функцию CLR (Vs2008) и попытался ее развернуть. Увидеть ниже

public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString Greatest(params SqlString[] p)
{
SqlString max=p[0];
foreach (string s in p)
max = s.CompareTo(max) > 0 ? s : max;

return max;

}
};

Но когда я пытаюсь скомпилировать или развернуть его, я получаю следующую ошибку. Не удается найти тип данных SqlString[].

Можно ли удовлетворить мое требование с помощью SQL CLR?


person josephj1989    schedule 24.05.2010    source источник
comment
почему это должна быть функция CLR?   -  person Mitch Wheat    schedule 24.05.2010


Ответы (3)


Вот решение с использованием функции Table-Valued:

CREATE FUNCTION fn_Split
(
    @text VARCHAR(8000), 
    @delimiter VARCHAR(20) = ','
)
    RETURNS @Strings TABLE 
        (
            position INT IDENTITY PRIMARY KEY,
            value VARCHAR(8000)
        )
AS BEGIN
    DECLARE @index int
    SET @index = -1

    WHILE (LEN(@text) > 0) BEGIN
        -- Find the first delimiter
        SET @index = CHARINDEX(@delimiter , @text)

        -- No delimiter left?
        -- Insert the remaining @text and break the loop
        IF (@index = 0) AND (LEN(@text) > 0) BEGIN  
            INSERT INTO @Strings VALUES (LTRIM(RTRIM(@text)))
            BREAK 
        END 

        -- Found a delimiter
        -- Insert left of the delimiter and truncate the @text
        IF (@index > 1) BEGIN
            INSERT INTO @Strings VALUES (LTRIM(RTRIM(LEFT(@text, @index - 1))))
            SET @text = RIGHT(@text, (LEN(@text) - @index))
        END
        -- Delimiter is 1st position = no @text to insert
        ELSE SET @text = RIGHT(@text, (LEN(@text) - @index))
    END
    RETURN
END
GO

Контрольная работа:

DECLARE @test varchar(120)

SET @test = 'Abcd, Efgh, Zxy, EAD'

SELECT Top(1) value FROM dbo.fn_Split(@test, ',')
ORDER BY value DESC

GO

(Функция разделения изменена из здесь)

Примечание. Это почти наверняка не самый быстрый способ сделать это. Если вам нужно выполнить это миллионы раз, может быть более подходящим другое решение.

person Mitch Wheat    schedule 24.05.2010

Нет, невозможно иметь переменное количество параметров (например, модификатор params в .NET) в пользовательских функциях SQL Server, будь то T-SQL или SQLCLR. Да, некоторые из встроенных функций допускают такие вещи (например, CHECKSUM(*) ), но они встроены непосредственно в SQL Server, а не в API, такие как определяемые пользователем функции / функции с табличным значением.

Чтобы наилучшим образом решить цель этого вопроса, в каком контексте вы получаете эти значения? Являются ли они несколькими столбцами таблицы или запроса? Это разные строки? Эти значения уже объединены в список CSV? T-SQL на самом деле неплохо справляется с сортировкой списка вещей. Возможно, вы сможете структурировать свой запрос, чтобы использовать OUTER APPLY (часть FROM предложение), которое можно использовать для этого в различных сценариях. Например:

SELECT tab.name AS [TableName],
       ind.name AS [IndexName],
       col.name AS [ColumnName],
       greatest.Item AS [GREATEST()]
FROM   sys.tables tab
LEFT JOIN (sys.indexes ind
    INNER JOIN sys.index_columns indcol
            ON indcol.[object_id] = ind.[object_id]
           AND indcol.index_id = ind.index_id
    INNER JOIN sys.columns col
            ON col.[object_id] = indcol.[object_id]
           AND col.column_id = indcol.column_id
          )
       ON ind.[object_id] = tab.[object_id]
OUTER APPLY (SELECT TOP 1 tmp.Name AS [Item]
             FROM (
                    SELECT tab.name UNION ALL SELECT ind.name UNION ALL SELECT col.name
                  ) tmp(Name)
             ORDER BY tmp.Name ASC
            ) greatest

Результатом является «наибольшее» значение среди 3 полей имени для каждой строки. Как видите, этот метод достаточно гибок, чтобы включать любое количество столбцов.

person Solomon Rutzky    schedule 24.07.2015

К сожалению, нет возможности объявить UDF в CLR с желаемой подписью (params SqlString[] p). UDF может иметь только список параметров строгого типа, а ключевое слово «params» в настоящее время не поддерживается (я надеюсь, что это изменится и в будущем).

Вот пример String.Format UDF.

[SqlFunction(DataAccess = DataAccessKind.None)]
    public static SqlString clr_StringFormat2(SqlString format, object s1, object s2)
    {
        return format.IsNull ? SqlString.Null : new SqlString(string.Format(format.Value, SqlTypeToNetType(s1, s2)));
    }

Если вы хотите больше параметров, вам нужно будет добавить еще один UDF.

[SqlFunction(DataAccess = DataAccessKind.None)]
    public static SqlString clr_StringFormat3(SqlString format, object s1, object s2, object s3)
    {
        return format.IsNull ? SqlString.Null : new SqlString(string.Format(format.Value, SqlTypeToNetType(s1, s2, s3)));
    }

Еще одна вещь, о которой следует помнить в .CLR, это отсутствие перегрузки методов, поэтому ваша UDF должна иметь уникальное имя.

В конце концов, ваш UDF невозможно реализовать в .CLR, если у вас есть/хотите неограниченное количество параметров. Это может быть только фиксированное количество параметров, например. 4 (как в случае, который вы упомянули).

Причина, по которой в таких случаях следует использовать CLR вместо SP, — гораздо лучшая производительность. Но я также хотел бы отметить, что это не означает, что вы получите лучшую производительность с .CLR для всех возможных вещей. В некоторых случаях T-SQL/PS работает намного лучше. Конечно, здесь все зависит от предположения, что в конце концов вы сможете развернуть .CLR в рабочей среде. Если я могу развернуть .CLR в рабочей среде и нуждаюсь в математике, обработке строк или подобных вещах, я всегда использую CLR.

person Nenad Prekupec    schedule 24.07.2015