¿Eficientes reemplazos ISNUMERIC () en SQL Server?
-
10-07-2019 - |
Pregunta
Así que pasé 5 horas resolviendo un problema que resultó ser debido no solo a antiguo no confiable ISNUMERIC
pero parece que mi problema solo aparece cuando el UDF en el que < code> ISNUMERIC se declara WITH SCHEMABINDING
y se llama dentro de un proceso almacenado (tengo mucho trabajo por hacer para resumirlo en un caso de prueba, pero mi primera necesidad es reemplazarlo con algo confiable).
Cualquier recomendación sobre reemplazos buenos y eficientes para ISNUMERIC ()
. Obviamente, realmente debe haber variaciones para int
, money
, etc., pero qué están usando las personas (preferiblemente en T-SQL, porque en este proyecto, estoy restringido a SQL Server porque esta es una tarea de procesamiento de datos de gran volumen de SQL Server a SQL Server)?
Solución
Puede usar las funciones T-SQL TRY_CAST () o TRY_CONVERT () si está ejecutando SQL Server 2012 como Bacon Bits menciona en los comentarios:
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 está utilizando SQL 2008 R2 o anterior, tendrá que usar una función .NET CLR y ajustar System.Decimal. TryParse ().
Otros consejos
Dependiendo de las circunstancias y las características de rendimiento de la validación, a veces uso una variación de la expresión LIKE en su lugar. Por ejemplo:
NOT LIKE '%[^0-9]%'
Tenga en cuenta que este ejemplo específico es bastante ingenuo. No garantiza que el valor sea válido para la conversión a un tipo de datos en particular. Tampoco permite signos +/- o puntos decimales si los necesita.
Otra opción podría ser escribir un procedimiento almacenado extendido en un lenguaje como C, convertirlo en un archivo DLL y ponerlo a disposición de SQL Server.
No me imagino que se necesitarían demasiadas líneas de código para hacerlo, y probablemente sería más rápido que escribir un Procedimiento almacenado administrado en .NET, porque no incurriría en el sobreesfuerzo adicional al cargar el CLR.
Aquí hay un dato de información: http://msdn.microsoft.com/en-us/library/ms175200. aspx
Aquí hay un código C ++ que podría funcionar para usted:
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;
}
Siguiendo la ruta .NET CLR, puede usar una expresión regular, especialmente si espera un cierto rango de números.
Generalmente, como práctica, trato de no permitir que los datos sin tipo ingresen a la base de datos, ya que es más adecuado para manejarlos en la capa de aplicación o para las importaciones por lotes manejarlos en SQL Integration Services para que los datos entren escrito correctamente desde el principio.
He tenido que hacerlo muchas veces en el pasado y, por lo general, la forma más rápida es escribir su propia función definida por el usuario para verificar que los datos estén en el formato que espera, ya que la mayoría de las veces la sobrecarga para llamar a un El proceso almacenado extendido o el código administrado para una validación simple es más lento que simplemente hacerlo en T-SQL.
Según el soporte de Microsoft, la única forma eficiente de reemplazar la función UDF es escribir su propia versión de la función .NET.
Por supuesto, si el administrador de su base de datos lo permite :).
El mío no :(.
Para SQL Server 2005 y superior ... aproveche 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.
Cambia @test = '01234' o @test = '01234.5' e imprime 1.
¿Alguna vez manejará sistemas de números fuera de su propio idioma (humano), como el chino, etc.? Si es así, sugiero usar la biblioteca libuninum .
¿Qué tal implementar estas dos funciones?
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 () parece tener problemas con espacios, 'D', 'E', signos de dólar y todo tipo de otros caracteres. Lo que normalmente queremos es algo que nos diga si CAST o CONVERT tendrán éxito. Este UDF, aunque no es la solución más rápida, me ha funcionado muy bien.
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 en adelante, puede usar la función TRY_PARSE () en lugar de ISNUMERIC ().
SELECT
TRY_PARSE('123' as int) as '123'
,TRY_PARSE('abc' as int) as 'abc'