Неверное преобразование SQL возвращает ноль вместо ошибки - PullRequest
7 голосов
/ 04 ноября 2010

У меня есть таблица со столбцом 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 ]

8 голосов
/ 22 июля 2013

Если вы используете SQL Server 2012, вы можете использовать 2 новых метода:

Оба метода эквивалентны. Они возвращают приведение значения к указанному типу данных, если приведение выполнено успешно; в противном случае возвращает ноль. Разница лишь в том, что CONVERT специфичен для SQL Server, CAST - для ANSI. использование CAST сделает ваш код более переносимым (хотя и не уверен, что любой другой поставщик баз данных реализует TRY_CAST)

7 голосов
/ 04 ноября 2010

SQL Server НЕ гарантирует короткое замыкание логического оператора, см. О коротком замыкании логического оператора SQL Server . Таким образом, все решения, использующие ISNUMERIC(...) AND CAST(...), в корне ошибочны (они могут работать, но в дальнейшем они могут произвольно потерпеть неудачу в зависимости от созданного плана). Лучшее решение - использовать CASE, как предлагает Томас: CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END. Но, как указал gbn, ISNUMERIC общеизвестно привередлив в определении того, что означает «числовое», и во многих случаях, когда можно ожидать, что он вернет 0, он возвращает 1. Итак, смешивая CASE с LIKE:

CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END

Но реальная проблема заключается в том, что если у вас есть миллионы строк, и вы должны искать их таким образом, вы всегда будете заканчивать сканирование сквозным, так как выражение не поддерживает SARG (независимо от того, как мы переписываем Это). Реальная проблема здесь заключается в чистоте данных, и ее следует решать на соответствующем уровне, где данные заполняются. Еще одна вещь, которую следует учитывать, - это если возможно создать постоянный вычисляемый столбец с этим выражением и создать отфильтрованный индекс, который исключает NULL (т. Е. Не числовой). Это немного ускорит процесс.

4 голосов
/ 04 ноября 2010

ISNUMERIC будет принимать пустую строку и значения, такие как 1.23 или 5E-04, поэтому могут быть ненадежными.

И вы не знаете, в каком порядке будут оцениваться объекты, поэтому он все равно может потерпеть неудачу (SQL декларативный, а не процедурный, поэтому предложение WHERE, вероятно, не будет оцениваться слева направо)

Итак:

  • вы хотите принять значение, состоящее из только символов 0-9
  • вам нужно материализовать фильтр "число", чтобы он применялся до CAST

Что-то вроде:

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

Редактировать: быстрый пример, который терпит неудачу.

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
1 голос
/ 04 ноября 2010
SELECT * 
    FROM MyTable 
    WHERE ISNUMERIC(MyRow) = 1
        AND CAST(MyRow as float) = 123456789012
0 голосов
/ 04 ноября 2010
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

Если вам нужны десятичные значения вместо целых, приведите вместо плавающего значения и измените регулярное выражение на «% [^ 0-9.]%»

0 голосов
/ 04 ноября 2010

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

...