ОЛЕГ ПОПОВ
Python глазами DBA
Почему, собственно, Python
Администраторы БД очень часто нуждаются в инструментах для автоматизации разной рутинной работы, например: загрузка, преобразования данных в разного рода форматы или сбор и анализ различной статистики. Для меня в свое время такой палочкой-выручалочкой стал Perl.
Через некоторое время я набрел на Python. Что я могу сказать после года знакомства: практичный и полезный инструмент.
Достоинства языка Python:
- Многоплатформенность и масштабируемость: работает на большинстве известных программно-аппаратных платформ от PDA до CRAY и IBM 390.
- Гармоничная архитектура языка:
- Простой и удобный для понимания логики программ синтаксис.
- Встроенные структуры данных: списки, кортежи, словари.
- Огромное количество библиотек:
- XML-парсеры;GUI (Win32, GTK/GNOME, KDE/QT, TK, FOX, WXWindows);
- Сетевые протоколы;
- Разработка веб-приложений.
- Переносимость кода между различными платформами.
- Мощные интерфейсы к конкретным ОС (особенно к Linux/UNIX и win32).
- Поддержка разных стилей программирования: процедурного, объектного, функционального.
- Встроенная поддержка Unicode и множества национальных кодировок (включая 5 русских).
- Возможность писать самодокументированные программы и автоматическая генерация документации на модули.
С чем в действительности сталкивается программист при использовании Python – это простой и ясный синтаксис. Я ловлю себя часто на мысли, что просмотр чужого кода зачастую не требует значительного напряжения. И здесь помогает то, что отступы являются частью синтаксиса и определяют вложенность операторов.
Библиотеки зачастую имеют очень лаконичную документацию, но этого достаточно, так как API очень выразительны и внятны.
Сам процесс кодирования и тестирования достаточно комфортен. Здесь помогает качественная диагностика ошибок. Оттестированные модули очень легко расширять и складывать из них приложения.
Для работы с RDBMS Oracle существует несколько различных модулей для Python, использующих разные механизмы:
- ODBC (Win32, Linux)
- DCOM (Win32)
- PERL_DBI (Win32, Linux, Solaris, HP-UX, AIX)
- NATIVE API (Win32, Linux, Solaris, HP-UX, AIX)
Более подробную информацию вы можете найти на www.python.org.
Выполняем простой запрос
Я предпочитаю использовать модуль cx_Oracle, который был проверен мною в ОС NT и Linux (автор Anthony Tuininga утверждает, что он работает в Solaris и, похоже, на всех UNIX-подобных ОС должен также работать). В основном этот модуль следует спецификации Python Database API 2.0 и поддерживает работу с RDBMS Oracle версий 8.1-9.xx.
Для быстрого погружения в предмет я приведу фрагменты кода, демонстрирующего основные приемы использования модуля.
try:
import cx_Oracle
except ImportError,info:
print "Import Error:",info
sys.exit()
if cx_Oracle.version<"3.0":
print "Very old version of cx_Oracle :",cx_Oracle.version
sys.exit()
В этом фрагменте демонстрируется безопасная загрузка модуля cx_Oracle и проверка версии. (Надеюсь, элегантность синтаксиса все-таки заметили).
Далее создадим экземпляр класса connect – именно этот объект и обеспечивает взаимодействие с сервером Oracle.
try:
my_connection=cx_Oracle.connect("system/manager@test_db")
except cx_Oracle.DatabaseError,info:
print "Logon Error:",info
exit(0)
Теперь создаем курсор и выполняем запрос:
my_cursor=my_connection.cursor()
try:
my_cursor.execute("""
SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SUM(BLOCKS)SIZE_BLOCKS, COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS
GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME
""")
except cx_Oracle.DatabaseError,info:
print "SQL Error:",info
exit(0)
: (name, type_code, display_size, internal_size, precision, scale, null_ok).
Далее следует форматированный вывод на stdout (почти как printf в языке C).
print
print "Database:",my_connection.tnsentry
print
print "Used space by owner, object type, tablespace "
print "-------------------------------------------------"
title_mask=("%-16s","%-16s","%-16s","%-8s","%-8s")
i=0
i=1+i
print ""
print "-------------------------------------------------"
row_mask="%-16s %-16s %-16s %8.0f %8.0f "
for recordset in my_cursor.fetchall():
print row_mask%recordset
В результате мы увидим что-то вроде:
Database: testdb
Used space by owner, object type, tablespace
-------------------------------------------------------------
OWNER SEGMENT_TYPE TABLESPACE_NAME SIZE_BLOCKS SIZE_EXTENTS
-------------------------------------------------------------
ADU2 INDEX USERS 784 25
ADU2 TABLE USERS 512 24
ADUGKS INDEX DEVELOP_DATA 984 123
ADUGKS TABLE DEVELOP_DATA 664 83
ADUGPA INDEX USERS 784 25
ADUGPA TABLE USERS 496 23
AGNKS_SG INDEX USERS 352 22
AGNKS_SG TABLE USERS 240 15
ATU INDEX USERS 3968 244
ATU TABLE DEVELOP_DATA 8 1
ATU TABLE USERS 2688 160
ATU1 INDEX DEVELOP_DATA 1600 200
ATU1 INDEX USERS 608 38
ATU1 TABLE DEVELOP_DATA 1032 129
ATU1 TABLE USERS 544 34
BUX INDEX DEVELOP_DATA 64 8
BUX TABLE DEVELOP_DATA 1736 217
DISP INDEX USERS 400 25
DISP TABLE USERS 528 33
EPE INDEX USERS 80 5
EPE TABLE USERS 48 3
EXZ INDEX USERS 1088 61
EXZ TABLE DEVELOP_DATA 8 1
EXZ TABLE USERS 832 41
|
Запросы с параметрами
Согласно спецификации Python Database API 2.0 для выполнения запросов с параметрами каждый модуль должен реализовывать переменную paramstyle, которая определяет, каким образом будут передаваться параметры запросов. Текущая версия cx_Oracle(3.0) поддерживает режим "named", то есть в модуле установлена переменная cx_Oracle.paramstyle="named" и можно создавать конструкции в запросах в виде:
select * from all_users where USERNAME LIKE :S
При этом связывание параметров запроса со значениями можно выполнить двумя способами.
Именованый параметeр метода exec:
cursor2.execute("select * from all_users where USERNAME LIKE :S ",S="S%")
Словарь {":переменная":значение,...}:
cursor2.execute("select * from all_users where USERNAME LIKE :S ",{":S":"S%"})
Анонимные блоки PL/SQL
В некоторых случаях очень удобно использовать нестандартные средства сервера: для Oracle таким нестандартным, но очень удобным механизмом является возможность исполнения анонимных блоков PL/SQL. Модуль сx_Oracle реализует этот механизм, который, естественно, не описан в спецификации Python Database API 2.0.
Чтобы связать переменные блока PL/SQL c переменными языка Python, в модуле сx_Oracle реализован класс var. Экземпляр можно создать следующим образом:
var=my_cursor.var(cx_Oracle.DATETIME)
Конструктор my_cursor.var(...) в качестве параметра требует указать тип создаваемой переменной. Варианты:
- BINARY
- DATETIME
- FIXEDCHAR
- LONGBINARY
- LONGSTRING
- NUMBER
- ROWID
- STRING
var=my_cursor.var(cx_Oracle.DATETIME)
try:
my_cursor.execute("""begin
SELECT SYSDATE INTO :p_Value from dual;
end;""",p_Value = var)
except cx_Oracle.DatabaseError,info:
print "SQL Error:",info
exit(0)
Очевидно, что теперь var содержит текущее время сервера. Доступ к значениям переменной выполняется с помощью метода var.getvalue():
CDATE=var.getvalue()
print "Date: %02u/%02u/%4u"%(CDATE.day,CDATE.month,CDATE.year)
print "Time: %02u:%02u:%02u"%(CDATE.hour,CDATE.minute,CDATE.second)
Этот пример демонстрирует также и форматирование даты и времени для экземпляра var. В результате напечатается нечто вроде:
Date: 12/05/2003
Time: 16:42:54
|
В связи с тем, что работа с типами времени и даты внутри сервера Oracle реализована особенным образом (независимо от ОС), модуль сx_Oracle реализует следующие функции для для преобразования значений дат и времени:
- Date( year, month, day)
- DateFromTicks( ticks)
- Time( hour, minute, second)
- TimeFromTicks( ticks)
- Timestamp( year, month, day, hour, minute, second)
- TimestampFromTicks( ticks)
var=my_cursor.var(cx_Oracle.DATETIME)
var.setvalue(0,cx_Oracle.Date( 2002, 02,12))
CDATE=var.getvalue()
print"Date:%02u/%02u/%4u"%(CDATE.day,CDATE.month,CDATE.year)
print"Time:%02u:%02u:%02u"%(CDATE.hour,CDATE.minute,CDATE.second)
Результат будет следующий:
Date: 12/02/2002
Time: 00:00:00
|
Ссылки:
- http://computronix.com/utilities.shtml
- http://www.python.org/topics/database/DatabaseAPI-2.0.html
Листинг 1
Выполняем простой запрос:
""
cx_Oracle demo
simple query
"""
__AUTHOR__='POPOV O.'
__COPYRIGHT__='POPOV O. 2002 Samara, Russia'
from sys import exit
try:
import cx_Oracle
except ImportError,info:
print "Import Error:",info
sys.exit()
if cx_Oracle.version<'3.0':
print "Very old version of cx_Oracle :",cx_Oracle.version
sys.exit()
try:
my_connection=cx_Oracle.connect('system/gasdba@sqlmt')
except cx_Oracle.DatabaseError,info:
print "Logon Error:",info
exit(0)
my_cursor=my_connection.cursor()
try:
my_cursor.execute("""
SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SUM(BLOCKS)SIZE_BLOCKS,COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS
GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME
""")
except cx_Oracle.DatabaseError,info:
print "SQL Error:",info
exit(0)
print
print 'Database:',my_connection.tnsentry
print
print "Used space by owner, object type, tablespace "
print "--------------------------------------------------"
title_mask=('%-16s','%-16s','%-16s','%-8s','%-8s')
i=0
for column_description in my_cursor.description:
print title_mask[i]%column_description[0],
i=1+i
print ''
print "--------------------------------------------------"
row_mask='%-16s %-16s %-16s %8.0f %8.0f '
for recordset in my_cursor.fetchall():
print row_mask%recordset
for column_description in my_cursor.description:
print column_description
Листинг 2
Запрос с параметрами:
""
cx_Oracle demo
query with parameters
"""
__AUTHOR__='POPOV O.'
__COPYRIGHT__='POPOV O. 2002 Samara, Russia'
from sys import exit
try:
import cx_Oracle
except ImportError,info:
print "Import Error:",info
sys.exit()
if cx_Oracle.version<'3.0':
print "Very old version of cx_Oracle :",cx_Oracle.version
sys.exit()
try:
my_connection=cx_Oracle.connect('system/manager@test_db')
except cx_Oracle.DatabaseError,info:
print "Logon Error:",info
exit(0)
my_cursor=my_connection.cursor()
try:
my_cursor.execute("""
SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME, SUM(BLOCKS)SIZE_BLOCKS,COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS
WHERE OWNER LIKE :S
GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME
""",S='SYS%')
except cx_Oracle.DatabaseError,info:
print "SQL Error:",info
exit(0)
print
print 'Database:',my_connection.tnsentry
print
print "Used space by owner, object type, tablespace "
print "---------------------------------------------------"
title_mask=('%-16s','%-16s','%-16s','%-8s','%-8s')
i=0
for column_description in my_cursor.description:
print title_mask[i]%column_description[0],
i=1+i
print ''
print "----------------------------------------------------"
row_mask='%-16s %-16s %-16s %8.0f %8.0f '
for recordset in my_cursor.fetchall():
print row_mask%recordset