Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лаврентев Освоение СQЛ 2009

.pdf
Скачиваний:
63
Добавлен:
16.08.2013
Размер:
2.47 Mб
Скачать

41

FROM sales_order WHERE TO_CHAR(order_date, 'YY')=89;

Задержка оплаты

---------------

6

1

3

12

16

15

13

4

16

1

11

3

8

13

7

3

16

5

15

13

3

21 строк выбрано.

Q3_10. Определим заказы, перешедшие на другой год

DEMO@ORCL>SELECT order_id, customer_id FROM sales_order WHERE TO_CHAR(ship_date,'yyyy') - TO_CHAR(order_date,'YYYY')=1;

ORDER_ID CUSTOMER_ID

----------

-----------

549

226

559

222

II.3.3. Некоторые функции преобразования

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

Q3_11. Работа с ROWID, ROWNUM

Извлечение столбца ROWID:

DEMO@ORCL>SELECT rowid, last_name FROM employee WHERE department_id=10;

Попробуем вывести только одну строку с номером (rownum – номер выводимой запросом строки) «1»

DEMO@ORCL>SELECT rowid, last_name FROM employee WHERE rownum=1;

ROWID LAST_NAME

------------------ ---------------

AAANVAAAEAAAAXnAAA KING

42

Для rownum=2:

DEMO@ORCL>SELECT rowid, last_name FROM employee WHERE rownum=2;

строки не выбраны

Так для любого rownum, не равного единице. Это ограничение можно обойти так:

DEMO@ORCL>SELECT rn, ri, last_name

FROM (SELECT rownum rn, rowid ri, last_name FROM employee) WHERE rn=2;

RN

RI

LAST_NAME

----------

------------------

---------------

2

AAANVAAAEAAAAXnAAB

DOYLE

Преобразуем rowid в вид restricted, в котором для каждой строки явно указывается номер файла, номер блока в файле ОС и номер строки в этом блоке

DEMO@ORCL>SELECT rn, rnr, last_name, object_id

FROM (SELECT rownum rn, last_name, DBMS_ROWID.ROWID_OBJECT(rowid) AS object_id, DBMS_ROWID.ROWID_TO_RESTRICTED(rowid,0) rnr

FROM employee) WHERE rn<5;

В представленном выводе object_id – номер таблицы employee, в столбце rnr число «0004» идентификатор файла, число «000005E7» номер блока, оставшаяся часть rnr – номер строки в блоке. Убедимся в этом, выполнив запрос к таблице user_objects словаря

Oracle:

DEMO@ORCL>SELECT object_name FROM user_objects WHERE object_id=54592;

OBJECT_NAME

--------------------

EMPLOYEE

Для получения номера файла воспользуемся функцией ROWID_TO_ABSOLUTE_FNO в составе программного модуля DBMS_ROWID:

DEMO@ORCL>SELECT DBMS_ROWID.ROWID_TO_ABSOLUTE_FNO(rowid,'demo','employee') FROM employee WHERE rownum=1;

DBMS_ROWID.ROWID_TO_ABSOLUTE_FNO(rowid,'demo','employee')

-------------------------------------------------------

4

Если мы пользователем SYS/SYS AS SYSDBA выполним команду: sys@10g> SELECT file_name

FROM dba_data_files WHERE file_id=4;

то получим:

FILE_NAME

------------------------------------------------------------

D:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\USERS01.DBF

43

Извлечем с помощью функции ROWID_BLOCK_NUMBER в составе программного модуля DBMS_ROWID номер блока, в котором хранятся строки таблицы employee: demo@10g>SELECT DISTINCT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) AS block_number FROM employee;

BLOCK_NUMBER

------------

1511

5E716(см. запрос со столбцом rnr выше) = 5*642 + 14*161 + 7*160 = 1280 + 224 + 7 = 1511.

Если мы пользователем SYS/SYS AS SYSDBA (у пользователя DEMO нет прав на работу с таблицей col$) выполним:

SYS@ORCL> SELECT ROUND(SUM(in_block_count_string)/count(*), 0) AS srednee_chislo_strok_v_bloke FROM

(SELECT COUNT(DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)) AS in_block_count_string FROM col$

GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid));

SREDNEE_CHISLO_STROK_V_BLOKE

----------------------------

74

то получим среднее число записей таблицы col$ в одном блоке. Таблица col$ взята здесь для примера таблицы с относительно большим количеством строк; для хранения в ней ~60 тысяч строк требуется ~700 блоков. Выполнять такой подсчет для таблиц пользователя DEMO нет смысла из-за малого количества строк в таблицах этого пользователя. Все таблицы этого пользователя, кроме item, хранят строки в одном блоке

(item – в трех).

Q3_12. Функция NUMTODSINTERVAL преобразования числа в интервал (дней, часов, минут, секунд).

Перед примером ее использования отформатируем столбцы:

DEMO@ORCL>COL число_дней_в_дни FORMAT A20

DEMO@ORCL>COL число_часов_в_дни FORMAT A20

DEMO@ORCL>COL число_минут_в_дни FORMAT A20

DEMO@ORCL>COL число_секунд_в_дни FORMAT A20

DEMO@ORCL>SELECT employee_id, (NUMTODSINTERVAL(employee_id,'DAY')) AS "ЧИСЛО_дней_в_дни", (NUMTODSINTERVAL(employee_id,'HOUR')) AS "число_часов_в_дни", (NUMTODSINTERVAL(employee_id,'MINUTE')) AS "Число_минут_в_дни", (NUMTODSINTERVAL(employee_id,'SECOND')) AS "Число_секунд_в_дни"

FROM employee

WHERE department_id=10;

44

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

DEMO@ORCL>SELECT employee_id, hire_date, NUMTODSINTERVAL(TO_CHAR(hire_date, 'DD'), 'DAY')

FROM employee

WHERE department_id=10;

Q3_14. Можно вычитать и складывать значения, возвращаемые функцией

NUMTODSINTERVAL.

DEMO@ORCL>SELECT NUMTODSINTERVAL(TO_CHAR(ship_date, 'DD'),'day') - NUMTODSINTERVAL(TO_CHAR(order_date, 'dd'),'DAY') day_diff FROM sales_order WHERE TO_CHAR(order_date, 'YY')=89;

45

Q3_15. Функция NUMTOYMINTERVAL преобразует число(лет, месяцев) в интервал

DEMO@ORCL>SELECT employee_id, NUMTOYMINTERVAL(employee_id,'YEAR') FROM employee

WHERE department_id=10;

DEMO@ORCL>SELECT employee_id, NUMTOYMINTERVAL(employee_id,'MONTH') FROM employee

WHERE department_id=10;

Q3_16. Функция SCN_TO_TIMESTAMP(ORA_ROWSCN) преобразует псевдостолбец

ORA_ROWSCN (содержащий номер последней транзакции с таблицей) во временную метку – время выполнения транзакции

DEMO@ORCL>UPDATE employee SET salary=3000

WHERE employee_id=7369;

DEMO@ORCL>commit;

DEMO@ORCL>SELECT SCN_TO_TIMESTAMP(ORA_ROWSCN) FROM employee WHERE employee_id = 7369;

SCN_TO_TIMESTAMP(ORA_ROWSCN);

----------------------------------

05.08.08 19:56:02,000000000

Через несколько минут:

DEMO@ORCL>UPDATE employee SET salary=800

WHERE employee_id=7369;

DEMO@ORCL>commit;

DEMO@ORCL>SELECT SCN_TO_TIMESTAMP(ORA_ROWSCN) FROM employee WHERE employee_id = 7369;

SCN_TO_TIMESTAMP(ORA_ROWSCN)

----------------------------------

05.08.08 19:59:17,000000000

Q3_17. Использование функции TO_CHAR(date).

Вывести дату и точное время оформления заказов на покупки, сделанные в ноябре

1989 г.

DEMO@ORCL>SELECT TO_CHAR(ship_date,'DD-MON-YYYY HH24:MI:SS') FROM sales_order

WHERE TO_CHAR(ship_date,'YYYY')=1989 AND TO_CHAR(ship_date,'MM')=11;

46

Так как в параметрах функции вывод месяцев показан, как «MON», месяц выводится символами «НОЯ» вне зависимости от формата даты в сеансе:

DEMO@ORCL>SELECT SYSDATE FROM dual;

SYSDATE

--------

06.08.08

Q3_18. Использование функции to_date(строка символов). Изменить дату оформления некоторых заказов.

DEMO@ORCL>UPDATE sales_order

SET ship_date=TO_DATE('30.11.89')

WHERE TO_CHAR(ship_date,'yyyy')=1989

AND TO_CHAR(ship_date,'MM')=11;

5 строк обновлено.

Измененение даты представлено символьной строкой '30.11.89', формат которой совпадает с форматом сеанса. Если такого совпадения нет, в функции to_date надо указывать дополнительные параметры.

DEMO@ORCL>UPDATE sales_order

SET ship_date=TO_DATE('30-nov-89','DD-MON-YY' , 'NLS_DATE_LANGUAGE=AMERICAN')

WHERE TO_CHAR(ship_date,'YYYY')=1989 AND TO_CHAR(ship_date,'MM')=11;

5 строк обновлено.

!!! Теперь обязательно выполним команду отмены транзакции demo@10g>ROLLBACK;

Откат завершен

II.3.4. Функции DECODE, CASE, DUMP, ORA_HASH, VSIZE

Q3_19. Формирование отчета, в котором содержимое столбца разворачивается в ряд столбцов отчета. Отчет должен содержать по строкам список сотрудников (номера и имена служащих), по столбцам наименования отделов, а на пересечении знак ‗+‘, если служащий работает в соответствующем отделе (только для отделов 10, 20, 30)

DEMO@ORCL>SELECT employee_id, last_name,

 

(SELECT DECODE(department_id,10,'

+

',NULL) FROM department

WHERE employee.department_id=department.department_id) acc_new_york,

(SELECT DECODE(department_id,20,'

+

',NULL) FROM department

WHERE employee.department_id=department.department_id) res_dallas,

(SELECT DECODE(department_id,30,'

+

',NULL) FROM department

WHERE employee.department_id=department.department_id) sal_chicago FROM employee WHERE department_id in(10, 20, 30);

47

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

Функция decode в данном запросе обрабатывает значения столбца department_id (первым параметром функции decode является название столбца, для которого эта функция используется). Если в этом столбце извлекаемой строки для части запроса с алиас «acc_new_york» содержится значение «10» (второй параметр функции decode в данной части запроса имеет значение «10»), функция decode вернет символ «+» (третий параметр функции decode имеет значение «+»), в противном случае – NULL (последний параметр функции decode имеет значение «NULL»). Если в этом столбце извлекаемой строки содержится значение «20» (для извлекаемой части запроса с алиас «res_dallas»), функция decode вернет символ «+», в противном случае – NULL. Если, наконец, в этом столбце извлекаемой строки содержится значение «30» (для извлекаемой части запроса с алиас «sal_chicago»), функция decode вернет символ «+», в противном случае – NULL.

Q3_20. А теперь усложним вывод отчета. На пересечении строк (сотрудников) и столбцов (названий отдела) надо выставить зарплату сотрудника в этом отделе (только для отделов 10, 20, 30)

DEMO@ORCL>SELECT employee_id, last_name,

(SELECT decode(department_id,10,salary,NULL) FROM department WHERE employee.department_id=department.department_id) acc_new_york, (SELECT decode(department_id,20,salary,NULL) FROM department WHERE employee.department_id=department.department_id) res_dallas, (SELECT decode(department_id,30,salary,NULL) FROM department WHERE employee.department_id=department.department_id) sal_chicago FROM employee

WHERE department_id in(10,20,30);

48

Q3_21. Повторим запросы Q3_19 и Q3_20 в другом варианте с использованием CASE выражения:

DEMO@ORCL>SELECT employee_id, last_name,

(CASE WHEN department_id=10 THEN '

+

' ELSE NULL END) acc_new_york,

(CASE WHEN department_id=20 THEN '

+

' ELSE NULL END) res_dallas,

(CASE WHEN department_id=30 THEN '

+

' ELSE NULL END) sal_chicago

FROM employee

 

 

WHERE department_id IN(10, 20, 30);

 

 

DEMO@ORCL>SELECT employee_id, last_name,

(CASE WHEN department_id=10 THEN salary ELSE NULL END) acc_new_york, (CASE WHEN department_id=20 THEN salary ELSE NULL END) res_dallas, (CASE WHEN department_id=30 THEN salary ELSE NULL END) sal_chicago FROM employee

WHERE department_id IN(10, 20, 30);

Q3_22. С помощью функции DUMP определим код типа данных, длину записанной в ячейке столбца информации, набор символов (CharacterSet), коды последовательности символов (указанной длины), начиная с указанной стартовой позиции.

DEMO@ORCL>SELECT DUMP(last_name, 8) FROM employee

WHERE department_id=10;

Если вместо «8» поставим «10» или «16» получим коды символов в соответствующей системе (десятичной или шестнадцатиричной).

DEMO@ORCL>SELECT DUMP(last_name, 10, 2, 2) FROM employee WHERE department_id=10;

DEMO@ORCL>SELECT DUMP(last_name,1016) FROM employee

WHERE department_id=10;

Добавление 1000 к цифре кодировки (16 в последнем запросе) позволяет вывести набор символов (CharacterSet).

DEMO@ORCL>COL DUMP(salary,1016) format a25 DEMO@ORCL>SELECT salary,DUMP(salary,1016) FROM employee WHERE department_id=10;

49

Q3_23. Функция VSIZE выводит число байт во внутреннем представлении Oracle DEMO@ORCL>SELECT last_name, VSIZE (last_name) "BYTES"

FROM employee

WHERE department_id = 10;

Q3_24. Функция ORA_HASH вычисляет хэш значение для значений столбца. В нижеприводимом варианте хэш значение разыгрывается в диапазоне чисел 0 – 4294967295.

DEMO@ORCL>SELECT ORA_HASH(salary) FROM employee WHERE department_id=10;

ORA_HASH(SALARY)

----------------

3983772665

929675335

4269363183

Можно изменить диапазон (установим, например, 100) и задать начальное значение последовательности случайных чисел (установим, например, 5), которая используется при формировании хэш значения

DEMO@ORCL>SELECT ORA_HASH(salary,100,5) FROM employee

WHERE department_id=10;

ORA_HASH(SALARY,100,5)

----------------------

97

79

9

II.3.5. Агрегатные и аналитические функции

Выше мы уже ознакомились с некоторыми агрегатными функциями (max(), min(), avg()). Общее число агрегатных и аналитических функций в Oracle10g более 50. Аналитические функции позволяют производить ранжирование (ranking), перенос сводных результатов (moving aggregates), сравнение данных за различные периоды (period comparisons), соотношение итогов (ratio of total), получение совокупных сводных результатов (cumulative aggregates) и другие действия.

Q3_25. Выполним запрос, устанавливающий ранг сотрудников компании, обслуживающих покупателей (salesperson_id в таблице customer) по суммам продаж «их» покупателям – за все годы продаж. Максимальной сумме продаж будет соответствовать ранг «1», следующей за максимальной – ранг «2» и т.д. Если сумма продаж одинаковая, то и ранг должен быть одинаковым.

50

DEMO@ORCL>SELECT c.salesperson_id, NVL(ROUND(SUM(so.total),0),0) tot_sales, RANK() OVER (ORDER BY NVL(SUM(so.total),0) DESC) sales_rank

FROM customer c, sales_order so WHERE c.customer_id=so.customer_id(+) GROUP BY salesperson_id;

Если бы в компании был сотрудник, принесший ей убыток (tot_sales<0), ранг этого работника был бы установлен «12», так как у двух сотрудников перед тем был установлен одинаковый ранг «10». При использовании функции DENSE_RANK:

DEMO@ORCL>SELECT c.salesperson_id, NVL(ROUND(SUM(so.total), 0), 0) tot_sales, DENSE_RANK() OVER (ORDER BY NVL(SUM(so.total), 0) DESC) sales_rank

FROM customer c, sales_order so WHERE c.customer_id=so.customer_id(+) group by salesperson_id;

Результат получаем такой же, как и предыдущий. Но если бы в компании был сотрудник, принесший ей убыток, ранг этого работника был бы установлен «11». В этом только отличие DENSE_RANK() от RANK().

Q3_26. Усложним запрос Q3_25. Установим ранг сотрудников внутри отделов, в которых они работают (опять по сумме продаж за все годы):

demo@10g>SELECT e.department_id, c.salesperson_id, NVL(ROUND(SUM(so.total), 0), 0) AS tot_sales, RANK() OVER (PARTITION BY department_id ORDER BY NVL(SUM(so.total), 0) DESC) sales_rank

FROM employee e,customer c, sales_order so WHERE c.salesperson_id=e.employee_id AND c.customer_id=so.customer_id(+) GROUP BY (department_id,salesperson_id);