понедельник, 2 ноября 2020 г.

SELECT * и CORRESPONDING

Часто в регламентах на разработку или в BestPracties нас пугают тем, что писать SELECT * - это плохо. Аргументируется это тем, что не надо выбирать лишнии данные чтобы избегать проблем с производительностью (передача большего числа полей из БД понятно приводит к замедлению этого процесса) и занимаемой памятью (понятно что переденное где то в итоге хранится). И аргументация правильная, но не многие знают, что SELECT * не всегда выбирает все поля.

Для начала рассмотрим вариант когда SELECT * выберет все. Это, очевидно, происходит, когда вы делаете выборку во внутреннюю таблицу той же структуры, что и таблица БД, из которой идет выборка. Например:

DATA lt_ekpo TYPE STANDARD TABLE OF ekko WITH DEFAULT KEY.
SELECT * FROM ekpo INTO CORRESPONDING TABLE OF lt_ekpo WHERE ....
Также очевидно, что в этом случае можно выбирать без CORRESPONDING и результат будет тот же.

Но, допустим, у вас примерно такой код:

DATA:
  BEGIN OF ls_ekpo,
    ebeln TYPE ekpo-ebeln,
    werks TYPE ekpo-werks,
  END OF ls_ekpo,
  lt_ekpo LIKE STANDARD TABLE OF ls_ekpo WITH DEFAULT KEY.

SELECT
  *
  FROM ekpo
  INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
  WHERE ebeln NE space.

Так вот, не смотря на *, из БД в данном случае будут выбираться только поля EBELN и WERKS. Что нам подтверждает план выполнения:

SQL Statement
----------------------------------------------------------------------------------------------------------------------
SELECT
  "EBELN","WERKS"
FROM
  "EKPO"
WHERE
  "MANDT"=:A0 AND "EBELN"<>:A1
То есть, построитель запроса в БД, анализирует таблицу, в которую мы выбираем данные, и формирует к выборке только поля, присутствующие в этой таблице. Поэтому SELECT * при использовании CORRESPONDING это правильный подход. Во-первых это дает профит от *, поскольку мы можем перечислять нужные поля только в одном месте при объявлении структуры. Во-вторых, это нам никак не снижает производительность.

Но не всегда все так просто. Очевидно, что здесь задача состояла в том, чтобы выбрать все уникальные WERKS для каждого EBELN. Поэтому логично, что разработчик первым делом добавит DISTINCT к выборке чтобы получилось вот так:

DATA:
  BEGIN OF ls_ekpo,
    ebeln TYPE ekpo-ebeln,
    werks TYPE ekpo-werks,
  END OF ls_ekpo,
  lt_ekpo LIKE STANDARD TABLE OF ls_ekpo WITH DEFAULT KEY.

SELECT DISTINCT
  *
  FROM ekpo
  INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
  WHERE ebeln NE space.
И удивится, что это не поможет - несмотря на DISTINCT в выборке будут дубли пар EBELN+WERKS (ну при условии, что в заказе есть несколько позиций с одним WERKS, конечно).

Дело в том, что при добавлении DISTINCT построитель запроса в случае, когда вместо списка полей указан *, дополнительно к полям из внутренней таблицы добавит недостающие поля-ключи таблицы БД. Например, наш запрос выше превращается в такое:

SQL Statement
----------------------------------------------------------------------------------------------------------------------
SELECT
  DISTINCT "MANDT","EBELN","EBELP","WERKS"
FROM
  "EKPO"
WHERE
  "MANDT"=:A0 AND "EBELN"<>:A1
Как видите, в запросе появились MANDT и EBELP - это ключевые поля EKPO, наряду с EBELN. Ну на мандант можно не смотреть, а вот выбор EBELP приводит к тому что у нас DISTINCT не сработает поскольку значения EBELP будут разные у одного EBELN и по факту дублей нет. То есть в итоге из БД у нас придет таблица с 4мя полями MANDT+EBELN+EBELP+WERKS и благополучно разложится во внутренню заполнив там поля EBELN и WERKS построчно. А так как DISTINCT выполняется на стороне БД, дублей как бы и нет в таком запросе, а во внутренней таблице они будут, поскольку EBELP будет исключен позже уже на стороне SAP при заполнении внутренней таблицы.

Тут не лишним вспомнить, что при использовании FOR ALL ENTRIES всегда неявно добаляется DISTINCT, и оба варианта такого кода:

DATA:
  BEGIN OF ls_ekpo,
    ebeln TYPE ekpo-ebeln,
    werks TYPE ekpo-werks,
  END OF ls_ekpo,
  lt_ekpo LIKE STANDARD TABLE OF ls_ekpo WITH DEFAULT KEY.

SELECT
  *
  FROM ekpo
  INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
  FOR ALL ENTRIES IN lt_ekko
  WHERE ebeln = lt_ekko-ebeln
.

SELECT DISTINCT
  *
  FROM ekpo
  INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
  FOR ALL ENTRIES IN lt_ekko
  WHERE ebeln = lt_ekko-ebeln
.
Превратятся примерно в такой запрос на стороне БД:
SQL Statement
----------------------------------------------------------------------------------------------------------------------
SELECT
  DISTINCT "MANDT","EBELN","EBELP","WERKS"
FROM
  "EKPO"
WHERE
  "MANDT"=:A0 AND "EBELN" IN (:A1,:A2,:A3,:A4,:A5)
Как и раньше DISTINCT (явный или нет) приводит к тому, что кроме нужных полей выбратся также и недостающие ключевые.

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

Кто виноват - понятно. Что делать? К сожалению, только одно - вмето использования * перечислять все поля вручную. Например:

DATA:
  BEGIN OF ls_ekpo,
    ebeln TYPE ekpo-ebeln,
    werks TYPE ekpo-werks,
  END OF ls_ekpo,
  lt_ekpo LIKE STANDARD TABLE OF ls_ekpo WITH DEFAULT KEY.

SELECT
  ebeln
  werks
  FROM ekpo
  INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
  FOR ALL ENTRIES IN lt_ekko
  WHERE ebeln = lt_ekko-ebeln
.
В этом случае план запроса будет таким:
SQL Statement
----------------------------------------------------------------------------------------------------------------------
SELECT
  DISTINCT "EBELN","WERKS"
FROM
  "EKPO"
WHERE
  "MANDT"=:A0 AND "EBELN" IN (:A1,:A2,:A3,:A4,:A5)
Как видно выбираются только указанные поля и дублей не будет.

Поэтому SELECT * при использовании DISTINCT это опасный подход. Да, вы могли бы указать ключевые поля как поля внутренней таблицы. В этом случае, скорее всего, все будет нормально. Но тогда вопрос - зачем вам в этом случае DISTINCT? Впрочем, при использовании FOR ALL ENTRIES выбора все равно нет. Поэтому SELECT * при использовании FOR ALL ENTRIES это нормальный подход, если все ключевые поля таблицы БД будут у вас во внутренней

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

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