Эффективные замены ISNUMERIC () на SQL Server?

поэтому я просто потратил 5 часов на устранение неполадок, которые оказались не только из-за старый ненадежный ISNUMERIC но похоже, что моя проблема появляется только тогда, когда UDF, в котором ISNUMERIC объявлен WITH SCHEMABINDING и вызывается в сохраненном proc (у меня есть много работы, чтобы перегнать его в тестовый случай, но моя первая потребность-заменить его чем-то надежным).

любые рекомендации по хорошей, эффективной замены ISNUMERIC(). Очевидно там действительно должны быть вариации для int, money, etc., но что используют люди (предпочтительно в T-SQL, потому что в этом проекте я ограничен SQL Server, потому что это задача обработки данных SQL Server с большим объемом SQL Server)?

11 ответов


вы можете использовать функции T-SQL TRY_CAST() или TRY_CONVERT (), если вы используете SQL Server 2012 как Bacon Bits упоминает в комментариях:

SELECT CASE WHEN TRY_CAST('foo' AS INT) IS NULL THEN 0 ELSE 1 END

SELECT CASE WHEN TRY_CAST(1 AS INT) IS NULL THEN 0 ELSE 1 END

Если вы используете SQL 2008 R2 или старше, вам придется использовать функцию .NET CLR и систему wrap.Десятичный.Метод tryparse().


в зависимости от обстоятельств и характеристик проверки, я иногда использую вариант как выражение. Например:

NOT LIKE '%[^0-9]%'

обратите внимание, что этот конкретный пример является довольно наивно. Это не гарантирует, что значение допустимо для преобразования в определенный тип данных. Он также не позволяет использовать знаки +/- или десятичные точки, если они вам нужны.


другой вариант-написать расширенную хранимую процедуру на языке C, сделать ее DLL и сделать ее доступной для SQL Server.

Я не думаю, что потребуется слишком много строк кода, и это, вероятно, будет быстрее, чем писать управляемую хранимую процедуру в .NET, потому что вы не понесете дополнительных подслушанных от загрузки среды CLR.

вот лакомый кусочек информация: http://msdn.microsoft.com/en-us/library/ms175200.aspx

вот некоторые код C++, который может работать для вас:

using namespace std;

int checkNumber() {
  int number = 0;
  cin >> number;
  cin.ignore(numeric_limits<int>::max(), '\n');

  if (!cin || cin.gcount() != 1)
    cout << "Not a number.";
  else
    cout << "Your entered: " << number;
  return 0;
}

следуя маршруту .NET CLR, вы можете использовать регулярное выражение, особенно если вы ожидаете определенный диапазон чисел.

SQL 2005 и регулярные выражения


обычно, как правило, я стараюсь не допускать нетипизированные данные в базу данных, так как она лучше подходит либо для обработки на уровне приложения, либо для пакетного импорта обрабатывать его в службах SQL Integration Services, так что данные вводятся правильно с самого начала.

Мне приходилось делать это много раз в прошлом, и обычно самый быстрый способ-написать свою собственную пользовательскую функцию для проверки данных в формате, который вы ожидаете, так как большую часть времени накладные расходы на вызов выход на расширенный хранимый proc или управляемый код для простой проверки медленнее, чем просто делать это в T-SQL.


по данным Microsoft поддерживают только эффективное способ заменить функцию UDF-это написать собственную версию функции .NET.

конечно, если ваш администратор базы данных позволяет :).

Мой нет :(.


для SQL Server 2005 и выше.... воспользуйтесь try/catch...

declare @test varchar(10), @num decimal
select @test = '0123A'

begin try
    select @num = cast(@test as decimal)
    print '1'
end try 
begin catch
    print '0'
end catch

выводит 0.

изменить @test = '01234' или @test = '01234.5' и он печатает 1.


вы когда-нибудь будете обрабатывать системы счисления вне вашего собственного (человеческого) языка, как китайский и т. д.? Если это так, я бы предложил использовать библиотека libuninum.


Как насчет реализации этих двух функций:

CREATE FUNCTION dbo.isReallyNumeric  
(  
    @num VARCHAR(64)  
)  
RETURNS BIT  
BEGIN  
    IF LEFT(@num, 1) = '-'  
        SET @num = SUBSTRING(@num, 2, LEN(@num))  

    DECLARE @pos TINYINT  

    SET @pos = 1 + LEN(@num) - CHARINDEX('.', REVERSE(@num))  

    RETURN CASE  
    WHEN PATINDEX('%[^0-9.-]%', @num) = 0  
        AND @num NOT IN ('.', '-', '+', '^') 
        AND LEN(@num)>0  
        AND @num NOT LIKE '%-%' 
        AND  
        (  
            ((@pos = LEN(@num)+1)  
            OR @pos = CHARINDEX('.', @num))  
        )  
    THEN  
        1  
    ELSE  
    0  
    END  
END  
GO  

CREATE FUNCTION dbo.isReallyInteger  
(  
    @num VARCHAR(64)  
)  
RETURNS BIT  
BEGIN  
    IF LEFT(@num, 1) = '-'  
        SET @num = SUBSTRING(@num, 2, LEN(@num))  

    RETURN CASE  
    WHEN PATINDEX('%[^0-9-]%', @num) = 0  
        AND CHARINDEX('-', @num) <= 1  
        AND @num NOT IN ('.', '-', '+', '^') 
        AND LEN(@num)>0  
        AND @num NOT LIKE '%-%' 
    THEN  
        1  
    ELSE  
        0  
    END  
END  
GO

Первоисточник


IsNumeric (), похоже, имеет проблемы с пробелами, "D", "E", знаками доллара и всевозможными другими символами. То, что мы обычно хотим, - это то, что говорит нам, будет ли приведение или преобразование успешным. Это UDF, хотя и не самое быстрое решение, сработало очень хорошо для меня.

create function dbo.udf_IsNumeric(@str varchar(50))
  returns int
as
begin
  declare @rtn int
  select @rtn =
    case
      when ltrim(rtrim(@str)) in('.', '-', '-.', '+', '+.') then 0
      when ltrim(rtrim(@str)) like '%[^-+.0-9]%' then 0
      else isnumeric(@str)
    end
  return @rtn
end

SQL 2012 и далее вы можете использовать функцию TRY_PARSE() вместо ISNUMERIC().

SELECT
 TRY_PARSE('123' as int) as '123'
,TRY_PARSE('abc' as int) as 'abc'