SQL no válida nula rentabilidad de conversión en lugar de error de tiro
-
29-09-2019 - |
Pregunta
Tengo una tabla con una columna varchar, y quiero encontrar valores que coinciden con un determinado número. Lo que permite decir que la columna contiene las siguientes entradas (excepto con millones de filas en la vida real):
123456789012
2345678
3456
23 45
713?2
00123456789012
Así que decido todas las filas que son numéricamente 123456789012 escribir una declaración que es como la siguiente:
SELECT * FROM MyTable WHERE CAST(MyColumn as bigint) = 123456789012
Debe devolver la primera y la última fila, pero en lugar de toda la consulta explota porque no puede convertir el "23 45" y "713? 2" a bigint.
¿Hay otra manera de hacer la conversión que devolverá NULL para valores que no se pueden convertir?
Solución
SQL Server no garantiza cortocircuito operador booleano, consulte En SQL Server operador booleano cortocircuito . Así que toda la solución usando ISNUMERIC(...) AND CAST(...)
están fundamentalmente equivocada (que pueden trabajar, pero bueno arbitrariamente pueden fallar después dependiong en el plan generado). Una mejor solución es utilizar CASE, como sugiere Thomas: CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END
. Pero, como señaló GBN, ISNUMERIC
es notoriamente quisquillosos en la identificación de lo que significa '' numéricos y muchos casos en los que uno esperaría que retorne 0 devuelve 1. Así mezclar el caso con el gusto:
CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END
Pero el verdadero problema es que si usted tiene millones de filas y hay que buscar en ellos como éste, que siempre terminan de extremo a extremo escanear ya que la expresión no es SARG-poder (no importa lo reescribimos eso). El verdadero problema aquí es la pureza de datos, y debe abordarse en el nivel adecuado, donde se rellena los datos. Otra cosa a considerar es si es posible crear una columna calculada persistido con esta expresión y crear un índice filtrado en el mismo que NULL elimina (es decir. No numérico). Eso sería acelerar las cosas un poco.
Otros consejos
Si está utilizando SQL Server 2012 puede utilizar los nuevos métodos 2:
Ambos métodos son equivalentes. Ellos devuelven un valor conversión al tipo de datos especificado si la conversión se realiza correctamente; de otro modo, devuelve null. La única diferencia es que se convierten es específico de SQL Server, CAST es ANSI. utilizando REPARTO hará que el código sea más portátil (aunque no estoy seguro si otros implementos de proveedor de base de datos TRY_CAST)
ISNUMERIC aceptará cadena y los valores vacíos como 1,23 o 5E-04 por lo que podrían no ser fiables.
Y usted no sabe qué cosas pedido será evaluado en lo que todavía podría fallar (SQL es declarativa, no de procedimiento, por lo que la cláusula WHERE probablemente no serán evaluadas de izquierda a derecha)
Así que:
- desea aceptar el valor que consisten solamente de los caracteres 0-9
- que necesita para materializar el filtro de "número" por lo que se aplica antes REPARTO
Algo así como:
SELECT
*
FROM
(
SELECT TOP 2000000000 *
FROM MyTable
WHERE MyColumn NOT LIKE '%[^0-9]%' --double negative rejects anything except 0-9
ORDER BY MyColumn
) foo
WHERE
CAST(MyColumn as bigint) = 123456789012 --applied after number check
Editar:. Rápido ejemplo que falla
CREATE TABLE #foo (bigintstring varchar(100))
INSERT #foo (bigintstring )VALUES ('1.23')
INSERT #foo (bigintstring )VALUES ('1 23')
INSERT #foo (bigintstring )VALUES ('123')
SELECT * FROM #foo
WHERE
ISNUMERIC(bigintstring) = 1
AND
CAST(bigintstring AS bigint) = 123
SELECT *
FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as float) = 123456789012
El ISNUMERIC () la función que debe darle lo que necesita.
SELECT * FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as bigint) = 123456789012
Y para añadir una declaración de caso como Thomas sugirió:
SELECT * FROM MyTable
WHERE CASE(ISNUMERIC(MyRow)
WHEN 1 THEN CAST(MyRow as bigint)
ELSE NULL
END = 123456789012
SELECT *
FROM MyTable
WHERE (ISNUMERIC(MyColumn) = 1) AND (CAST(MyColumn as bigint) = 123456789012)
Además se puede utilizar una instrucción CASE con el fin de obtener los valores nulos.
SELECT
CASE
WHEN (ISNUMERIC(MyColumn) = 1) THEN CAST(MyColumn as bigint)
ELSE NULL
END AS 'MyColumnAsBigInt'
FROM tableName
Si usted requiere un filtrado adicional, por lo que los valores numéricos no son válidos para ser echada a bigint, puede utilizar el siguiente lugar de ISNUMERIC:
PATINDEX ( '% [^ 0-9]%', MiColumna)) = 0
Si necesita valores decimales en lugar de números enteros, fundido a flote lugar y cambiar la expresión regular a '% [^ 0-9.]%'