Недопустимое преобразование 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

SELECT * 
    FROM MyTable 
    WHERE ISNUMERIC(MyRow) = 1
        AND CAST(MyRow as float) = 123456789012

функция 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

http://msdn.microsoft.com/en-us/library/ms186272.aspx


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.]%'