Sostituzioni ISNUMERIC () efficienti su SQL Server?
-
10-07-2019 - |
Domanda
Quindi ho appena trascorso 5 ore a risolvere un problema che si è rivelato non solo dovuto alla vecchio non affidabile ISNUMERIC
ma sembra che il mio problema appaia solo quando l'UDF in cui < code> ISNUMERIC è dichiarato WITH SCHEMABINDING
ed è chiamato all'interno di un proc memorizzato (ho molto lavoro da fare per distillarlo in un caso di test, ma il mio primo bisogno è per sostituirlo con qualcosa di affidabile).
Eventuali raccomandazioni su sostituzioni valide ed efficienti per ISNUMERIC ()
. Ovviamente ci sono davvero delle variazioni per int
, money
, ecc., Ma cosa usano le persone (preferibilmente in T-SQL, perché su questo progetto sono limitato a SQL Server perché si tratta di un'attività di elaborazione dati da SQL Server a SQL Server ad alto volume)?
Soluzione
È possibile utilizzare le funzioni T-SQL TRY_CAST () o TRY_CONVERT () se si esegue SQL Server 2012 come Bacon Bits menziona nei commenti:
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
Se si utilizza SQL 2008 R2 o versioni precedenti, è necessario utilizzare una funzione CLR .NET e avvolgere System.Decimal.TryParse ().
Altri suggerimenti
A seconda delle circostanze e delle caratteristiche prestazionali della convalida, a volte utilizzo invece una variante dell'espressione LIKE. Ad esempio:
NOT LIKE '%[^0-9]%'
Nota che questo esempio specifico è abbastanza ingenuo. Non garantisce che il valore sia valido per la conversione in un particolare tipo di dati. Inoltre non consente segni +/- o punti decimali se ne hai bisogno.
Un'altra opzione potrebbe essere quella di scrivere una stored procedure estesa in una lingua come C, trasformarla in una DLL e renderla disponibile a SQL Server.
Non immagino che ci vorrebbe troppe righe di codice da fare, e probabilmente sarebbe più veloce della scrittura di una Stored procedure gestita in .NET, perché non dovresti incorrere in un extra sentito dal caricamento del CLR.
Ecco un po 'di informazioni: http://msdn.microsoft.com/en-us/library/ms175200. aspx
Ecco del codice C ++ che potrebbe funzionare per te:
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;
}
Seguendo il percorso CLR .NET, potresti usare un'espressione regolare, in particolare se ti aspetti un determinato intervallo di numeri.
Generalmente, come pratica, cerco di non lasciare dati non tipizzati nel database, poiché è più adatto a gestirli a livello di applicazione o per le importazioni in batch a gestirli in SQL Integration Services in modo che i dati arrivino digitato correttamente dall'inizio.
Ho dovuto farlo molte volte in passato e di solito il modo più veloce è scrivere la tua funzione definita dall'utente per verificare che i dati siano nel formato che ti aspetti, come il più delle volte l'overhead per chiamare un il proc memorizzato esteso o il codice gestito per una semplice convalida è più lento del semplice farlo in T-SQL.
Secondo il supporto di Microsoft, l'unico modo efficiente per sostituire la funzione UDF è scrivere la propria versione della funzione .NET.
Naturalmente, se l'amministratore del database lo consente :).
Il mio no :(.
Per SQL Server 2005 e versioni successive ... approfitta di 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
stampa 0.
Cambia @test = '01234' o @test = '01234.5' e stampa 1.
Hai mai intenzione di gestire sistemi numerici al di fuori della tua lingua (umana), come il cinese ecc.? In tal caso, suggerirei di utilizzare la libreria libuninum .
Che ne dici di implementare queste due funzioni:
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 () sembra avere problemi con spazi, 'D', 'E', simboli di dollari e ogni sorta di altri caratteri. Ciò che in genere vogliamo è qualcosa che ci dice se un CAST o un CONVERT avranno successo. Questo UDF, sebbene non sia la soluzione più veloce, ha funzionato molto bene per me.
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
A partire da SQL 2012, è possibile utilizzare la funzione TRY_PARSE () anziché ISNUMERIC ().
SELECT
TRY_PARSE('123' as int) as '123'
,TRY_PARSE('abc' as int) as 'abc'