Oracle

индекс
77,32

Некоторые примеры нестандартных возможностей синтаксиса Oracle SQL из песочницы

Введение


Примерно полтора года назад я сдал экзамены на OCP Advanced PL/SQL Developer, далее специфика работы несколько изменилась, и после стандартного производственного использования Oracle я занимался разработкой архитектуры двухуровневой клиент-серверной системы на основе Oracle для нужд компьютерной лингвистики. Далее был этап развития системы и решения наукоемких задач на ее основе, пришлось заниматься использованием иерархических запросов в решении нестандартных задач и другими специфическими вещами. Результатом углубления в специфику стало некоторое «проседание» базы, а значит, наступило время снова просмотреть материалы, использовавшиеся для подготовки к экзаменам.

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


Несколько слов о специфике оператора order by


Как вы думаете, будет ли ошибка в результате выполнения данного запроса?
--Запрос №1
select * from dual
order by 1+2||dummy

Ответ: ошибки не будет, т.к. спецификация order by определена следующим образом:
ORDER BY {col(s)|expression|numeric_pos} [ASC|DESC] [NULLS FIRST|LAST];
Где expression — A sort key expression, such as numeric, string, and datetime expressions. Expression can also be a row value expression such as a scalar subquery or case expression.
Подробнее, например, здесь download.oracle.com/javadb/10.6.2.1/ref/rrefsqlj13658.html

Таким образом, мы отсортировали таблицу dual по выражению «3X», что бессмысленно, однако в качестве выражения для сортировки можно, например, использовать выражение с функцией substr. Важна сама возможность использования выражений.

Как мы знаем, таблица dual содержит один столбец, вернет ли ошибку такой запрос:
--Запрос №2
select * from dual
order by 2

А такой?
--Запрос №3
select * from dual
order by 2+0

В первом случае мы имеем дело с позиционным указанием колонок – т.е. ссылкой на колонку №2, которой не существует, соответственно, получим ошибку.

Во втором случае мы имеем дело с выражением, т.е. это уже не номер колонки, а сортировка по числу 2, аналогичная сортировке по строке «3X» в запросе №1. Поэтому ошибки не будет.

Посмотрите на следующий пример. Какой порядок строк мы получим в результате выполнения этого запроса? Будет ли ошибка?
--Запрос №4
select 3, dummy from dual
union all
select 2, dummy from dual
order by "3"

Union all не сортирует строки объединяемых множеств (в отличии от union), т.е. без order by мы получим строки в указанном в запросе порядке (union all гарантирует сохранение исходного порядка строк). Теперь настала очередь order by, главный вопрос, что такое «3» в этом случае? Поскольку у нас использованы двойные кавычки ", а не одинарные ', то «3» – это алиас колонки. Как известно, операции с множествами требуют использования сходных типов данных, а имена колонок берутся из первого запроса, поскольку мы явно не указали имя первой колонки, то, по умолчанию, она получила имя выражения, т.е. «3». Работа с такими алиасами показана, например, в запросе №5 (главное не забывать про аппер-кейс).

Сортировка по умолчанию – всегда asc, т.е. результаты запроса №4 сортируются по первой колонке по возрастанию. Результат: строка «2, Х», потом «3, Х».
--Запрос №5
select substr(dummy,1,1) from dual
order by "SUBSTR(DUMMY,1,1)" 

Повторим эксперимент из запроса №3 на множествах. Каким будет результат запроса?
--Запрос №6
select 3, dummy from dual
union all
select 2, dummy from dual
order by 2+0

Может показаться, что результат запроса будет следующим: строка «3, Х» потом «2, Х». Предпосылки для этого следующие: данный порядок гарантирует union all, а выражение 2+0 не повлияет на сортировку записей (что показано в запросе №7). Так бы оно и было, если бы не одно «но»: в операциях с множествами order by можно использовать только в конце составного запроса с именами или номерами столбцов первого запроса, выражения не допускаются:
For compound queries containing set operators UNION, INTERSECT, MINUS, or UNION ALL, the ORDER BY clause must specify positions or aliases rather than explicit expressions. Also, the ORDER BY clause can appear only in the last component query. The ORDER BY clause orders all rows returned by the entire compound query. download.oracle.com/docs/cd/B19306_01/server.102/b14200/queries005.htm#i2053998

Поэтому запрос №6 вернет ошибку.

--Запрос №7
select * from
        (select 3, dummy from dual
         union all
         select 2, dummy from dual)
order by 2+0


Использование not in


Наверняка, большинство людей, прошедших различные курсы, помнят, что следует избегать оператора not in, а сходную функциональность можно получить, используя операторы in либо exists. Причина такого негативного отношения к not in кроется в специфике его работы с null-значениями.

Определите результат выполнения следующего запроса:
--Запрос №1
select * from dual
where 1 not in (select 2 from dual union select null from dual) 

Для начала рассмотрим такой запрос:
--Запрос №2
select * from dual
where 2 in (select 2 from dual union select null from dual) 

Ничего необычного в запросе №2 нет: подзапрос возвращает множество из двух строк со значениями «2» и «null», условие where принимает значение true, весь запрос №2 возвращает 1 строку – стандартное поведение.

Теперь рассмотрим разницу в поведении in и not in, здесь нам понадобятся знания о null-арифметике:
1) Любые арифметические операции с null возвращают null
2) Булевые операторы с null работают следующим образом:
  • null AND false = false
  • false AND null = false
  • null AND true = null
  • true AND null = null
  • null OR true = true
  • true OR null = true
  • null OR false = null
  • false OR null = null

Пока все логично. Теперь рассмотрим, как работает IN в запросе №2:
2 IN (2,null) => (2=2) OR (2=null) => true OR null => true
Рассмотрим работу NOT IN в запросе №1:
1 NOT IN (2, null) => (1!=2) AND (1!=null) => true AND null => null

Таким образом, условие where в запросе №1 преобразуется в null, если хотя бы один операнд null, поэтому весь запрос №1 приблизительно эквивалентен следующему запросу:
--Запрос №3
select * from dual
where null=null

Очевидно, что запрос №3 не вернет ни одной строки, соответственно, запрос №1 также не вернет ни одной строки.

Неявное преобразование типов


Тема явного и неявного преобразования типов очень обширна, поэтому, не пытаясь охватить ее в целом, я хотел бы рассмотреть лишь один пример. Пускай сегодня 10.09.11 10:00:00 и Оракл сконфигурирован так, что формат DD.MM.RR распознается по умолчанию, какой из запросов вернет одну строку?
--Запрос №1
select * from dual where sysdate>'10.09.11';
--Запрос №2
select * from dual where sysdate||''='10.09.11'; 

Ответ – оба. Почему так и как это, вообще, возможно? Ответ лежит в механизме неявного преобразования типов.

Рассмотрим запрос №1: в where мы сравниваем дату со строкой, в этом случае Оракл пытается преобразовать строку в дату, если формат строки соответствует одному из форматов даты по умолчанию (формат даты по умолчанию для сессии можно посмотреть в параметре NLS_DATE_FORMAT, выполнив запрос
select * from nls_session_parameters). Если формат строки не отвечает формату даты по умолчанию, то получим ошибку. В нашем случае форматы соответствуют и строка '10.09.11' преобразуется в дату 10.09.11 00:00:00, поскольку sysdate = 10.09.11 10:00:00, то запрос №1 вернет 1 строку.

Рассмотрим запрос №2: в where происходит конкатенация даты со строкой, результатом такой операции будет строка, причем при неявном преобразовании даты в строку происходит урезание данных до формата DD.MM.RR, т.е. мы сравниваем две строки '10.09.11' и '10.09.11'. В результате условие where выполняется, и запрос возвращает одну строку.

Заключение


Приведенный обзор не претендует на полноту освещения какой-либо функциональности Oracle, создан «for fun» и призван продемонстрировать несколько случаев нестандартного использования возможностей синтаксиса sql.
+26
13 сентября 2011, 19:35
54

комментарии (16)

+6
abyrvalg #
Из «не совсем» стандартного мне удобная нравится фишка с оператором IN:
select * from tab where (col1, col2) IN ( (1,2), (2,3), (1,3) )

+2
Nike01 #
Тоже часто использую такой формат условий для множеств состоящих из пар элементов.
Обычно либо в условиях:
select case when (1,2) in ((1,2),(2,3)) then 1 else 2 end as col1 from dual 
Либо, что более полезно, для обработки данных из вложенных подзапросов:
select * from dual
where (dummy, dummy) in (select dummy col1, dummy col2 from dual)
0
aspect #
Зачем в order by clause проводить какие-либо вычисления, почему бы не вынести их в select где им самое место? Это как связывать таблицы через where а не через join — результат один, а человекочитаемость разная.
0
valergrad #
Как зачем? Чтобы избавиться от дополнительного уровня вложенности.
0
aspect #
nicht verstehen, приведи пример
0
trak #
У меня есть такой пример, только он длинный очень.
Действительно, иногда надо, ох, очень специфический пример.
Ну например, есть геообъект, у него в характеристиках прописано, что он начинается на 100 километре, а закачивается на 200, а при этом внутри его точки имеют линейную дистанцию на встречу.

Вот в таких случаях мы используем ORDER BY line_coord*SIGN(KM_START-KM_END)
:)

+1
Nike01 #
Бывают разные специфические требования к порядку строк и выдаче данных. Предположим, что мы составляем отчет по клиентам, в отчете должно быть только ФИО и дата начала обслуживания, а отсортировать их нужно по «толщине кошелька», чтоб руководство сразу видело, кто им больше люб. «Толщина кошелька» вычисляется по нескольким полям с помощью формулы.
Тогда вы либо вводите «толщину кошелька» как еще одно поле в select и потом из полученных данных делаете еще одну выборку уже без этого поля (т.е. как писали выше, получаете дополнительный уровень вложенности), либо сразу используете формулу в order by.

На самом деле всяко бывает, например, может сложиться ситуация, когда ФИО целиком лежит в одной колонке и его нужно вывести, а сортировка должна быть по имени. Тогда вы в order by используете substr, не забивая результирующую выборку ненужным полем с именем.
0
aspect #
Теперь понял про какой дополнительный уровень вложенности, просто редко приходилось прятать поле из выборки
+1
trak #
Union all не сортирует строки объединяемых множеств (в отличии от union)
Блеать!!!
Садитесь два!
Ни union, ни UNION ALL не сортируют!

Назад в школу, читать теорию множеств!

Proof: Возьмите многопроцессорную тачку с включенным PQ и большими таблицами в запросах, и посмотри, где будет твоя сортировка!
+2
trak #
Особенно вредно писать про UNION и сортировку для новичков, ибо потом эта уверенность сильно бьет по голове.
+2
zhekappp #
+1!
еще group by не сортирует — это тоже частое заблуждение.
+2
valergrad #
ну и union all не сохраняет порядок объединяемых множеств, на это тоже нельзя закладываться.
0
trak #
О чем и речь. Множества не упорядочены по своей природе, а отношения наследуют это свойство.
+1
Nike01 #
Да, действительно, фраза про union написана так, что может быть прочитана двояко. Я имел ввиду механику работы union, когда для построения объединения множеств они вначале сортируются, и не подразумевал, что union может вернуть отсортированный результат и заменить таким образом order by.

Про «union all гарантирует сохранение исходного порядка строк» я имел ввиду, что при объединении множества А с множеством В в результирующей выборке будут идти сначала строки из множества А, потом из множества В. Сам порядок выдачи строк начиная то ли с 10-ки, то ли даже раньше, Оракл не гарантирует (из обычного select), что уж говорить про union all.
Поскольку в примере, к которому относится эта фраза, каждое множество было представлено одной строкой, то union all гарантировал сохранение этого порядка строк :) В общем, тоже весьма двояко написал, впредь постараюсь точнее излагать.
–2
bugman #
бить тряпкой за такой код кроме секции демонстрация возможностей
–1
Cresh #
+1 от всего сердца — жаль что уже вечер — такое нужно утром читать =)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.