|
Предлагаю обсудить вопрос для FAQ Oracle
|
|||
---|---|---|---|
#18+
Как вернуть значение столбца: первое, второе, третье и.т.д перед максимальным значением? Допустим есть таблица test_for_find со следующими значениями столбца: serial_key ---------- 1 3 2 7 4 5 6 Необходимо написать запрос, который должен возвратить число 6, как первое перед максимальным (число 7);число 5 как второе перед максимальным и.т.д Условие: без использования PL/SQL и подзапросов. -------------------------------------------------------------------------------------- Для наилучшего понимания создадим таблицу, заполним данными и создадим индекс: create table test_for_find ( serial_key NUMBER(16) ); insert into test_for_find values(1); insert into test_for_find values(3); insert into test_for_find values(2); insert into test_for_find values(7); insert into test_for_find values(4); insert into test_for_find values(5); insert into test_for_find values(6); commit; create unique index i1_test_for_find on test_for_find(serial_key); Как известно для определения максимального значения столбца, используется функция max(). Что-бы определить максимальное значение необходим следующий запрос: select max(serial_key) from test_for_find Выполним: SQLWKS> select max(serial_key) from test_for_find 2> MAX(SERIAL ---------- 7 Выбрана 1 строка. Но нам необходимо не максимальное, а значение перед максимальным: select max(serial_key) from test_for_find where serial_key != (select max(serial_key) from test_for_find); Выполним: SQLWKS> select 2> max(serial_key) 3> from 4> test_for_find 5> where 6> serial_key != (select max(serial_key) from test_for_find) 7> 8> MAX(SERIAL ---------- 6 Выбрана 1 строка. Однако непонятно, как определить второе перед максимальным, тем более что уже было нарушено условие - "без подзапросов"? Очевидно что в условии запроса, должно быть дополнительное условие, которое бы указывало на порядковый номер этого значения. Как известно, для упорядочивания используется фраза "order by". С её помошью можно упорядочить данные по убыванию, а используя функцию ROWNUM - можно ограничить количество строк. Если упорядочить данные по убыванию и указать ROWNUM<4, то запрос должен вернуть три последних значения:7,6,5. Применив же функцию min() - мы определим, что второе перед максимальным - это 5. Запрос же такой: select min(serial_key) from test_for_find where rownum<4 order by serial_key desc; Однако, на практике убеждаемся что, это не так: SQLWKS> select 2> min(serial_key) 3> from 4> test_for_find 5> where 6> rownum<4 7> order by 8> serial_key desc; MIN(SERIAL ---------- 1 Выбрана 1 строка. Почему же не сработала логика наших рассуждений? Всё дело в том, что ROWNUM срабатывает раньше, чем упорядочивание(т.е действие фразы order by). Поэтому определение минимального значения происходит из случайного набор данных: SQLWKS> select 2> serial_key 3> from 4> test_for_find 5> where 6> rownum<4 7> order by 8> serial_key desc; SERIAL_KEY ---------- 3 2 1 Выбрано 3 строк. Отсюда вывод, что-бы использовать ROWNUM, нам необходимо иметь заранее упорядоченный список значений. Для это изменим запрос так: select min(serial_key) from (select serial_key from test_for_find order by serial_key desc) where rownum<4; Выполним: SQLWKS> select 2> min(serial_key) 3> from 4> (select serial_key from test_for_find order by serial_key desc) 5> where 6> rownum<4 7> 8> MIN(SERIAL ---------- 5 Выбрана 1 строка. Теперь запрос работает правильно. Однако, опять же нарушается правило "без подзапроса". Что же делать, где еще можно взять такой список? - конечно из индекса!!! Для того что-бы правильно решить задачу необходимо использовать хинты. А точнее хинт, который позволяет конкретно указать на использование индекса,в котором данные уже упорядочены по убыванию. Для того что-бы задействовать индекс, столбец serial_key обязательно должен присутствовать во фразе "WHERE": select /*+ INDEX_DESC(test_for_find i1_test_for_find) */ min(serial_key) from test_for_find where serial_key is not null and rownum<4; Выполним: SQLWKS> select /*+ INDEX_DESC(test_for_find i1_test_for_find) */ 2> min(serial_key) 3> from 4> test_for_find 5> where 6> serial_key is not null 7> and 8> rownum<4; MIN(SERIAL ---------- 5 Выбрана 1 строка. Третий перед максимальным: SQLWKS> select /*+ INDEX_DESC(test_for_find i1_test_for_find) */ 2> min(serial_key) 3> from 4> test_for_find 5> where 6> serial_key is not null 7> and 8> rownum<5; MIN(SERIAL ---------- 4 Выбрана 1 строка. Запрос работает правильно и выполнено условие задачи. и.т.д. ... |
|||
:
Нравится:
Не нравится:
|
|||
04.11.2002, 17:54 |
|
Предлагаю обсудить вопрос для FAQ Oracle
|
|||
---|---|---|---|
#18+
Ну, это все известные факты. Работая под веб, все эти фишки используются повсеместно. А вот еше прикол. Попробуйте сделать запрос с хинтом не с INDEX_DESC, а просто с INDEX (ну и естественно ограничив ROWNUM-ом). Результат получится забавный: будет взято максимально значение индекса (в данном случае 7), а ROWNUM роли уже играть не будет. Обходится еще смешнее: SELECT /*+ ALL_ROWS INDEX(...) */ MAX(serial_key+0) ... WHERE serial_key>0 В общем оптимизатор должен видеть не голый индекс, а обработку его (в данном случае прибавляем 0). Приоизводительность при этом фактически не падает. Но это конечно все забавно на небольших таблицах, ну записей эдак до 50-100 тысяч. Возможно выборку из "центра" индекса меньше чем за секунду вы и получите. А вот таблица у меня в 500тыс - 1млн записей. Очевидно имеет смысл комбинировать INDEX_DESC и INDEX, смотря к чему мы ближе - к началу или концу. Хотя есть мнение - что взятие выборки из цента таких таблиц равносильно взятию абсолютно случайной выборки,то есть как бы и не требуется вовсе. Но, очевидно, пример для ФАКА очень хороший. Потомучто метод вроде как и известный, но почемуто малоюзаемый. И народ, вроде, приходит в тихий ужас от слова ХИНТ. Удачи! ... |
|||
:
Нравится:
Не нравится:
|
|||
04.11.2002, 20:02 |
|
|
start [/forum/topic.php?fid=52&fpage=2835&tid=1992815]: |
0ms |
get settings: |
11ms |
get forum list: |
16ms |
check forum access: |
4ms |
check topic access: |
4ms |
track hit: |
31ms |
get topic data: |
13ms |
get forum data: |
2ms |
get page messages: |
58ms |
get tp. blocked users: |
2ms |
others: | 265ms |
total: | 406ms |
0 / 0 |