🐍

Pandasを使ったデータベースとの接続

2023/10/20に公開
12

Pandasを使ったデータベースとの接続

このページでは python でDBを扱う方法を紹介します。
今回はsqlAlchemyを使ってpandasのdataframeにDBの値を格納する方法を紹介します。

使用するライブラリ

pandas

python上でExcelのような表形式のデータを簡単に高速で扱うことが可能なライブラリです。

DBから吸い出したデータを加工するのに便利です。

公式ドキュメント:http://pandas.pydata.org/pandas-docs/stable/

sqlAlchemy

pandasでは直接DBと接続して読み込むことができる関数に

pandas.read_sql()

がありますが、編集時点(2023年10月)時点では警告文が出るようになっています(UserWarning)。

内容を読むと「pandas.read_sql()によるsql接続は今後非推奨になります。以降はsqlAlchemyを使って接続しろ」と書かれていたので、sqlAlchemyを使って接続するようにします。

公式ドキュメント:https://www.sqlalchemy.org

1.pandasのインストール

pip install pandas

2.sqlAlchemyのインストール

pip install sqlalchemy

3.sqlAlchemyを使ってDBと接続する

Accessとの接続

pip install sqlalchemy-access

Accessを使う場合↑をインストールしておかないと下記のエラーが出ます。

🚫 sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:access.pyodbc

import pandas as pd
from sqlalchemy import create_engine

#Access設定
Access_path = r"C:XXXX" #接続するAccessのPATH

#接続に使用するドライバーの設定 ※頻発エラー
driver = r'DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};' 
dbq = "DBQ={};".format(Access_path)
conn_str = (
        driver+dbq
)

engine_Access = create_engine("access+pyodbc:///?odbc_connect=" + conn_str)
sql = "SELECT * FROM table名"

df = pd.read_sql(sql,engine_Access) #dataframe dfにtableデータを格納

※1:Accessに接続するためのドライバーが必要になりますが、専用のページからダウンロードする必要があります。(Accessファイルが入っていても未インストールの場合があります!)

※1現在入っているドライバを確認するには

pip install pyodbc
import pyodbc
 
for driver in sorted(pyodbc.drivers()):
    print(driver)

Microsoft Access Driver (*.mdb, *.accdb)が入っていない場合は下記リンクより取得する必要があります。

Microsoft Access データベースエンジン2016 最頻布可能コンポーネント

インストールされているMicrosoft Officeに合ったプロセッサ(x86かx64)を選択し、インストールします。

インストールが終わったら、pyodbcで使えるドライバを使ってもう一度確認。おそらく下記が含まれているはず。

`Microsoft Access Driver (*.mdb, *.accdb)`←これが必要!
`SQL Server`

このようになっていれば、ドライバが入っているので準備OK

(インストールできない場合はOfficeのバージョンとpythonのバージョンがあっていない可能性などが考えられる(Office 32bit、python 64bitとか))

主キーを設定する

Accessの場合忘れがちになります

テーブルをデザインビューで開く

→主キーに設定する列を選択

→タブにある主キーのボタンを押すと列の横に鍵マークができてOK

参考:主キーがない場合のエラーコード

raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Mapper Mapper[Table(テーブル名)] could not assemble any primary key columns for mapped table 'テーブル名'

SQLserverとの接続

Windows認証の場合

import pandas as pd
import urllib
from sqlalchemy import create_engine,text

start = time.time()
driver='{SQL Server}'
server = "" #サーバ名
database = '' #データベース名
trusted_connection='yes'   #Windows認証の場合

odbc_connect = urllib.parse.quote_plus(
    'DRIVER='+ driver +';SERVER='+server+';DATABASE='+database+';trusted_connection=yes')

engine_SQLserver = create_engine('mssql+pyodbc:///?odbc_connect=' + odbc_connect)

sql = "SELECT * FROM table名"
  
df = pd.read_sql(sql,engine_SQLserver)

ユーザ名・パスワード接続の場合

import pandas as pd
import urllib
from sqlalchemy import create_engine,text

start = time.time()
driver='{SQL Server}'
server = "
username = '' #ログインユーザ名
password = '' #パスワード
database = '' #データベース名

odbc_connect = urllib.parse.quote_plus(
    'DRIVER='+ driver +';SERVER='+server+';DATABASE='+database+'UID='
		+username+';PWD='+password+';DATABASE='+database)

engine_SQLserver = create_engine('mssql+pyodbc:///?odbc_connect=' + odbc_connect)

sql = "SELECT * FROM table名"
  
df = pd.read_sql(sql,engine_SQLserver)

Discussion

名誉監督名誉監督

Accessとの接続について参考にさせていただいてます
一つ質問ですがclose処理の方法を教えてください

ゆうまゆうま

結論から申しますと、

#create_engineまでは省略してます
engine_Access = create_engine("access+pyodbc:///?odbc_connect=" + conn_str)
sql = "SELECT * FROM テーブル名"
# DBとの接続を切断
engine_Access.dispose()

engine_Access.dispose()を加えると接続が切れます。

☆Accessとpythonで作ったシステムを作ったときの所感
・pythonの処理が一度の実行で終了するような場合は、.dispose()をしなくても処理終了時に接続が切れます。そのため、dispose()を入れる処理を入れませんでした。

また、サーバーなどを起動して待機式の場合、Accessファイルと常時接続したような状態になりますが(.laccdbファイルができている状態)、別のユーザが直接Accessを編集可能な状態なので、自分はdispose()処理は入れていませんでした。

閲覧ありがとうございます。誰かの参考になっていることが分かってとても嬉しく思います。
記事投稿が3日坊主になっていましたが、また投稿を再開してみようと思います。

名誉監督名誉監督

ゆうまさん
コメントありがとうございます
無事にCloseできました
去年末からWorningが出てて何とかしたいと悩んでましたが
なんの知識もない見様見真似のど素人の私にはどうすることもできなくて
探しまくってたところ唯一、親切丁寧に記載されてたサイトがここでした。
本当に助かりました。

あと一つ長い間解決できない事があります
わかりましたら教えてください

Pythen + openpyxlでExcelの制御をしてますが
Bookをcloseしてもopenpyxlが掴んだままのようでPythonで作ったプログラムを
閉じるまでは外部Excelから該当Bookを保存することはできません
仕方がないのでopenpyxlからxlwingsに変えて今はしのいでますが
アクションごとにExcelのOpen/Closeを繰り返すため時間がかかって困ってます。
openpyxlのプロセスを強制的に終了できればうまくいくのかな。
と思い探してますが方法が見つかりません。
何か方法を知っていたらご教示お願いします。
もし分野外でしたら、無理に労力/時間を費やしていただくのは申し訳ないので無視してください。

ゆうまゆうま

名誉監督さん
openpyxlの件ですが、文面から考えるに
①常時起動しているプログラムがある
②起動しているプログラム上でExcelを開く処理がある
③book.closeの処理を入れても、Excelファイルが開いた状態になっている。
(windowsの場合、タスクマネージャーでは起動しているようなイメージ?)
という状態なのかなと推測しました。
私の時は原因は「正しくcloseできていなかった」ことが原因でした。
その時対策は下記のようにしました
☆bookを開く・閉じるを一つの関数で完結させた
・別の関数で行うとbookの指定の仕方がややこしくなり、closeしたいbookが正しく指定できておらず開いた状態になっていた
・開く処理の後に閉じる処理が実行されていない場合があり、開いた状態になっていた

今度自分で作ってみたコードを記事にしてみます。見比べてみると参考になるかもしれません。
(すぐにはできませんが、、、)

Accessの件は本当に苦労しました。AccessをDBに設定しようという人がかなり少ないんでしょうね。
参考になって何よりです。今後とも頑張りましょう

名誉監督名誉監督

ゆうまさん
言葉足らずで申し訳ありません
PythonでBookを書込みするのは一瞬ですが1日のうち数十回はボタン押下で繰り返しますが
Pythonプログラムはほぼ一日起動したままです。
ボタンを押下される度にBook.open ⇒ データ書き込み ⇒ Book.save ⇒ Book.closeを繰り返します
この処理は時間にすると1秒程度で同じ関数内で処理してます
待機状態にExcelから同Bookを編集しようとすると上書き時に「使用中・・うんぬん」の警告が出て上書き保存ができません。
その状態でPythonプログラムを閉じれば上書き可能です。
Openpyxlが掴んで離さないと思った根拠は下記のとおりです
・Pythonプログラム起動後OpenpyxlでBook.openする前はExcelから編集可能
・Book.Close後でも編集不可
・Pythonプログラムを閉じれば編集可能
上記の経緯からPythonプログラムを起動したままBook.closeのタイミングで
openpyxlのプロセス強制終了ができれば解決するのかな?
と素人考えで思いました。
的外れでしたらすみません。

名誉監督名誉監督

私が書いたソースを提示します
何かミスがありましたらご指摘いただければ幸いです

import tkinter as tk
import openpyxl

BG="#FFFFE5" #薄い黄色

def Excel_set():
FileName='\\192.168.1.4\Volume_A1\基本情報.xlsx'
book = openpyxl.load_workbook(FileName, read_only=True,keep_vba=False)
WS=book.worksheets[0]
data=WS.cell(1,1).value
print(data)
book.close()
return

def menu():
#-------------------------- メイン画面 -------------------------------
root = tk.Tk()
root.geometry('360x150')
root.title('Excel Test V1.00')
canvas = tk.Canvas(root, bg = "#FFFFE5")
canvas.pack(fill = tk.BOTH, expand = True)
#---------------------------------------------------------------------
#------------------------ 実行ボタン ------------------------------
btn = tk.Button(root,text='実行',font=('',13),command=Excel_set)
btn.place(x=50, y=30,width=120,height=36,)
#---------------------------------------------------------------------
root.mainloop()

def main():
menu()
return

if name == 'main':
main()

ゆうまゆうま

名誉監督さん
おはようございます。コードを私のほうで環境準備して試してみましたが、特に問題なく動作していました。
なので、気になる点があるとすれば下記2か所かなぁと思います

FileName='\\192.168.1.4\Volume_A1\基本情報.xlsx'
#試した環境
FileName='test.xlsx'

私が試したときはこの部分を実行ファイルと同じ部分にExcelファイルを置いていました。
そこで、まず試してもらいたいのが「別サーバーに読みにいかなかった場合は問題なく動作するかどうか」です。そこで問題がなければ、別サーバにExcelファイルをloadしに行くことで問題が起こっている
→対策:読み込みが完了する時間までbook.closeを実行しないようにする(time.sleep()を入れてみるなど)
といった原因と対策ができると思います。

もう一点は

if name == 'main':
main()
if __name__ == '__main__':
    main()

nameっていうのを見たことがなかったのと、nameのままだとエラーが出るので、修正しました。
おそらく実行できているのでこの部分ではないと思いますが、

名誉監督名誉監督

ゆうまさん
御多忙にもかかわらず迅速な対応ありがとうございます
まず、nameの部分ですが貼り付ける際に__が消えてしまってたようです
ソースはif name == 'main':になってました
申し訳ありませんでした

対象Bookをソースと同じ場所に置いてやってみましたが結果は同じで編集できませんでした。
手順は下記になります
① Pythonプログラムを起動
② 実行ボタンをクリック
③ 普通にExcelから対象ブックを修正 ⇒ 読み取り専用でになってしまい上書き保存不可
④ Pythonプログラムを終了
⑤ 普通にExcelから対象ブックを修正 ⇒ 上書き保存成功

③の部分が悩みの箇所になっています
①の待機状態なら正常に上書きできます

ゆうまゆうま

多分わかりました!

#試した環境
book = openpyxl.load_workbook(FileName, read_only=False,keep_vba=False)

read_only をFalseにしてみて下さい!
おそらくread_onlyで開いたときに、読み込みを早くするために「キャッシュ」と呼ばれる内部データができています。(表示はされていませんが「Excel名(読み取り専用)」みたいなやつです)
これがpythonのコード実行中にずっといるので、python実行中にexcelを開くと読み取り専用のデータと元のデータがどっちも存在しているみたいになって、上書きできませんみたいになっている気がします。

今回の処理の場合、実行時間も1秒以下とのことなので、読み取り専用で開かなくても問題は起きないかと思います。

名誉監督名誉監督

ゆうまさん
コメントありがとうございます
read_only=Falseにしましたが結果は同じでした。
今回、提示したソースは事象再現用に作ったもので
実運用のソースはread_only=Falseで開いて一行追加してSaveしてCloseする。
といった流れです。
read_only=Trueで1セル表示するだけでも再現するのでこのソースを提示しました。
読み書き云々より
book = openpyxl.load_workbook(FileName, read_only=False,keep_vba=False)
を実行しちゃうとダメみたいです
openpyxlってのは一度Book.openしちゃうとbook.closeしても
Pythonを終了しないとBookをつかみっぱなしになる仕様なんですかねぇ
openpyxl.load_workbookがあるなら
openpyxl.unload_workbookがあってopenpyxlを解放してくれてもいいと思うんですが・・・
その方法がみつかりません

ゆうまゆうま

名誉監督さん
うーん、こっちで作った環境ではtkinker実行しながら保存できるようになったのですが、、、
ちょっとよく分からないですね。すみません。

今度自分で作ってみたコードを記事にしてみます。見比べてみると参考になるかもしれません。

プログラムってこういう部分が難しいですね(´;ω;`)ほどほどに頑張りましょう。

名誉監督名誉監督

ゆうまさん
貴重なGWにもかかわらずお付き合いありがとうごさいました。
詳しい方と知り合えて心強いです。
今後ともよろしくお願いします。