Казалось бы, выборка единственной записи из таблицы довольно простая и логически понятная операция, однако все еще частой ошибкой находимой на code-review является следующее предупреждение Code Inspector’a:
Syntax check warning
In «SELECT SINGLE …», the WHERE condition for a key field does not test for equality or the FROM clause contains a join. This means the result is possibly not unique. Internal message code: MESSAGE GSB
Deactivatable using pragma ##WARN_OK. Message Code WRN 1305
Далее рассмотрим что это такое и почему это не нужно игнорировать.
Выборка с указанием полного ключа
Однозначное определение записи в таблице может быть выполнено только при передаче полного ключа таблицы.
1 2 3 4 |
SELECT * FROM dbtab INTO TABLE @DATA(lt_itab) WHERE full_key. |
Либо через цикл:
1 2 3 4 5 |
SELECT * FROM dbtab INTO @DATA(ls_result) WHERE full_key. ENDSELECT. |
Но чтобы более точно передать намерение в коде о выборке единственной записи из таблицы, следует использовать либо SELECT SINGLE и тогда результат формируется в виде структуры:
1 2 3 4 |
SELECT SINGLE * FROM dbtab INTO @DATA(ls_result) WHERE full_key. |
Либо использовать дополнение UP TO 1 ROWS в цикле:
1 2 3 4 5 |
SELECT * UP TO 1 ROWS FROM dbtab INTO @DATA(ls_result) WHERE full_key. ENDSELECT. |
Либо тоже дополнение без цикла и тогда получаем табличный результат, но с одной единственной строкой:
1 2 3 4 |
SELECT * UP TO 1 ROWS FROM dbtab INTO TABLE @DATA(lt_itab) WHERE full_key. |
Не следует использовать FAE и UP TO 1 ROWS:
- If the addition FOR ALL ENTRIES is also specified, all selected rows are initially read into a system table and the addition UP TO n ROWS only takes effect during the passing from the system table to the actual target area. This can produce unexpected memory bottlenecks.
Выборка с указанием частичного ключа
Возвращаясь к рассматриваемой ошибке в Code Inspector-е, одна возникает в случае использования SELECT SINGLE с указанием не всех полей первичного ключа таблицы.
Подобный код может быть логичен с точки зрения разработчика если он точно знает что структура таблицы и содержащихся в ней данных позволяет выбрать уникальную запись по неполному ключу. Однако разработчик не всегда может быть уверенным на 100% что структура хранимых в таблице данных не поменяется (особенно для custom таблиц) и его код не перестанет работать в будущем.
Кроме того, с точки зрения СУБД такая операция позволяет выбрать абсолютно любую запись из таблицы удовлетворяющей указанному неполному ключу, что может приводить к плавающим ошибкам в одной системе и отсутствию таких же на другой, просто потому что записались они в в таблице в разном порядке и данные анализируемые после выборки отличаются в этих записях.
Кроме того тут следует быть внимательным в случае использования конструкции UP TO 1 ROWS, несмотря на семантику выбора единственной записи, никакого предупреждения мы не увидим в случае указания неполного ключа.
Относительно безопасным решением в данном случае является выборка с использованием UP TO 1 ROWS и указанием порядка сортировки в дополнении ORDER BY по первичному ключу:
1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT * UP TO 1 ROWS FROM dbtab INTO TABLE @DATA(lt_result) WHERE partly_specified_key ORDER BY PRIMARY KEY. " Или... SELECT * UP TO 1 ROWS FROM dbtab INTO @DATA(ls_result) WHERE partly_specified_key ORDER BY PRIMARY KEY. ENDSELECT. |
Однако дополнение не может быть использовано в случае JOIN или при использовании PATH EXPRESSIONS. Кроме того там еще много других ограничений. Соответственно ключевые поля лучше указывать вручную:
1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT dbtab~* UP TO 1 ROWS FROM dbtab INNER JOIN another_dbtab ON ... INTO TABLE @DATA(lt_result) WHERE partly_specified_key ORDER BY key_fields. " Или... SELECT dbtab~* UP TO 1 ROWS FROM dbtab INNER JOIN another_dbtab ON ... INTO @DATA(ls_result) WHERE partly_specified_key ORDER BY key_fields. ENDSELECT. |
Еще менее безопасным является указание сортировки без указания всех полей первичного ключа, т.к. есть вероятность наличия дубликатов относительно отсортированного массива, среди которых та запись которую мы выбираем опять будет неоднозначной.
Указать ORDER BY в SELECT SINGLE не позволит синтаксис, просто потому что это противоречит назначению данной конструкции.
Проверка существования записи в таблице
Если же конструкция SELECT SINGLE используется для анализа существования записи в таблице, без необходимости анализа самих данных, вы вполне можете использовать данную конструкцию без указания полного ключа. Но если оставить конструкцию просто в следующем виде:
1 2 3 4 |
SELECT SINGLE * FROM dbtab INTO @DATA(ls_result) WHERE partly_specified_key. |
Code-Inspector продолжит ругаться. Соответственно чтобы QA-менеджер (ревьювер) не обратил внимание на данную ошибку, мы либо можем воспользоваться следующей конструкцией (ABAP 7.40 SP5+):
1 2 3 4 |
SELECT SINGLE @abap_true FROM dbtab INTO @DATA(lv_exists) WHERE partly_specified_key. |
И тогда Code-Inspector не покажет ошибку (кажется эта проверка была исправлена только в 7.50, в ABAP Platform 1909 она точно исчезла).
Либо воспользоваться прагмой для сокрытия предупреждения (желательно с комментарием почему применили прагму):
1 2 3 4 |
SELECT SINGLE * FROM dbtab ##WARN_OK INTO @DATA(ls_result) WHERE partly_specified_key. |
Либо если такие прагмы запрещены, должны переделать запрос на использование дополнения UP TO 1 ROWS.
1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT * UP TO 1 ROWS FROM dbtab INTO TABLE @DATA(lt_result) WHERE partly_specified_key. " Или... SELECT * UP TO 1 ROWS FROM dbtab INTO @DATA(ls_result) WHERE partly_specified_key. ENDSELECT. IF sy-subrc = 0... |
Экзотические варианты
Еще одним вариантом без использования дополнительных переменных может быть использование агрегата COUNT( * ), однако производительность такого варианта может быть ниже, т.к. СУБД будет вынуждена не просто взять запись, а еще выполнить подсчёт записей.
1 2 3 4 |
SELECT COUNT( * ) FROM dbtab WHERE partly_specified_key. IF sy-dbcnt > 0... |
Можно еще так, но с точки зрения семантики выглядит странно:
1 2 |
SELECT COUNT( * ) UP TO 1 ROWS FROM dbtab WHERE partly_specified_key. |
Будет преобразовано в NativeSQL:
1 2 |
SELECT COUNT( * ) FROM ( SELECT 1 FROM dbtab WHERE partly_specified_key LIMIT 1 ) |
Еще экзотические варианты:
1 2 3 4 5 6 7 8 9 10 |
SELECT * PACKAGE SIZE 1 FROM dbtab INTO TABLE @DATA(lt_result) WHERE partly_specified_key ORDER BY key_fields. EXIT. ENDSELECT. " Либо OPEN CURSOR с PACKAGE SIZE 1... |