Ограничить количество строк от соединения в оракуле - PullRequest
2 голосов
/ 12 марта 2011

Я заранее прошу прощения за мой скучный вопрос, и если форматирование не соответствует номиналу (новичок), то здесь.

У меня есть таблица MY_TABLE со следующей схемой -

 MY_ID | TYPE | REC_COUNT
 1     | A    | 1
 1     | B    | 3
 2     | A    | 0
 2     | B    | 0
 ....

Первый столбец соответствует идентификатору, второй - некоторому типу, а третий - некоторому.Обратите внимание, что столбец MY_ID не является первичным ключом, может быть много записей с одинаковым MY_ID.

Я хочу написать хранимую процедуру, которая будет принимать массив идентификаторов и возвращать их подмножество, соответствующееследующие критерии - идентификатор должен совпадать с полем MY_ID, по крайней мере, 1 записи в таблице, и, по крайней мере, 1 соответствующая запись не должна иметь TYPE = A ИЛИ REC_COUNT = 0.

Это процедура, которую я придумал -

PROCEDURE get_id_subset(
    iIds IN ID_ARRAY,
    oMatchingIds OUT NOCOPY ID_ARRAY
)
IS
BEGIN
SELECT t.column_value
BULK COLLECT INTO oMatchingIds
FROM TABLE(CAST(iIds AS ID_ARRAY)) t
WHERE EXISTS (
    SELECT /*+ NL_SJ */ 1
    FROM MY_TABLE m
    WHERE (m.my_id = t.column_value)
    AND (m.type != 'A' OR m.rec_count != 0)
);
END get_id_subset;

Но я действительно беспокоюсь о производительности, и некоторые идентификаторы могут соответствовать тысячам записей в таблице.Существует индекс для столбца MY_ID и TYPE, но нет индекса для столбца REC_COUNT.Поэтому я подумал, что если есть более 1000 строк с совпадающим полем MY_ID, я просто верну идентификатор без применения предикатов TYPE и REC_COUNT.Вот эта версия -

PROCEDURE get_id_subset(
        iIds IN ID_ARRAY,
        oMatchingIds OUT NOCOPY ID_ARRAY
)
IS
BEGIN
SELECT t.column_value
BULK COLLECT INTO oMatchingIds
FROM TABLE(CAST(iIds AS ID_ARRAY)) t, MY_TABLE m
WHERE (m.my_id = t.column_value)
AND ( ((SELECT COUNT(m.my_id) FROM m WHERE 1) >= 1000)
    OR EXISTS (m.type != 'F' OR m.rec_count != 0)
);
END get_id_subset;

Но это не компилируется, я получаю следующую ошибку при внутреннем выборе -

PL / SQL: ORA-00936: отсутствует выражение

Есть ли другой способ написать это?Внутренний выбор должен работать с объединенной таблицей.

И чтобы уточнить, я согласен с набором результатов, отличающимся для этого запроса.Я предполагаю, что, поскольку в столбце my_id есть индекс, выполнение count (*) будет намного дешевле, чем фактическое применение предиката rec_count к 10000s строк, поскольку в этом столбце нет индекса.Я не прав?

1 Ответ

1 голос
/ 12 марта 2011

Я не вижу ваш второй запрос значительным, если есть какие-либо улучшения по сравнению с первым. В лучшем случае первый подзапрос должен выполнить 1000 подходящих записей, чтобы определить, меньше ли это число, чем 1000, поэтому я не думаю, что это сэкономит много работы. Кроме того, он меняет фактический результат, и из вашего описания не ясно, говорите ли вы, что все в порядке, пока это более эффективно. (И если все в порядке, то бизнес-логика очень непонятна - почему другие условия вообще имеют значение, если они не имеют значения, когда записей много?)

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

  1. Где предикаты
  2. Группировка по
  3. Имея предикаты

Оптимизатор может изменить порядок, в котором вещи действительно оцениваются, но результат всегда должен быть логически эквивалентен вышеуказанному порядку оценки (за исключением ошибок оптимизатора).

1000 записей на самом деле не так много. Вы действительно сталкивались со случаем, когда выполнение первого запроса недопустимо?

Для любого запроса может быть лучше переписать коррелированный подзапрос EXISTS как некоррелированный подзапрос IN. Вам нужно проверить это.

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

Редактировать

Для того типа короткого замыкания, о котором вы говорите, я думаю, что вам нужно переписать свой подзапрос (из начальной версии запроса) следующим образом (извините, моя первая попытка этого не сработала, потому что я пытался чтобы получить доступ к столбцу из таблицы верхнего уровня в подзапросе):

WHERE EXISTS (
    SELECT /*+ NL_SJ */ 1
      FROM MY_TABLE m
      WHERE (m.my_id = t.column_value)
        AND rownum <= 1000
      HAVING MAX( CASE WHEN m.type != 'A' OR m.rec_count != 0 THEN 1 ELSE NULL END ) I S NOT NULL
          OR MAX(rownum) >= 1000
)

Это должно заставить его набирать не более 1000 записей на идентификатор, а затем возвращать строку, если хотя бы одна строка соответствует условиям type и rec_count, или достигнут предел в 1000 записей. Если вы просматриваете план выполнения, вы должны увидеть операцию COUNT STOPKEY, которая показывает, что Oracle прекратит выполнение блока запроса после того, как будет возвращено определенное количество строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...