今日も見に来てくださって、ありがとうございます。石川さんです。
今日は、データベースオラクルへ接続、そのスキーマのテーブル情報を取得してTreeviewへ表示するプログラムにチャレンジしました。いや~、けっこう時間がかかりましたねぇ。Treeviewを思ったように操作するのが難しかったなぁ。
Oracleのバージョンは18c Express Edition、PythonはAnaconda3です。
出来上がりイメージ
今回のソースを実行して、「load table」ボタンをクリックすると、こんな感じのウィンドウになります。左側のテーブル一覧を選択すると右側に項目の一覧が表示されます。
ソースコード
import cx_Oracle as db import tkinter as tk import tkinter.ttk as ttk LOGIN_USER = "ユーザー名をここに入力" LOGIN_PASSWORD = "パスワードをここに入力" LOGIN_HOST = "localhost:1521/xepdb1" class Application(tk.Tk): def __init__(self): super().__init__() self.title("Table viewer sample for ORACLE XE 18c") self.conn = db.connect(LOGIN_USER, LOGIN_PASSWORD, LOGIN_HOST) self.cur = self.conn.cursor() self.style = ttk.Style(self) self.style.configure("Treeview", rowheight=30) columns = ("#1", "#2") self.table_list = ttk.Treeview(self, show="headings", columns=columns, selectmode="browse") self.table_list.heading("#1", text="TABLE_NAME") self.table_list.column("#1", width=125) self.table_list.heading("#2", text="COMMENTS") self.table_list.column("#2", width=300) self.table_list.grid(row=0, column=0, rowspan=2, sticky=tk.NSEW) self.scroll_bar = ttk.Scrollbar(self, command=self.table_list.yview) self.table_list.configure(yscroll=self.scroll_bar.set) self.scroll_bar.grid(row=0, column=1, rowspan=2, sticky=tk.NS) self.table_list.bind("<<TreeviewSelect>>", self.show_table) columns = ("#1","#2","#3") self.table_info = ttk.Treeview(self, show="headings", columns=columns) self.table_info.heading("#1", text="COLUMN_NAME") self.table_info.heading("#2", text="COMMENTS") self.table_info.heading("#3", text="DATA_TYPE") self.table_info.grid(row=0, column=2, sticky=tk.NSEW) self.load_button = ttk.Button(self, text="load table", command=self.load_table) self.load_button.grid(row=1, column=2) self.grid_rowconfigure(0, weight=1) def load_table(self): self.table_list.delete(*self.table_list.get_children()) self.table_info.delete(*self.table_info.get_children()) sql = ("SELECT TABLE_NAME, COMMENTS" " FROM USER_TAB_COMMENTS" " WHERE TABLE_TYPE = 'TABLE'") results = self.cur.execute(sql) for row in results: self.table_list.insert("", tk.END, values=row) self.table_list.selection_set(self.table_list.get_children()[0]) def show_table(self, event): sel = self.table_list.selection() table_name = self.table_list.item(sel[0])["values"][0] sql = ("SELECT T1.COLUMN_NAME, T1.COMMENTS," " CASE WHEN T2.DATA_TYPE IN ('TIMESTAMP(6)','DATE') THEN T2.DATA_TYPE" " WHEN T2.DATA_TYPE IN ('CHAR','VARCHAR2') THEN T2.DATA_TYPE ||'('||T2.DATA_LENGTH||')'" " WHEN T2.DATA_TYPE = 'NUMBER' THEN T2.DATA_TYPE || " " CASE WHEN T2.DATA_PRECISION IS NULL AND T2.DATA_SCALE IS NULL THEN ''" " WHEN T2.DATA_PRECISION IS NOT NULL AND NVL(T2.DATA_SCALE,0) = 0 THEN '('||T2.DATA_PRECISION||')'" " WHEN T2.DATA_PRECISION IS NOT NULL AND T2.DATA_SCALE IS NOT NULL THEN '('||T2.DATA_PRECISION||'.'||T2.DATA_SCALE||')'" " WHEN T2.DATA_PRECISION IS NULL THEN '(.'||T2.DATA_SCALE||')'" " END" " END" " FROM USER_COL_COMMENTS T1, USER_TAB_COLUMNS T2" " WHERE T1.TABLE_NAME = T2.TABLE_NAME" " AND T1.COLUMN_NAME = T2.COLUMN_NAME" " AND T1.TABLE_NAME = :table_name") results = self.cur.execute(sql, table_name=table_name) self.table_info.delete(*self.table_info.get_children()) for row in results: self.table_info.insert("", tk.END, values=row) if __name__ == "__main__": application = Application() application.mainloop()
解説
Oracleへの接続にはおなじみのcx_Oracleを利用しました。cx_Oracleのセットアップについては、こちらを参考にしてください。
5~7行目の接続情報はご自分の環境にあわせて変更してください。Oracle18c Express Editionをデフォルトインストールしたぼくの環境では、このLOGIN_HOSTで接続できました。もしかしたらlocalhostをローカルPCのIPアドレスに変更する必要があるかも知れません。
14、15行目の初期化処理で、Oracleへの接続とカーソル取得しています。21行目でTreeviewを作成しています。showオプションは”tree”と”heading”が指定できますが、組み合わせで以下のように変わってきます。
- “tree” カラム#0を表示します。
- “headings” ヘッダ行を表示します。
- “tree headings” カラム#0とヘッダ行の両方を表示します。(デフォルト値)
- “” カラム#0もヘッダ行も表示しない。
selectmodeは、”extended”、”browse”、”none”が選択可能で、以下のような意味があります。
- “extended” 複数アイテムが選択可能。(デフォルト値)
- “browse” 単一のアイテムが選択可能。
- “none” 選択状態は変わらない。
ちなみに、このTreeviewの行間、ぼくの環境Windows10では低く設定されていて文字が切れてしまっていたので、17、18行目のtkk.Styleを使って”Treeview”のrowheightを30にセットしています。
スクロールバーは27、28行目で作成してセットしています。イディオムだと思って覚えてしまいましょう。30行目でバインドしているのは、仮想イベントの<<TreeviewSelect>>です。セレクションが変更されたときに発生します。ここでは、show_tableをバインドしています。これで選択されたテーブルの情報を隣のTreeviewに表示しています。
42行目のself.grid_rowconfigure(0, weight=1)
ですが、ウィンドウのサイズを変更したときに、行がどれくらいの割合で広がるか、というのを設定しています。指定しない場合のweightは0にセットされています。これによりgridで指定された0行目がウィンドウのサイズ変更に伴って変更されます。ちなみに、ウィジェットにgridを適用するときにstickyを指定しない場合は大きさは変わらないので注意が必要です。
45、46行目では、Treeviewの項目を削除しています。すべての項目を取得するのに、self.table_list.get_children()
を利用しています。このメソッドはすべての項目をタプルで戻します。self.table_list.delete()
の引数は、削除したい項目をすべて渡す必要があるため、先頭に「*」アスタリスクを付けて、タプルのアンパックをしています。タプルのままでは動作しませんので、注意が必要です。
47~49行目はSQL文を定義しています。複数文字列をインデントを無視した形で改行するのに丸括弧「()」を使っています。丸括弧の中はインデント無視できるのですよね。ちなみにここで指定しているテーブルのUSER_TAB_COMMENTSは、オラクルのデータディクショナリで、テーブルに設定されているコメントを参照することができるビューになります。
SQL文の実行は50行目です。51行目で結果セットを順番に取り出すforループを記述しています。データを取得して、ループして一行ずつ処理する、このパターンもよく使いますので、覚えておくと便利です。
Treeviewへのデータの追加はinsert()メソッドです。valuesパラメータを使うことで複数項目を一気にセットすることができます。
あと、53行目は、テーブルを取得したときに、1行目を選択させて、テーブル情報を右側に表示するために追加しました。1行目を選択するのに結構悩みましたよ~。selection_set()
メソッドへ渡すパラメータは項目名なのですよね。0とか-1とかを指定してもエラーになります。そこで、ここでは、self.table_list.get_children()[0]
を使って最初の項目を取得しています。この0を-1に変更すると最後の項目、任意の行の場合も数値を変更するだけで対応可能ですね。
56行目、sel = self.table_list.selection()
は、選択中のアイテム名を取得しています。取得結果は複数の場合もあるのでタプルで戻ってきます。ここでは一項目しか取得できない設定ですので、sel[0]
と指定すれば、選択中の項目が利用可能になります。57行目のtable_name = self.table_list.item(sel[0])["values"][0]
で、Treeviewにセットされた値を取得することができます。
まとめ
いや~、時間かかったなぁ。あ、二つ目のSQLの説明を忘れてました。ま、そんなに難しくないし、見ればわかるか。Treeviewの扱いに慣れるのには、ちょっと時間がかかりそうですね。またサンプル作ってみますね。