Опубликован: 28.12.2011 | Уровень: для всех | Доступ: свободно
Лекция 4:

Выражения в Oracle SQL

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Конструкции (операторы) CASE для построения выражений

В качестве альтернативы функции DECODE (отсутствующее в стандарте решение Oracle, оформленное в виде функции) и других функций условной подстановки значений NVL, NVL2, NANVL и COALESCE начиная с версии 8.1.6 можно пользоваться "поисковым" CASE-выражением, а с версии 9 — "простым" CASE-выражением (оба входят в стандарт SQL-92). Формально конструкцию CASE можно считать оператором (с более сложной синтаксической структурой, нежели в случае, положим, арифметических операторов), предназначенным для построения выражений из более простых. Для употребления существенно, что результат CASE не "окончателен"; он представляет собой выражение, которое не возбраняется использовать для построения очередного более сложного. В этом конструкция CASE не отличается от прочих операторов.

Синтаксис "поискового" оператора CASE:

CASE
  WHEN условное-выражение1 THEN выражение-результат1
  WHEN условное-выражение2 THEN выражение-результат2
  …
  WHEN условное-выражениеN THEN выражение-результатN
  [ ELSE выражение-результат ]
END

Проверки происходят сверху вниз, пока первое по порядку условное-выражениеI не станет TRUE. Тогда проверки прекратятся, и результатом CASE будет значение выражения-результатаI.

Синтаксис "простого" оператора CASE:

CASE выражение0
  WHEN выражение1 THEN выражение-результат1
  WHEN выражение2 THEN выражение-результат2
  …
  WHEN выражениеN THEN выражение-результатN
  [ ELSE выражение-результат ]
END

Проверки происходят сверху вниз, пока значение первого по порядку выраженияI не станет равным значению выражения0. Тогда проверки прекратятся, и результатом CASE будет значение выражения-результатаI.

Синтаксис условного-выражения в CASE соответствует синтаксису подобного в части WHERE предложений SELECT, UPDATE и DELETE, описываемых далее, и допускает достаточно сложные конструкции, как показывает пример ниже:

SELECT 
  ename
, sal
, deptno
, CASE 
  WHEN sal > 4000
    THEN 'Highly paid'
  WHEN deptno IN ( SELECT deptno FROM dept WHERE loc = 'NEW YORK' )
    THEN 'Works in New York'
  ELSE 'Nothing interesting'
  END || ' !'
  attention
FROM emp
;

Заметьте, что по нашим данным в результате служащий KING будет помечен как "высокооплачиваемый". Если в операторе CASE проверку зарплаты и местонахождения отдела поменять местами, KING окажется помечен как "работающий в Нью-Йорке".

Упражнение. Проверьте последнее утверждение.

Тем самым конструкция CASE вносит элемент процедурности в описательное в целом построение запроса, принятое в SQL.

Отсутствие конструкции ELSE может приводить к отсутствию значения в результате (к NULL), однако же не к ошибке:

SQL> SELECT NVL ( CASE 1 WHEN 2 THEN 3 END, -1 ) FROM dual;
NVL(CASE1WHEN2THEN2END,-1)
--------------------------
                        -1

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

CASE loc 
WHEN 'NEW YORK' THEN 'NEW YORK CITY' 
WHEN 'BOSTON'   THEN 'BOSTON AREA'
END

Вместо этого лучше написать:

CASE loc 
WHEN 'NEW YORK' THEN 'NEW YORK CITY' 
WHEN 'BOSTON'   THEN 'BOSTON AREA'
ELSE NULL
END

"Поисковая" разновидность CASE носит более общий характер, нежели "простая", так как допускает условные выражения, которые получены операторами сравнения, отличными от = (равенства).

Из-за того, что конструкция CASE оформлена в виде оператора языка, а не функции, как DECODE, NVL, NVL2, NANVL и COALESCE, она становится не только их более общим заменителем, но к тому же и быстрее их вычислимой, хотя бы и ненамного в каждом отдельном случае. Это создает стимул к применению в программировании именно ее, а не перечисленных функций условной подстановки значений. В то же время, в тексте запроса она обычно занимает больше места.

Скалярный запрос

Еще одна конструкция для формирования выражений существует с версии Oracle 9. Если запрос одностолбцовый и возвращает не более одной строки, его можно указать в круглых скобках в составе выражения на правах значения.

Пример:

SELECT
  ename
, '-> ' || ( SELECT dname FROM dept WHERE dept.deptno = emp.deptno )
FROM emp
WHERE 
   TRUNC ( hiredate, 'year' ) >
   TRUNC
   ( ( SELECT hiredate FROM emp WHERE job = 'PRESIDENT' ), 'year' )
;

При этом множественный результат воспринимается как ошибка, а пустой результат — как отсутствие значения, NULL:

SELECT
   ename
 , ( SELECT deptno FROM dept WHERE 1 = 2 ) + 0
FROM emp
;

Добавление нуля в выражении выше сделано, чтобы убедить читателя в отсутствии значения у приведенного скалярного выражения. Иначе подошло бы использование функции NVL.

Упражнение. Перепишите последний запрос с использованием функции NVL для выяснения реакции СУБД на отсутствие строк в скалярном запросе.

Одностолбцовость скалярного запроса Oracle в состоянии контролировать синтаксически, а вот однострочность — нет. Для повышения надежности текста некоторые предлагают в качестве искусственной меры включать в условное выражение во фразе WHERE запроса дополнительное условие ROWNUM <= 1, например:

( SELECT hiredate FROM emp WHERE job = 'PRESIDENT' AND ROWNUM <= 1 )

Не исключено, что такая мера более важна как способ привлечения внимания программиста к содержательно правильному построению запроса, и такое дополнительное условие служит своего рода "активным комментарием" к тексту программы. Обратите внимание, что добавление AND ROWNUM <= 1 несколько изменяет смысл запроса.

Скалярный подзапрос скалярен в том же смысле, что и упоминавшиеся скалярные функции, то есть результат его не может быть массивом (например, столбцом значений). В то же время единственное возвращаемое им значение вполне может быть объектом (в смысле объектных возможностей Oracle) и иметь понятную СУБД структуру.

Условные выражения

Условные выражения в Oracle существуют, но в отличие от числовых, строковых и временных не могут использоваться для придания значений полям строк таблиц БД, так как в Oracle отсутствует тип BOOLEAN (хотя он есть в стандарте SQL:1999). Не будучи в той же степени равными, они активно используются для проверки условия в операторе CASE (см. выше), а также в части START WITH фразы CONNECT BY и во фразах WHERE и HAVING предложений SELECT, UPDATE, DELETE (см. ниже).

Отдельные замечания по поводу отсутствия значения в выражениях

Выражение с операндом, значение которого отсутствует (обозначено как NULL), приведет к отсутствующему же значению (NULL) в случае:

  • числовых и временных выражений, построенных арифметическими операциями;
  • сравнения выражений всех видов.

Понять обработку NULL иногда помогает следующее правило: SQL воспринимает в выражениях NULL как неизвестное значение.

При работе с отсутствующими значениями в БД часто используют функцию NVL. Сравните ответы:

( 1 ) SELECT ename, sal, comm, sal + comm FROM emp;

и:

( 2 ) SELECT ename, sal, comm, sal + NVL ( comm, 0 ) FROM emp;

В случае (1) получим:

ENAME             SAL       COMM   SAL+COMM
---------- ---------- ---------- ----------
SMITH             800
ALLEN            1600        300       1900
WARD             1250        500       1750
JONES            2975
MARTIN           1250       1400       2650
BLAKE            2850
CLARK            2450
SCOTT            3000
KING             5000
TURNER           1500          0       1500
ADAMS            1100
JAMES             950
FORD             3000
MILLER           1300

В случае (2) получим:

ENAME             SAL       COMM SAL+NVL(COMM,0)
---------- ---------- ---------- ---------------
SMITH             800                        800
ALLEN            1600        300            1900
WARD             1250        500            1750
JONES            2975                       2975
MARTIN           1250       1400            2650
BLAKE            2850                       2850
CLARK            2450                       2450
SCOTT            3000                       3000
KING             5000                       5000
TURNER           1500          0            1500
ADAMS            1100                       1100
JAMES             950                        950
FORD             3000                       3000
MILLER           1300                       1300

К сожалению, формального обоснования применения функции NVL в подобных случаях не существует. Стоит ее употребить или нет, решается смыслом, который проектировщик БД закладывает в допущение пропуска значения в столбце. В нашем случае, если смысл — "комиссионные неизвестны" (unknown, "значение отсутствует, потому что неизвестно базе данных, не поступило в БД"), то следует применить запрос (1). Если же смысл "комиссионных нет" ("сотрудник не получил комиссионных"), то запрос (2). Смысл пропущенного значения в таблице SQL никак не означен в БД; он существует вне БД, однако же должен учитываться в программе, работающей с БД. Это одна из давно известных неприятностей SQL.

Частично решить именно эту проблему можно было бы использованием вместо одного "безликого" признака отсутствия значения NULL хотя бы двух с разным смыслом (предлагалось "неприменимо" — missing but inapplicable — и "неизвестно" — missing but applicable). Однако в этом случае возникли бы другие проблемы, связанные со сложностью употребления четырехзначной логики, и по этой причине в SQL от этого отказались. Разработчики SQL советуют использовать пропущенные значения в столбцах только в смысле unknown = missing but applicable. В Oracle этот совет имеет относительную ценность, так как некоторые запросы (примеры встретятся далее) способны порождать пропущенные значения именно в смысле missing but inapplicable.

Полным же решением мог стать отказ от отсутствующих значений вообще. Поскольку в SQL этого не сделано, некоторые советуют добровольно избегать употребления отсутствующих значений по мере возможности и моделировать отсутствие значений (в силу разных причин) без использования NULL. Оборотной стороной такого самоограничения окажется загромождение схемы данных и усложнение запросов к БД.

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Ярослав Прозоров
Ярослав Прозоров

В лекции № 7 "Введение в Oracle SQL" в подразделе "Несамостоятельность группировки с обобщениями ROLLUP, CUBE и GROUPING SETS"  представленная таблица сравнения содержит ошибки - окончания запросов пропущены. Видимо, ошибки вызваны некорректным переносом материала лекции.

Володимир Миколайчук
Володимир Миколайчук
Помогите разобраться поетапно с логикой запроса
-------TOOLS
NAME PRICE TYPE
drill 155 A
sawzall 192 N
mitre saw 292 M
router 86 I
RAD 145 M
jigsaw 128 I
screwdriver 77 P
------TOOL_TYPES
TYPE USAGE
A Always
I Often
M Sometimes
N Rarely
P Never

Запрос SQL:
SELECT t.type, SUM(t.price)
FROM tools t
GROUP BY t.type
HAVING SUM(t.price) >= (SELECT AVG(price)
FROM tools
WHERE type IN (SELECT type
FROM tool_types
WHERE usage = 'Often'));

И сколько строк он все таки вернет
Жанбек Сарсенов
Жанбек Сарсенов
Россия, Москва, Московский Государственный Университет имени Ломоносова М.В., 2002