Неверный номер Oracle в предложении соединения - PullRequest
4 голосов
/ 24 марта 2019

Я получаю ошибку Oracle Invalid Number, которая не имеет смысла для меня.Я понимаю , что означает эта ошибка , но это не должно происходить в этом случае.Извините за длинный вопрос, но, пожалуйста, потерпите меня, чтобы я смог это подробно объяснить.

У меня есть таблица, в которой идентификаторы хранятся в разных источниках, а некоторые идентификаторы могут содержать буквы.Поэтому столбец имеет вид VARCHAR.

. Один из источников имеет числовые идентификаторы, и я хочу присоединиться к этому источнику:

SELECT *
FROM (
    SELECT AGGPROJ_ID -- this column is a VARCHAR
    FROM AGG_MATCHES -- this is the table storing the matches
    WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);

В большинстве случаев это работает, но в зависимости отна случайных вещах, таких как столбцы в предложении select, если он использует левое или внутреннее соединение и т. д., я начну видеть ошибку Invalid Number.

Я неоднократно проверял, что всезаписи в AGG_MATCHES, где AGGSRC = 'source_a' не содержат нецифровых символов в столбце AGGPROJ_ID:

-- this returns no results
SELECT AGGPROJ_ID
FROM AGG_MATCHES
WHERE AGGSRC = 'source_a' AND REGEXP_LIKE(AGGPROJ_ID, '[^0-9]');

Я знаю, что Oracle в основном переписывает запрос для оптимизации.Возвращаясь к первому примеру SQL, я думаю, что в зависимости от того, как написан весь запрос, в в некоторых случаях Oracle пытается выполнить JOIN перед подзапросом.Другими словами, он пытается объединить все таблицы AGG_MATCHES в SOURCE_A вместо только подмножества, возвращаемого подзапросом.Если это так, в столбце AGGPROJ_ID будут строки, содержащие не числовые значения.

Кто-нибудь наверняка знает, является ли это причиной ошибки?Если это причина, могу ли я в любом случае заставить Oracle сначала выполнить часть подзапроса, поэтому он пытается присоединиться только к подмножеству таблицы AGG_MATCHES?


Немногодополнительная справочная информация:

Это, очевидно, упрощенный пример для иллюстрации проблемы.Таблица AGG_MATCHES используется для хранения «совпадений» между различными источниками (например, проектами).Другими словами, используется, чтобы сказать, что проект в sourceA сопоставлен с проектом в sourceB.

Вместо того, чтобы писать один и тот же SQL снова и снова, я создал представления для источников, которые мы обычно используем.Идея состоит в том, чтобы иметь представление с двумя столбцами, один для SourceA и один для SourceB.По этой причине я не хочу использовать TO_CHAR в столбце ID исходной таблицы, потому что разработчики должны помнить об этом каждый раз, когда они выполняют соединение, и я пытаюсь удалить коддублирования.Кроме того, поскольку идентификатор в SOURCE_A является числом, я считаю, что любое представление, хранящее SOURCE_A.ID, должно преобразиться в число.

Ответы [ 2 ]

5 голосов
/ 24 марта 2019

Вы правы в том, что Oracle выполняет оператор в другом порядке, чем тот, который вы написали, вызывая ошибки преобразования.

Наилучшие способы исправить эту проблему по порядку:

  1. Изменить модель данных, чтобы всегда сохранять данные как правильный тип.Всегда храните числа как числа, даты как даты и строки как строки.(Вы уже знаете это и сказали, что не можете изменить свою модель данных, это предупреждение для будущих читателей.)
  2. Преобразование чисел в строки с TO_CHAR.
  3. Если вы используете 12.2, преобразуйте строки в числа, используя синтаксис DEFAULT return_value ON CONVERSION ERROR, например:

    SELECT *
    FROM (
        SELECT AGGPROJ_ID -- this column is a VARCHAR
        FROM AGG_MATCHES -- this is the table storing the matches
        WHERE AGGSRC = 'source_a'
    ) m
    JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID default null on conversion error);
    
  4. Добавьте ROWNUM во встроенное представление, чтобы предотвратить преобразования оптимизатора, которыеможет переписать заявления.ROWNUM всегда оценивается в конце, и это заставляет Oracle запускать вещи в определенном порядке, даже если ROWNUM не используется.(Официально подсказки являются способом сделать это, но получить правильные подсказки слишком сложно.)

    SELECT *
    FROM (
        SELECT AGGPROJ_ID -- this column is a VARCHAR
        FROM AGG_MATCHES -- this is the table storing the matches
        WHERE AGGSRC = 'source_a'
            --Prevent optimizer transformations for type safety.
            AND ROWNUM >= 1
    ) m
    JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
    
0 голосов
/ 24 марта 2019

Я думаю, что самое простое решение использует case, что дает больше гарантий на порядок оценки:

SELECT a.*
FROM AGG_MATCHES m JOIN
     SOURCE_A a
     ON a.ID = (CASE WHEN m.AGGSRC = 'source_a' THEN TO_NUMBER(m.AGGPROJ_ID) END);

Или, еще лучше, преобразовать в строки:

SELECT a.*
FROM AGG_MATCHES m JOIN
     SOURCE_A a
     ON TO_CHAR(a.ID) = m.AGGPROJ_ID AND
        m.AGGSRC = 'source_a' ;

Тем не менее, лучший совет - исправить модель данных.

Возможно, лучшим решением в вашем случае является просто представление или столбец генерации:

create view v_agg_matches_a as 
    select . . .,
           (case when regexp_like(AGGPROJ_ID, '^[0-9]+$')
                 then to_number(AGGPROJ_ID)
            end) as AGGPROJ_ID
    from agg_matches am
    where m.AGGSRC = 'source_a';

case может не понадобиться, если вы используете вид, но он безопаснее.

Затем используйте представление в последующих запросах.

...