Введение в Oracle SQL

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

Разбить на страницы
Показывать лекцию целиком

Общие элементы запросов и предложений DML: выражения

Готовые приложения, как правило, работают с данными уже существующих таблиц. Основная группа предложений SQL, используемых в работающих приложениях, — это SELECT для выборки и операторы DML для изменения данных таблиц. При всем их синтаксическом различии операторы этой группы роднит использование выражений, составляемых по одним и тем же одинаковым правилам.

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

Исходные значения

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

  • явно,
  • через "системные переменные",
  • именем поля строки таблицы.
  • Явно обозначенные величины ("литералы")

    Для явно обозначенных величин (values) в русской литературе часто используется калька с американского английского: "литералы". Оригинальное слово literal представляет собой возникшее со временем в североамериканской литературе жаргонное сокращение от literal value, что дословно означает напрямую ("буквально") указанную в тексте величину (средневековое английское значение слова literal не имеет никакого отношения к компьютерному).

    Нелишне помнить, что одни и те же величины часто могут быть обозначены по-разному, например, 1 и +1 и так далее (в известном "треугольнике Фреге" предмет — обозначение — смысл literal value скорее "обозначение"). Для разных видов данных в выражениях предусмотрены разные способы обозначения.

    Числовые величины

    Примеры обозначения целых чисел:

    38, +12, -3404
    

    Примеры обозначения "десятичных" чисел (decimal), иначе чисел с возможной дробной частью, записанных в десятичной системе счисления:

    342.16, 49, -16, 0.83459
    

    Отделение целой части от дробной осуществляется с помощью десятичной точки или же запятой, в зависимости от установок местности ("языковых"). Русский формат записи чисел (в Oracle устанавливается параметром сеанса NLS_NUMERIC_CHARACTERS как ', ') унаследовал исторически французскую традицию употребления в качестве разделителя запятую в отличие от английской точки, унаследованной Северной Америкой как местом разработки СУБД Oracle. Если не обращать внимания на эту мелочь, могут возникать ошибки вывода числовых данных из БД.

    В записи рациональных чисел могут присутствовать в служебных целях форматирующие буквы:

    49, 18.47, -34e2, 0.16e4, 4e-3, 4e-3, 
    123f (явное указание BINARY_FLOAT), 
    -123.25d (явное указание BINARY_DOUBLE)
    

    Регистр букв, как обычно, не имеет значения.

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

    Пример для всех версий:

    SQL> SELECT 1000, DUMP ( 1000 ) FROM dual;
          1000 DUMP(1000)          
    ---------- ------------------- 
          1000 Typ=2 Len=2: 194,11 
    Пример для версий 10+:
    SQL> SELECT 123f, DUMP ( 123f ), DUMP ( 123d ) FROM dual;
          123F DUMP(123F)                 DUMP(123D)
    ---------- -------------------------- -----------------------------------
     1.23E+002 Typ=100 Len=4: 194,246,0,0 Typ=101 Len=8: 192,94,192,0,0,0,0,0
    

    В качестве упражнения предлагается выполнить другие проверки, например, при записи числа как 1000.1 и 1.0e+3.

    Строки текста

    Примеры указания строк:

    'Collins', '''tis' (кавычки в кавычках), '!?-@ ', '', '''', '1234'
    n'Многобайтовая кодировка; по правилам ANSI можно писать и N, и n', 
    n'Тоже многобайтовая кодировка'
    q'[Строка без 'искажений'. Возможно начиная с версии 10]',  
    q'|ограничивающий символ может быть практически любой|'
    u'строка в Unicode начиная с версии 10'
    

    Функция DUMP помогает понять, как воспринимает СУБД по-разному оформленные строки. Последние два предложения SELECT работают начиная с версии 10:

    SQL> SELECT 'a''bc', DUMP ( 'a''bc' ) FROM dual;
    'A'' DUMP('A''BC')
    ---- -------------------------
    a'bc Typ=96 Len=4: 97,39,98,99
    SQL> SELECT 'a''bc', DUMP ( q'wa'bcw' ) FROM dual;
    'A'' DUMP(Q'WA'BCW')
    ---- -------------------------
    a'bc Typ=96 Len=4: 97,39,98,99
    SQL> SELECT 'a''bc', DUMP ( nq'wa'bcw' ) FROM dual;
    'A'' DUMP(NQ'WA'BCW')
    ---- ---------------------------------
    a'bc Typ=96 Len=8: 0,97,0,39,0,98,0,99
    

    Моменты и интервалы времени

    Начиная с версии 9 в Oracle поддерживается система указаний моментов и интервалов времени, принятая для SQL комитетами ANSI/ISO по стандартизации SQL. Использованный в примерах ниже формат указания самого значения жестко регламентирован ANSI/ISO. Это касается и типа DATE, для которого Oracle принимает формулировку из стандарта, но по-своему раскрывает ее содержание.

    Для обозначения моментов времени используются конструкции DATE, TIME и TIMESTAMP.

    Примеры:

    DATE '2003-04-14' 
      (14 апреля 2003 00:00:00; имеет тип DATE, а временная компонента обнулена)
    TIME '12:30:45' 
      (12.30.45.000000000 пополудни; тип не играет самостоятельной роли в БД и может использоваться только в выражении)
    TIMESTAMP '2003-04-14 15:16:17' 
      (14 апреля 2003 15:16:17.000000000; этот и следующий пример имеет тип TIMESTAMP ( 9 ) )
    TIMESTAMP '2003-04-14 15:16:17.88' 
      (14 апреля 2003 15:16:17.880000000)
    TIMESTAMP '1997-01-31 09:26:56.66 +02:00' 
      (31 января 1997 09:26:56.660000000 во второй временной зоне; имеет тип TIMESTAMP ( 9 ) WITH TIME ZONE)
    TIMESTAMP '1997-01-31 09:26:56.66 Europe/Moscow' 
      (31 января 1997 09:26:56.660000000 во временной зоне г. Москвы; имеет тип TIMESTAMP ( 9 ) WITH TIME ZONE)
    

    Для обозначения интервалов времени используются конструкции INTERVAL, допускающие указание подынтервалов "грубого" и "точного" диапазона интервалов и, в дополнение к синтаксису ANSI/ISO, указание точности. Деление на "грубые" и "точные" диапазоны условно. Примеры формулирования "точных" интервалов (диапазон от дней до долей секунд и подынтервалы):

    INTERVAL '5 04:03:02.01' DAY TO SECOND 
      (5 дней, 4 часа, 3 минуты, 2,01 секунды; имеет тип INTERVAL DAY ( 2 ) TO SECOND ( 6 )),
    INTERVAL '04:03' HOUR TO MINUTE 
      (0 дней, 4 часа, 3 минуты; этот и следующий пример имеет тип INTERVAL DAY ( 2 ) TO SECOND ( 0 )),
    INTERVAL '03' MINUTE 
      (0 дней, 0 часов, 3 минуты)
    

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

    Примеры формулирования "грубых" интервалов (диапазон из лет и месяцев и два подынтервала):

    INTERVAL '04-5' YEAR TO MONTH 
      (плюс 4 года и 5 месяцев; этот и два следующих примера имеют тип INTERVAL YEAR ( 2 ) TO MONTH),
    INTERVAL '-4' YEAR 
      (минус 4 года),
    INTERVAL '5' MONTH 
      (плюс 5 месяцев)
    

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

    INTERVAL ' + 4- 0005 ' YEAR TO MONTH
    

    "Системные переменные" и "псевдостолбцы"

    Оба названия в заголовке заключены в кавычки в силу своей условности.

    "Системные переменные" по сути представляют собой ряд иногда полезных для употребления системных функций без аргументов. Вот некоторые примеры:

    ПеременнаяТипОписание
    USERVARCHAR2 ( 256 )Имя пользователя, выдавшего предложение SQL
    UIDNUMBERНомер пользователя, выдавшего предложение SQL
    SYSDATEDATEТекущая дата + время суток с точностью до секунды
    SYSTIMESTAMP(9-)TIMESTAMP ( 9 ) WITH TIME ZONE(9-)Текущая дата + время суток с точностью до 1/100 секунды
    DBTIMEZONE(9-) и SESSIONTIMEZONE(9-)VARCHAR2 ( 6 ) и VARCHAR2 ( 75 )Зоны времени, определенные для БД и в рамках конкретного сеанса

    (9-) начиная с версии 9

    Пример использования в выражении:

    SELECT object_name, owner FROM all_objects WHERE owner <> USER;
    

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

    Примеры:

    ПсевдостолбецТипОписание
    ROWNUMNUMBERПоследовательный номер строки в результате SELECT
    LEVELNUMBERНомер уровня выдаваемой строки в предложении SELECT с использованием CONNECT BY
    CONNECT_BY_ISCYCLE[10-)NUMBERВ предложении SELECT с использованием CONNECT BY: 1, если потомок узла является одновременно его предком, иначе 0
    CONNECT_BY_ISLEAF[10-)NUMBERВ предложении SELECT с использованием CONNECT BY:1, если узел не имеет потомков
    ROWIDVARCHAR2 ( 256 )Физический адрес строки или хранимого объекта
    XMLDATA[9.2-)CLOBТекст документа объекта типа XMLTYPE
    OBJECT_ID[10-)RAW ( 16 )Идентификатор объекта в таблице первичных или виртуальных объектов (представлений)
    OBJECT_VALUE[10-)тип объектаСистемное имя для столбца в таблице первичных или виртуальных объектов (в том числе типа XMLTYPE)
    ORA_ROWSCN[10-)NUMBERПорядковый номер изменения в БД (SCN), соответствующий строке таблицы или же блоку данных с этой строкой
    COLUMN_VALUE[10.2-)тип объектаТип элемента результата функций TABLE и XMLTABLE
    VERSIONS_STARTSCN[10-)
    VERSIONS_STARTTIME[10-)
    VERSIONS_ENDSCN[10-)
    VERSIONS_ENDTIME[10-)
    VERSIONS_XID[10-)
    VERSIONS_OPERATION[10-
    
    Используются в "быстрых" запросах к прошлым данным (flashback queries)

    [9.2-) начиная с версии 9.2

    [10-) начиная с версии 10.1

    [10.2-) начиная с версии 10.2

    Пример использования в выражениях:

    SELECT object_name, ROWNUM FROM all_objects WHERE ROWNUM <= 15;
    

    Величины, взятые из полей строк таблицы

    Исходные значения в выражении можно также брать из приведенных в выражении полей записей (строк) таблиц, на которые ссылается предложение SQL. В предыдущем запросе OBJECT_NAME — тривиальное (неразложимое) выражение, составленное из обращения к значению в поле с этим названием в очередной строке таблицы ALL_OBJECTS.

    Составные выражения

    Составные выражения построены из более простых выражений путем применения к ним "конструкторов"; в житейском восприятии они, в отличие от исходных значений, собственно и образуют выражения. Они имеют тип, соответствующий типу величины-результата вычисления, то есть "типизированы типом результата".

    Ниже приводятся разные варианты конструкторов, позволяющих строить из более простых выражений более сложные:

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

    Числовые выражения можно получить из других числовых выражений с помощью арифметических операций *, /, + и -, и, возможно, скобок.

    Предположим, что поле COMM в очередной строке имеет значение 25. Тогда справедливы следующие примеры:

    Числовое выражениеЗначение
    6 + 4 * 25106
    0.6E1 + 4 * COMM106.E0
    (50 / 10) * 525
    1 + '25'26 (неявное преобразование типа)
    123d + 123246 в формате BINARY_DOUBLE
    123d + 123f246 в формате BINARY_DOUBLE
    6 + 4 * COMM106
    (6 + 4) * 25250
    NULL * 30NULL
    1f - BINARY_FLOAT_INFINITYBINARY_FLOAT_INFINITY
    1d + BINARY_DOUBLE_NANBINARY_DOUBLE_NAN

    Числовая арифметика в SQL несколько отличается от школьной. Вот некоторые особенности.

  • Если значение элемента числового выражения отсутствует (то есть помечено как NULL, в обозначениях SQL), значение выражения тоже отсутствует (NULL). Это не всегда привычно. Например:
    SELECT 1 / 0 FROM dual;
    -- Ошибка !
    SELECT NULL / 0 FROM dual;
    -- OK
    
  • Тип данных результата приводится к наиболее точному из употребленных в выражении.
  • Типы элементов — участников числового выражения должны быть числовыми. Если же встречается строка текста (указанная явно или вычисленная подвыражением), она автоматически приводится к числовому значению, то есть происходит неявное преобразование типа. Фактическое преобразование осуществляется функциями TO_NUMBER, TO_BINARY_FLOAT или TO_BINARY_DOUBLE (в зависимости от контекста), например:
    SELECT '1' / 2 FROM dual;
    -- будет обработано как:
    SELECT TO_NUMBER ( '1' ) / 2 FROM dual;
    
    Соответственно, невозможность такого преобразования в конкретных случаях (строка текста несводима к числу) определяется правилами работы этих функций.
  • Многие специалисты полагают неправильным использование неявного преобразования типов, считая необходимым все преобразования выписывать явно, хотя бы и в ущерб краткости записи. Явное указание преобразования повышает качество кода и снижает риск возникновения ненамеренных ошибок. Однако даже если бы у разработчиков Oracle возникло желание отменить неявное преобразование типов, сделать это уже было бы невозможно, не нарушив правило обратной совместимости кода. Есть и другая причина: функции в Oracle не поддерживают всего допустимого в БД разнообразия типов. Например, отсутствуют целые функции, так что автоматическое преобразование типов становится неизбежным.

    Простые выражения над строками текста

    Простейшие выражения над строками текста (алфавитно-цифровые, или строковые выражения) можно получить из непосредственно указанных значений и единственной текстовой операцией || ("склейки").

    Предположим, что поле ENAME (типа VARCHAR2 ( 10 )) в очередной строке имеет значение 'SMITH'. Тогда справедливы следующие примеры:

    Строковое выражениеЗначение
    'Работник'Вася и Петя (типа CHAR ( 8 ))
    ENAMESMITH (типа VARCHAR2 ( 10 ))
    USERSCOTT (VARCHAR2 ( 30 ))
    'зиг' || 'заг'зигзаг
    'абв' || n'абв' || 'абв'абвабвабв (в многобайтовой кодировке)
    'Работник' || ENAMEРаботник SMITH (типа VARCHAR2 ( 18 ))
    1845 || ' г.'1845 г.

    Как и в случае с числовыми выражениями, здесь происходит неявное преобразования типов: когда в выражении встречается число (возможно, полученое вычислением числового подвыражения), оно автоматически переводится в строку текста. Фактическое преобразование осуществляется функцией TO_CHAR. Такое преобразование не может окончиться ошибкой, но не всегда выглядит совсем уж очевидно:

    SELECT '->' || 001.00 || '<-' FROM dual;
    -- будет обработано как:
    SELECT '->' || TO_CHAR ( 001.00 ) || '<-' FROM dual;
    

    Возникающие тонкости неявного приведения к строке (в вышеприведенном примере ведущие и незначащие нули в записи числа) следует уточнять по описанию функции TO_CHAR.

    Операции над типами "момент" и "интервал времени"

    Два вида временных типов — для моментов и для интервалов — имеют свою "временную арифметику", основанную на сложении и вычитании и позволяющую строить простые выражения:

    Выражение для времениЗначение
    DATE '1990-08-28' + 331 августа 1990 года, 00:00:00
    3 + DATE '1990-08-28'31 августа 1990 года, 00:00:00
    DATE '1988-12-04' - 529 ноября 1988 года, 00:00:00
    SYSDATE + 1 / 24[сейчас](*) плюс час
    SYSTIMESTAMP(9-) - INTERVAL '3' HOUR(9-)[сейчас](*) минус три часа (типа TIMESTAMP(9) WITH TIME ZONE)
    DATE '2005-1-1' - SYSDATEчисло нецелых суток до/после Нового 2005 года (типа NUMBER)
    TIMESTAMP '2005-1-1 0:0:0'(9-) - SYSTIMESTAMP(9-)время до/после Нового 2005 года (типа INTERVAL DAY(9) TO SECOND(9))
    SYSDATE - INTERVAL '3' HOUR(9-)[сейчас](*) минус три часа (типа DATE, т. е. с неявным преобразованием типа)
    SYSTIMESTAMP(9-) - 3 / 24[сейчас](*) минус три часа (типа DATE, т. е. с неявным преобразованием типа)
    DATE '2005-1-1' - SYSTIMESTAMP(9-)время до/после Нового 2005 года (типа INTERVAL DAY(9) TO SECOND(9) , т. е. с неявным преобразованием типа)

    (*) время компьютера с СУБД

    (9-) начиная с версии 9

    Пример употребления:

    SELECT projno FROM proj WHERE bdate > SYSDATE + 1;
    

    Упражнение. Проверить значения следующих выражений:

    DATE '2009-01-28' + INTERVAL '1' MONTH
    DATE '2009-01-29' + INTERVAL '1' MONTH
    DATE '2008-01-29' + INTERVAL '1' MONTH
    DATE '2009-01-30' + INTERVAL '1' MONTH
    

    Формат выдачи момента времени можно устанавливать для БД, СУБД и отдельного сеанса. Например, применительно к типу DATE:

    SQL> SELECT value FROM nls_session_parameters
      2> WHERE  parameter = 'NLS_DATE_FORMAT';
    VALUE
    ----------------------------------------
    DD-MON-RR
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    ---------
    14-SEP-09
    SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
    Session altered.
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    -------------------
    2009-09-14 00:36:11
    SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'Day HH:MI:SS am';
    Session altered.
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    ---------------------
    Monday    12:36:11 am
    

    Непосредственно в выражениях формат указывается маской в функциях TO_DATE, TO_TIMESTAMP, TO_CHAR и подобных; обширный перечень способов указать маску приводится в документации по Oracle.

    Функции

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

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

    Ниже приводятся примеры некоторых характерных групп функций.

    Функции для строк текста

    Используются для работы со строками типов VARCHAR2 и CHAR. Примеры функций:

  • LENGTH — вычисление длины строки;
  • LOWER, UPPER — понижение и повышение регистра букв;
  • INITCAP — повышение регистра первых букв в словах и понижение остальных;
  • RTRIM, LTRIM — убирание одинаковых символов в конце либо в начале (по умолчанию пробелов);
  • RPAD, LPAD — дополнение строки текста одинаковыми символами справа либо слева (по умолчанию — пробелами);
  • INSTR, SUBSTR — поиск вхождения подстроки и замена.
  • Примеры действия функций на строки:

    SQL> SELECT ename, LOWER ( ename ), INITCAP ( ename ) FROM emp;
    ENAME      LOWER(ENAM INITCAP(EN
    ---------- ---------- ----------
    SMITH      smith      Smith
    ALLEN      allen      Allen
    ...
    SQL> SELECT LPAD ( ename, 7, '*' ), RTRIM ( ename, 'ITH' ) FROM emp;
    LPAD(EN RTRIM(ENAM
    ------- ----------
    **SMITH SM
    **ALLEN ALLEN
    ***WARD WARD
    ...
    

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

    Функции преобразования типов данных

    В соответствии со стандартом SQL-92 в Oracle есть общая функция преобразования типов CAST.

    Примеры:

    SELECT CAST ( '0123' AS NUMBER ( 5 ) ) FROM dual;
    SELECT CAST ( SYSDATE AS VARCHAR2 ( 20 ) ) FROM dual;
    

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

    TO_CHAR
    TO_CLOB
    TO_NUMBER
    TO_BINARY_FLOAT/DOUBLE 
    TO_DATE
    TO_TIMESTAMP
    TO_YMINTERVAL, TO_DSINTERVAL
    NUMTOYMINTERVAL, NUMTODSINTERVAL
    других.
    

    Большинство из них имеет имена, начинающиеся с 'TO_', однако Oracle не пунктуальна в соблюдении этого неформального правила.

    Обычно эти функции допускают уточнение способа преобразования, с помощью маски или географических установок местности. Примеры употребления:

    SELECT TO_TIMESTAMP ( '10-APR-56' ) FROM dual;
    SELECT
     TO_TIMESTAMP (
       '10-Апрель-56'
     , 'DD-MONTH-RR'
     , 'NLS_DATE_LANGUAGE=RUSSIAN' 
    ) 
    FROM dual;
    SELECT TO_CHAR ( SYSDATE, 'Day HH24:MI:SS' ) FROM dual;
    

    Использование маски позволяет в частности поставить под контроль выдачу номера недели. В США, где разрабатывается СУБД Oracle, неделя начинается с воскресения, а недели отсчитываются с первого дня года. По правилам ISO это не так. Правильно выбранная маска способна заставить СУБД выдать желаемое. Вот пояснительная пара запросов со сравнительной выдачей:

    COLUMN "Неделя в США"  FORMAT A13
    COLUMN "Неделя по ISO" FORMAT A13
    COLUMN "Название дня"  FORMAT A13
    SELECT
      TO_CHAR ( DATE '2010-1-1', 'ww' )  "Неделя в США"
    , TO_CHAR ( DATE '2010-1-1', 'iw' )  "Неделя по ISO"
    , TO_CHAR ( DATE '2010-1-1', 'day' ) "Название дня"
    FROM dual
    ;
    SELECT
      TO_CHAR ( DATE '2010-1-4', 'ww' )  "Неделя в США"
    , TO_CHAR ( DATE '2010-1-4', 'iw' )  "Неделя по ISO"
    , TO_CHAR ( DATE '2010-1-5', 'day' ) "Название дня"
    FROM dual
    ;
    

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

    Функции для работы со временем

    Позволяют пополнить "временную арифметику" необходимыми или практичными операциями. Примеры функций:

    ADD_MONTHS
    LAST_DAY
    MONTHS_BETWEEN
    NEXT_DAY
    ROUND
    TRUNC
    EXTRACT(9-)
    

    (9-) начиная с версии 9

    Допустим, что в поле BDATE типа DATE текущей строки находится значение "5 сентября 1999 г., 13 часов 30 минут 05 секунд". Справедливы следующие оценки выражений:

    ВыражениеЗначение
    EXTRACT ( DAY FROM SYSTIMESTAMP )
    EXTRACT ( HOUR FROM SYSTIMESTAMP )
    
    EXTRACT ( MINUTE FROM INTERVAL '04:03' HOUR TO MINUTE )
    [сегодняшний день](*) (типа NUMBER)
    [время суток](*) (типа NUMBER)
    и так далее
    3 "минуты" (типа NUMBER)
    TRUNC ( BDATE )
    TRUNC ( BDATE, 'year' )
    5 сентября 1999 года, 00:00:00
    1 января 1999 года, 00:00:00
    и так далее
    ADD_MONTHS ( BDATE, 2 )5 ноября 1999 года, 13:30:05
    ( ADD_MONTHS ( TRUNC ( SYSDATE, 'year' ), 12 ) - 1 ) - TRUNC ( SYSDATE )число суток до ближайшего Нового года (типа NUMBER)

    (*) время компьютера с СУБД

    Можно заметить, что Oracle, как и стандарт SQL, непоследователен в своем синтаксисе. Сравните указание компоненты момента времени в виде строки текста ('year' в функции TRUNC, а также ROUND) и с помощью ключевого слова (DAY, HOUR в функции EXTRACT).

    Примеры использования:

    SELECT EXTRACT ( YEAR FROM SYSTIMESTAMP ) FROM dual;
    SELECT MONTHS_BETWEEN ( DATE '2009-03-01', DATE '2009-02-28' ) 
    FROM   dual
    ;
    

    Заметьте, что функции "месячной арифметики" ADD_MONTHS и MONTHS_BETWEEN вовсе не так очевидны.

    Упражнение. Проверьте значения следующих выражений:

    ADD_MONTHS ( DATE '2009-01-28', 1 )
    ADD_MONTHS ( DATE '2009-01-29', 1 )
    ADD_MONTHS ( DATE '2008-01-29', 1 )
    ADD_MONTHS ( DATE '2009-01-30', 1 )
    

    Функции условной подстановки значений

    Дают возможность выполнить "преобразование" аргументов, а по сути — условную замену конкретных величин. Часть таких функций связана с желательной для программиста переработкой отсутствующих значений (в отдельном случае Not a Number), а функция DECODE — нет:

    ФункцияЛогический эквивалент
    NVL (E1, E2)IF E1 IS NULL THEN E2 ELSE E1
    NVL2 (E1, E2, E3)IF E1 IS NULL THEN E3 ELSE E2
    NANVL (E1, E2)(IEEE 754)IF E1 IS NAN THEN E2 ELSE E1
    COALESCE (E1, E2, E3, …[9-)первое по списку Ei со значением не NULL
    DECODE ( E1, E2, E3, …[, EN])IF E1 = E2 THEN E3 [ ELSE IF E1 = E4 THEN E5 […] ] [ELSE EN]

    (IEEE 754) для типов BINARY_FLOAT/BINARY_DOUBLE

    [9-) начиная с версии 9

    Примеры:

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

    В отличие от NVL и NVL2, COALESCE не вычисляет выражения-аргументы без надобности:

    SELECT NVL ( 123, 1 / 0 ) FROM dual;
    -- Ошибка !
    SELECT COALESCE ( 123, 1 / 0 ) FROM dual;
    -- OK 
    

    Пример DECODE:

    SELECT 
      deptno
    , loc
    , DECODE ( loc, 'NEW YORK', 'NEW YORK CITY', 'BOSTON', 'BOSTON AREA' )
    FROM dept
    ;
    

    Фактически DECODE позволяет сформулировать в тексте запроса таблицу подстановки значений. В нашем случае, если потребуется выдать исходное значение LOC, когда там не значения 'NEW YORK' и 'BOSTON', нужно будет добавить замыкающий четный аргумент:

    DECODE 
    ( loc, 'NEW YORK', 'NEW YORK CITY', 'BOSTON', 'BOSTON AREA', loc )
    

    Это не самое хорошее решение, так как иногда приходится повторять сложное выражение вторично, что чревато ошибками и лишними вычислениями. Кроме того, методически оправданно держать правила преобразования в БД, а не в тексте запроса (если только эти правила имеют прикладное значение). На практике же нахождение таблицы преобразования в БД резко замедлит вычисление.

    Нескалярные функции для анализа данных

    Таковых имеется две категории: "агрегатные", то есть обобщающие, и "аналитические".

    Агрегатные функции иначе называют "стандартными агрегатными" функциями и "агрерирующими" функциями. До версии 8.1.6 они (сокращенным количеством) назывались "статистическими". Они дают скалярный результат, но аргументом им служит столбец значений, чем они конструктивно отличаются от большинства других встроенных функций. Это же сообщает им обобщающий характер.

    Некоторые из них:

    ФункцияОписание
    COUNTЧисло значений в столбце или строк в таблице
    MINНаименьшее значение в столбце
    MAXНаибольшее значение в столбце
    SUMСумма значений в столбце
    AVGСреднее арифметическое значений в столбце
    VARIANCEДисперсия (мера отклонения от математического ожидания)
    STDDEV,
    STDDEV_POP,
    STDDEV_SAM
    
    Стандартное отклонение (квадратный корень от дисперсии) в разных вариациях
    MEDIAN[10-)Медиана значений в столбце

    [10-) с версии 10

    Некоторые другие примеры: CORR, COVAR_POP, COVAR_SAMP, CUME_DIST, PERCENTILE_COUNT и так далее.

    Начиная с версии 10 Oracle позволяет производить более "серьезные" статистические обобщения данных столбца таблицы, но уже не средствами SQL, а программно, с помощью процедур из встроенного пакета DBMS_STAT_FUNCS. Они позволяют определить соответствие указанного значения тому или иному виду статистического распределения, а также обобщать данные столбца всеми способами стандартных агрегатных функций, но вдобавок со значительным количеством дополнительной обобщающей информации.

    Аналитические функции идут дальше агрегатных, не только имея столбцовые аргументы, но и возвращая в виде столбца результат. Они не только позволяют обобщить данные, как агрегатные, но способны делать это без потери детализации.

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

    Конструкции (операторы) 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. Оборотной стороной такого самоограничения окажется загромождение схемы данных и усложнение запросов к БД.

    Страницы:

    Общие элементы запросов и предложений DML: выражения

    Готовые приложения, как правило, работают с данными уже существующих таблиц. Основная группа предложений SQL, используемых в работающих приложениях, — это SELECT для выборки и операторы DML для изменения данных таблиц. При всем их синтаксическом различии операторы этой группы роднит использование выражений, составляемых по одним и тем же одинаковым правилам.

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

    Исходные значения

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

  • явно,
  • через "системные переменные",
  • именем поля строки таблицы.
  • Явно обозначенные величины ("литералы")

    Для явно обозначенных величин (values) в русской литературе часто используется калька с американского английского: "литералы". Оригинальное слово literal представляет собой возникшее со временем в североамериканской литературе жаргонное сокращение от literal value, что дословно означает напрямую ("буквально") указанную в тексте величину (средневековое английское значение слова literal не имеет никакого отношения к компьютерному).

    Нелишне помнить, что одни и те же величины часто могут быть обозначены по-разному, например, 1 и +1 и так далее (в известном "треугольнике Фреге" предмет — обозначение — смысл literal value скорее "обозначение"). Для разных видов данных в выражениях предусмотрены разные способы обозначения.

    Числовые величины

    Примеры обозначения целых чисел:

    38, +12, -3404
    

    Примеры обозначения "десятичных" чисел (decimal), иначе чисел с возможной дробной частью, записанных в десятичной системе счисления:

    342.16, 49, -16, 0.83459
    

    Отделение целой части от дробной осуществляется с помощью десятичной точки или же запятой, в зависимости от установок местности ("языковых"). Русский формат записи чисел (в Oracle устанавливается параметром сеанса NLS_NUMERIC_CHARACTERS как ', ') унаследовал исторически французскую традицию употребления в качестве разделителя запятую в отличие от английской точки, унаследованной Северной Америкой как местом разработки СУБД Oracle. Если не обращать внимания на эту мелочь, могут возникать ошибки вывода числовых данных из БД.

    В записи рациональных чисел могут присутствовать в служебных целях форматирующие буквы:

    49, 18.47, -34e2, 0.16e4, 4e-3, 4e-3, 
    123f (явное указание BINARY_FLOAT), 
    -123.25d (явное указание BINARY_DOUBLE)
    

    Регистр букв, как обычно, не имеет значения.

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

    Пример для всех версий:

    SQL> SELECT 1000, DUMP ( 1000 ) FROM dual;
          1000 DUMP(1000)          
    ---------- ------------------- 
          1000 Typ=2 Len=2: 194,11 
    Пример для версий 10+:
    SQL> SELECT 123f, DUMP ( 123f ), DUMP ( 123d ) FROM dual;
          123F DUMP(123F)                 DUMP(123D)
    ---------- -------------------------- -----------------------------------
     1.23E+002 Typ=100 Len=4: 194,246,0,0 Typ=101 Len=8: 192,94,192,0,0,0,0,0
    

    В качестве упражнения предлагается выполнить другие проверки, например, при записи числа как 1000.1 и 1.0e+3.

    Строки текста

    Примеры указания строк:

    'Collins', '''tis' (кавычки в кавычках), '!?-@ ', '', '''', '1234'
    n'Многобайтовая кодировка; по правилам ANSI можно писать и N, и n', 
    n'Тоже многобайтовая кодировка'
    q'[Строка без 'искажений'. Возможно начиная с версии 10]',  
    q'|ограничивающий символ может быть практически любой|'
    u'строка в Unicode начиная с версии 10'
    

    Функция DUMP помогает понять, как воспринимает СУБД по-разному оформленные строки. Последние два предложения SELECT работают начиная с версии 10:

    SQL> SELECT 'a''bc', DUMP ( 'a''bc' ) FROM dual;
    'A'' DUMP('A''BC')
    ---- -------------------------
    a'bc Typ=96 Len=4: 97,39,98,99
    SQL> SELECT 'a''bc', DUMP ( q'wa'bcw' ) FROM dual;
    'A'' DUMP(Q'WA'BCW')
    ---- -------------------------
    a'bc Typ=96 Len=4: 97,39,98,99
    SQL> SELECT 'a''bc', DUMP ( nq'wa'bcw' ) FROM dual;
    'A'' DUMP(NQ'WA'BCW')
    ---- ---------------------------------
    a'bc Typ=96 Len=8: 0,97,0,39,0,98,0,99
    

    Моменты и интервалы времени

    Начиная с версии 9 в Oracle поддерживается система указаний моментов и интервалов времени, принятая для SQL комитетами ANSI/ISO по стандартизации SQL. Использованный в примерах ниже формат указания самого значения жестко регламентирован ANSI/ISO. Это касается и типа DATE, для которого Oracle принимает формулировку из стандарта, но по-своему раскрывает ее содержание.

    Для обозначения моментов времени используются конструкции DATE, TIME и TIMESTAMP.

    Примеры:

    DATE '2003-04-14' 
      (14 апреля 2003 00:00:00; имеет тип DATE, а временная компонента обнулена)
    TIME '12:30:45' 
      (12.30.45.000000000 пополудни; тип не играет самостоятельной роли в БД и может использоваться только в выражении)
    TIMESTAMP '2003-04-14 15:16:17' 
      (14 апреля 2003 15:16:17.000000000; этот и следующий пример имеет тип TIMESTAMP ( 9 ) )
    TIMESTAMP '2003-04-14 15:16:17.88' 
      (14 апреля 2003 15:16:17.880000000)
    TIMESTAMP '1997-01-31 09:26:56.66 +02:00' 
      (31 января 1997 09:26:56.660000000 во второй временной зоне; имеет тип TIMESTAMP ( 9 ) WITH TIME ZONE)
    TIMESTAMP '1997-01-31 09:26:56.66 Europe/Moscow' 
      (31 января 1997 09:26:56.660000000 во временной зоне г. Москвы; имеет тип TIMESTAMP ( 9 ) WITH TIME ZONE)
    

    Для обозначения интервалов времени используются конструкции INTERVAL, допускающие указание подынтервалов "грубого" и "точного" диапазона интервалов и, в дополнение к синтаксису ANSI/ISO, указание точности. Деление на "грубые" и "точные" диапазоны условно. Примеры формулирования "точных" интервалов (диапазон от дней до долей секунд и подынтервалы):

    INTERVAL '5 04:03:02.01' DAY TO SECOND 
      (5 дней, 4 часа, 3 минуты, 2,01 секунды; имеет тип INTERVAL DAY ( 2 ) TO SECOND ( 6 )),
    INTERVAL '04:03' HOUR TO MINUTE 
      (0 дней, 4 часа, 3 минуты; этот и следующий пример имеет тип INTERVAL DAY ( 2 ) TO SECOND ( 0 )),
    INTERVAL '03' MINUTE 
      (0 дней, 0 часов, 3 минуты)
    

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

    Примеры формулирования "грубых" интервалов (диапазон из лет и месяцев и два подынтервала):

    INTERVAL '04-5' YEAR TO MONTH 
      (плюс 4 года и 5 месяцев; этот и два следующих примера имеют тип INTERVAL YEAR ( 2 ) TO MONTH),
    INTERVAL '-4' YEAR 
      (минус 4 года),
    INTERVAL '5' MONTH 
      (плюс 5 месяцев)
    

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

    INTERVAL ' + 4- 0005 ' YEAR TO MONTH
    

    "Системные переменные" и "псевдостолбцы"

    Оба названия в заголовке заключены в кавычки в силу своей условности.

    "Системные переменные" по сути представляют собой ряд иногда полезных для употребления системных функций без аргументов. Вот некоторые примеры:

    ПеременнаяТипОписание
    USERVARCHAR2 ( 256 )Имя пользователя, выдавшего предложение SQL
    UIDNUMBERНомер пользователя, выдавшего предложение SQL
    SYSDATEDATEТекущая дата + время суток с точностью до секунды
    SYSTIMESTAMP(9-)TIMESTAMP ( 9 ) WITH TIME ZONE(9-)Текущая дата + время суток с точностью до 1/100 секунды
    DBTIMEZONE(9-) и SESSIONTIMEZONE(9-)VARCHAR2 ( 6 ) и VARCHAR2 ( 75 )Зоны времени, определенные для БД и в рамках конкретного сеанса

    (9-) начиная с версии 9

    Пример использования в выражении:

    SELECT object_name, owner FROM all_objects WHERE owner <> USER;
    

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

    Примеры:

    ПсевдостолбецТипОписание
    ROWNUMNUMBERПоследовательный номер строки в результате SELECT
    LEVELNUMBERНомер уровня выдаваемой строки в предложении SELECT с использованием CONNECT BY
    CONNECT_BY_ISCYCLE[10-)NUMBERВ предложении SELECT с использованием CONNECT BY: 1, если потомок узла является одновременно его предком, иначе 0
    CONNECT_BY_ISLEAF[10-)NUMBERВ предложении SELECT с использованием CONNECT BY:1, если узел не имеет потомков
    ROWIDVARCHAR2 ( 256 )Физический адрес строки или хранимого объекта
    XMLDATA[9.2-)CLOBТекст документа объекта типа XMLTYPE
    OBJECT_ID[10-)RAW ( 16 )Идентификатор объекта в таблице первичных или виртуальных объектов (представлений)
    OBJECT_VALUE[10-)тип объектаСистемное имя для столбца в таблице первичных или виртуальных объектов (в том числе типа XMLTYPE)
    ORA_ROWSCN[10-)NUMBERПорядковый номер изменения в БД (SCN), соответствующий строке таблицы или же блоку данных с этой строкой
    COLUMN_VALUE[10.2-)тип объектаТип элемента результата функций TABLE и XMLTABLE
    VERSIONS_STARTSCN[10-)
    VERSIONS_STARTTIME[10-)
    VERSIONS_ENDSCN[10-)
    VERSIONS_ENDTIME[10-)
    VERSIONS_XID[10-)
    VERSIONS_OPERATION[10-
    
    Используются в "быстрых" запросах к прошлым данным (flashback queries)

    [9.2-) начиная с версии 9.2

    [10-) начиная с версии 10.1

    [10.2-) начиная с версии 10.2

    Пример использования в выражениях:

    SELECT object_name, ROWNUM FROM all_objects WHERE ROWNUM <= 15;
    

    Величины, взятые из полей строк таблицы

    Исходные значения в выражении можно также брать из приведенных в выражении полей записей (строк) таблиц, на которые ссылается предложение SQL. В предыдущем запросе OBJECT_NAME — тривиальное (неразложимое) выражение, составленное из обращения к значению в поле с этим названием в очередной строке таблицы ALL_OBJECTS.

    Составные выражения

    Составные выражения построены из более простых выражений путем применения к ним "конструкторов"; в житейском восприятии они, в отличие от исходных значений, собственно и образуют выражения. Они имеют тип, соответствующий типу величины-результата вычисления, то есть "типизированы типом результата".

    Ниже приводятся разные варианты конструкторов, позволяющих строить из более простых выражений более сложные:

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

    Числовые выражения можно получить из других числовых выражений с помощью арифметических операций *, /, + и -, и, возможно, скобок.

    Предположим, что поле COMM в очередной строке имеет значение 25. Тогда справедливы следующие примеры:

    Числовое выражениеЗначение
    6 + 4 * 25106
    0.6E1 + 4 * COMM106.E0
    (50 / 10) * 525
    1 + '25'26 (неявное преобразование типа)
    123d + 123246 в формате BINARY_DOUBLE
    123d + 123f246 в формате BINARY_DOUBLE
    6 + 4 * COMM106
    (6 + 4) * 25250
    NULL * 30NULL
    1f - BINARY_FLOAT_INFINITYBINARY_FLOAT_INFINITY
    1d + BINARY_DOUBLE_NANBINARY_DOUBLE_NAN

    Числовая арифметика в SQL несколько отличается от школьной. Вот некоторые особенности.

  • Если значение элемента числового выражения отсутствует (то есть помечено как NULL, в обозначениях SQL), значение выражения тоже отсутствует (NULL). Это не всегда привычно. Например:
    SELECT 1 / 0 FROM dual;
    -- Ошибка !
    SELECT NULL / 0 FROM dual;
    -- OK
    
  • Тип данных результата приводится к наиболее точному из употребленных в выражении.
  • Типы элементов — участников числового выражения должны быть числовыми. Если же встречается строка текста (указанная явно или вычисленная подвыражением), она автоматически приводится к числовому значению, то есть происходит неявное преобразование типа. Фактическое преобразование осуществляется функциями TO_NUMBER, TO_BINARY_FLOAT или TO_BINARY_DOUBLE (в зависимости от контекста), например:
    SELECT '1' / 2 FROM dual;
    -- будет обработано как:
    SELECT TO_NUMBER ( '1' ) / 2 FROM dual;
    
    Соответственно, невозможность такого преобразования в конкретных случаях (строка текста несводима к числу) определяется правилами работы этих функций.
  • Многие специалисты полагают неправильным использование неявного преобразования типов, считая необходимым все преобразования выписывать явно, хотя бы и в ущерб краткости записи. Явное указание преобразования повышает качество кода и снижает риск возникновения ненамеренных ошибок. Однако даже если бы у разработчиков Oracle возникло желание отменить неявное преобразование типов, сделать это уже было бы невозможно, не нарушив правило обратной совместимости кода. Есть и другая причина: функции в Oracle не поддерживают всего допустимого в БД разнообразия типов. Например, отсутствуют целые функции, так что автоматическое преобразование типов становится неизбежным.

    Простые выражения над строками текста

    Простейшие выражения над строками текста (алфавитно-цифровые, или строковые выражения) можно получить из непосредственно указанных значений и единственной текстовой операцией || ("склейки").

    Предположим, что поле ENAME (типа VARCHAR2 ( 10 )) в очередной строке имеет значение 'SMITH'. Тогда справедливы следующие примеры:

    Строковое выражениеЗначение
    'Работник'Вася и Петя (типа CHAR ( 8 ))
    ENAMESMITH (типа VARCHAR2 ( 10 ))
    USERSCOTT (VARCHAR2 ( 30 ))
    'зиг' || 'заг'зигзаг
    'абв' || n'абв' || 'абв'абвабвабв (в многобайтовой кодировке)
    'Работник' || ENAMEРаботник SMITH (типа VARCHAR2 ( 18 ))
    1845 || ' г.'1845 г.

    Как и в случае с числовыми выражениями, здесь происходит неявное преобразования типов: когда в выражении встречается число (возможно, полученое вычислением числового подвыражения), оно автоматически переводится в строку текста. Фактическое преобразование осуществляется функцией TO_CHAR. Такое преобразование не может окончиться ошибкой, но не всегда выглядит совсем уж очевидно:

    SELECT '->' || 001.00 || '<-' FROM dual;
    -- будет обработано как:
    SELECT '->' || TO_CHAR ( 001.00 ) || '<-' FROM dual;
    

    Возникающие тонкости неявного приведения к строке (в вышеприведенном примере ведущие и незначащие нули в записи числа) следует уточнять по описанию функции TO_CHAR.

    Операции над типами "момент" и "интервал времени"

    Два вида временных типов — для моментов и для интервалов — имеют свою "временную арифметику", основанную на сложении и вычитании и позволяющую строить простые выражения:

    Выражение для времениЗначение
    DATE '1990-08-28' + 331 августа 1990 года, 00:00:00
    3 + DATE '1990-08-28'31 августа 1990 года, 00:00:00
    DATE '1988-12-04' - 529 ноября 1988 года, 00:00:00
    SYSDATE + 1 / 24[сейчас](*) плюс час
    SYSTIMESTAMP(9-) - INTERVAL '3' HOUR(9-)[сейчас](*) минус три часа (типа TIMESTAMP(9) WITH TIME ZONE)
    DATE '2005-1-1' - SYSDATEчисло нецелых суток до/после Нового 2005 года (типа NUMBER)
    TIMESTAMP '2005-1-1 0:0:0'(9-) - SYSTIMESTAMP(9-)время до/после Нового 2005 года (типа INTERVAL DAY(9) TO SECOND(9))
    SYSDATE - INTERVAL '3' HOUR(9-)[сейчас](*) минус три часа (типа DATE, т. е. с неявным преобразованием типа)
    SYSTIMESTAMP(9-) - 3 / 24[сейчас](*) минус три часа (типа DATE, т. е. с неявным преобразованием типа)
    DATE '2005-1-1' - SYSTIMESTAMP(9-)время до/после Нового 2005 года (типа INTERVAL DAY(9) TO SECOND(9) , т. е. с неявным преобразованием типа)

    (*) время компьютера с СУБД

    (9-) начиная с версии 9

    Пример употребления:

    SELECT projno FROM proj WHERE bdate > SYSDATE + 1;
    

    Упражнение. Проверить значения следующих выражений:

    DATE '2009-01-28' + INTERVAL '1' MONTH
    DATE '2009-01-29' + INTERVAL '1' MONTH
    DATE '2008-01-29' + INTERVAL '1' MONTH
    DATE '2009-01-30' + INTERVAL '1' MONTH
    

    Формат выдачи момента времени можно устанавливать для БД, СУБД и отдельного сеанса. Например, применительно к типу DATE:

    SQL> SELECT value FROM nls_session_parameters
      2> WHERE  parameter = 'NLS_DATE_FORMAT';
    VALUE
    ----------------------------------------
    DD-MON-RR
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    ---------
    14-SEP-09
    SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
    Session altered.
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    -------------------
    2009-09-14 00:36:11
    SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'Day HH:MI:SS am';
    Session altered.
    SQL> SELECT SYSDATE FROM dual;
    SYSDATE
    ---------------------
    Monday    12:36:11 am
    

    Непосредственно в выражениях формат указывается маской в функциях TO_DATE, TO_TIMESTAMP, TO_CHAR и подобных; обширный перечень способов указать маску приводится в документации по Oracle.

    Функции

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

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

    Ниже приводятся примеры некоторых характерных групп функций.

    Функции для строк текста

    Используются для работы со строками типов VARCHAR2 и CHAR. Примеры функций:

  • LENGTH — вычисление длины строки;
  • LOWER, UPPER — понижение и повышение регистра букв;
  • INITCAP — повышение регистра первых букв в словах и понижение остальных;
  • RTRIM, LTRIM — убирание одинаковых символов в конце либо в начале (по умолчанию пробелов);
  • RPAD, LPAD — дополнение строки текста одинаковыми символами справа либо слева (по умолчанию — пробелами);
  • INSTR, SUBSTR — поиск вхождения подстроки и замена.
  • Примеры действия функций на строки:

    SQL> SELECT ename, LOWER ( ename ), INITCAP ( ename ) FROM emp;
    ENAME      LOWER(ENAM INITCAP(EN
    ---------- ---------- ----------
    SMITH      smith      Smith
    ALLEN      allen      Allen
    ...
    SQL> SELECT LPAD ( ename, 7, '*' ), RTRIM ( ename, 'ITH' ) FROM emp;
    LPAD(EN RTRIM(ENAM
    ------- ----------
    **SMITH SM
    **ALLEN ALLEN
    ***WARD WARD
    ...
    

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

    Функции преобразования типов данных

    В соответствии со стандартом SQL-92 в Oracle есть общая функция преобразования типов CAST.

    Примеры:

    SELECT CAST ( '0123' AS NUMBER ( 5 ) ) FROM dual;
    SELECT CAST ( SYSDATE AS VARCHAR2 ( 20 ) ) FROM dual;
    

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

    TO_CHAR
    TO_CLOB
    TO_NUMBER
    TO_BINARY_FLOAT/DOUBLE 
    TO_DATE
    TO_TIMESTAMP
    TO_YMINTERVAL, TO_DSINTERVAL
    NUMTOYMINTERVAL, NUMTODSINTERVAL
    других.
    

    Большинство из них имеет имена, начинающиеся с 'TO_', однако Oracle не пунктуальна в соблюдении этого неформального правила.

    Обычно эти функции допускают уточнение способа преобразования, с помощью маски или географических установок местности. Примеры употребления:

    SELECT TO_TIMESTAMP ( '10-APR-56' ) FROM dual;
    SELECT
     TO_TIMESTAMP (
       '10-Апрель-56'
     , 'DD-MONTH-RR'
     , 'NLS_DATE_LANGUAGE=RUSSIAN' 
    ) 
    FROM dual;
    SELECT TO_CHAR ( SYSDATE, 'Day HH24:MI:SS' ) FROM dual;
    

    Использование маски позволяет в частности поставить под контроль выдачу номера недели. В США, где разрабатывается СУБД Oracle, неделя начинается с воскресения, а недели отсчитываются с первого дня года. По правилам ISO это не так. Правильно выбранная маска способна заставить СУБД выдать желаемое. Вот пояснительная пара запросов со сравнительной выдачей:

    COLUMN "Неделя в США"  FORMAT A13
    COLUMN "Неделя по ISO" FORMAT A13
    COLUMN "Название дня"  FORMAT A13
    SELECT
      TO_CHAR ( DATE '2010-1-1', 'ww' )  "Неделя в США"
    , TO_CHAR ( DATE '2010-1-1', 'iw' )  "Неделя по ISO"
    , TO_CHAR ( DATE '2010-1-1', 'day' ) "Название дня"
    FROM dual
    ;
    SELECT
      TO_CHAR ( DATE '2010-1-4', 'ww' )  "Неделя в США"
    , TO_CHAR ( DATE '2010-1-4', 'iw' )  "Неделя по ISO"
    , TO_CHAR ( DATE '2010-1-5', 'day' ) "Название дня"
    FROM dual
    ;
    

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

    Функции для работы со временем

    Позволяют пополнить "временную арифметику" необходимыми или практичными операциями. Примеры функций:

    ADD_MONTHS
    LAST_DAY
    MONTHS_BETWEEN
    NEXT_DAY
    ROUND
    TRUNC
    EXTRACT(9-)
    

    (9-) начиная с версии 9

    Допустим, что в поле BDATE типа DATE текущей строки находится значение "5 сентября 1999 г., 13 часов 30 минут 05 секунд". Справедливы следующие оценки выражений:

    ВыражениеЗначение
    EXTRACT ( DAY FROM SYSTIMESTAMP )
    EXTRACT ( HOUR FROM SYSTIMESTAMP )
    
    EXTRACT ( MINUTE FROM INTERVAL '04:03' HOUR TO MINUTE )
    [сегодняшний день](*) (типа NUMBER)
    [время суток](*) (типа NUMBER)
    и так далее
    3 "минуты" (типа NUMBER)
    TRUNC ( BDATE )
    TRUNC ( BDATE, 'year' )
    5 сентября 1999 года, 00:00:00
    1 января 1999 года, 00:00:00
    и так далее
    ADD_MONTHS ( BDATE, 2 )5 ноября 1999 года, 13:30:05
    ( ADD_MONTHS ( TRUNC ( SYSDATE, 'year' ), 12 ) - 1 ) - TRUNC ( SYSDATE )число суток до ближайшего Нового года (типа NUMBER)

    (*) время компьютера с СУБД

    Можно заметить, что Oracle, как и стандарт SQL, непоследователен в своем синтаксисе. Сравните указание компоненты момента времени в виде строки текста ('year' в функции TRUNC, а также ROUND) и с помощью ключевого слова (DAY, HOUR в функции EXTRACT).

    Примеры использования:

    SELECT EXTRACT ( YEAR FROM SYSTIMESTAMP ) FROM dual;
    SELECT MONTHS_BETWEEN ( DATE '2009-03-01', DATE '2009-02-28' ) 
    FROM   dual
    ;
    

    Заметьте, что функции "месячной арифметики" ADD_MONTHS и MONTHS_BETWEEN вовсе не так очевидны.

    Упражнение. Проверьте значения следующих выражений:

    ADD_MONTHS ( DATE '2009-01-28', 1 )
    ADD_MONTHS ( DATE '2009-01-29', 1 )
    ADD_MONTHS ( DATE '2008-01-29', 1 )
    ADD_MONTHS ( DATE '2009-01-30', 1 )
    

    Функции условной подстановки значений

    Дают возможность выполнить "преобразование" аргументов, а по сути — условную замену конкретных величин. Часть таких функций связана с желательной для программиста переработкой отсутствующих значений (в отдельном случае Not a Number), а функция DECODE — нет:

    ФункцияЛогический эквивалент
    NVL (E1, E2)IF E1 IS NULL THEN E2 ELSE E1
    NVL2 (E1, E2, E3)IF E1 IS NULL THEN E3 ELSE E2
    NANVL (E1, E2)(IEEE 754)IF E1 IS NAN THEN E2 ELSE E1
    COALESCE (E1, E2, E3, …[9-)первое по списку Ei со значением не NULL
    DECODE ( E1, E2, E3, …[, EN])IF E1 = E2 THEN E3 [ ELSE IF E1 = E4 THEN E5 […] ] [ELSE EN]

    (IEEE 754) для типов BINARY_FLOAT/BINARY_DOUBLE

    [9-) начиная с версии 9

    Примеры:

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

    В отличие от NVL и NVL2, COALESCE не вычисляет выражения-аргументы без надобности:

    SELECT NVL ( 123, 1 / 0 ) FROM dual;
    -- Ошибка !
    SELECT COALESCE ( 123, 1 / 0 ) FROM dual;
    -- OK 
    

    Пример DECODE:

    SELECT 
      deptno
    , loc
    , DECODE ( loc, 'NEW YORK', 'NEW YORK CITY', 'BOSTON', 'BOSTON AREA' )
    FROM dept
    ;
    

    Фактически DECODE позволяет сформулировать в тексте запроса таблицу подстановки значений. В нашем случае, если потребуется выдать исходное значение LOC, когда там не значения 'NEW YORK' и 'BOSTON', нужно будет добавить замыкающий четный аргумент:

    DECODE 
    ( loc, 'NEW YORK', 'NEW YORK CITY', 'BOSTON', 'BOSTON AREA', loc )
    

    Это не самое хорошее решение, так как иногда приходится повторять сложное выражение вторично, что чревато ошибками и лишними вычислениями. Кроме того, методически оправданно держать правила преобразования в БД, а не в тексте запроса (если только эти правила имеют прикладное значение). На практике же нахождение таблицы преобразования в БД резко замедлит вычисление.

    Нескалярные функции для анализа данных

    Таковых имеется две категории: "агрегатные", то есть обобщающие, и "аналитические".

    Агрегатные функции иначе называют "стандартными агрегатными" функциями и "агрерирующими" функциями. До версии 8.1.6 они (сокращенным количеством) назывались "статистическими". Они дают скалярный результат, но аргументом им служит столбец значений, чем они конструктивно отличаются от большинства других встроенных функций. Это же сообщает им обобщающий характер.

    Некоторые из них:

    ФункцияОписание
    COUNTЧисло значений в столбце или строк в таблице
    MINНаименьшее значение в столбце
    MAXНаибольшее значение в столбце
    SUMСумма значений в столбце
    AVGСреднее арифметическое значений в столбце
    VARIANCEДисперсия (мера отклонения от математического ожидания)
    STDDEV,
    STDDEV_POP,
    STDDEV_SAM
    
    Стандартное отклонение (квадратный корень от дисперсии) в разных вариациях
    MEDIAN[10-)Медиана значений в столбце

    [10-) с версии 10

    Некоторые другие примеры: CORR, COVAR_POP, COVAR_SAMP, CUME_DIST, PERCENTILE_COUNT и так далее.

    Начиная с версии 10 Oracle позволяет производить более "серьезные" статистические обобщения данных столбца таблицы, но уже не средствами SQL, а программно, с помощью процедур из встроенного пакета DBMS_STAT_FUNCS. Они позволяют определить соответствие указанного значения тому или иному виду статистического распределения, а также обобщать данные столбца всеми способами стандартных агрегатных функций, но вдобавок со значительным количеством дополнительной обобщающей информации.

    Аналитические функции идут дальше агрегатных, не только имея столбцовые аргументы, но и возвращая в виде столбца результат. Они не только позволяют обобщить данные, как агрегатные, но способны делать это без потери детализации.

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

    Конструкции (операторы) 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. Оборотной стороной такого самоограничения окажется загромождение схемы данных и усложнение запросов к БД.

    Вернуться к учебному плану