Краткий список изменений:
- Расширено описание пункта 8 – Определение внутренних таблиц, добавлен раздел 8.1 описывающий внутреннюю организацию и принципы выделения памяти системой для внутренних таблиц, так же описан параметр INITIAL SIZE используемый при объявлении внутренних таблиц в программах и общие рекомендации по его использованию.
- Расширено описание пункта 8 – Определение внутренних таблиц, добавлен раздел 8.2 описывающий организацию индексов для внутренних таблиц и принципов их построения.
- Расширено и изменено описание пункта 9, работа операторов REFRESH и FREE, в связи с получением более подробной информации по их функционированию.
- Небольшие несущественные правки по тексту из-за внесенных выше изменений в раздел 8
Краткий список изменений:
- Расширено описание пункта 10 – Тестирование программ, дополнено описание использования транзакций расширенной проверки программы SLIN и анализатора код транзакции SCI / SCII.
DATA: lt_mara LIKE mara OCCURS 1 WITH HEADER LINE.
SELECT * INTO CORRESPONDIG FIELDS OF TABLE lt_mara
FROM mara
работает медленнее, чемSELECT * INTO TABLE lt_mara
FROM mara
В общем как не крутись а писать на ABAP приходится и очень часто это какие-то отчеты/обработк-- ну вижу (у нас) то знание абап - только вред - так тебя начинают грузить программерской работой, никагого росту, за деревьями леса не видиш ....
-- а что лучше select sum(xxx) from yyy или ....Для больших таблиц... ну скажем так, как отрабатывает операция SUM(XXX), при такой конструкции всегда подразумевается что есть группа записей с одинаковым значением одного поля и числовым полем, т.е.
ID COST
------------
01 2
02 2
01 3
01 1
02 1
Без индекса к чему это ведет? А к тому что при операции суммирования, для каждого из значений ID в общем случае система пробегает по всей таблице, да кеширование и оптимизация при обработки SQL-запросов + индексы ускорят процесс, но в общем случае без индекса сумму для каждого ID можно получить выполнив для данного примера двойной проход по таблице, сначала для ID = 01, затем для ID = 02, а поэтому иногда может быть быстрее загрузить все во внутреннюю таблицу (если по памяти есть куда) и уже в памяти пройтись и просуммировать данные.а рядок девушки - абпапа нифига - так у них и задачи поинтересней, и загрузка полутше ...Ну открывай, в другой ветке конечно ::)
p.s. может тему начать ? Типа консультант без знания абап, хорошо или плохо ....
или "Насколько большой недостаток - знать абап"
уже в памяти пройтись и просуммировать данные.
Можеш пример в студию - простенький лиш бы понять смысл ?Так а чего там пример то? Ну схематично, без системы типа так:
*&---------------------------------------------------------------------*
*& Report YUUK_TEST
*&
*&---------------------------------------------------------------------*
*& В общем пример суммы свободного запаса по заводам для материалов
*&
*&---------------------------------------------------------------------*
REPORT yuuk_test.
DATA: BEGIN OF lt_mard OCCURS 1.
INCLUDE STRUCTURE mard.
DATA: index LIKE sy-tabix,
END OF lt_mard.
DATA: l_mard LIKE lt_mard.
*--------------------------
SELECT * INTO TABLE lt_mard
FROM mard
ORDER BY werks matnr.
* Опять же может окажется что SORT lt_mard BY werks matnr.
* может быстрее быть чем сортировка в запросе. Тут надо смотреть
* на память и объемы выборки...
LOOP AT lt_mard.
ON CHANGE OF lt_mard-werks OR lt_mard-matnr.
IF sy-tabix = 1.
lt_mard-index = sy-tabix.
l_mard = lt_mard.
ELSE.
MODIFY lt_mard FROM l_mard INDEX l_mard-index.
lt_mard-index = sy-tabix.
l_mard = lt_mard.
ENDIF.
CONTINUE.
ENDON.
l_mard-labst = l_mard-labst + lt_mard-labst.
ENDLOOP.
IF lt_mard-index = 0.
l_mard-labst = l_mard-labst + lt_mard-labst.
ENDIF.
MODIFY lt_mard FROM l_mard INDEX l_mard-index.
DELETE lt_mard WHERE index = 0.
LOOP AT lt_mard.
WRITE: / lt_mard-werks, lt_mard-lgort, lt_mard-matnr, lt_mard-labst.
ENDLOOP.
DELETE lt_mard WHERE index = 0.
Вопрос: а зачем такНет, конечно... я убираю из памяти лишние запаси которые уже вошли в суммирование... т.е. пример есть пять записей на двух заводах одного материала:
Как я понял - для очистки строки (шапки таблицы) ...
SELECT * FROM mard.
ENDSELECT.
Однако исключать данный способ из применения не следует, так как он дает наименьшую нагрузку на сервер + минимальное использование памяти. Так что если таблица небольшая или выборка данных будет небольшой, то вполне можно обойтись и данной конструкцией, т.е. без операции SELECT * INTO TABLE и дальнейшим LOOP AT lt_tab. ENDLOOP. Ну и как обычно смотрим трассировку запросов. Если эта конструкция является самой тормознутой в вашем приложении, то имеет смысл ее заменить, а иначе, нефиг заниматься оптимизацией так где и без нее все бегает ;)
PS: При этом не надо думать, что если что-то вам кажется общеизвестным, то это таки действительно общеизвестно ;)Добавлю свои пять копеек... про конструкцию SELECT .... FOR ALL ENTRIES IN itab
SELECT * FROM <table> INTO <work_line>
WHERE <Field> = 'xxx'.
EXIT.
ENDSELECT.
илиSELECT * FROM <table> INTO <work_line>
UP TO 1 ROWS
WHERE <Field> = 'xxx'.
ENDSELECT.
Так вот второй вариант с UP TO 1 ROWS (кстати многие похоже о такой конструкции даже не подозревают), работает так раз в 6-7 быстрее, чем первый вариант.
DATA: MAX_MSGNR type t100-msgnr.
MAX_MSGNR = '000'.
SELECT * FROM T100 INTO T100_WA
WHERE SPRSL = 'D' AND
ARBGB = '00'.
CHECK: T100_WA-MSGNR > MAX_MSGNR.
MAX_MSGNR = T100_WA-MSGNR.
ENDSELECT.
иDATA: MAX_MSGNR type t100-msgnr.
SELECT MAX( MSGNR ) FROM T100 INTO max_msgnr
WHERE SPRSL = 'D' AND
ARBGB = '00'.
Разница в выполнении этих двух примеров где-то раз в 100, т.е. первый пример будет работать ну на много дольше. Однако если вернуться к конструкции UP TO 1 ROWS, то добавив конструкцию ORDER BY с соответсующей сортировке по убыванию для MAX или по возрастанию для MIN получим время выполнения такое же как и в примере с агрегирующей функцией. Пример ниже:DATA: MAX_MSGNR type t100-msgnr.
MAX_MSGNR = '000'.
SELECT * FROM T100 INTO T100_WA UP TO 1 ROWS
WHERE SPRSL = 'D' AND
ARBGB = '00'
ORDER BY MSGNR DESCENDING.
ENDSELECT.
В общем замеры времени выполнения для примера 2 и 3 показали 81 и 85 миллисекунд (для примера 1, кстати было 7360), т.е. для функций MIN и MAX, можно использовать альтернативу UP TO 1 ROWS совместно с ORDER BY. Ну а сумма и среднее значение, это уже по обстоятельствам надо смотреть ;)
SELECT * FROM DD01L INTO DD01L_WA
WHERE DOMNAME LIKE 'CHAR%'
AND AS4LOCAL = 'A'.
ENDSELECT.
иSELECT DOMNAME FROM DD01L
INTO DD01L_WA-DOMNAME
WHERE DOMNAME LIKE 'CHAR%'
AND AS4LOCAL = 'A'.
ENDSELECT.
В первом случае получаем время выполнения порядка 8285 миллисекунд, а во втором случае 2596. Ну т.е. где-то раза в 4.DATA: MAX_MSGNR type t100-msgnr.
SELECT MSGNR FROM T100 INTO (MAX_MSGNR) UP TO 1 ROWS
WHERE SPRSL = 'D' AND
ARBGB = '00'
ORDER BY MSGNR DESCENDING.
ENDSELECT.
т.е. задав выбор только поля по которому требуется получить максимальное значение и после этого выполнение станет практически такое же как и с использованием функции MAX, у меня получилось после нескольких прогонов разница в 1-2 миллисекунды. В общем так как не ассемблер, то на эти миллисекунды уже можно и забить.
SELECT SINGLE * FROM T100 INTO T100_WA
BYPASSING BUFFER
WHERE SPRSL = 'D'
AND ARBGB = '00'
AND MSGNR = '999'.
иSELECT SINGLE * FROM T100 INTO T100_WA
WHERE SPRSL = 'D'
AND ARBGB = '00'
AND MSGNR = '999'.
В общем случае скорость для первого запроса ~80 миллисекунд, а во втором порядка 5 миллисекунд.
SELECT * FROM T006
INTO TABLE X006.
LOOP AT X006 INTO X006_WA.
ENDLOOP.
иSELECT * FROM T006 INTO X006_WA.
ENDSELECT.
Первый вариант работает быстрее в среднем в полтора-два раза чем вариант два. По замерам время исполнения в среднем колебалось 60 и 110 миллисекунд.
SELECT * FROM SPFLI INTO SPFLI_WA.
SELECT * FROM SFLIGHT INTO SFLIGHT_WA
WHERE CARRID = SPFLI_WA-CARRID
AND CONNID = SPFLI_WA-CONNID.
ENDSELECT.
ENDSELECT.
была быстрее чем конструкцияSELECT * INTO WA
FROM SPFLI AS P JOIN SFLIGHT AS F
ON P~CARRID = F~CARRID AND
P~CONNID = F~CONNID.
ENDSELECT.
По времени выполнения получается 6100 миллисекунд против 8300 во втором случае. Но пять же, требуется смотреть на таблицы из которых выбираются данные и на объем который требуется выбрать.
SELECT * FROM SPFLI
INTO TABLE T_SPFLI
WHERE CITYFROM = 'FRANKFURT'
AND CITYTO = 'NEW YORK'.
SELECT * FROM SFLIGHT AS F
INTO SFLIGHT_WA
FOR ALL ENTRIES IN T_SPFLI
WHERE SEATSOCC < F~SEATSMAX
AND CARRID = T_SPFLI-CARRID
AND CONNID = T_SPFLI-CONNID
AND FLDATE BETWEEN '19990101' AND '19990331'.
ENDSELECT.
иSELECT * FROM SFLIGHT AS F INTO SFLIGHT_WA
WHERE SEATSOCC < F~SEATSMAX
AND EXISTS ( SELECT * FROM SPFLI
WHERE CARRID = F~CARRID
AND CONNID = F~CONNID
AND CITYFROM = 'FRANKFURT'
AND CITYTO = 'NEW YORK' )
AND FLDATE BETWEEN '19990101' AND '19990331'.
ENDSELECT.
В общем случае второй вариант работает быстрее, 210 простив 130 миллисекунд, чем использование FOR ALL ENTRIES. Кстати по поводу FOR ALL ENTRIES, не забываем, что если табличка T_SPFLI, будет пустой, то как уже писал №1, будет полный проход таблицы.
прекрасный инструмент: Сode Inspector(Код инспектор)Тада свой полтинник... от избыточных выборок по базе это чудо бессильно 8)
Начиная с какой-то версии 4.x(точно не скажу) - есть прекрасный инструмент: Сode Inspector(Код инспектор)Это транзакция SLIN имеется в виду?
Это транзакция SLIN имеется в виду?SCI
SCIЧего-то в 4.6С сказало нет такой буквы...
А в 4.6С надо отдельным транспортом это тащить - есть нотаА номерок ноты? А то я что-то не нашел, о разных исправлениях ошибок есть... а саму программку где тянуть не находиться...
А номерок ноты? А то я что-то не нашел, о разных исправлениях ошибок есть... а саму программку где тянуть не находиться...Ну может убрали. Поищи на ftp у сапов транспорт K900427.XB4 (EILENBERGER CI Downport 3.Version) - 2003 год.
CALL FUNCTION 'ENQUEUE_EANLA'
EXPORTING
bukrs = l_bukrs
anln1 = l_anln1
anln2 = l_anln2
_wait = 'X'
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
* Запись находится в БД, блокировку можно снять и бежать дальше
CALL FUNCTION 'DEQUEUE_EANLA'
EXPORTING
bukrs = l_bukrs
anln1 = l_anln2
anln2 = l_anln3.
ENDIF.
Т.е. система будет пытаться поставить блокировку на созданный/изменный объект, если блокировка установилась, то мы ее тут же снимаем и идем обрабатывать следующие данные, так как запись точно уже существует в БД.
А номерок ноты? А то я что-то не нашел, о разных исправлениях ошибок есть... а саму программку где тянуть не находиться...
...подходит ??Подходит конечно ;)
SELECT * UP TO 10 ROWS FROM mara
WHERE MTART = 'HAWA' AND
MATKL = '100'
%_HINTS ORACLE 'index(mara"T")'.
SELECT mseg~mblnr mseg~mjahr mseg~zeile mseg~bwart mseg~xauto
mseg~matnr mseg~werks mseg~lgort mseg~insmk mseg~shkzg
mseg~dmbtr mseg~bwtar mseg~menge mseg~bustm mkpf~budat
INTO TABLE lt_mseg
FROM mseg JOIN mkpf ON mkpf~mblnr = mseg~mblnr AND
mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~budat IN r_date
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).
* Единицы измерения
SELECT mara~matnr t006a~mseh3 INTO TABLE gt_meins FROM mara
JOIN t006a ON mara~meins = t006a~msehi
WHERE mara~matnr IN s_matnr
AND t006a~spras = sy-langu ORDER BY mara~matnr.
Затем, в цикле по lt_mseg вместо SELECT SINGLE ... мы пользуемся оператором READ TABLE gt_meins WITH KEY matnr = <fs_mseg>-matnr BINARY SEARCH (используется LOOP AT lt_mseg ASSIGNING <fs_mseg>). Т.о. быстродействие цикла можно увеличить на 10-15%, поскольку в течение несколькомиллионных итераций мы обращаемся не к серверу БД, а уже исключительно к серверу приложений. В догонку к примечаниям: перед оператором READ ... в том же цикле по lt_mseg ЕИ и количества материалов были трансформированы в "стандартные" (из пачек в "ШТ." в данном случае) посредством соотв. ФМ.Соответствующий ФМ описан тут: http://sapforum.biz/index.php/topic,108.0.html ;)
Есть большое сомнение, что 2 выборка по mseg в таком виде будет работать быстро, недавно как раз был случай, что такая именно конструкция работала _очень_ медленно, не смотря на то, что и индекс по сторно был, и выборка шла по номеру документа+год материала (по ключу),Не вопрос, замеры нам помогут. Нет под рукой системы! :(
спасло: дополнительные условия на mseg поля ebeln ebelp
Есть большое сомнение, что 2 выборка по mseg в таком виде будет работать быстро, недавно как раз был случай, что такая именноЭто имелось в виду конструкция с:
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).
Да, похоже именно эта. MKPF join MSEG вообще темная тема, индексы нужно делать, иначе...Ну например можно ораклово (ну если там оракл) таблицу вынести на отдельный раздел, на отдельном диске... и уже это добавит производительности в выборке. А вообще подселекты, вещь тоже темная и как их оптимизатор разрулит, нужно смотреть в каждом отдельном случае.
вот и призадумаешься, может лучше все таки _дернуть_ из базы select single по ключам чем read tableНу тут в каждом случае надо смотреть отдельно... если памяти хватает, то врядли READ SINGLE будет быстрее, хотя конечно если там одно и тоже читается, т.е. например для 100 записей реально будет только 10 различных чтений данных, тогда скорее всего что данные при SELECT будут браться из кэша, что возможно будет быстрее чем выборка с последующей сотрировкой... короче в каждом случае надо смотреть на данные и сервер на котором все это работает.
Есть большое сомнение, что 2 выборка по mseg в таком виде будет работать быстро, недавно как раз был случай, что такая именно конструкция работала _очень_ медленно, не смотря на то, что и индекс по сторно был, и выборка шла по номеру документа+год материала (по ключу),У меня вообще долго крутился отчет, самый "энергоемкий" был, т.к. начальству хотелось "всю информацию" сначала в общем виде, а потом с детализацией в гриде. Миллионы записей - это что-то))) А со сторно разрулилось по-другому: разнесли по видам движений. На использовании упомянутой мной конструкции не настаиваю, но реально замеры показали, что она в моем случае работала быстрее. Хотя тут больше скажут MM-щики, я таки ABAP-ер и насколько понимаю, часть видов движений стандартна, но можно свои настроить. ;)
спасло: дополнительные условия на mseg поля ebeln ebelp
*&---------------------------------------------------------------------*
*& Report YXXX
*&---------------------------------------------------------------------*
*& Данный отчет - тестовый, для отладки всевозможных ситуаций
*&---------------------------------------------------------------------*
REPORT yxxx.
* Для заголовков
TYPES: BEGIN OF t1,
bukrs TYPE bukrs,
belnr TYPE belnr_d,
gjahr TYPE gjahr,
* Здесь еще какие-нибудь наши поля
* ................,
END OF t1.
* Для позиций
TYPES: BEGIN OF t2,
bukrs TYPE bukrs,
belnr TYPE belnr_d,
gjahr TYPE gjahr,
buzei TYPE buzei,
buzid TYPE buzid,
* Здесь еще какие-нибудь наши поля
* ................,
sgtxt TYPE sgtxt,
END OF t2.
* Таб. заг., ее в нашем случае можно было бы объявить произвольно
DATA: gt_h TYPE SORTED TABLE OF t1
WITH UNIQUE KEY bukrs belnr gjahr. " Заголовки
* Таб. позиций, объявляем именно так
DATA: gt_p TYPE SORTED TABLE OF t2
WITH UNIQUE KEY bukrs belnr gjahr buzei. " Позиции
* Рабочую область таблицы позиций объявляем так для иллюстрации примера
* классического использования оператора ASSIGN, можно найти в примерах:
* SE80-)>Среда-)>Примеры-)>Примеры производительности-)>Internal tables
* -)>Using the Assigning Comand. Для таблицы заголовков можно было бы
* обойтись обычной Work area:
* DATA: gs_h LIKE LINE OF gt_h.
FIELD-SYMBOLS: <gs_h> LIKE LINE OF gt_h,
<gs_p> LIKE LINE OF gt_p.
*&---------------------------------------------------------------------*
START-OF-SELECTION.
PERFORM selection. " Выбор данных
PERFORM processing. " Обработка данных
END-OF-SELECTION.
* Далее - процедура изменения FI-документов в БД
*&---------------------------------------------------------------------*
*& Form SELECTION
*&---------------------------------------------------------------------*
FORM selection.
* Выбор из БД
ENDFORM. " SELECTION
*&---------------------------------------------------------------------*
*& Form PROCESSING
*&---------------------------------------------------------------------*
FORM processing.
LOOP AT gt_h INTO <gs_h>.
* { Здесь какая-либо обработка
* ..........................
* Конец обработки }
LOOP AT gt_p ASSIGNING <gs_p> WHERE bukrs = <gs_h>-bukrs
AND belnr = <gs_h>-belnr
AND gjahr = <gs_h>-gjahr.
* { Здесь какая-либо обработка
* ..........................
* Конец обработки }
IF <gs_p>-buzid NE space. " Позиция контрагента
* Замена текста позиции (MODIFY строки вн.табл.)
<gs_p>-sgtxt = 'Добавлено из старой системы'.
ENDIF.
ENDLOOP.
ENDLOOP.
ENDFORM. " PROCESSING
В данном случае, при использовании вложенного цикла, происходит уменьшение числа итераций засчет соответствующего объявления вн. таблицы позиций. В противном случае, не смотря на условие WHERE, происходит полный перебор всех записей таблицы gt_p.
а не проходит where r_file is null ?не пробовал... но тут похоже если так пройдет, то надо будет все запросы по этому полю по всей программе менять на r_file is null or r_file = 0, что уже не кошерно, так что проще таки новое поле принудительно присвоить в space или в ноль, если возможно и потом уже не париться.
как по мне так правильно выбирать .... хотя сам помню когда-то сидел пытался выбрать по условию что поле типа дата пустое ..... самое интересное, сюда не написал .... а теперь не помню ....Не с датой там фишка другая, там точно известно что поле дата будет '00000000'.
кажись писал WHERE docdate = '' ....
Не с датой там фишка другая, там точно известно что поле дата будет '00000000'.буду на работе поищу, там ксати у меня так не прошло ..... не помню точно
как по мне закладыватся на то что ты обновил, источник ошибок .... вдруг забудеш обновить и выборка пойдет ....Условие я написал какое... а обновить, так это надо в любом случае первый раз сделать для всех записей которые существовали до внесения нового поля, для записей, который будут вставлены после добавления нового поля, уже корректно работает выборка на space или ноль. Так что тут именно грабли в том что поле не конвертировано. Возможно что IS NULL пройдет, но если у меня запросов уже три десятка по программе, то добавлять лишнее сравнение на то что пока обработаются все старые записи, да и вообще лишнее сравнение, которое через месяц точно уже будет лишним, короче апдейтик будет самое оно.
REPORT yuuk_test_select.
DATA: lt_mseg LIKE mseg OCCURS 1 WITH HEADER LINE,
runtime_1 TYPE i,
runtime_2 TYPE i,
time_diff TYPE i.
FIELD-SYMBOLS: <fs> LIKE LINE OF lt_mseg.
SELECT * INTO TABLE lt_mseg
FROM mseg.
GET RUN TIME FIELD runtime_1.
LOOP AT lt_mseg.
lt_mseg-bwart = '000'.
MODIFY lt_mseg.
ENDLOOP.
GET RUN TIME FIELD runtime_2.
time_diff = runtime_2 - runtime_1.
WRITE: / time_diff.
GET RUN TIME FIELD runtime_1.
LOOP AT lt_mseg ASSIGNING <fs>.
<fs>-bwart = '000'.
ENDLOOP.
GET RUN TIME FIELD runtime_2.
time_diff = runtime_2 - runtime_1.
WRITE: / time_diff.
Результат на экране, комментарии я так думаю излишние, разница в производительности даже не на лице ;) в надцать раз практически, т.е. 192 580 миллисекунд против 12 172, кажется закрывают данный вопрос как нужно делать обновления внутренних таблиц.
1) call function селект ...new task и потом их ловить в процедурке on end taskЯ не тестировал оба варианта, делал по второму пункту.
2) запустить job_open submit селект
селект к одной и тойже таблице в 2 разных фоновых заданиях будет быстрее чем 2 селекта последовательно ?Быстрее будет работать два параллельных запроса, чем два последовательных, но само собой каждый отдельный запрос будет медленнее при параллельной обработке чем при последовательно.
ого какая существенная разница в тормозах ,Ссори, привел полные логи, но реально изменения касались только последней строки
для LOOP AT gt_alvtab ASSIGNING <fs_alvtab>.
WHERE
matnr IN so_matnr AND
werks IN so_werks AND
sobkz = 'K' AND
lifnr IN so_lifnr AND " даже если пустой so_lifnr, он все равно влияет на выбор индекса
WHERE
matnr IN so_matnr AND
werks IN so_werks AND
sobkz = 'K' AND
"!!!!! lifnr IN so_lifnr AND " закомментировал, теперь выбирается нужный индекс...
На выбор индекса, как я понял, влияют только количество параметров в условии WHERE.Ну ты же сам написал... порядок полей в условии WHERE!!!, а какая разница есть справа значение для сравнения или нет?!
Ну ты же сам написал... порядок полей в условии WHERE!!!, а какая разница есть справа значение для сравнения или нет?!Ну вот, хотя если реально в данный момент времени, данные не передаются, оптимизатор должен бы отбросить несипользуемые условия ....
Ну вот, хотя если реально в данный момент времени, данные не передаются, оптимизатор должен бы отбросить несипользуемые условия ....Ну я бы про отбросить так не говорил, так как я как раз иногда использую такую конструкцию чтобы выбирался нужный мне индекс при выборке, а вообще у тебя оракловая база? Прибей индекс гвоздями и буде тебе счастье...
Ну я бы про отбросить так не говорил, так как я как раз иногда использую такую конструкцию чтобы выбирался нужный мне индекс при выборке, а вообще у тебя оракловая база? Прибей индекс гвоздями и буде тебе счастье...да нет "ДИБИ Цвай" .... :-), это как "гвоздями"?
да нет "ДИБИ Цвай" .... :-), это как "гвоздями"?Ну под DB2 не знаю надо документацию смотреть, опять же может Дмитрий знает, там типа хинт использования индекса можно указывать... но админы БД обычно это сильно не любят... т.е. мы жестко говорим какой индекс должна использовать система.
уфффф.... сссори, что сумбурно, радость переполняет ...По праву передавшего эстафетную палочку месье Паганелю. ;)
Ну под DB2 не знаю надо документацию смотреть, опять же может Дмитрий знает, там типа хинт использования индекса можно указывать... но админы БД обычно это сильно не любят... т.е. мы жестко говорим какой индекс должна использовать система.Хинт иногда нужно указывать, однозначно.
По праву передавшего эстафетную палочку месье Паганелю. ;)Все правильно Вы написали, мой уважаемый друг Дима, только не учли одного: природы человека по имени Паганель ...... который и получил сие прозвище еще в школе, за сию рассеянность и непоследовательность ... ссори ... стараюсь.... буду исправляется ..
2 Паганель: как-то внятно писать сразу нужно писать, задача, результат, выводы, стройность текста сообщения должна быть на уровне. А то как же месье Паганель, вы модератором тематики в скором времени будете: начинающий не поймет о чем это он, куда плыть? ::)
Хинт иногда нужно указывать, однозначно.Слышал, вроде бы видел, тока не помню как пишется .... ;-)
По праву передавшего эстафетную палочку месье Паганелю. ;)
P.S. Это так, оффтоп, просто контент должен быть четким. 8)
..... вы модератором тематики в скором времени будете:.....Ну это бабка на двое сказала, как говортся "ры...м еще не дорос"
Хинт иногда нужно указывать, однозначно.Синтаксис не подскажешь сразу, а то я что-то найти не могу..
Синтаксис не подскажешь сразу, а то я что-то найти не могу..ага, а то видел, кажись в mb5b... сижу, ищу ... :-(
Поддежрживаю, "должен быть четким".Еще раз поддерживаю, "дурак, это тот кого никто не понимает"
Ну это бабка на двое сказала, как говортся "ры...м еще не дорос"
Синтаксис не подскажешь сразу, а то я что-то найти не могу..Нет, к сожелению, этот код не "засолил", повезло в свое время по поводу мегаотчетов, но может мужики-абаперы подскажут что, залез в исходники - много их... Нормально просто всегда индексы работали, а еще и с базисом общался, грамотный базисник дюжины начинающих абаперов стоит, если не вредный. ;) А потом уже (по поводу больших отчетов), мы просто присаживались с консулами (когда они были!) и предметно общаясь, договаривались, что они, как постановщики, будут клиенту говорить, можно/нельзя, а то иногда такие казусы... ::) Пошукаю, если найду - отпишу.
А потом уже (по поводу больших отчетов), мы просто присаживались с консулами (когда они были!) и предметно общаясь, договаривались, что они, как постановщики, будут клиенту говорить, можно/нельзя, а то иногда такие казусы... ::) Пошукаю, если найду - отпишу.Хорошо, наверное тебе, Дима, просто, нет у меня консулов, «Аз есмь…», и косул, и абапер, в одном лице :-), и задачу ставят не в виде ТЗ, а ближе к ФД (функциональному требованию, общему описанию, как должно быть) ... ну как говорят "и кузнец, и жнец, и трубе дудец"....
Хорошо, наверное тебе, Дима, просто, нет у меня консулов, «Аз есмь…», и косул, и абапер, в одном лице :-), и задачу ставят не в виде ТЗ, а ближе к ФД (функциональному требованию, общему описанию, как должно быть) ... ну как говорят "и кузнец, и жнец, и трубе дудец"....Да это понятно, просто когда опыта поднаберешь, сможешь авторитетом "давить". А то мечтателей много, дай им то, это, а SAP ведь тоже не резиновый. :D
Да это понятно, просто когда опыта поднаберешь, сможешь авторитетом "давить". А то мечтателей много, дай им то, это, а SAP ведь тоже не резиновый. :DЗнаем"с мы таких "мечтателей", сами такие, как сделаем, потом еще недельку оптимизируем .....
Знаем"с мы таких "мечтателей", сами такие, как сделаем, потом еще недельку оптимизируем .....О понимании бизнес-процессов компании самим, руководством оной и умением объяснить Клиенту, не то, что он не прав, а то, как надо, т.е. научить/объяснить и подвести к тому, чтобы он был прав, но с учетом специфики параметров "железа" на котором все это дело функционирует и системных настроек.
Дима, а ты о чем?
не могу найти пост где месье Паганель рассказывал про зависимость условий where и выбор индексастраницей выше в этой же теме (только это вопрос спорный)
действительно ли это так
1 и тот же селект и условия, при одинаковых индексах в разных мандантах выбор разный.Ну там еще статистика индекса смотрится... т.е. для теста желательно сбор статистики сделать в обоих системах, посмотреть на результат, ну если данные в системах одинаковые, то должно совпасть и уже потом запускать отчеты и смотреть на индексы.
" Очищаем все пустые
LOOP AT gt_alvtab INTO ls_alvtab WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
"DELETE gt_alvtab FROM ls_alvtab. " INDEX sy-tabix.
ENDLOOP.
REPORT z_test_delete.
DATA: BEGIN OF gs_alvtab,
belnr TYPE bkpf-belnr,
is_null(1) TYPE c,
END OF gs_alvtab,
gt_alvtab LIKE STANDARD TABLE OF gs_alvtab,
gt_alvtab2 LIKE STANDARD TABLE OF gs_alvtab,
tabix TYPE sy-tabix,
t1 TYPE i,
t2 TYPE i.
FIELD-SYMBOLS: <fs_alvtab> LIKE gs_alvtab.
START-OF-SELECTION.
SELECT belnr
INTO CORRESPONDING FIELDS OF TABLE gt_alvtab
FROM bkpf
WHERE bukrs = '1100'.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab>.
tabix = sy-tabix MOD 5.
WRITE tabix TO <fs_alvtab>-is_null LEFT-JUSTIFIED.
ENDLOOP.
gt_alvtab2 = gt_alvtab.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Count of records', tabix.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop w/o sort', t1, tabix.
GET RUN TIME FIELD t1.
SORT gt_alvtab2 BY is_null.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
WRITE: /(25) 'Sort', t1.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop with sort', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH
TRANSPORTING NO FIELDS.
tabix = sy-tabix.
WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
* ADD 1 TO tabix.
ENDIF.
ENDWHILE.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Read .. binary search', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
DELETE gt_alvtab WHERE is_null = 1.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Delete', t1, tabix.
Замени выборку данных на свою и посмотри результаты разных методов ;)REPORT z_test_delete.
DATA: BEGIN OF gs_alvtab,
belnr TYPE bkpf-belnr,
is_null(1) TYPE c,
END OF gs_alvtab,
gt_alvtab LIKE STANDARD TABLE OF gs_alvtab,
gt_alvtab2 LIKE STANDARD TABLE OF gs_alvtab,
tabix TYPE sy-tabix,
t1 TYPE i,
t2 TYPE i.
FIELD-SYMBOLS: <fs_alvtab> LIKE gs_alvtab.
START-OF-SELECTION.
SELECT belnr
INTO CORRESPONDING FIELDS OF TABLE gt_alvtab
FROM bkpf
WHERE bukrs = '1100'.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab>.
tabix = sy-tabix MOD 5.
WRITE tabix TO <fs_alvtab>-is_null LEFT-JUSTIFIED.
ENDLOOP.
gt_alvtab2 = gt_alvtab.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Count of records', tabix.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop w/o sort', t1, tabix.
GET RUN TIME FIELD t1.
SORT gt_alvtab2 BY is_null.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
WRITE: /(25) 'Sort', t1.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop with sort', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH
TRANSPORTING NO FIELDS.
tabix = sy-tabix.
WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
* ADD 1 TO tabix.
ENDIF.
ENDWHILE.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Read .. binary search', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
DELETE gt_alvtab WHERE is_null = 1.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Delete', t1, tabix.
подскажите как средствами abap
сделать такой запрос только к внутренней itab
select p1 max(p2) sum(p3)
from blabla
group by p1
collect мне суммирует p2
Спасибо.
Замени выборку данных на свою и посмотри результаты разных методов ;)REPORT z_test_delete.
DATA: BEGIN OF gs_alvtab,
belnr TYPE bkpf-belnr,
is_null(1) TYPE c,
END OF gs_alvtab,
gt_alvtab LIKE STANDARD TABLE OF gs_alvtab,
gt_alvtab2 LIKE STANDARD TABLE OF gs_alvtab,
tabix TYPE sy-tabix,
t1 TYPE i,
t2 TYPE i.
FIELD-SYMBOLS: <fs_alvtab> LIKE gs_alvtab.
START-OF-SELECTION.
SELECT belnr
INTO CORRESPONDING FIELDS OF TABLE gt_alvtab
FROM bkpf
WHERE bukrs = '1100'.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab>.
tabix = sy-tabix MOD 5.
WRITE tabix TO <fs_alvtab>-is_null LEFT-JUSTIFIED.
ENDLOOP.
gt_alvtab2 = gt_alvtab.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Count of records', tabix.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop w/o sort', t1, tabix.
GET RUN TIME FIELD t1.
SORT gt_alvtab2 BY is_null.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
WRITE: /(25) 'Sort', t1.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Loop with sort', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH
TRANSPORTING NO FIELDS.
tabix = sy-tabix.
WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
* ADD 1 TO tabix.
ENDIF.
ENDWHILE.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Read .. binary search', t1, tabix.
gt_alvtab = gt_alvtab2.
GET RUN TIME FIELD t1.
DELETE gt_alvtab WHERE is_null = 1.
GET RUN TIME FIELD t2.
t1 = t2 - t1.
tabix = LINES( gt_alvtab ).
WRITE: /(25) 'Delete', t1, tabix.
READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH
TRANSPORTING NO FIELDS.
tabix = sy-tabix.
WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
* ADD 1 TO tabix.
ENDIF.
ENDWHILE.
collect мне суммирует p2Ну таки да... руками через LOOP AT ENDLOOP. разве что... я что-то никаких описаний типа отключения суммирования для цифровых полей не нашел ::)
2УдавКонечно :)
Можеш обьяснить этот кусок кода:READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH
TRANSPORTING NO FIELDS.
tabix = sy-tabix.
WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
* ADD 1 TO tabix.
ENDIF.
ENDWHILE.
Ну ты же сам написал... порядок полей в условии WHERE!!!, а какая разница есть справа значение для сравнения или нет?!
этот порядок полей должен соответствовать порядку следования полей в индексе, который вы предполагаете, здесь будет использован или чему-то другому
этот порядок полей должен соответствовать порядку следования полей в индексеПричем в индексе если таблица мандантно-зависимая, то обязательно должно быть включено поле манданта. Или иначе надо в запросе явно указывать что мандант не использовать.
Причем в индексе если таблица мандантно-зависимая, то обязательно должно быть включено поле манданта. Или иначе надо в запросе явно указывать что мандант не использовать.
Это вопрос?
Если да, то порядок не знаю, но наличие полей и значений - да.
У нас есть таблица t1 в которой помимо ключа, есть индекс по полям f1, f3,f6, f7.
Есть
select * from t1
where f7 = z7 and f1 = z1 and f6 = z6 and f3 = z3.
Есть ли разница в скорости работы в том, в какой последовательности перечислены поля в where, если в индексе они идут – см выше.
Пример, или подробнее можно?Ну индекс желательно делать так... как на картинке, первое поле мандантик, так как при запросе
SELECT * FROM EQUI
WHERE MATNR = xxx AND
SERNR = zzzz AND
WERK = yyy.
Будет работать индекс, так как на уровень запроса пойдут так же данные манданта.
Хотя может на это влияет еще и используемая БД......Ну для оракла порядок важен... если его не соблюдать, то индексы просто не используются в таком случае...
Ну для оракла порядок важен... если его не соблюдать, то индексы просто не используются в таком случае...Это для какой версии Oracle?
Это для какой версии Oracle?У нас 10 с копейками, админы настаивают на соблюдении порядка следования индексов... хотя я тоже был уверен что оптимизатор типа умнее должен быть... в противном случае индекс почему-то не подхватывается.
Для 9-ки без разницы, в каком порядке идут поля в WHERE...
SQL Statement
SELECT
*
FROM
mseg
WHERE
bwart = :A1 and charg = :a2 and werks = :a3
Execution Plan
SELECT STATEMENT ( Estimated Costs = 1 , Estimated #Rows = 1 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 1 , Estim. #Rows = 1 )
1 INDEX SKIP SCAN MSEG~ZZ2
( Estim. Costs = 3 , Estim. #Rows = 1 )
Search Columns: 3
NONUNIQUE Index MSEG~ZZ2
Column Name #Distinct
MANDT 1
CHARG 193 201
WERKS 58
BWART 63
SELECT
*
FROM
mseg
WHERE
bwart = :A1 and werks = :a3
Execution Plan
SELECT STATEMENT ( Estimated Costs = 1 916 , Estimated #Rows = 3 700 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 1 916 , Estim. #Rows = 3 700 )
1 INDEX SKIP SCAN MSEG~M
( Estim. Costs = 17 536 , Estim. #Rows = 3 700 )
Search Columns: 2
NONUNIQUE Index MSEG~M
Column Name #Distinct
MANDT 1
MATNR 17 533
WERKS 58
LGORT 729
BWART 63
SOBKZ 3
SELECT
*
FROM
mseg
WHERE
mandt = :A4 and werks = :a3 and bwart = :A1
Execution Plan
SELECT STATEMENT ( Estimated Costs = 228 , Estimated #Rows = 3 700 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 228 , Estim. #Rows = 3 700 )
1 INDEX RANGE SCAN MSEG~ZZ5
( Estim. Costs = 1 053 , Estim. #Rows = 3 700 )
Search Columns: 3
SELECT
*
FROM
mseg
WHERE
mandt = :A4 and bwart = :A1 and werks = :a3
Execution Plan
SELECT STATEMENT ( Estimated Costs = 228 , Estimated #Rows = 3 700 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 228 , Estim. #Rows = 3 700 )
1 INDEX RANGE SCAN MSEG~ZZ5
( Estim. Costs = 1 053 , Estim. #Rows = 3 700 )
Search Columns: 3
NONUNIQUE Index MSEG~ZZ5
Column Name #Distinct
MANDT 1
WERKS 58
LGORT 729
BWART 63
SOBKZ 3
SELECT
*
FROM
mseg
WHERE
bwart = :A1 and charg = :a2 and werks = :a3
Давай этот запрос со сменой порядка следования полей, т.е. количество полей тоже 3, но порядок разный.
SQL Statement
SELECT
*
FROM
mseg
WHERE
mandt = :a01 and charg = :a2 and werks = :a3 and bwart = :a4
Execution Plan
SELECT STATEMENT ( Estimated Costs = 1 , Estimated #Rows = 1 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 1 , Estim. #Rows = 1 )
1 INDEX RANGE SCAN MSEG~ZZ2
( Estim. Costs = 3 , Estim. #Rows = 1 )
Search Columns: 4
Без мандантаSQL Statement
SELECT
*
FROM
mseg
WHERE
werks = :a3 and charg = :a2 and bwart = :a4
Execution Plan
SELECT STATEMENT ( Estimated Costs = 1 , Estimated #Rows = 1 )
5 2 TABLE ACCESS BY INDEX ROWID MSEG
( Estim. Costs = 1 , Estim. #Rows = 1 )
1 INDEX SKIP SCAN MSEG~ZZ2
( Estim. Costs = 3 , Estim. #Rows = 1 )
Search Columns: 3
По моим наблюдениям (т.е. это ИМХО), разницы нет, порядок и количество влияет только на выбор индекса, т.е. если у вас есть два индекса с полями f1, f2, f3 и f1,f3 то выбор идет как раз по порядку следования полей......
Хотя может на это влияет еще и используемая БД......
Кстати вы смотрели какой индекс используется (st05)?
У вас какая то конкретная проблема? Может приведете код и какие у вас индексы?
как посмотреть какие индексы используются? в st05 я вижу только запрос
как посмотреть какие индексы используются? в st05 я вижу только запрос
SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont = '0070300800' AND
zuonr IN sa_lifnr AND
blart = 'RV' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
А с чего ты взял что первичный должен использоваться? У тебя тут совпадает только BUKRS и HKONT, дальше все мимо кассы так как значения AUGDT и AUGBL не заданы вообще, то до ZUONR оно вообще не доходит, так что правильно оно его не берет. Ну если ты конечно уверен что нужен именно этот индекс, тогда можно через %_HINTS его задать, если правильно синтаксис помню. Но я бы так не делал, оптимизатору виднее.SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont = '0070300800' AND
zuonr IN sa_lifnr AND
blart = 'RV' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
У bsis есть три индекса:
Первичный, включающий такие поля:
MANDT
BUKRS
HKONT
AUGDT
AUGBL
ZUONR
GJAHR
BELNR
BUZEI
Проблему решил, убрал из выборки поле "Присвоение" (zuonr), выборка срабатывает мнговенно, предидущая - минут 20 и больше и падала по таймауту ......Что и требовалось доказать, стал использоваться правильный индекс. Вообще выводы я могу сделать, но они не будут иметь никакого отношения к SAP, а скорее в теории баз данных, будет немного наверное непонятные слова, но это вообще-то не моя задача прочитать курс введение в базы данных ;). Так вот постараюсь по очень простому. В общем случае если это не хеширование, то индекс это вариации построенные на тему B-дерева, так вот у тебя в ключе данные перечислены в таком порядке
Выводы? .....
Кстати твой следующий индекс "ZAN" и подхаватывается потому что идут поля которые есть в индексе и между ними нет пропусков:
BUKRS
ZUONR
<Последние поля не важны BEWAR>, так вот если бы в индексе было вот это BEWAR и оно стояло между BUKRS и ZUONR, но ты его не задавал бы как в предыдущем AUGDT и AUGBL, то у тебя бы и индекс "ZAN" вряд ли бы использовался, получил бы фулскан таблицы.
Поэтому когда ты убрал ZUONR, оно сравнило этих два индекса, в первом есть два поля, во втором только одно.. само собой искать надо по первому.
Про B-дерево для начала тут: http://ru.wikipedia.org/wiki/B-дерево (http://ru.wikipedia.org/wiki/B-дерево) , хотя там тема большая и вариаций на его тему много.
Правда, так и не понял, почему все-таки при выборе индекса, рассматривалось поле zuonr, по моему ситуация в обеих случаях аналогична, в первом индексе есть два поля которые иду подряд, во втором тоже 2 поля, которые идут подряд..... далее идут поля которыеНу что тебе сказать... так как слова B-Tree тебе ничего не говорят то как бы сложно объяснить почему оно так берет индексы, я вот попытался но похоже не получилось. Так что проехали.
Ок, может на досуге почитаю про твои бе-три :)Да оно только зря время потратишь.. .читать надо что-то про то как строяться индексные файлы в базах данных и далее по нарастающей... а так оно все равно ничего не ясно будет.
Да оно только зря время потратишь.. .читать надо что-то про то как строяться индексные файлы в базах данных и далее по нарастающей... а так оно все равно ничего не ясно будет.
SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont = '0063109900' AND
blart = 'WA' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
Опять я с теми же вопросами, делаю ту же выборку, только поменял № счета и вид документа, и снова выборка не отрабатывает за 20 мин.Ну план запроса однако давай... какие индексы потянулись, какие есть?
SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont ='0070300800' AND
blart = 'WA' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont ='0063109900' AND
blart = 'WA' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
Большой объем данных никак не влияет на выбор индекса :)Та ему не поможет... у него данных он же говорит по счету много, так что падать будет при любом раскладе для этого счета.
Та ему не поможет... у него данных он же говорит по счету много, так что падать будет при любом раскладе для этого счета.не факт :)
не факт :)Да у него дамп из-за памяти, а не времени выборки, так что факт ::) или есть способ динамического сжатия выбираемой информации на леут ;D
SELECT SUM( dmbtr ) AS dmbtr bukrs zuonr shkzg hkont blart
APPENDING CORRESPONDING FIELDS OF TABLE gt_bsis
FROM bsis
WHERE
bukrs IN so_bukrs AND
hkont = '0070300800' AND
zuonr IN sa_lifnr AND
blart = 'RV' AND
budat IN so_budat AND
shkzg = 'H'
GROUP BY bukrs hkont zuonr budat shkzg blart.
Ширина выборки - 50 байт.Это же сколько нужно записей выбрать, чтобы лимита памяти достичь :)Ну так кто ж его знает какая у него там машинка стоит и сколько памяти на процесс выделено... и что у него еще в памяти крутиться кроме этого запроса. Кстати у него ритейл, так что... количество документов там может быть ну очень большим.
Если на выделение памяти для процесса ограничение 500 МБ (ztta/roll_extension), то для приведенного примера нужно использовать конструкцию SELECT .. PACKAGE SIZE, которая позволяет выгружать данные во внутреннюю таблицу порциями.Вот сейчас автора запроса придет и узнаем что у него там на процесс выделено...
http://sy-subrc.blogspot.com/2008/08/sql.html (http://sy-subrc.blogspot.com/2008/08/sql.html)
Самый тормознутый способ выборки чего либо из таблиц это операция:SELECT * FROM mard.
Однако исключать данный способ из применения не следует, так как он дает наименьшую нагрузку на сервер + минимальное использование памяти. Так что если таблица небольшая или выборка данных будет небольшой, то вполне можно обойтись и данной конструкцией, т.е. без операции SELECT * INTO TABLE и дальнейшим LOOP AT lt_tab. ENDLOOP. Ну и как обычно смотрим трассировку запросов. Если эта конструкция является самой тормознутой в вашем приложении, то имеет смысл ее заменить, а иначе, нефиг заниматься оптимизацией так где и без нее все бегает ;)
ENDSELECT.
SELECT * FROM <таб1> WHERE <по какому-то индексу>.
*пара экранов каких-то действий, включая штук 5
Select single * from <таб2> where <ключ>
*Из разных таблиц
И пару
SELECT * FROM Z### WHERE <field_key> = <значение>.
* какие-то действия
ENDSELECT.
* какие-то действия
ENDSELECT.
SELECT * FROM BSEG WHERE BUKRS = ZBUKRS
AND GJAHR = ZGJAHR
AND BELNR = itab-BELNR
AND ( ( SHKZG = '##' AND XNEGP = '##' ) OR
( SHKZG = '@@' AND XNEGP = '@@' ) ).
SELECT * FROM Z### WHERE <field_key> = <значение>.
* какие-то действия
ENDSELECT.
* какие-то действия
ENDSELECT.
Из светлых мыслей на эту тему замена select по bseg на:types: begin of t_bseg,
только необходимые нам поля
end of t_bseg.
data: i_bseg type standard table of t_bseg with header line.
clear i_bseg[].
SELECT <только необходимые нам поля> FROM BSEG
WHERE BUKRS = ZBUKRS
AND GJAHR = ZGJAHR
AND BELNR = itab-BELNR
AND ( ( SHKZG = '##' AND XNEGP = '##' ) OR
( SHKZG = '@@' AND XNEGP = '@@' ) ).
loop at i_bseg.
SELECT * FROM Z### WHERE <field_key> = <значение>.
* какие-то действия
ENDSELECT.
* какие-то действия
Endloop.
Для оптимизации циклов LOOP/ENDLOOP также рекомендуется использование FIELD-SYMBOLS если вн. таблица модифицируется.Даже если она не модифицируется это будет быстрее...
Для оптимизации некоторых select join раньше использовала ракурсы БД по соответствующим таблицам (без фанатизма, конечно) . т.е. mkpf+mseg или lips+likp. В местной системе обнаружила некое кол-во ракурсов на интересующую меня тему, но с разным набором полей. Что оптимально: создать ракурс с большим кол-вом полей и использовать его в разных программах или держать несколько ракурсов под разные нужды. Как создание ракурса увеличивает нагрузку на БД.Оно конечно неплохо бы проверить, но мне кажется что несколько ракурсов с узкими наборами полей будут немного быстрее чем одни ракурс объединяющий все поля из нескольких ракурсов. С точки же нагрузки, ракурс это подготовленный запрос к базе данных и все, т.е. теоретически его вызов быстрее чем просто запрос, но в рамках SAP, на это можно забить. Вообще-то идея ракурсов была в том чтобы скрыть структуру базы данных от приложения, т.е. например если у вас изменилась схема базы данных (но не сами данные), то перестроив ракурс, приложение пользователя не заметит таких изменений и продолжит работу.
Вообще-то идея ракурсов была в том чтобы скрыть структуру базы данных от приложения, т.е. например если у вас изменилась схема базы данных (но не сами данные), то перестроив ракурс, приложение пользователя не заметит таких изменений и продолжит работу.
т.е. join лучше обходить без использования ракурсов?Ну я не могу так сказать что лучше без ракурсов, просто ракурс для своей программы вещь как по мне не очень нужная и необходимая, но опять же смотря какая программа и кто ее пишет и сколько людей в этом участвуют. Выигрыша в производительности особого, да и не особого не будет.
SELECT * FROM A596
into table ia596
WHERE KAPPL = '# ' AND
KSCHL = '####' AND
VKORG = '####' AND PLTYP = '##'
AND WAERK = '###' AND MATNR = MATNR .
есть код, в котором прописаны константы. как оптимально их убрать?Тут варианты сложно вам предложить, все зависит от задачи. Внутренняя таблица со значениями, ну это если у вас есть какие-то варианты работы, а если вариантов нет, т.е. у вас например вид документа всегда NB, тогда используйте константы в запросе и в принципе таблица не нужна, ну можно для наглядности завести в заголовке программы что-то типа:
(как вариант - завести внутреннюю таблицу , доступную избранному человеку или есть другой хороший способ)
например:
Для оптимизации некоторых select join раньше использовала ракурсы БД по соответствующим таблицам (без фанатизма, конечно) . т.е. mkpf+mseg или lips+likp. В местной системе обнаружила некое кол-во ракурсов на интересующую меня тему, но с разным набором полей. Что оптимально: создать ракурс с большим кол-вом полей и использовать его в разных программах или держать несколько ракурсов под разные нужды. Как создание ракурса увеличивает нагрузку на БД.В результате практических наблюдений было выяснено, что для БД Oracle 9 выборка с одинаковым составом полей и условиями
1. по по MKPF+MSEG идет быстрее, чем по ракурсу WB2_V_MKPF_MSEG2А приблизительную разницу в скорости можно озвучить? Ну там 2-3% или 10%?
2.По likp+lips, vbak+vbap, ekko+ekpo идет медленнее, чем по соответствующим ракурсам.
есть код, в котором прописаны константы. как оптимально их убрать?Орг.единицы лучше помещать на экран выбора отчета.
(как вариант - завести внутреннюю таблицу , доступную избранному человеку или есть другой хороший способ)
например:SELECT * FROM A596
into table ia596
WHERE KAPPL = '# ' AND
KSCHL = '####' AND
VKORG = '####' AND PLTYP = '##'
AND WAERK = '###' AND MATNR = MATNR .
и просто в коде много всяких констанкт для исключений и пр.
вообще стоит ли их куда-нибудь в отдельное место перемещать?
есть код, в котором прописаны константы. как оптимально их убрать?Собственно, варианты «устранения» хардкода уже перечислены, резюмируем...
(как вариант - завести внутреннюю таблицу , доступную избранному человеку или есть другой хороший способ)
например:SELECT * FROM A596
into table ia596
WHERE KAPPL = '# ' AND
KSCHL = '####' AND
VKORG = '####' AND PLTYP = '##'
AND WAERK = '###' AND MATNR = MATNR .
и просто в коде много всяких констанкт для исключений и пр.
вообще стоит ли их куда-нибудь в отдельное место перемещать?
Посмотри решения в нотах 902157 (https://service.sap.com/sap/support/notes/902157) и 902675 (https://service.sap.com/sap/support/notes/902675).
* Наименование кредитора
TYPES: BEGIN OF t_lfa1,
lifnr TYPE lifnr,
name1 TYPE name1_gp,
END OF t_lfa1.
DATA: gs_lfa1 TYPE t_lfa1,
gt_lfa1 TYPE SORTED TABLE OF t_lfa1
WITH UNIQUE KEY lifnr.
DATA: gt_report TYPE TABLE OF bseg,
gs_report TYPE bseg.
FIELD-SYMBOLS: <fs_lfa1> LIKE LINE OF gt_lfa1.
* --------------------------------------------
LOOP AT gt_report INTO gs_report.
* Какой-либо код
* ..............
* Какой-либо код
IF report-lifnr IS NOT INITIAL.
* тут SELECT SINGLE заменим на хитрую конструкцию
UNASSIGN <fs_lfa1>. " Dump off
READ TABLE gt_lfa1 ASSIGNING <fs_lfa1> WITH KEY
lifnr = gs_report-lifnr BINARY SEARCH.
IF sy-subrc <> 0.
* Не нашли однако! надо селектнуть и добавить по индексу,
* который определил read (он установил SY-TABIX именно в то место,
* где в сортированной таблице должна была быть запись)!
IF <fs_lfa1> IS NOT ASSIGNED.
ASSIGN gs_lfa1 TO <fs_lfa1>.
ENDIF.
SELECT SINGLE name1 INTO <fs_lfa1>-name1 FROM lfa1
WHERE lifnr = gs_report-lifnr.
IF sy-subrc = 0.
<fs_lfa1>-lifnr = gs_report-lifnr.
INSERT <fs_lfa1> INTO gt_lfa1 INDEX sy-tabix.
ENDIF.
ENDIF.
gs_report-lifname = <fs_lfa1>-name1.
ENDIF.
* Какой-либо код
* Модификация основной таблицы GT_REPORT
* Какой-либо код
ENDLOOP.
еще интересная фича...Самым быстрым будет использование сортированной таблицы (SORTED TABLE) с уникальным ключом
в случае когда нам в цикле (например по fi-документам, очень много записей) нужно к каждому кредитору вытащить наименование (причем кредиторов заведомо намного меньше чем документов).
REPORT ZTEST_12 .
.
* Наименование кредитора
TYPES: BEGIN OF t_lfa1,
lifnr TYPE lifnr,
name1 TYPE name1_gp,
END OF t_lfa1.
DATA: gs_lfa1 TYPE t_lfa1,
gt_lfa1 TYPE SORTED TABLE OF t_lfa1
WITH UNIQUE KEY lifnr.
DATA: gt_lfa_main TYPE standard TABLE OF t_lfa1,
gs_report TYPE t_lfa1.
FIELD-SYMBOLS: <fs_lfa1> LIKE LINE OF gt_lfa1.
* --------------------------------------------
select lifnr
into table gt_lfa_main
from lfa1
* up to 10000 rows
.
sort gt_lfa_main by lifnr DESCENDING.
LOOP AT gt_lfa_main INTO gs_report.
* Какой-либо код
* ..............
* Какой-либо код
IF gs_report-lifnr IS NOT INITIAL.
* тут SELECT SINGLE заменим на хитрую конструкцию
UNASSIGN <fs_lfa1>. " Dump off
READ TABLE gt_lfa1 ASSIGNING <fs_lfa1> WITH KEY
lifnr = gs_report-lifnr BINARY SEARCH.
IF sy-subrc <> 0.
* Не нашли однако! надо селектнуть и добавить по индексу,
* который определил read (он установил SY-TABIX именно в то место,
* где в сортированной таблице должна была быть запись)!
IF <fs_lfa1> IS NOT ASSIGNED.
ASSIGN gs_lfa1 TO <fs_lfa1>.
ENDIF.
SELECT SINGLE name1 INTO <fs_lfa1>-name1 FROM lfa1
WHERE lifnr = gs_report-lifnr.
IF sy-subrc = 0.
<fs_lfa1>-lifnr = gs_report-lifnr.
append <fs_lfa1> TO gt_lfa1.
ENDIF.
ENDIF.
gs_report-name1 = <fs_lfa1>-name1.
ENDIF.
* Какой-либо код
* Модификация основной таблицы GT_REPORT
* Какой-либо код
ENDLOOP.
tables: bkpf.
data: itab type sorted table of bkpf
with unique key bukrs belnr gjahr.
field-symbols: <itab> like line of itab.
select * into table itab from bkpf
where bukrs eq 'SNNG'
and gjahr eq '2007'.
data: t1 type i,
t2 type i,
t3 type i,
t4 type i.
data: delta type i.
* ВАРИАНТ 1 ---------------------------------------------------------&
get run time field t1.
loop at itab assigning <itab>
where bukrs eq 'SNNG'
and belnr cp '55*'.
endloop.
* ВАРИАНТ 2 ---------------------------------------------------------&
get run time field t2.
read table itab transporting no fields
with key bukrs = 'SNNG'
belnr = '5500000000'
binary search.
loop at itab assigning <itab> from sy-tabix.
if <itab>-belnr+0(2) <> '55'.
exit.
endif.
endloop.
* ВАРИАНТ 3 ---------------------------------------------------------&
get run time field t3.
read table itab transporting no fields
with key bukrs = 'SNNG'
belnr = '5500000000'
binary search.
loop at itab assigning <itab>
from sy-tabix
where bukrs eq 'SNNG'
and belnr cp '55*'.
endloop.
get run time field t4.
* результат сравнения -----------------------------------------------&
delta = t2 - t1.
write: / 'ASSIGNING + WHERE: ', delta.
delta = t3 - t2.
write: / 'ASSIGNING + FROM + IF: ', delta.
delta = t4 - t3.
write: / 'ASSIGNING + FROM + WHERE: ', delta.
как не странно но очень большая разница между этими тремя реализациями...SAT is the transaction name of the new ABAP Runtime Analysis Tool, which is one of the most significant improvements in ABAP in the NetWeaver 7.0 EhP2. For those of you who are already familiar with the transaction SE30, the former ABAP Runtime Analysis Tool, transaction SAT is the successor of SE30. In SAT, ABAP's Runtime Analysis has been reworked by SAP's R&D department and has been enhanced with effective new analysis tools and features.
SAT offers benefits such as a modern state-of-the art UI, new analysis tools, easy navigation between tools and so on. This first blog of the series about SAT gives overview of the most useful SAT improvements (particularly in comparison with the former SE30) and gives a quick guideline how to use new features and tools of the SAT to execute effective runtime analysis of any ABAP application.
В финансах как и в жизни, есть так называемый принцип уместности или в народе, овчинка должна стоит выделки, или если еще ближе к теме, то не надо оптимизировать то, что в оптимизации не нуждается. Хотите пример.. ну что ж никто не оценит если вы потратите пару дней на оптимизацию в результате которой программа вместо часа будет работать 55 минут, поверьте - пользователи этого не поймут и не оценят, а вот два дня вашей работы... ну как по мне они этого не стоят, хотя если вам больше нечем заняться, то оптимизируйте конечно ::)
Выложена промежуточная версия Оптимизация ABAP 1.2 В которой частично расширено описание раздела 10 – Тестирование программ, дополнено описанием использования транзакций расширенной проверки программы SLIN и анализатора код транзакции SCI / SCII.Всё пишешь, брат... 8)
Всё пишешь, брат... 8)Ну настоящему джентльмену всегда есть что по чем сказать :P
Кросавцег, что тут скажешь! Медведа меняй. :P
DATA:
l_rowbuffer TYPE i.
FIELD-SYMBOLS:
<ls_mseg_short> LIKE LINE OF lt_mseg_short
<ls_t006a> TYPE t006a .
LOOP AT lt_mseg_short
ASSIGNING <ls_mseg_short>.
l_rowbuffer = 0.
IF <ls_t006a> IS ASSIGNED.
IF <ls_t006a>-msehi = <ls_mseg_short>-meins AND
<ls_t006a>-spras = sy-lang.
l_rowbuffer = 1.
ENDIF.
ENDIF.
IF l_rowbuffer = 0.
READ TABLE lt_t006a INTO ls_t006a
WITH KEY msehi = <ls_mseg_short>-meins
BINARY SEARCH.
IF sy-subrc <> 0.
SELECT SINGLE msehi mseht msehl INTO ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = <ls_mseg_short>-meins.
IF sy-subrc <> 0.
CLEAR: ls_t006a.
ls_t006a-spras = sy-lang.
ls_t006a-msehi = <ls_mseg_short>-meins.
ENDIF.
INSERT ls_t006a INTO lt_t006a INDEX sy-tabix
ASSIGNING <ls_t006a>.
ENDIF.
ENDIF.
<ls_mseg_short>-mseht = <ls_t006a>-mseht.
<ls_mseg_short>-msehl = <ls_t006a>-msehl.
ENDLOOP.
БОЛЬШОЕ Спасибо за документ по оптимизации... Было очень познавательно почитать и почерпнуть мудрость.Да не за что... заходите вносите предложения. Ваш пример проверим и добавим. Спасибо за замечания.
Рисунок 11: INDEX-TBL-1.pngПроверю...
Рисунок 12: Index-Tree-1.png
Да, ещё почему то не отображаются картинки в скаченном файле Оптимизация ABAP v1-2.pdfПричина в том, что при передаче в PDF я эти две диаграммы не преобразовал из Visio в картинку и поэтому при сборке файла в PDF ничего не попало. В общем скоро выложу чуть дополненную очередную подверсию 1.3 где это уже будет исправлено.
Рисунок 11: INDEX-TBL-1.png
Рисунок 12: Index-Tree-1.png
Оптимизация ABAP v1-2.pdf, стр. 15, f6). Вопрос: зачем используется вторичная рабочая область *mseg в подзапросе?Это к Дмитрию вопрос 8)
SELECT mseg~mblnr mseg~mjahr mseg~zeile mseg~bwart mseg~xauto
mseg~matnr mseg~werks mseg~lgort mseg~insmk mseg~shkzg
mseg~dmbtr mseg~bwtar mseg~menge mseg~bustm mkpf~budat
INTO TABLE lt_mseg
FROM mseg
JOIN mkpf ON mkpf~mblnr = mseg~mblnr AND
mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~budat IN r_date
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).
Примечание: В данном случае, при чтении данных в операторе READ TABLE неКак-то нечётко. Binary search как раз можно применять для таблиц, объявленных как standard, и для которых выполнена предварительная сортировка по нужным полям. Применение этого оператора для таблиц, объявленных как sorted, смысла не имеет, т.к. при указании полного ключа (либо выровненных по левому краю полей полного ключа) будет использоваться бинарный поиск, в противном случае - полный перебор, повлиять на него невозможно.
использовалось расширение BINARY SEARCH, так как таблица lt_t006a не была объявлена
как сортированная, а для не сортированных данных, использовать такой поиск нельзя.
Более подробно о том, как идет выбор данных, из внутренних таблиц, рассмотрим в
части, где будет описаны способы объявления таких таблиц.
Обрабатываются все SAP блокировки, установленные в текущей программе поСнятие блокировок типа 1 и 3 не происходит после commit work, они снимаются только при выходе из программы либо с помощью DEQUEUE_<enq_obj> (DEQUEUE_ALL). Тип 3 ставит две блокировки - одну для модулей обновления вида V1, вторую - внутри программы, для локальных обновлений, например. Для типа 2 блокировка не снимается, если модуль обновления не вызван.
значению формального параметра _SCOPE для соответствующих функций
блокирования, происходит снятие таких блокировок.
Оптимизация ABAP v1-2.pdf, стр. 15, f6). Вопрос: зачем используется вторичная рабочая область *mseg в подзапросе?Давно дело было... Выборка без подзапроса и последующим циклом по внутренней с выборкой SELECT SINGLE из MSEG (с удалением сторно) заняла больше времени, чем указанная конструкция (больше миллионов позиций mseg). Не рекомендую так делать, сторно по другому удаляется. На одном проекте только удалось такое увидеть.
Не рекомендую так делать, сторно по другому удаляется. На одном проекте только удалось такое увидеть.А какие будут рекомендации? Насколько я помню, для ММ в документе сторно заполняется ссылка на изначальный документ, а вот в изначальном соответствующей перекрёстной ссылки нет.
А какие будут рекомендации?Ну вот это и была рекомендация, как быстро обработать такую ситуацию. Еще как вариант в экзите ловить операцию сторно и делать отметку в первичном документе. Для быстрого исключения сторнированных документов.
Насколько я помню, для ММ в документе сторно заполняется ссылка на изначальный документ, а вот в изначальном соответствующей перекрёстной ссылки нет.В MSEG, по индексу (номеру и год сторно) можно выбрать сторно изначальных.
Ещё хотел бы сказать, что есть орфографические и пунктуационные ошибки в тексте. Не принципиально для смысла, но режет глаз. И шрифт ужасный - строчная наклонная "и" ненормальная :)Ну это конвертация так в PDF проходит с И, хотя у меня показывает нормально, но у меня дебиан, так что вполне может быть под виндой чуток по другому выглядит. Ошибки, подправим, но лучше конечно или мне отдельно на почту или тут написать. Остальные замечания то же учтем... если не очень поздно 8)
READ TABLE itab TRANSPORTING NO FIELDS
WITH KEY bukrs = '1000'
belnr = '5500000000'
BINARY SEARCH.
LOOP AT itab ASSIGNING <itab> FROM sy-tabix.
IF <itab>-belnr+0(2) <> '55'.
EXIT.
ENDIF.
ENDLOOP.
Это к Дмитрию вопрос 8)Рекомендую измерить. Вопрос к пытливым умам и просто знающим людям.SELECT mseg~mblnr mseg~mjahr mseg~zeile mseg~bwart mseg~xauto
mseg~matnr mseg~werks mseg~lgort mseg~insmk mseg~shkzg
mseg~dmbtr mseg~bwtar mseg~menge mseg~bustm mkpf~budat
INTO TABLE lt_mseg
FROM mseg
JOIN mkpf ON mkpf~mblnr = mseg~mblnr AND
mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~budat IN r_date
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).
TYPES: BEGIN OF t1,
mblnr TYPE mblnr,
mjahr TYPE mjahr,
zeile TYPE mblpo,
bwart TYPE bwart,
matnr TYPE matnr,
werks TYPE werks_d,
lgort TYPE lgort_d,
lifnr TYPE lifnr,
menge TYPE menge_d,
meins TYPE meins,
END OF t1.
DATA: lt_mseg TYPE STANDARD TABLE OF t1,
lt_bkpf TYPE STANDARD TABLE OF bkpf.
DATA: t1 TYPE i,
t2 TYPE i,
t_prev TYPE i,
t_next TYPE i.
DATA: delta TYPE p DECIMALS 3.
DATA: lv1 TYPE n LENGTH 10,
lv2 TYPE n LENGTH 10.
DATA: ls_mseg TYPE t1.
FIELD-SYMBOLS: <l> TYPE t1.
GET RUN TIME FIELD t_prev.
SELECT mseg~mblnr
mseg~mjahr
mseg~zeile
mseg~bwart
mseg~matnr
mseg~werks
mseg~lgort
mseg~lifnr
mseg~menge
mseg~meins INTO TABLE lt_mseg FROM mseg JOIN mkpf
ON mkpf~mblnr = mseg~mblnr
AND mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~mjahr EQ p_mjahr
AND mkpf~budat IN s_budat.
LOOP AT lt_mseg ASSIGNING <l>.
SELECT SINGLE mblnr INTO ls_mseg-mblnr FROM mseg WHERE smbln = <l>-mblnr
AND sjahr = <l>-mjahr
AND smblp = <l>-zeile.
IF sy-subrc = 0.
DELETE lt_mseg.
ENDIF.
ENDLOOP.
GET RUN TIME FIELD t_next.
t1 = t_next - t_prev.
lv1 = lines( lt_mseg ).
* Сброс предыдущего плана запроса
SELECT mblnr
mjahr
zeile
bwart
matnr
werks
lgort
lifnr
menge
meins INTO TABLE lt_mseg FROM mseg WHERE mblnr = '4900364642'.
FREE lt_mseg.
* Забиваем кэш БД "левыми" данными
SELECT * INTO TABLE lt_bkpf FROM bkpf WHERE bukrs = '1000' AND gjahr = '2012'.
FREE lt_bkpf.
GET RUN TIME FIELD t_prev.
SELECT mseg~mblnr
mseg~mjahr
mseg~zeile
mseg~bwart
mseg~matnr
mseg~werks
mseg~lgort
mseg~lifnr
mseg~menge
mseg~meins INTO TABLE lt_mseg FROM mseg JOIN mkpf
ON mkpf~mblnr = mseg~mblnr
AND mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~mjahr EQ p_mjahr
AND mkpf~budat IN s_budat
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).
GET RUN TIME FIELD t_next.
t2 = t_next - t_prev.
lv2 = lines( lt_mseg ).
FREE lt_mseg.
*------------------------------------------------------------------------
* Коэффициент
delta = t2 / t1.
WRITE: 'Выборка № 1', t1, 'mks', lv1, 'lines',
/ 'Выборка № 2', t2, 'mks', lv2, 'lines',
/ 'Дельта'(008), delta.
При объемах выборки из MSEG ~ 1 млн. позиций увеличение скорости выборки-обработки при использовании AND NOT EXISTS варьировалось в пределах 1,3 - 4 раза. Что-то не так, наверное, я опять делаю?.. Вопрос такой, идея подхода - если заменять for all entries на создание индекса по нужным полямВообще-то на сколько я знаю такой операции как for all entries в языке SQL нет, это расширение языка абап которое транслируется вроде как в подзапросы, поэтому построение индекса по любому должно ускорить выбор вроде как.
Вопрос такой, идея подхода - если заменять for all entries на создание индекса по нужным полям, в условии where указывать диапазон значений для каждого поля от и до, предварительно определив их.Предлагаю попробовать и измерить, как раз как 3-й вариант выборки, в дополнение к коду выше.
Кто-нибудь пробовал? Стоит того?
MANDT
MATNR
WERKS
LGORT
BWART
SOBKZ
Индекс № 2: SMBLN
SJAHR
SMBLP
Можно учесть сторно по виду движения BWART, если предварительно созданы (или считываются стандартные) настройки, тогда сразу же за 1 выборку в одну вн. таблицу можно взять и исходные и сторно и затем в цикле работать уже со внутренней, к которой можно, допустим, объявить несколько ключей (прямой-сторно) ... Где и как взять виды движений сторно к "прямым" толковые ММ-щики знают. ;)
DATA lt_t006a LIKE HASHED TABLE OF ls_t006a WITH UNIQUE KEY msehi.
...
LOOP AT lt_mseg_short ASSIGN <ls_mseg_short>.
READ TABLE lt_t006a INTO ls_t006a WITH TABLE KEY msehi = <ls_mseg_short>-meins.
IF sy-subrc <> 0.
SELECT SINGLE msehi mseht msehl
INTO ls_t006a
FROM t006a
WHERE spras = sy-langu AND
msehi = <ls_mseg_short>-meins.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
INSERT ls_t006a INTO TABLE lt_t006a.
ENDIF.
<ls_mseg_short>-mseht = ls_t006a-mseht.
<ls_mseg_short>-msehl = ls_t006a-msehl.
ENDLOOP.
2. Таблицу lt_t006a объявить как хэш-таблицу с уникальным ключом msehi. И работать как с хэш-таблицей.Вот тут не согласен, в этой таблице в общем случае будет ну пусть 20 единиц измерения, SAP для таких таблиц вообще не рекомендует использовать хэш-таблицы, так как накладные расходы на ведение B-дерева будут больше чем вообще просто фулсканом проходить эти 20 записей. Так что тут или цифры в студию или верим рекомендациям разработчика системы, который говорит что хэш-таблицы объявляйте для таблиц с большим количеством данных, а единицы измерения ну никак не попадают под этот критерий.
REPORT z_read_table_test.
DATA:
BEGIN OF gt_table OCCURS 0,
matnr TYPE matnr,
maktx TYPE maktx,
meins TYPE meins,
msehl TYPE msehl,
END OF gt_table,
gt_t006a TYPE STANDARD TABLE OF t006a,
gt_makt TYPE STANDARD TABLE OF makt.
FIELD-SYMBOLS:
<wa_table> LIKE LINE OF gt_table,
<wa_t006a> LIKE LINE OF gt_t006a,
<wa_makt> LIKE LINE OF gt_makt.
INITIALIZATION.
START-OF-SELECTION.
PERFORM main.
*&---------------------------------------------------------------------*
*& Form main
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM main.
SELECT *
FROM t006a
INTO TABLE gt_t006a
WHERE spras EQ 'R'.
SELECT *
FROM makt
INTO TABLE gt_makt
WHERE spras EQ 'R'.
SORT gt_t006a BY msehi.
SORT gt_makt BY matnr.
SELECT matnr meins
INTO CORRESPONDING FIELDS OF TABLE gt_table
FROM z_big_data_table
WHERE gjahr GE 2010.
DO 100 TIMES.
PERFORM fill_msehl_test_standard.
PERFORM fill_msehl_test_sorted.
PERFORM fill_msehl_test_hashed.
PERFORM fill_maktx_test_standard.
PERFORM fill_maktx_test_sorted.
PERFORM fill_maktx_test_hashed.
ENDDO.
ENDFORM. "main
*&---------------------------------------------------------------------*
*& Form get_msehl
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
* -->MEINS text
* -->MSEHL text
*----------------------------------------------------------------------*
FORM get_msehl USING meins CHANGING msehl.
READ TABLE gt_t006a WITH KEY msehi = meins ASSIGNING <wa_t006a> BINARY SEARCH.
CHECK sy-subrc EQ 0.
msehl = <wa_t006a>-msehl.
ENDFORM. "get_msehl
*&---------------------------------------------------------------------*
*& Form get_maktx
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
* -->MATNR text
* -->MAKTX text
*----------------------------------------------------------------------*
FORM get_maktx USING matnr CHANGING maktx.
READ TABLE gt_makt WITH KEY matnr = matnr ASSIGNING <wa_makt> BINARY SEARCH.
CHECK sy-subrc EQ 0.
maktx = <wa_makt>-maktx.
ENDFORM. "get_maktx
*&---------------------------------------------------------------------*
*& Form fill_msehl_test_standard
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_msehl_test_standard.
DATA lt_t006a TYPE STANDARD TABLE OF t006a.
DATA ls_t006a TYPE t006a.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_t006a INTO ls_t006a WITH KEY msehi = <wa_table>-meins.
IF sy-subrc <> 0.
ls_t006a-msehi = <wa_table>-meins.
PERFORM get_msehl USING ls_t006a-msehi CHANGING ls_t006a-msehl.
IF sy-subrc <> 0.
CLEAR ls_t006a-msehl.
ENDIF.
INSERT ls_t006a INTO TABLE lt_t006a.
ENDIF.
<wa_table>-msehl = ls_t006a-msehl.
ENDLOOP.
ENDFORM. "fill_msehl_test_standard
*&---------------------------------------------------------------------*
*& Form fill_msehl_test_sorted
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_msehl_test_sorted.
DATA lt_t006a TYPE SORTED TABLE OF t006a WITH UNIQUE KEY msehi.
DATA ls_t006a TYPE t006a.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_t006a INTO ls_t006a WITH TABLE KEY msehi = <wa_table>-meins.
IF sy-subrc <> 0.
ls_t006a-msehi = <wa_table>-meins.
PERFORM get_msehl USING ls_t006a-msehi CHANGING ls_t006a-msehl.
IF sy-subrc <> 0.
CLEAR ls_t006a-msehl.
ENDIF.
INSERT ls_t006a INTO TABLE lt_t006a.
ENDIF.
<wa_table>-msehl = ls_t006a-msehl.
ENDLOOP.
ENDFORM. "fill_msehl_test_sorted
*&---------------------------------------------------------------------*
*& Form fill_msehl_test_hashed
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_msehl_test_hashed.
DATA lt_t006a TYPE HASHED TABLE OF t006a WITH UNIQUE KEY msehi.
DATA ls_t006a TYPE t006a.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_t006a INTO ls_t006a WITH TABLE KEY msehi = <wa_table>-meins.
IF sy-subrc <> 0.
ls_t006a-msehi = <wa_table>-meins.
PERFORM get_msehl USING ls_t006a-msehi CHANGING ls_t006a-msehl.
IF sy-subrc <> 0.
CLEAR ls_t006a-msehl.
ENDIF.
INSERT ls_t006a INTO TABLE lt_t006a.
ENDIF.
<wa_table>-msehl = ls_t006a-msehl.
ENDLOOP.
ENDFORM. "fill_msehl_test_hashed
*&---------------------------------------------------------------------*
*& Form fill_maktx_test_standard
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_maktx_test_standard.
DATA lt_makt TYPE STANDARD TABLE OF makt.
DATA ls_makt TYPE makt.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_makt INTO ls_makt WITH KEY matnr = <wa_table>-matnr.
IF sy-subrc <> 0.
ls_makt-matnr = <wa_table>-matnr.
PERFORM get_maktx USING ls_makt-matnr CHANGING ls_makt-maktx.
IF sy-subrc <> 0.
CLEAR ls_makt-maktx.
ENDIF.
INSERT ls_makt INTO TABLE lt_makt.
ENDIF.
<wa_table>-maktx = ls_makt-maktx.
ENDLOOP.
ENDFORM. "fill_maktx_test_standard
*&---------------------------------------------------------------------*
*& Form fill_maktx_test_sorted
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_maktx_test_sorted.
DATA lt_makt TYPE SORTED TABLE OF makt WITH UNIQUE KEY matnr.
DATA ls_makt TYPE makt.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_makt INTO ls_makt WITH TABLE KEY matnr = <wa_table>-matnr.
IF sy-subrc <> 0.
ls_makt-matnr = <wa_table>-matnr.
PERFORM get_maktx USING ls_makt-matnr CHANGING ls_makt-maktx.
IF sy-subrc <> 0.
CLEAR ls_makt-maktx.
ENDIF.
INSERT ls_makt INTO TABLE lt_makt.
ENDIF.
<wa_table>-maktx = ls_makt-maktx.
ENDLOOP.
ENDFORM. "fill_maktx_test_sorted
*&---------------------------------------------------------------------*
*& Form fill_maktx_test_hashed
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM fill_maktx_test_hashed.
DATA lt_makt TYPE HASHED TABLE OF makt WITH UNIQUE KEY matnr.
DATA ls_makt TYPE makt.
LOOP AT gt_table ASSIGNING <wa_table>.
READ TABLE lt_makt INTO ls_makt WITH TABLE KEY matnr = <wa_table>-matnr.
IF sy-subrc <> 0.
ls_makt-matnr = <wa_table>-matnr.
PERFORM get_maktx USING ls_makt-matnr CHANGING ls_makt-maktx.
IF sy-subrc <> 0.
CLEAR ls_makt-maktx.
ENDIF.
INSERT ls_makt INTO TABLE lt_makt.
ENDIF.
<wa_table>-maktx = ls_makt-maktx.
ENDLOOP.
ENDFORM. "fill_maktx_test_hashed
Случай с ЕИ скорее исключение из правил. В моих отчетах, заказчик хочет видеть: наименования БЕ, заводов, групп материалов, материалов, складов, счетов, контрагентов, подразделений, и т.п. Во всех отчетах с 1000+ строк лучший результат по времени давала хэш-таблица.Ну в этом списке к исключениям единиц измерения относятся перечисленные вами БЕ, Заводы, Группы ОЗМ, Склады, Счета и не относятся сами ОЗМ и Контрагенты, т..е. это как раз ОЗМ и контрагенты являются исключением в данном случае 8), ну а так конечно все правильно написали.
DATA: l_long_char(10000) TYPE c,
l_sy_index(10) TYPE c.
l_long_char = `Create long char, line = START`.
DO 100 TIMES.
l_sy_index = sy-index.
CONCATENATE l_long_char 'Create long char, line =' l_sy_index '.' INTO l_long_char.
ENDDO.
CONCATENATE l_long_char `Create long char, line = FINISH` INTO l_long_char.
Возможной оптимизацией в данном случае являлся только вариант прямой записи цепочки знаков в определенное место строки, без вызова операции соединения строк.DATA: l_long_char(10000) TYPE c,
l_sy_index(10) TYPE c,
l_add_char(100) TYPE c.
l_long_char = `Create long char, line = START`.
DO 100 TIMES.
l_sy_index = sy-index.
CONCATENATE 'Create long char, line =' l_sy_index '.' INTO l_add_char.
DATA(l_len) = strlen( l_long_char ).
l_long_char+l_len = l_add_char.
ENDDO.
CONCATENATE l_long_char `Create long char, line = FINISH` INTO l_long_char.
DATA: l_long_char(10000) TYPE c,
l_long_char = `Create long char, line = START`.
DO 100 TIMES.
l_long_char = |{ l_long_char }Create long char, line ={ sy-index }.|.
ENDDO.
l_long_char = l_long_char && 'Create long char, line = FINISH'.
DATA: l_long_char(10000) TYPE c,
l_long_char = `Create long char, line = START`.
DO 100 TIMES.
l_long_char = l_long_char && 'Create long char, line =' && sy-index && '.'.
ENDDO.
l_long_char = l_long_char && 'Create long char, line = FINISH'.
Из приятного, код примеров 6.5.3 и 6.5.4, выглядит менее перегруженным, а так же отсутствуют лишние переменные, так как нет ограничений на использование только определенных типов, как для оператора CONCATENATE. Но самое приятное это появление типа STRING, с которым оператор CONCATENATE работает, по моему мнению, очень плохо, а вот новые операторы работы со строками работают просто великолепно и быстро. Для примера перепишем пример 6.5.3 и 6.5.4 используя тип STRING. DATA: l_long_string TYPE string.
l_long_string = `Create long string, line = START`.
DO 100 TIMES.
l_long_string = |{ l_long_string }Create long char, line ={ sy-index }.|.
ENDDO.
l_long_string = l_long_string && 'Create long char, line = FINISH'.
ВремВып: 50 микросек DATA: l_long_string TYPE string.
l_long_string = `Create long string, line = START`.
DO 100 TIMES.
l_long_string = l_long_string && 'Create long char, line =' && sy-index && '.'.
ENDDO.
l_long_string = l_long_string && 'Create long char, line = FINISH'.
Как видим, скорость обработки, отличается практически на порядок, т.е. более чем в 10 раз. Так же большим плюсом использования новых операторов является возможность, как указания различных типов переменных, так и вызова методов классов или выполнения каких-либо вычислений в процессе выполнения соединения данных. Например, выполнение вычисления значения переменной используя внутренние функции системы. В примере 6.5.7, используется возведение числа в степень.DATA: l_long_string TYPE string.
l_long_string = `Create long string, line = START`.
DO 100 TIMES.
l_long_string = l_long_string && 'Create long char, line =' &&
CONV string( ipow( base = sy-index exp = 2 ) ) &&
'.'.
ENDDO.
l_long_string = l_long_string && 'Create long char, line = FINISH'.
Однако, стоит вынести вычисления из операции соединения строк, как скорость выполения резко возрастает, что видно на примере 6.5.8. При этом аналогичное правило так же работает и в случае обработки соединения строк используя шаблоны, т.е. время обработки для примера 6.5.8 меньше времени выполнения для примера 6.5.7, в пять раз.DATA: l_ipow type i,
l_long_string TYPE string.
l_long_string = `Create long string, line = START`.
DO 100 TIMES.
l_ipow = ipow( base = sy-index exp = 2 ).
l_long_string = l_long_string && 'Create long char, line =' && l_ipow && '.'.
ENDDO.
l_long_string = l_long_string && 'Create long char, line = FINISH'.
*&---------------------------------------------------------------------*
*& Report Z_TEST_SQL
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z_test_sql.
DATA: time2 TYPE i,
time1 TYPE i,
time_diff TYPE i,
time_sql TYPE i,
time_cds TYPE i.
DATA: l_swap TYPE xfeld.
DATA: lt_line TYPE STANDARD TABLE OF ifmrepgax.
SELECT ebeln AS refbn,
ebelp AS rfpos
INTO CORRESPONDING FIELDS OF TABLE @lt_line
FROM ekpo WHERE konnr <> @space.
l_swap = abap_true.
LOOP AT lt_line INTO DATA(ls_line).
IF l_swap = abap_false.
l_swap = abap_true.
GET RUN TIME FIELD time1.
SELECT SINGLE konnr,
ktpnr,
meins
INTO (@ls_line-zz_ebeln, @ls_line-zz_ebelp, @ls_line-zz_meins)
FROM ekpo
WHERE ebeln = @ls_line-refbn AND
ebelp = @ls_line-rfpos. " Сист.номер договору
SELECT SINGLE ihrez,
kdatb,
kdate,
ktwrt,
waers,
matkl
INTO (@ls_line-zz_ihrez, @ls_line-zz_kdatb, @ls_line-zz_kdate, @ls_line-zz_ktwrt, @ls_line-zz_waers, @ls_line-zz_cn_matkl)
FROM ekko
INNER JOIN ekpo ON ekko~ebeln = ekpo~ebeln
WHERE ekko~ebeln = @ls_line-refbn AND
ebelp = @ls_line-rfpos AND
ekko~bstyp = 'K'.
GET RUN TIME FIELD time2.
time_diff = time2 - time1.
time_sql = time_sql + time_diff.
ELSE.
l_swap = abap_false.
GET RUN TIME FIELD time1.
SELECT SINGLE ord_konnr AS zz_ebeln,
ord_ktpnr AS zz_ebelp,
ord_matkl AS zz_matkl,
knt_ihrez AS zz_ihrez,
knt_kdatb AS zz_kdatb,
knt_kdate AS zz_kdate,
knt_ktwrt AS zz_ktwrt,
knt_waers AS zz_waers,
knt_matkl AS zz_cn_matkl
INTO (@ls_line-zz_ebeln, @ls_line-zz_ebelp, @ls_line-zz_matkl, @ls_line-zz_ihrez, @ls_line-zz_kdatb, @ls_line-zz_kdate, @ls_line-zz_ktwrt, @ls_line-zz_waers, @ls_line-zz_cn_matkl)
FROM zekko_tv WHERE ebeln = @ls_line-refbn AND
ebelp = @ls_line-rfpos.
GET RUN TIME FIELD time2.
time_diff = time2 - time1.
time_cds = time_cds + time_diff.
ENDIF.
ENDLOOP.
WRITE: / 'SQL:', time_sql,
/ 'CDS:', time_cds.
@AbapCatalog.sqlViewName: 'ZEKKO_TV'
@VDM.viewType: #BASIC
@EndUserText.label: 'Тестовий приклад'
define view ZUTN_EKKO_TEST as select from ekko as ek
inner join ekpo as ep on ep.ebeln = ek.ebeln
inner join ekko as kt on kt.ebeln = ep.konnr and kt.bstyp = 'K'
inner join ekpo as kp on kp.ebeln = kt.ebeln and kp.ebelp = ep.ktpnr
{
key ek.ebeln as ebeln,
key ep.ebelp as ebelp,
ep.konnr as ord_konnr,
ep.ktpnr as ord_ktpnr,
ep.matkl as ord_matkl,
kt.ihrez as knt_ihrez,
kt.kdatb as knt_kdatb,
kt.kdate as knt_kdate,
kt.ktwrt as knt_ktwrt,
kt.waers as knt_waers,
kp.matkl as knt_matkl
}
where ek.bstyp = 'F'
group by ek.ebeln,
ep.ebelp,
ep.konnr,
ep.ktpnr,
ep.matkl,
kt.ihrez,
kt.kdatb,
kt.kdate,
kt.ktwrt,
kt.waers,
kp.matkl