Создание CLR UDF с переменным количеством параметров - PullRequest
1 голос
/ 24 мая 2010

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

Я хочу вызвать его как Выбрать наибольший ('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?

Ответы [ 3 ]

1 голос
/ 24 июля 2015

Нет, невозможно иметь переменное количество параметров (то есть модификатор 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 полей имени для каждой строки. И, как вы можете видеть, этот метод достаточно гибок, чтобы включать любое количество столбцов.

1 голос
/ 24 мая 2010

Вот решение с использованием табличной функции:

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

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

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

0 голосов
/ 24 июля 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 over SP в таких случаях, намного выше производительности. Но я также хотел бы отметить, что это не означает, что вы получите лучшую производительность с .CLR для всех возможных вещей. В некоторых случаях T-SQL / PS будет работать намного лучше. Конечно, все здесь зависит от того, что в конце вы можете развернуть .CLR в производственной среде. Если я могу развернуть .CLR в производстве и мне нужна математика, манипуляции со строками или подобные вещи, я всегда использую CLR.

...