Недопустимое преобразование SQL возвращает null вместо ошибки
у меня есть таблица со столбцом varchar, и я хочу найти значения, соответствующие определенному числу. Итак, допустим, что столбец содержит следующие записи (за исключением миллионов строк в реальной жизни):
123456789012
2345678
3456
23 45
713?2
00123456789012
поэтому я решаю, что хочу, чтобы все строки, которые численно 123456789012, написали заявление, которое выглядит примерно так:
SELECT * FROM MyTable WHERE CAST(MyColumn as bigint) = 123456789012
Он должен возвращать первую и последнюю строку, но вместо этого весь запрос взрывается, потому что он не может преобразовать "23 45" и "713?2" в bigint.
есть ли другой способ сделать преобразование, которое вернет NULL для значений, которые не могут преобразовать?
6 ответов
SQL Server не гарантирует короткого замыкания логического оператора, см. на SQL Server логический оператор короткого замыкания. Так что все решение с помощью ISNUMERIC(...) AND CAST(...)
принципиально ущербны (они могут работать, но могут произвольно не позднее dependiong на созданный план). Лучшим решением является использование CASE, как предлагает Томас:CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END
. Но, как отметил gbn,ISNUMERIC
заведомо привередлив в определении того, что означает "числовой", и во многих случаях, когда можно было бы ожидать, что он вернет 0 возвращает 1. Итак, смешивая случай с подобным:
CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END
но реальная проблема заключается в том, что если у вас есть миллионы строк, и вам нужно искать их так, вы всегда будете сканировать конец в конец, так как выражение не SARG-able (независимо от того, как мы его переписываем). Реальная проблема здесь заключается в чистоте данных и должна решаться на соответствующем уровне, где данные заполняются. Другое дело, если возможно создать сохраненный вычисляемый столбец с этим выражением и создайте на нем отфильтрованный индекс, который исключает NULL(т. е. нечисловой.) Это немного ускорит дело.
Если вы используете SQL Server 2012, Вы можете использовать 2 новых метода:
оба метода эквивалентны. Они возвращают значение, приведенное к указанному типу данных, если приведение выполняется успешно; в противном случае возвращает null. Единственное отличие заключается в том, что CONVERT-это SQL Server specific, CAST-ANSI. использование CAST сделает ваш код более переносимым (хотя не уверен, что какой-либо другой поставщик баз данных реализует то try_cast)
ISNUMERIC примет пустую строку и значения, такие как 1.23 или 5E-04, поэтому могут быть ненадежными.
и вы не знаете, в каком порядке вещи будут оцениваться, чтобы он все еще мог потерпеть неудачу (SQL декларативен, а не процедурный, поэтому предложение WHERE, вероятно, не будет оцениваться слева направо)
Так:
- вы хотите принять значение, которое состоит только символы 0-9
- вам нужно материализовать фильтр "номер", чтобы он применяется перед броском
что-то типа:
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
Edit: быстрый пример, который терпит неудачу.
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
функция ISNUMERIC () должна дать вам то, что вам нужно.
SELECT * FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as bigint) = 123456789012
и добавить заявление случая, как Томас предложил:
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)
кроме того, вы можете использовать оператор CASE для получения нулевых значений.
SELECT
CASE
WHEN (ISNUMERIC(MyColumn) = 1) THEN CAST(MyColumn as bigint)
ELSE NULL
END AS 'MyColumnAsBigInt'
FROM tableName
Если вам требуется дополнительная фильтрация, для чисел, которые недопустимы для приведения к bigint, вы можете использовать следующее вместо ISNUMERIC:
PATINDEX('%[^0-9]%',MyColumn)) = 0
Если вам нужны десятичные значения вместо целых чисел, приведите к float и измените регулярное выражение на " %[^0-9.]%'