Готовые приложения, как правило, работают с данными уже существующих таблиц. Основная группа предложений 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
Оба названия в заголовке заключены в кавычки в силу своей условности.
"Системные переменные" по сути представляют собой ряд иногда полезных для употребления системных функций без аргументов. Вот некоторые примеры:
| Переменная | Тип | Описание |
|---|---|---|
USER | VARCHAR2 ( 256 ) | Имя пользователя, выдавшего предложение SQL |
UID | NUMBER | Номер пользователя, выдавшего предложение SQL |
SYSDATE | DATE | Текущая дата + время суток с точностью до секунды |
SYSTIMESTAMP(9-) | TIMESTAMP ( 9 ) WITH | Текущая дата + время суток с точностью до 1/100 секунды |
DBTIMEZONE(9-) и SESSIONTIMEZONE(9-) | VARCHAR2 ( 6 ) и VARCHAR2 ( 75 ) | Зоны времени, определенные для БД и в рамках конкретного сеанса |
(9-) начиная с версии 9
Пример использования в выражении:
SELECT object_name, owner FROM all_objects WHERE owner <> USER;
Фигурирующие в документации по Oracle "псевдостолбцы" подобны "системным переменным", но в отличие от них способны давать в запросах на разных строках разные значения, которые вычисляются по мере выполнения определенных фаз обработки запроса и доступны для использования на последующих фазах обработки, образуя как бы дополнительный "столбец".
Примеры:
| Псевдостолбец | Тип | Описание |
|---|---|---|
ROWNUM | NUMBER | Последовательный номер строки в результате SELECT |
LEVEL | NUMBER | Номер уровня выдаваемой строки в предложении SELECT с использованием CONNECT BY |
CONNECT_BY_ISCYCLE[10-) | NUMBER | В предложении SELECT с использованием CONNECT BY: 1, если потомок узла является одновременно его предком, иначе 0 |
CONNECT_BY_ISLEAF[10-) | NUMBER | В предложении SELECT с использованием CONNECT BY:1, если узел не имеет потомков |
ROWID | VARCHAR2 ( 256 ) | Физический адрес строки или хранимого объекта |
XMLDATA[9.2-) | CLOB | Текст документа объекта типа XMLTYPE |
OBJECT_ID[10-) | RAW ( 16 ) | Идентификатор объекта в таблице первичных или виртуальных объектов (представлений) |
OBJECT_VALUE[10-) | тип объекта | Системное имя для столбца в таблице первичных или виртуальных объектов (в том числе типа XMLTYPE) |
ORA_ROWSCN[10-) | NUMBER | Порядковый номер изменения в БД (), соответствующий строке таблицы или же блоку данных с этой строкой |
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 * 25 | 106 |
0.6E1 + 4 * COMM | 106.E0 |
(50 / 10) * 5 | 25 |
1 + '25' | 26 ( |
123d + 123 | 246 в формате BINARY_DOUBLE |
123d + 123f | 246 в формате BINARY_DOUBLE |
6 + 4 * COMM | 106 |
(6 + 4) * 25 | 250 |
NULL * 30 | NULL |
1f - BINARY_FLOAT_INFINITY | BINARY_FLOAT_INFINITY |
1d + BINARY_DOUBLE_NAN | BINARY_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 )) |
ENAME | SMITH (типа VARCHAR2 ( 10 )) |
USER | SCOTT (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' + 3 | 31 августа 1990 года, 00:00:00 |
3 + DATE '1990-08-28' | 31 августа 1990 года, 00:00:00 |
DATE '1988-12-04' - 5 | 29 ноября 1988 года, 00:00:00 |
SYSDATE + 1 / 24 | [сейчас](*) плюс час |
SYSTIMESTAMP(9-) - INTERVAL '3' HOUR(9-) | [сейчас](*) минус три часа (типа TIMESTAMP(9) WITH |
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)( | 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] |
(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 | Стандартное отклонение (квадратный корень от дисперсии) в разных вариациях |
| Медиана значений в столбце |
[10-) с версии 10
Некоторые другие примеры: CORR, COVAR_POP, COVAR_SAMP, CUME_DIST, PERCENTILE_COUNT и так далее.
Начиная с версии 10 Oracle позволяет производить более "серьезные" статистические обобщения данных столбца таблицы, но уже не средствами SQL, а программно, с помощью процедур из встроенного пакета DBMS_STAT_FUNCS. Они позволяют определить соответствие указанного значения тому или иному виду статистического распределения, а также обобщать данные столбца всеми способами стандартных агрегатных функций, но вдобавок со значительным количеством дополнительной обобщающей информации.
Аналитические функции идут дальше агрегатных, не только имея столбцовые аргументы, но и возвращая в виде столбца результат. Они не только позволяют обобщить данные, как агрегатные, но способны делать это без потери детализации.
Агрегатные и аналитические функции отличаются от скалярных по формальному употреблению в тексте запроса и требуют в силу этого отдельного рассмотрения, которое последует в соответствующих разделах описания предложения SELECT ниже.
В качестве альтернативы функции 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. Оборотной стороной такого самоограничения окажется загромождение схемы данных и усложнение запросов к БД.
Готовые приложения, как правило, работают с данными уже существующих таблиц. Основная группа предложений 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
Оба названия в заголовке заключены в кавычки в силу своей условности.
"Системные переменные" по сути представляют собой ряд иногда полезных для употребления системных функций без аргументов. Вот некоторые примеры:
| Переменная | Тип | Описание |
|---|---|---|
USER | VARCHAR2 ( 256 ) | Имя пользователя, выдавшего предложение SQL |
UID | NUMBER | Номер пользователя, выдавшего предложение SQL |
SYSDATE | DATE | Текущая дата + время суток с точностью до секунды |
SYSTIMESTAMP(9-) | TIMESTAMP ( 9 ) WITH | Текущая дата + время суток с точностью до 1/100 секунды |
DBTIMEZONE(9-) и SESSIONTIMEZONE(9-) | VARCHAR2 ( 6 ) и VARCHAR2 ( 75 ) | Зоны времени, определенные для БД и в рамках конкретного сеанса |
(9-) начиная с версии 9
Пример использования в выражении:
SELECT object_name, owner FROM all_objects WHERE owner <> USER;
Фигурирующие в документации по Oracle "псевдостолбцы" подобны "системным переменным", но в отличие от них способны давать в запросах на разных строках разные значения, которые вычисляются по мере выполнения определенных фаз обработки запроса и доступны для использования на последующих фазах обработки, образуя как бы дополнительный "столбец".
Примеры:
| Псевдостолбец | Тип | Описание |
|---|---|---|
ROWNUM | NUMBER | Последовательный номер строки в результате SELECT |
LEVEL | NUMBER | Номер уровня выдаваемой строки в предложении SELECT с использованием CONNECT BY |
CONNECT_BY_ISCYCLE[10-) | NUMBER | В предложении SELECT с использованием CONNECT BY: 1, если потомок узла является одновременно его предком, иначе 0 |
CONNECT_BY_ISLEAF[10-) | NUMBER | В предложении SELECT с использованием CONNECT BY:1, если узел не имеет потомков |
ROWID | VARCHAR2 ( 256 ) | Физический адрес строки или хранимого объекта |
XMLDATA[9.2-) | CLOB | Текст документа объекта типа XMLTYPE |
OBJECT_ID[10-) | RAW ( 16 ) | Идентификатор объекта в таблице первичных или виртуальных объектов (представлений) |
OBJECT_VALUE[10-) | тип объекта | Системное имя для столбца в таблице первичных или виртуальных объектов (в том числе типа XMLTYPE) |
ORA_ROWSCN[10-) | NUMBER | Порядковый номер изменения в БД (), соответствующий строке таблицы или же блоку данных с этой строкой |
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 * 25 | 106 |
0.6E1 + 4 * COMM | 106.E0 |
(50 / 10) * 5 | 25 |
1 + '25' | 26 ( |
123d + 123 | 246 в формате BINARY_DOUBLE |
123d + 123f | 246 в формате BINARY_DOUBLE |
6 + 4 * COMM | 106 |
(6 + 4) * 25 | 250 |
NULL * 30 | NULL |
1f - BINARY_FLOAT_INFINITY | BINARY_FLOAT_INFINITY |
1d + BINARY_DOUBLE_NAN | BINARY_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 )) |
ENAME | SMITH (типа VARCHAR2 ( 10 )) |
USER | SCOTT (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' + 3 | 31 августа 1990 года, 00:00:00 |
3 + DATE '1990-08-28' | 31 августа 1990 года, 00:00:00 |
DATE '1988-12-04' - 5 | 29 ноября 1988 года, 00:00:00 |
SYSDATE + 1 / 24 | [сейчас](*) плюс час |
SYSTIMESTAMP(9-) - INTERVAL '3' HOUR(9-) | [сейчас](*) минус три часа (типа TIMESTAMP(9) WITH |
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)( | 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] |
(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 | Стандартное отклонение (квадратный корень от дисперсии) в разных вариациях |
| Медиана значений в столбце |
[10-) с версии 10
Некоторые другие примеры: CORR, COVAR_POP, COVAR_SAMP, CUME_DIST, PERCENTILE_COUNT и так далее.
Начиная с версии 10 Oracle позволяет производить более "серьезные" статистические обобщения данных столбца таблицы, но уже не средствами SQL, а программно, с помощью процедур из встроенного пакета DBMS_STAT_FUNCS. Они позволяют определить соответствие указанного значения тому или иному виду статистического распределения, а также обобщать данные столбца всеми способами стандартных агрегатных функций, но вдобавок со значительным количеством дополнительной обобщающей информации.
Аналитические функции идут дальше агрегатных, не только имея столбцовые аргументы, но и возвращая в виде столбца результат. Они не только позволяют обобщить данные, как агрегатные, но способны делать это без потери детализации.
Агрегатные и аналитические функции отличаются от скалярных по формальному употреблению в тексте запроса и требуют в силу этого отдельного рассмотрения, которое последует в соответствующих разделах описания предложения SELECT ниже.
В качестве альтернативы функции 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. Оборотной стороной такого самоограничения окажется загромождение схемы данных и усложнение запросов к БД.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.