Запрос с использованием нескольких вызовов функции замены — Как повысить производительность?

#sql #oracle

#sql #Oracle

Вопрос:

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

Итак, у меня есть эта функция, которую я создал:

 create or replace FUNCTION F_REMOVE_SPECIAL_CHARACTERS 
(
  IN_PARAM_EMAIL_NAME IN VARCHAR2,
  IN_PARAM_NUMBER_FLAG IN VARCHAR2 DEFAULT 'N'
) RETURN VARCHAR2 AS 
BEGIN
  /* If flag is Y then remove all numbers too. Otherwise, keep numbers in the string */
  IF IN_PARAM_NUMBER_FLAG = 'Y' THEN
    RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._0-9]', ''), ' ', '');
  ELSE
    RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._]', ''), ' ', '');
  END IF;
END F_REMOVE_SPECIAL_CHARACTERS;
  

У меня также есть запрос, который выглядит следующим образом:

 SELECT a.ID, LISTAGG(b.BUSINESS_EMAIL) WITHIN GROUP (ORDER BY a.ID)
FROM tableA a, tableB b
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(b.LAST_NAME)) IN (
  (SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) FROM tableC c
      WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) IN (
        (SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(c.NAME)) FROM tableC c
           WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) = UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.LAST_NAME))
           )
      )
  )
)
  

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

Есть ли у кого-нибудь идея о том, как уменьшить замедление производительности при использовании нескольких вызовов функций в запросе? Спасибо.

Комментарии:

1. Добавьте столбцы в свою таблицу и предварительно вычислите все удаленные строки. Затем вы можете выполнять обычные операции SQL с предварительно вычисленными значениями. Предварительное вычисление всего столбца требует больших единовременных затрат, но будущие запросы будут выполняться намного быстрее.

2. Спасибо за ваше предложение. Я обязательно рассмотрю это решение

3. Итак, у нас есть четкое описание ПРОБЛЕМЫ (отдельно от РЕШЕНИЯ, которое у вас есть в настоящее время): вам предоставляется (возможно, длинная) строка (всегда ли это VARCHAR2 или это может быть CLOB?), список символов для удаления: тире, запятая, точка, подчеркивание и пробел, а также входной параметр, который сообщает вам, следует ли также удалять цифры. Правильно? Если это так, вам не нужно писать функцию, и вам не нужны регулярные выражения. И в вашем текущем решении вы можете включить пробел во [ ... ] внутренней функции, не требуя дополнительных шагов.

4. @mathguy, да, это правильно. Все входные данные для функции являются varchar2. Я создал функцию, чтобы ее можно было настраивать, поскольку я бы удалял строки несколько раз. У вас есть предложения, которые я мог бы сделать для повышения производительности? Если я не буду использовать регулярное выражение, это увеличит производительность?

5. Я опубликовал полный ответ. К вашему последнему вопросу — если вы избегаете регулярных выражений, очень вероятно, что производительность будет улучшена, возможно, значительно, но это также зависит от того, что замедляло выполнение запроса. (Например, если множество переключений контекста между SQL и PL / SQL были узким местом, регулярное выражение против not не будет иметь большого значения; в этом случае другие вещи — например, pragma udf в Oracle 12.1 или более поздней версии — дадут наибольшее улучшение).

Ответ №1:

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

 create or replace function f_remove_special_characters 
(
  in_param_email_name in varchar2,
  in_param_number_flag in varchar2 default 'N'
) 
return varchar2
deterministic
as 
pragma udf;   -- if on Oracle 12.1 or higher, and function is only for SQL use
  /* If flag is Y then remove all numbers too. 
     Otherwise, keep numbers in the string
  */
  chars_to_remove varchar2(16) := 'z-,._ ' || 
                         case in_param_number_flag when 'Y' then '0123456789' end;
begin
  return translate(in_param_email_name, chars_to_remove, 'z');
end f_remove_special_characters;
/
  

Глупый трюк с ‘z’ во translate (во втором и третьем аргументах) связан со странной обработкой Oracle null . В translate , если какой-либо из аргументов является null результатом является null , в отличие от обработки Oracle null в других строковых операциях.

Ответ №2:

Если вы используете 12c или выше, то в качестве быстрого исправления вы можете использовать предложение WITH FUNCTION

Насколько я помню, это устраняет переключение контекста PL / SQL<-> SQL, чтобы ваш запрос выполнялся лучше.

Я никогда не тестировал это, но очень вероятно, что это будет быстрее даже в 30-50 раз. Дайте мне знать, как быстро это будет, потому что мне любопытно

 WITH FUNCTION F_REMOVE_SPECIAL_CHARACTERS 
(
  IN_PARAM_EMAIL_NAME IN VARCHAR2,
  IN_PARAM_NUMBER_FLAG IN VARCHAR2 DEFAULT 'N'
) RETURN VARCHAR2 AS 
BEGIN
  /* If flag is Y then remove all numbers too. Otherwise, keep numbers in the string */
  IF IN_PARAM_NUMBER_FLAG = 'Y' THEN
    RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._0-9]', ''), ' ', '');
  ELSE
    RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._]', ''), ' ', '');
  END IF;
END;
SELECT a.ID, LISTAGG(b.BUSINESS_EMAIL) WITHIN GROUP (ORDER BY a.ID)
FROM tableA a, tableB b
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(b.LAST_NAME)) IN (
  (SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) FROM tableC c
      WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) IN (
        (SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(c.NAME)) FROM tableC c
           WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) = UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.LAST_NAME))
           )
      )
  )
)
  

Комментарии:

1. Спасибо. Я попробую это и прокомментирую результаты.

2. Внедрение функций в предложение WITH действительно намного быстрее, чем вызов традиционной функции PL / SQL (хотя 30-50 раз быстрее, вероятно, является исключением, а не нормой). В любом случае, Oracle 12.1 также принес pragma udf , объявленный в функции PL / SQL, что на самом деле даже быстрее, чем объявление функций в предложении WITH.