Есть ли способ заставить Oracle оценить фильтр после присоединения? - PullRequest
2 голосов
/ 26 июня 2019

Я хочу отфильтровать результат объединения, используя приведение. Проблема в том, что часть исходного поля не может быть преобразована в целое число. Это не будет проблемой, если фильтр был применен после объединения. Вот почему мне интересно, есть ли способ (возможно, совет оптимизатора или что-то в этом роде), чтобы подтолкнуть оценку фильтра после операции соединения.

Это запрос, который я построил для примера. Я ожидаю, что это сработает, но не с «недействительным номером ORA-01722»:

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
SELECT *
FROM "scalar"
         JOIN "literal" USING ("literal_id")
WHERE TO_NUMBER("literal") > 6;

ORA-01722 выбрасывается, потому что он применяется к «буквальному» CTE, поэтому происходит сбой, потому что «abc», очевидно, не число. Мы можем видеть это в плане выполнения:

План выполнения запроса

Чтобы уменьшить возможности, связанные с причиной моей проблемы, я выполнил этот запрос:

CREATE TABLE "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
);
CREATE TABLE "scalar" AS (
    SELECT 3 AS "scalar_id", 2 AS "literal_id"
    FROM "DUAL"
    CONNECT BY ROWNUM <= 10000
);
CREATE TABLE "joined" AS (
    SELECT *
    FROM "scalar"
             JOIN "literal" USING ("literal_id")
);
SELECT *
FROM "joined"
WHERE TO_NUMBER("literal") > 6;

Который отлично работает.

Итак, есть ли способ переписать этот запрос (мне все еще нужно, чтобы он был одним запросом), чтобы он не пытался преобразовать 'abc'?

Для справки, я попробовал это на Oracle Database 18c Standard Edition 2 Release 18.0.0.0.0, а также на Oracle Database 11g Enterprise Edition Release 11.2.0.1.0

Большое спасибо.

Ответы [ 2 ]

2 голосов
/ 26 июня 2019

Уловка старой школы состоит в том, чтобы структурировать запрос так, чтобы предикат не мог быть вставлен в объединение. Если вы поместите соединение во встроенное представление и добавите rownum, это не даст оптимизатору оценить предикат до завершения соединения

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
select *
  from (
SELECT "scalar_id",  "literal_id", "literal", rownum
FROM "scalar"
         JOIN "literal" USING ("literal_id")
)
WHERE TO_NUMBER("literal") > 6;

Если вы используете 12.2 или более позднюю версию, у вас есть возможность воспользоваться преимуществами функции to_number для возврата значения NULL в случае ошибки преобразования.

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
SELECT "scalar_id",  "literal_id", "literal"
FROM "scalar"
         JOIN "literal" USING ("literal_id")
WHERE to_number("literal" default null on conversion error) > 6;
1 голос
/ 26 июня 2019

Для where используйте условное преобразование. Например:

SELECT *
FROM "scalar" s JOIN
     "literal" l
      USING ("literal_id")
WHERE (CASE WHEN REGEXP_LIKE(l.literal, '[^[0-9]+$')
            THEN TO_NUMBER(l.literal)
       END) > 6;

Что касается вашего вопроса, я так не думаю. Oracle имеет довольно сложный оптимизатор, поэтому он будет перестраивать операции для оптимизации производительности. Вы могли бы использовать CTE и подсказку компилятора для материализации CTE, но это кажется излишним.

...