Des remplacements efficaces D'ISNUMERIC () sur SQL Server?
donc je viens de passer 5 heures à résoudre un problème qui s'est avéré être due non seulement à la Vieux peu fiable ISNUMERIC
mais il semble que mon problème apparaît seulement quand L'UDF dans lequel ISNUMERIC
est déclaré WITH SCHEMABINDING
et est appelé dans un proc stocké (j'ai beaucoup de travail à faire pour le distiller vers le bas dans un cas de test, mais mon premier besoin est de le remplacer par quelque chose de fiable).
toute recommandation relative à une remplacements pour ISNUMERIC()
. Évidemment, il y a vraiment besoin de variations pour int
, money
, etc., mais qu'est-ce que les gens utilisent (de préférence en T-SQL, parce que sur ce projet, je suis limité à SQL Server parce que c'est un haut volume SQL Server à SQL Server tâche de traitement de données)?
11 réponses
vous pouvez utiliser les fonctions T-SQL TRY_CAST () ou TRY_CONVERT() si vous utilisez SQL Server 2012 comme les Bits Bacon mentionnés dans les commentaires:
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
si vous utilisez SQL 2008 R2 ou plus, vous devrez utiliser une fonction .NET CLR, et le système wrap.Décimal.TryParse().
selon les circonstances et les caractéristiques de performance de la validation, j'utilise parfois une variante de l'expression similaire. Par exemple:
NOT LIKE '%[^0-9]%'
Notez que cet exemple est assez naïf. Il ne garantit pas que la valeur est valide pour la conversion à un type de données particulier. Il ne tient pas compte non plus des signes +/- ou des points décimaux si vous en avez besoin.
une autre option pourrait être d'écrire une procédure stockée prolongée dans une langue telle que C, la rendre dans une DLL et la rendre disponible pour SQL Server.
Je ne pense pas qu'il faudrait trop de lignes de code pour le faire, et il serait probablement plus rapide que d'écrire une procédure gérée stockée dans .NET, parce que vous ne seriez pas encourir la surchauffe supplémentaire de charger le CLR.
Voici une information: http://msdn.microsoft.com/en-us/library/ms175200.aspx
voici un peu de code C++ qui pourrait fonctionner pour vous:
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;
}
en suivant la route .NET CLR, vous pouvez utiliser une expression régulière, surtout si vous vous attendez à une certaine plage de nombres.
en général, en tant que pratique, j'essaie de ne pas laisser les données non dactylographiées dans la base de données, car il est mieux adapté pour le traiter soit à la couche application, ou pour les importations par lots le traiter dans les Services D'intégration SQL de sorte que les données sont dactylographiées correctement dès le début.
j'ai dû le faire de nombreuses fois dans le passé et habituellement la manière la plus rapide est d'écrire votre propre fonction définie par l'utilisateur pour vérifier les données est dans le format que vous attendez, comme la plupart du temps le l'appel direct à un code proc ou managé stocké étendu pour une validation simple est plus lent que juste le faire en T-SQL.
selon le soutien de Microsoft la seule façon efficace pour remplacer la fonction UDF est d'écrire votre propre version de fonction .NET.
bien sûr, si votre administrateur de base de données permet de ça :).
Mine de ne pas :(.
pour SQL Server 2005 et au-dessus.... profitez de 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
imprime 0.
Modification @test = '01234" ou @test = '01234.5" et de l'impression 1.
comment mettre en œuvre ces deux fonctions:
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 () semble avoir des problèmes avec les espaces, "D", "E", les signes dollar et toutes sortes d'autres caractères. Ce que nous voulons typiquement c'est quelque chose qui nous dit si un casting ou un CONVERT réussira. Cet UDF, bien que n'étant pas la solution la plus rapide, a très bien fonctionné pour moi.
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
SELECT
TRY_PARSE('123' as int) as '123'
,TRY_PARSE('abc' as int) as 'abc'