Показаны сообщения с ярлыком tips. Показать все сообщения
Показаны сообщения с ярлыком tips. Показать все сообщения

вторник, 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 тоже живут компании и не собираются обновляться. Что делать?

пятница, 8 ноября 2019 г.

Запись VBKD-TRATY при использовании BAPI_SALESORDER_CREATEFROMDAT2

BAPI  BAPI_SALESORDER_CREATEFROMDAT2  не содержит входящих параметров чтобы сохранять в создаваемом заказе VBKD-TRATY. В иторнетах встречается предложение вместо этого использовать ФМ SD_SALESDOCUMENT_CREATE (который внутри BAPI и вызывается), передавая TRATY в BUSINESS_EX. Это бред, поскольку BUSINESS_EX это исходящий параметр. Еще есть вариант обновлять VBKD-TRATY прямым update после вызова BAPI и сохранения по полученному номеру заказа. Это в принципе вариант, но все можно сделать прямо через BAPI описанным ниже способом.

VBKD может хранить данные как на уровне позиции, так и на уровне заголовка - в последнем случае в VBKD-POSNR  будут 0. От того куда нужно записать TRATY зависит, что нужно расширять и что передавать в параметрах. В примерах будет про позиции, но для заголовка тоже указано что и как.


вторник, 10 сентября 2019 г.

Связь средств поиска между собой внутри другого средства поиска (F4 on F4)


В средстве поиска (СП) могут быть входящие параметры, которыми можно ограничивать результат, выдаваемый пользователю для выбора.
Возьмем для примера СП для склада H_T001L. Склады LGORT лежат в таблице T001L, склады принадлежат какому либо заводу WERKS. В СП по складу WERKS помечен как импортируемый параметр (IMP):




Также WERKS присутствует в диалоге ограничений:
 

Обратите внимание, когда завод не задан, выводятся склады всех заводов. Но если завод указать, будут выведены только его склады - например укажем завод 0002:

Также на скриншоте выше видно, что у поля Склад в ограничениях также есть кнопка средства поиска, но при её нажатии мы увидим список складов без ограничения по заводу (предприятию):


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

вторник, 18 сентября 2018 г.

Парсинг XML с помощью SimpleTransformation с пропуском тегов.

Итак, допустим, есть такой XML:
И, допустим, нас интересует только значение тега material (которого, кстати, может и не быть). Напишем такую версию simple transformation:

Применение данной трансформации вызовет исключительную ситуацию, так как в XML нам встретиться тег prod и тег foo, которые мы не предусмотрели в трансформации.

Конечно, их можно добавить в трансформацию и никак не обрабатывать их значения. Но таких ненужных нам тегов может быть много и прописывать их все это некомильфо. Кроме того, могут быть ситуации, когда XML описан не четко и подразумевает, что внутри values могут появляться не известные нам на этапе разработки теги.

Как пропускать все что нам не нужно или мы не знаем что это? Для этого нужно в конец tt:group добавить tt:skip следующим образом:
Здесь мы "говорим", что если нам встречается тег material, то мы берем его значение и помещаем в ROOT.MATERIAL, иначе мы пропускаем 1 тег.
Тег tt:group работает как цикл + switch/case - берет на каждом шаге следующий тег в values и сравнивает по условиям tt:d-cond, если условие проходит - выполняется то, что внутри tt:d-cond, и происходит следующая итерация "цикла". Таким образом, по примеру выше, мы найдем на первой итерации тег material, запишем его значение в root.material, найдем на следующей итерации prod и пропустим его, далее найдем на следующей итерации foo и также пропустим его (и аналогично для других тегов если они будут) и выйдем из values. Тег tt:skip должен быть в конце tt:group.

Но с данной реализацией есть одна неприятность. Толи это баг в реализации SimpleTransfomation, толи это by design. Заключается она в следующем:

Если в исходном XML будет только тег material, это также вызовет ошибку трансформации. Отладка данного случая показывает, что обработав тег material, парсер все равно пытается выполнить tt:skip хотя бы один раз. Делает он это для следующего тега, которым будет закрывающий тег </values>. В итоге правило для самого values не найдет своего закрывающего тега и будет выброшено исключение. Но если в XML есть хотя бы один тег, который нам надо пропустить, то tt:skip сработает 1 раз и далее не будет пытаться пропустить </values>.

Обойти это можно, явно добавив еще один фиктивный тег в конец набора тегов в values в исходном XML. Сделать это можно перед вызовом трансформации например так (XML находится в переменной gv_xml):

REPLACE ALL OCCURRENCES OF '</values>' IN gv_xml WITH '<dummyEnd /></values>' IGNORING CASE.'

а в трансформацию явно добавить обработку тега dummyEnd: Не понятно почему, но данный подход срабатывает, и после обработки dummyEnd парсер не пытается выполнить tt:skip.

Если же работать перестанет, то есть более сложный, но более логичный вариант трансформации (в XML также все еще нужно добавлять фиктивный тег):
Здесь мы заводим переменную skip. Далее при начале обработки тега values мы записываем в переменную skip значение 0. В условии tt:cond-var check="skip=0" мы проверяем, что skip равно 0 и, если это так, то пропускаем один тег с помощью tt:skip. Если мы встречаем тег dummyEnd, то записываем в переменную skip значение 1. Таким образом, после последнего тега в values (а мы позаботились чтобы им был dummyEnd), больше не требуется пропускать теги, tt:skip не будет выполнен, и будет обработан закрывающий </values>

среда, 14 февраля 2018 г.

Исключение дубликатов. Сравнение производительности.

Часто в солидных компаниях для abap-еров выпускают некий документ, регламентирующий разработку в них. Помимо всяких требований к наименованию объектов и запросов, зачастую там бывают общие правила. Обычно, все подобные документы однотипны, поскольку слизаны где-то у кого-то, слегка заточены под специфику конкретной компании и включают всякую корпоративную лапшу типа "командная работа" и "нетерпимость к некачественным решениям", а также банальности типа "проверяйте таблицу на пустоту перед FOR ALL ENTRIES" и "не делайте SELECT в цикле". Хотя большинство пунктов в такой памятке вполне разумны, пусть и не несут опытному специалисту чего-то нового, иногда встречаются эксклюзивы.

Например, недавно увидел такой пункт, который фигурировал как типовая ошибка:

Использование SORT+DELETE ADJACENT DUPLICATES вместо цикла со вставкой в хеш-таблицу.

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

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

Внимание! Сразу хочу уточнить, что ранее здесь был другой текст с диаметрально противоположными результатами, поскольку я забыл про одну особенность SORT, на что мне и было указано в комментариях. Так что если вы его читали, а теперь видите совсем другое - не удивляйтесь. А выводы в принципе те же остались. 


Для тестирования была накатана небольшая программка, которая измеряет время выполнения двух вариантов. В первом из исходной таблицы в обычную добавляются все строки, потом сортируются и потом удаляются дубликаты. Во втором варианте строки исходной таблицы добавляются в хэшированную (попадают туда, естественно, только уникальные). Количество строк в исходной таблице задается параметром LV_COUNT, количество выполнений - параметром LV_TIMES. Оба параметра увеличиваются в 10 раз на каждой итерации цикла.
Исходная таблица заполняется случайными целыми числами до выполнения измерений по любому из вариантов. Чтобы там заведомо были совпадения числа берутся по модулю 10.
В итоге выводится результат в виде таблицы. Это некие "попугаи", приблизительно соответствующие микросекундам, но нам это не очень важно - нам важно соотношение двух вариантов. Если программу будете сами запускать, она, скорее всего. упадет по таймауту, поскольку общее время выполнения около 20 минут - запускайте в фоне, потом смотрите в спул.

Смотрим на результаты:
  • Одна строка в исходной таблице. 

    Что мы тут видим? Прежде всего примерный паритет между обоими вариантами. На самом деле эти измерения с 1 строкой  - туфта... Во-первых, чисто логически, тут не было никаких поисков и сравнений, чтобы удалять там какие-то дубликаты - строка тупо одна, она просто добавилась и все. Все затраты времени тут скорее на накладные расходы - цикл, добавление строки, очистка таблицы. Во-вторых, реально цифры тут прыгали как хотели, то есть в том же варианте с 100000 повторов иногда вариант S+D вырывался вперед, иногда Hash.Смотрим дальше

  • 10 и более строк в исходной таблице
  •  
    Итак самое главное - вариант с Hash действительно раза в 1,5 быстрее S+D! (там, где деление на 0, я замеры не проводил поскольку просто долго, а картинка и так уже складывалась)
    Также видно, что результат на 1 повторе можно не рассматривать. Это следует из значений соотношения времени к количеству повторов (S+D/time и Hash/time). Например видно, что в случае S+D затраты на 1 прогон при 10-1000000 повторах в среднем одинаковые. А в случае 1 прогона цифра значительно выше - это опять же играют роль накладные расходы, а не основная логика. Вот поэтому и нужно несколько прогонов, чтобы нивелировать время на накладные расходы.
    Почему то большее ускорение Hash получает с увеличением числа строк в исходной таблице. Возможно тут сказывается выделение/освобождение памяти для S+D, которое и дает это отставание. Также на 10 строках Hash тоже почему то "более быстрый", чем на Hash на 100. Причина не понятна, да и не важно это. В среднем алгоритм Hash на ~50% быстрее чем S+D

Итак, я был обескуражен результатами, но давайте смотреть на вещи реально. Возьмем последний вариант, когда в исходной таблице было 100тыс строк (что уже довольно редко бывает). И, как правило, нескольких прогонов как в этом тесте не бывает - он один. Поэтому, посмотрев на значение 5 и 6 столбцах, мы увидим, что S+D отработает за примерно 27 миллисекунд, а Hash - примерно за 16 миллисекунд. Милли-секунд! Вы не заметите эту разницу никак вообще! А значит требование как и запрет использовать то или другое - бред. Как удобнее в каждом конкретном случае, так и нужно делать. Кто знает что вы там дальше с этой таблицей делаете - может там потом поиск по полному ключу 100500 раз? А может по неполному? Нет и не может быть тут универсального совета.

Я не прав (опять)? - welcome в комменты. 

понедельник, 21 декабря 2015 г.

Пользовательские поля в товаре

Когда вы расширяете какую-то стандартную сущность (заказ там или товар) добавляя свои ZZ-поля в стандартные таблицы с помощью доп.структур, то часто приходится решать задачу передачи значений этих полей извне. То есть фактически надо как-то передавать эти поля в BAPI.
Стандарт для этих целей в "приличных" BAPI предусматривает поля EXTENSIONIN или типа того. Чтобы все работало на автомате, как правило, этими же ZZ-полями нужно еще расширить определенную структуру. Ну, и структуру с X-полями (это которые надо ставить в X там где реально данные)
"Внутре" данные из EXTENSIONIN перекладываюся в эту структуру (либо move-correcponging'ом, либо через assign componet), а потом и в реальную структуру таблицы.
Правда иногда этого бывает мало - надо реализовывать либо user-exit, либо BADI и перекладывать там самим.
А бывает еще веселее. Вот, например, BAPI BAPI_MATERIAL_SAVEDATA. Для создания и изменения товара. Она же используется в IDOC MATMAS (ФМ IDOC_INPUT_MATMAS_BAPI). В наличии параметры EXTENSIONIN и EXTENSIONINX. Дополнительно надо расширять структуры BAPI_TE_* и BAPI_TE_*X. Ну, то есть, если расширяли MARA то расширяем и BAPI_TE_MARA, если MARM - то BAPI_TE_MARM. Ну и так далее.
Но просто так все равно работать не будет. Стандартные поля будут обновляться, а ваши нет. Чтобы побороть это есть транзакция OMSR. В ней надо прописать поля, которые можно редактировать (там и стандартные присутствуют).  Тогда они будут "проходить" через BAPI.
Подробностей не подскажу - не абаперское это дело настройки настраивать. Но куда послать консультанта вы теперь знаете =)

пятница, 5 июня 2015 г.

Значения в варианте для фонового задания

Когда мы создаем фоновое задание через JOB_OPEN/JOB_CLOSE и вызываем там отчет с селекционным экраном (например, через submit), система генерит вариант запуска автоматически. Имя варианта потом можно увидеть в SM37 в шаге. Что сохранено в варианте можно посмотреть через "Перейти к -> Вариант" в просмотре шага задания (также можно получить с помощью ФМ RS_VARIANT_CONTENTS).

вторник, 12 февраля 2013 г.

Заполнение LIKP-TDDAT при создании поставки

Создаем поставки с помощью ФМ BAPI_DELIVERYPROCESSING_EXEC.
Кроме всего прочего пытаемся прописать в поставке дату планирования транспортировки LIKP-TDDAT. В параметрах ФМ она лежит в поле TRANSP_PLAN_DATE в таблице, которая передается как параметр REQUEST.
Проблема в том, что ФМ отрабатывает, поставка создается, но  поле TDDAT пустое.

Исследования показали, что проблема комплексная, и нужно обратить внимание как минимум на две вещи:

  1. Кроме заполнения поля TRANSP_PLAN_DATE, которое потом запишется в LIKP-TDDAT, при вызове ФМ необходимо заполнять также поля MAT_AVAILABIL_DATE, LOADING_DATE, GOODS_ISSUE_DATE, DELIVERY_DATE. В принципе есть и другие варианты - например когда заполняются TIMESTAMPы (подробнее можно посмотреть по исходному коду в form MATCH_INTERFACE_DATES_MM в include LV50R_CREAF30). Как альтернативу можно использовать user-exit EXIT_SAPLV50R_CREA_003 и дописать там нужное.
  2. В таблице TVCPL по виду поставки LFART, виду заказа AUART (определяется по виду поставки из таблицы TVLK)  и пустому типу позиции образца (поле PSTYV) выбирается поле GRUAK "Групповое условие копирования данных VBAK". Значение этого поля используется при формировании имени подпрограммы, которая будет вызвана из программы SAPLV50S по шаблону DATEN_KOPIEREN_<GRUAK> для копирования данных в поставку. Таким образом, если в GRUAK было 201, то вызовется подпрограмма  DATEN_KOPIEREN_201. От подпрограммы зависит то, какие поля будут скопированы в поставку. В частности, копирование TDDAT поддерживают только 301 и 311 подпрограммы.