вторник, 6 апреля 2021 г.

Регистро-независимый поиск по полю БД, которое это не предусматривает (также костыль в версиях SAP BASIS ниже 7.51)

Постановка

Допустим есть таблица ZDOCUMENTS c полями DOCNR типа BELNR и полем SENDERNAME типа TEXT255. DOCNR - ключевое, SENDERNAME заполняется как угодно и может содержать разное написание одного и того же, например "ООО Компания" и "ООО КОМПАНИЯ". То есть принудительно SENDERNAME ни к какому регистру не приводится.

Необходимо построить отчет с селекционным экраном с 2 полями типа SELECT-OPTION и для DOCNR и для SENDERNAME. 
При запуске отчета должны выбираться данные из ZDOCUMENTS по заданным параметрам и выводится на экран.
Причем поиск по SENDERNAME должен быть регистронезависимым. То есть, по приведенному выше примеру, при вводе "*компания*" должны быть найдены документы и "ООО Компания", и "ООО КОМПАНИЯ".

БД Oracle ( на самом деле не важно, тут главное что не HANA)

Решение

В 7.51 и в OpenSQL и в CDS была добавлена функция UPPER, которая решает нашу задачу. Подробности тут

Проблема

Ниже 7.51 тоже живут компании и не собираются обновляться. Что делать?

Решение для <7.51

По хорошему, SENDERNAME нужно менять на SENDERID и создавать отдельную таблицу где будет связь SENDERID и SENDERNAME, а также поле SENDERNAME_UP, где будет хранится  SENDERNAME  в верхнем регистре. При записи в эту таблицу необходимо програмно записывать в SENDERNAME_UP преобразованный в верхний регистр SENDERNAME и в отчете искать уже по нему, связывая таблицы через INNER JOIN или в WHERE через EXISTS.

Но допустим, данных уже в ZDOCUMENTS много и структуру менять никто не собирается. Работаем с тем что есть.

Вариант 1. 

Выбираем все (ну не все - а с учетом других критериев), SENDERNAME в SELECT не учитываем, потом в ABAPе уже в цикле приводим SENDERNAME к верхнему регистру и фильтруем по параметру с селекционного экрана (который тоже предварительно переводим в верхний регистр), выкидывая лишнее. Просто, сердито, дорого по памяти и производительности. Тем более, если другие критерии будут отсутствовать - реально будет выбор, передача, и временное хранение в памяти всей таблицы. Впрочем, выбирать можно частями. Как бы это примерно могло выглядеть:

DATA lr_sender TYPE RANGE OF  zdocuments-sendername.
DATA lt_docs TYPE STANDARD TABLE OF zdocuments WITH EMPTY KEY.
DATA lt_docs_part TYPE STANDARD TABLE OF zdocuments WITH EMPTY KEY.
DATA lv_last_docnr TYPE zdocument-docnr.
DATA lv_count TYPE i VALUE 1000.

lr_sender = VALUE #(
  FOR <fs_sender> IN s_sender
  ( VALUE #( BASE <fs_sender>
      low = to_upper( <fs_sender>-low )
      high = to_upper( <fs_sender>-high )
    )
  )
).

DO.
	SELECT *
        FROM zdocuments
        WHERE docnr IN @s_docnr
          AND docnr >  @lv_last_docnr
        ORDER BY docnr
        INTO CORRESPONDING FIELDS OF TABLE @lt_docs_part
        UP TO @lv_count ROWS
    .
	LOOP AT lt_docs_part REFERENCE INTO DATA(lps_doc).
	  IF to_upper( lps_doc->sendername ) IN lr_sender.
		APPEND lps_doc->* TO lt_docs.
	  ENDIF.
	ENDLOOP.

	DATA(lv_lines) = lines( lt_docs_part ).

	IF lv_lines < lv_count.
	  EXIT.
	ENDIF.
	lv_last_docnr = lt_docs_part[ lv_lines ]-docnum.

ENDDO.




Вариант 2.

Если бы у нас был не SELECT-OPTIONS или хотя бы SELECT-OPTIONS, в который можно вводить исключительно SIGN = 'I' и OPTIONS = 'EQ', то можно попробовать работать через ADBC.

Это возможно так как обычно в самой СУБД все таки есть функция UPPER, просто она не прокинута в OpenSQL. 

Это было бы похоже на что-то типа:
DATA lt_data TYPE STANDARD TABLE OF zdocuments WITH DEFAULT KEY.
DATA lt_data_part TYPE STANDARD TABLE OF zdocuments WITH DEFAULT KEY.
DATA lt_sender TYPE STANDARD TABLE OF zdocuments-sendername WITH EMPTY KEY.
  
* тут какое то реальное заполнение,например, из некоего select-options s_sender выбором LOW и преобразованием в верхний регистр
lt_sender = VALUE #(  FOR <fs_sender> IN s_sender  ( to_upper( <fs_sender>-low ) ) ).

CONCATENATE
' SELECT'
' DOCNR,'
' SENDERNAME'
' FROM ZDOCUMENTS'
' WHERE UPPER(SENDERNAME)  = ?'
INTO DATA(lv_sql) RESPECTING BLANKS.

DATA(lo_sql) = NEW cl_sql_prepared_statement(
   statement          = lv_sql
).

DATA(lpv_sendername) = NEW zdocuments-sendername( ).
lo_sql->set_param(
  EXPORTING
    data_ref             = lpv_sendername
).
  
LOOP AT lt_sender INTO lpv_sendername->*.
  DATA(lo_result) = lo_sql->execute_query( ).

  lo_result->set_param_table(
    itab_ref = REF #( lt_data_part )
    corresponding_fields = VALUE #(
      ( 'DOCNUM' )
      ( 'SENDERNAME' )
    )
  ).

  lo_result->next_package( ).
  APPEND LINES OF lt_data_part TO lt_data.
  CLEAR lt_data_part.
ENDLOOP.
lo_sql->close( ).
Возможно, тут было бы еще быстрее через IF_SQLPORT_TEMPTABLE и CL_SQLPORT_TEMPTABLE_FACTORY с созданием временной таблицы со значениями SENDERNAME в верхнем регистре и JOIN ее с ZDOCUMENTS. 

Но все это имеет массу минусов. Начиная от того, что мы уходим от OpenSQL с проверкой синтаксиса к обычным строкам и заканчивая тем что на самом деле полей в таблице и соответствующих параметров на селекционном экране может быть много и все они типа select-options и сформировать для этой массы параметров корректный SQL это та еще задачка. А поддерживать это вообще ужас. Но в каких-то простых случаях это вполне применимо.

Вариант 3.

Используем CDS.
Тут основная проблема в том, что у нас еще пока нет UPPER в CDS. Но собственно если подумать что он делает? Меняет одни буквы на те же в верхнем регистре. И мы можем реализовать эту функциональность самостоятельно через REPLACE. Да это будет костыль (о чем и написано в заголовке), да это применимо только для конкретных языков, да наверное это скажется на производительности не в лучшую сторону, но это будет работать и это будет лучше чем выбирать все и фильтровать в ABAP.
Поэтому открываем Eclipse и создаем Data View со следующим содержимым:

@AbapCatalog.sqlViewName: 'ZCDS_DOCS'
@AbapCatalog.compiler.CompareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'CDS for ZDOCUMENTS'
define view ZDOCUMENTS_CDS as select from zdocuments {
    key docnr,
    sendername,
    //Поскольку в версии до 7.51 у нас нет UPPER в CDS извращаемся через REPLACE для всех латинских и русских букв,
    //заменяя строчную на прописную 
    SUBSTRING( 
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
    replace(
      sendername,
      'a','A'),
      'b','B'),
      'c','C'),
      'd','D'),
      'e','E'),
      'f','F'),
      'g','G'),
      'h','H'),
      'i','I'),
      'j','J'),
      'k','K'),
      'l','L'),
      'm','M'),
      'n','N'),
      'o','O'),
      'p','P'),
      'q','Q'),
      'r','R'),
      's','S'),
      't','T'),
      'u','U'),
      'v','V'),
      'w','W'),
      'x','X'),
      'y','Y'),
      'z','Z'),
      'а','А'),
      'б','Б'),
      'в','В'),
      'г','Г'),
      'д','Д'),
      'е','Е'),
      'ё','Ё'),
      'ж','Ж'),
      'з','З'),
      'и','И'),
      'й','Й'),
      'к','К'),
      'л','Л'),
      'м','М'),
      'н','Н'),
      'о','О'),
      'п','П'),
      'р','Р'),
      'с','С'),
      'т','Т'),
      'у','У'),
      'ф','Ф'),
      'х','Х'),
      'ц','Ц'),
      'ч','Ч'),
      'ш','Ш'),
      'щ','Щ'),
      'ъ','Ъ'),
      'ы','Ы'),
      'ь','ь'),
      'э','Э'),
      'ю','Ю'),
      'я','Я'),
    1, 255) as sendername_UP  
}
Поддерживаем мы тут, как видите, только английский и русский.

Теперь в нашем отчете мы можем писать что-то типа такого для выбора данных:
SELECT
  *
  FROM zdocuments
  INTO TABLE @DATA(Lt_docs)
  WHERE docnr IN @s_docnr
    AND EXISTS (
      SELECT docnr FROM zcds_docs 
        WHERE zcds_docs~docnr = zdocuments~docnr
      	  and zcds_docs~sendername_up in @s_sender
    )
.
PROFIT

Стало проще с выборками, но производительность относительно существенно падает.
То есть, если, например, есть выборка напрямую через условный SELECT * FROM zdocuments WHERE UPPER( sendername) in @s_sender., то выборка через JOIN или AND EXISTS к CDS с UPPER будет медленнее раза в 2, а через CDS с REPLACE'ами - раз в 7-10. И тут надо смотреть на абсолютные показатели - если просто так данные выбираются за секунду, то через CDS с REPLACE это будет всего 10 секунд, а если изначально данных столько или запрос такой, что  оригинальная выборка идет минуту, то через REPLACE это может быть и 10 минут. Возможно выбирать частями и фильтровать в ABAP будет быстрее.

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

Отправить комментарий