先日Accessで作成したJリーグの順位表データベースをお試しでMariaDBへ移行してみました。
基本的にはちまちま、MariaDBでテーブルの新規作成をした後、Access側でレコードセットを取得し、MariaDBへINSERTする、という単純なものです。
順位の計数のスクリプトがフォーム上にあったので、せっかくなので(?)クラスモジュールに変更してみました。
MariaDBをダウンロード
MariaDBというデータベースエンジンがございますのでそちらをダウンロードし、使用する準備をする必要があります。詳しくは以前の記事にちょっと似た内容がありますのでどうぞ。
MariaDBをAccessから操作する、という方針です。AccessとMariaDBを橋渡ししてくれるODBCドライバと、Windwosに標準装備されているODBCデータソースアドミニストレーターというアプリを使用します。
MariaDBでデータベースを作成する
MariaDBのインストールと接続準備ができましたら、MariaDBのコマンドプロンプトで移植先のデータベースを作成します。
CREATE DATABASE J_LeagueData
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
どうやら、作業用のユーザーを作成するのがセオリーみたいです。後ほどユーザー名とパスワードは使用するのでメモしておきます。
CREATE USER 'access_user'@'%' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON J_LeagueData.* TO 'access_user'@'%';
FLUSH PRIVILEGES;
MariaDBにテーブルを新規作成する
データベースができました、テーブルを作成して受け入れ態勢を整えておきます。
AccessとMariaDBではややデータ型の表記などが異なるようで注意が必要です。
今回のデータベースには「Matches」テーブル「Points」テーブル「Teams」テーブルがありますので、作成します。
なるほど、こういうことが起きるのを想定してAccessでも英語風のテーブル名にしておくのは便利ですね。
CREATE TABLE Matches (
match_id INT AUTO_INCREMENT PRIMARY KEY,
season INT,
matchday INT,
match_date DATE,
home_team_id INT,
away_team_id INT,
home_score INT,
away_score INT
);
CREATE TABLE Points (
matchday INT,
team_id INT,
season INT,
total_points INT,
goals_for INT,
goals_against INT,
goal_diff INT,
rank INT,
PRIMARY KEY (matchday, team_id, season)
);
CREATE TABLE Teams (
team_id INT PRIMARY KEY,
team_name VARCHAR(100)
);
3つのテーブルが作成できました。
Access側からMariaDBへ移行する
いよいよAccess側から、MariaDBへ移行していきます。
移植用のVBAを標準モジュールに用意して使用しました。
基本的には各テーブルの全件を取得して、MariaDBへINSERTするSQL文を成形して使用する、という流れです。
今まではDAO使用していたのですが、MariaDBとの接続にはADODBオブジェクトを使用する必要があるようです。
…そう言われるとなんかインターネットで見たことある…。
ですので、VBEの「ツール」「参照設定」から、「Microsoft ActiveX Data Objects 6.1 Library」にチェックを入れて使用しました。
Sub InsertMatches()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
'① Access側のローカルMatchesACから全件取得
Set rs = New ADODB.Recordset
rs.Open "SELECT season, matchday, match_date, home_team_id, away_team_id, home_score, away_score FROM MatchesAC", _
CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
'② MariaDBへ接続
Set conn = New ADODB.Connection
conn.Open "DSN=JLeagueDB;" ' DSNはMariaDB用のもの
'③ MariaDBへ1件ずつINSERT
Do Until rs.EOF
strSQL = "INSERT INTO Matches (season, matchday, match_date, home_team_id, away_team_id, home_score, away_score) VALUES (" & _
rs!season & ", " & rs!matchday & ", '" & Format(rs!match_date, "yyyy-mm-dd") & "', " & _
rs!home_team_id & ", " & rs!away_team_id & ", " & rs!home_score & ", " & rs!away_score & ")"
conn.Execute strSQL
rs.MoveNext
Loop
'④ 後処理
rs.Close: Set rs = Nothing
conn.Close: Set conn = Nothing
MsgBox "試合データをMariaDBに登録しました!", vbInformation
End Sub
Sub InsertPoints()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
'① Access側からデータ取得
Set rs = New ADODB.Recordset
rs.Open "SELECT matchday, team_id, season, total_points, goals_for, goals_against, goal_diff, rank FROM PointsAC", _
CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
'② MariaDBへ接続
Set conn = New ADODB.Connection
conn.Open "DSN=JLeagueDB;"
'③ MariaDBへINSERT
Do Until rs.EOF
strSQL = "INSERT INTO Points (matchday, team_id, season, total_points, goals_for, goals_against, goal_diff, rank) VALUES (" & _
rs!matchday & ", " & rs!team_id & ", " & rs!season & ", " & rs!total_points & ", " & _
rs!goals_for & ", " & rs!goals_against & ", " & rs!goal_diff & ", " & rs!rank & ")"
conn.Execute strSQL
rs.MoveNext
Loop
'④ 終了処理
rs.Close: Set rs = Nothing
conn.Close: Set conn = Nothing
MsgBox "ポイントデータをMariaDBに登録しました!", vbInformation
End Sub
Sub InsertTeams()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
'① Accessローカルからデータ取得
Set rs = New ADODB.Recordset
rs.Open "SELECT team_id, team_name FROM TeamsAC", _
CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
'② MariaDBへ接続
Set conn = New ADODB.Connection
conn.Open "DSN=JLeagueDB;" ' ← DSNはMariaDB側を指定
'③ INSERT処理(1件ずつ)
Do Until rs.EOF
strSQL = "INSERT INTO Teams (team_id, team_name) VALUES (" & _
rs!team_id & ", '" & Replace(rs!team_name, "'", "''") & "')"
conn.Execute strSQL
rs.MoveNext
Loop
'④ 終了処理
rs.Close: Set rs = Nothing
conn.Close: Set conn = Nothing
MsgBox "クラブデータをMariaDBに登録しました!", vbInformation
End Sub
Sub TestConnect()
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
conn.Open "DSN=JLeagueDB;"
MsgBox "接続成功!"
conn.Close
End Su
基本的には同じことをテーブル3つ分行っています。念のためAccess側のテーブルの名前を変更してから行いました。
最後は別の記事にも記載しましたが、「外部データ」タブからINSERTしたテーブルへの接続をすれば、元通りになります。
フォーム更新スクリプトを修正
最後はテーブルの更新用にVBAのスクリプトを使用していましたので、そちらもMariaDBを更新するように修正します。
やはりDAOを使用していましたので、ADODBからMariaDBを使用する形へ修正しました。
こういう時のために、やはり技術的な要素に依存する部分は別のクラスモジュールにしてインターフェースをかましてカプセル化しておくのが良かったようです。
せっかくなので、クラスモジュールに変更しました。
IPointsRepository
Option Compare
DatabaseOption Explicit
Public Sub InsertPoints(seasonTarget As Integer, matchdayTarget As Integer)
End Sub
MariaDBPointsRepository
Option Compare Database
Option Explicit
Implements IPointsRepository
Public Sub IPointsRepository_InsertPoints(seasonTarget As Integer, matchdayTarget As Integer)
Dim conn As ADODB.Connection
Dim rsTeams As ADODB.Recordset
Dim teamID As Long
Dim strSQL As String
Dim totalPoints As Long, gf As Long, ga As Long
' 接続設定(MariaDBへのDSN)
Set conn = New ADODB.Connection
conn.Open "DSN=JLeagueDB;" ' ← 自宅・職場で同じDSN名で揃えると便利です
' ① 指定年度・節の既存レコードを削除
strSQL = "DELETE FROM Points WHERE season = " & seasonTarget & " AND matchday = " & matchdayTarget
conn.Execute strSQL
' ② MariaDB側のTeams一覧を取得
Set rsTeams = New ADODB.Recordset
rsTeams.Open "SELECT team_id FROM Teams", conn, adOpenForwardOnly, adLockReadOnly
' ③ 各チームごとに集計しINSERT
Do Until rsTeams.EOF
teamID = rsTeams!team_id
' 勝ち点集計
strSQL = "SELECT SUM(CASE " & _
"WHEN home_team_id=" & teamID & " AND home_score>away_score THEN 3 " & _
"WHEN away_team_id=" & teamID & " AND away_score>home_score THEN 3 " & _
"WHEN (home_score=away_score AND (home_team_id=" & teamID & " OR away_team_id=" & teamID & ")) THEN 1 " & _
"ELSE 0 END) AS pts " & _
"FROM Matches WHERE season=" & seasonTarget & " AND matchday <= " & matchdayTarget & " " & _
"AND (home_team_id=" & teamID & " OR away_team_id=" & teamID & ")"
totalPoints = Nz(conn.Execute(strSQL).Fields(0).Value, 0)
' 得点集計
strSQL = "SELECT SUM(CASE " & _
"WHEN home_team_id=" & teamID & " THEN home_score " & _
"WHEN away_team_id=" & teamID & " THEN away_score " & _
"ELSE 0 END) AS gf " & _
"FROM Matches WHERE season=" & seasonTarget & " AND matchday <= " & matchdayTarget & " " & _
"AND (home_team_id=" & teamID & " OR away_team_id=" & teamID & ")"
gf = Nz(conn.Execute(strSQL).Fields(0).Value, 0)
' 失点集計
strSQL = "SELECT SUM(CASE " & _
"WHEN home_team_id=" & teamID & " THEN away_score " & _
"WHEN away_team_id=" & teamID & " THEN home_score " & _
"ELSE 0 END) AS ga " & _
"FROM Matches WHERE season=" & seasonTarget & " AND matchday <= " & matchdayTarget & " " & _
"AND (home_team_id=" & teamID & " OR away_team_id=" & teamID & ")"
ga = Nz(conn.Execute(strSQL).Fields(0).Value, 0)
' INSERT文を構築
strSQL = "INSERT INTO Points (season, matchday, team_id, total_points, goals_for, goals_against, goal_diff, rank) VALUES (" & _
seasonTarget & ", " & matchdayTarget & ", " & teamID & ", " & totalPoints & ", " & _
gf & ", " & ga & ", " & (gf - ga) & ", 0)"
conn.Execute strSQL
rsTeams.MoveNext
Loop
' 後処理
rsTeams.Close: Set rsTeams = Nothing
conn.Close: Set conn = Nothing
'Me.lblLog.Caption = seasonTarget & "年 第" & matchdayTarget & "節 の順位集計が完了しました!(MariaDB版)"
End Su
PointsService
Option Compare Database
Option Explicit
Private repository_ As IPointsRepository
Public Sub InjectRepository(repository As IPointsRepository)
Set repository_ = repository
End Sub
Public Sub AwardPoints(seasonTarget As Integer, matchdayTarget As Integer)
If Not repository_ Is Nothing Then
Call repository_.InsertPoints(seasonTarget, matchdayTarget)
End If
End Su
Form_F_PointsUpdate
Option Compare Database
Option Explicit
'ツール、参照設定
'Microsoft ActiveX Data Objects 6.1 Library にチェック
Private Mediator As Mediator
Private ColleagueCommandButtonCollection As Collection
Private Const IDNAME As String = "team_id"
Private Const SUBFORM_NAME As String = "F_Pints_RankedStats"
Private PointsService As PointsService
Private Sub cmbMatchday_AfterUpdate()
Call RequerySubForm
End Sub
Private Sub cmbSeason_AfterUpdate()
Call RequerySubForm
End Sub
Private Sub Form_Load()
Me.lblLog.Caption = ""
Set Mediator = New Mediator
Call Mediator.Construct(Me, IDNAME)
Dim CreateColleagueCommandButton As CreateColleagueCommandButton
Set CreateColleagueCommandButton = New CreateColleagueCommandButton
Set ColleagueCommandButtonCollection = CreateColleagueCommandButton.Create(Me)
Set PointsService = New PointsService
Call PointsService.InjectRepository(New MariaDBPointsRepository)
End Sub
Public Sub CommandButtonNotify(form_name As String)
Call Mediator.CommandButtonNotify(form_name)
End Sub
Private Sub CommandButtonUpdate_Click()
If IsNull(Me.cmbSeason) Or IsNull(Me.cmbMatchday) Then
MsgBox "年度と節を選択してください。", vbExclamation
Exit Sub
End If
Call PointsService.AwardPoints(Me.cmbSeason, Me.cmbMatchday)
Me.lblLog.Caption = Me.cmbSeason & "年 第" & Me.cmbMatchday & "節 の順位集計が完了しました!(MariaDB版)"
Me.Form.Form(SUBFORM_NAME).Requery ' 表示用サブフォームの再読み込み
End Sub
Private Sub RequerySubForm()
Me.Form.Form(SUBFORM_NAME).Requery
Me.lblLog.Caption = ""
End Sub
一つのプロシージャが3つのクラスに増えました。やや増長かもしれませんが、せっかくなので。
データベースなど技術的な部分を操作するスクリプトはフォームモジュールにあるよりはいいのかなぁと思います。依存関係逆転の法則ですね。

