Ошибка неверного номера! Не могу обойти это - PullRequest
4 голосов
/ 04 декабря 2009

Oracle 10g БД. У меня есть таблица под названием s_contact. Эта таблица имеет поле с именем person_uid. Это person_uid поле является varchar2, но содержит действительные числа для некоторых строк и недействительные числа для других строк. Например, одна строка может иметь person_uid из 2-lkjsdf, а другая - 1234567890.

Я хочу вернуть только строки с действительными числами в person_uid. SQL я пытаюсь ...

select person_uid 
from s_contact 
where decode(trim(translate(person_uid, '1234567890', ' ')), null, 'n', 'c') = 'n'

В переводе все числа заменяются пробелами, так что обрезка будет иметь нулевое значение, если поле содержит только числа. Затем я использую оператор декодирования, чтобы установить небольшой код для фильтрации. n = число, c = символ

Кажется, это работает, когда я запускаю только предварительный просмотр, но я получаю ошибку 'недопустимое число', когда добавляю фильтр ...

and person_uid = 100
-- or
and to_number(person_uid) = 100

Я просто не понимаю, что происходит! Он должен отфильтровывать все записи, которые являются недопустимыми числами, а 100, очевидно, является числом ...

Есть идеи у кого-нибудь? С благодарностью!

Ответы [ 6 ]

6 голосов
/ 04 декабря 2009

К сожалению, различные предложенные подходы подзапроса не гарантируют работу. Oracle может вставить предикат в подзапрос, а затем оценивать условия в любом порядке, который он сочтет целесообразным. Если произойдет оценка условия PERSON_UID перед фильтрацией нечисловых строк, вы получите ошибку. У Джонатана Дженника есть отличная статья Subquery Madness , в которой эта проблема обсуждается довольно подробно.

Это оставляет вам несколько вариантов

1) Переработать модель данных. Как правило, не рекомендуется хранить числа в столбцах NUMBER. Помимо возникновения такого рода проблем, он имеет тенденцию искажать оценки количества элементов оптимизатора, что приводит к неоптимальным планам запросов.

2) Измените условие, указав строковое значение, а не число. Если предполагается, что PERSON_UID является строкой, ваше условие фильтрации может быть PERSON_UID = '100'. Это позволяет избежать необходимости выполнять неявное преобразование.

3) Напишите пользовательскую функцию, которая выполняет преобразование строки в число, игнорирует все ошибки и использует ее в своем коде, т.е.

CREATE OR REPLACE FUNCTION my_to_number( p_arg IN VARCHAR2 )
  RETURN NUMBER
IS
BEGIN
  RETURN to_number( p_arg );
EXCEPTION
  WHEN others THEN
    RETURN NULL;
END;

, а затем my_to_number(PERSION_UID) = 100

4) Используйте подзапрос, который предотвращает выдвижение предиката. Это можно сделать несколькими разными способами. Лично я предпочитаю бросать ROWNUM в подзапрос, то есть опираться на решение OMG Ponies

WITH valid_persons AS (
  SELECT TO_NUMBER(c.person_uid) 'person_uid',
         ROWNUM rn
    FROM S_CONTACT c
   WHERE REGEXP_LIKE(c.personuid, '[[:digit:]]'))
SELECT *
  FROM valid_persons vp
 WHERE vp.person_uid = 100

Oracle не может вставить предикат vp.person_uid = 100 в подзапрос, потому что это изменило бы результаты. Вы также можете использовать подсказки для принудительной материализации подзапроса или для предотвращения нажатия предикатов.

4 голосов
/ 04 декабря 2009

Другой альтернативой является объединение предикатов:

where case when translate(person_uid, '1234567890', ' ')) is null 
  then to_number(person_uid) end = 100
3 голосов
/ 04 декабря 2009

Когда вы добавляете эти числа в предложение WHERE, он все еще выполняет эти проверки. Вы не можете гарантировать порядок в предложении WHERE. Поэтому он все еще пытается сравнить 100 с '2-lkjsdf'.

Можете ли вы использовать «100»?

2 голосов
/ 04 декабря 2009

Другим вариантом является применение подвыбора

SELECT * FROM (
 select person_uid 
 from s_contact 
 where decode(trim(translate(person_uid, '1234567890', ' ')), null, 'n', 'c') = 'n'
)
WHERE TO_NUMBER(PERSON_UID) = 100
0 голосов
/ 07 января 2011

Регулярные выражения на помощь!

where regexp_like (person_uid, '^[0-9]+$')

0 голосов
/ 04 декабря 2009

Используйте первую часть вашего запроса для создания временной таблицы. Затем запросите временную таблицу на основе person_uid = 100 или чего-либо другого.

Проблема в том, что oracle пытается преобразовать каждый person_uid в int по мере его поступления из-за дополнительных операторов и в предложении where. Такое поведение может отображаться или не отображаться в предварительном просмотре в зависимости от того, какие записи были выбраны.

...