0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

どうにかして複雑なSQLを読みやすく整形できないのか

Posted at

まず王道な手段から。

1. リレーションシップビュー

  • 場所: メニューの「データベースツール」タブ → 「リレーションシップ」
  • 機能: テーブル同士の結合線を図で表示してくれます。
    → 外部キーや主キーの関係も一目で分かるので、「どのテーブルがどのテーブルと繋がっているか」が確認できます。
  • 用途: SQLのJOINを文字だけで追うよりも、結合の構造を俯瞰するのに便利。

2. クエリデザインビュー

  • クエリをSQLビューではなくデザインビューで開くと、結合線がテーブル間に表示されます。
  • デザインビューでは、結合条件をダブルクリックして「結合の種類(内部結合/左外部結合など)」も確認・変更可能です。

3. クエリの依存関係ビュー

  • 場所: ナビゲーションペインで対象クエリを右クリック → 「オブジェクトの依存関係」
  • 機能: 「このクエリはどのテーブルや別クエリを参照しているか」「逆にどのフォームやレポートから参照されているか」をツリー形式で表示してくれます。
  • 用途: 複数のサブクエリが重なっている場合に「どの層でどの結合が発生しているか」を把握しやすい。

4. SQLテキストの整理支援(手動)

  • Access自体にはSQLフォーマッタ(自動整形機能)は搭載されていません。
  • そのため、VBAでクエリのSQLを抽出して整形するツールを作る、あるいは外部エディタ(例えばSQL Pretty PrinterやVSCodeのSQLプラグイン)で読みやすくするのが一般的です。
    → 以前あなたが取り組んでいた「AccessクエリをExcelに出力してシンタックスハイライト」する方法はまさにこの補助的な解決策です。

まとめ

  • Access標準で「結合関係を図で確認する」なら → リレーションシップビュー or クエリデザインビュー
  • 「依存関係を俯瞰する」なら → オブジェクトの依存関係ビュー
  • 「SQL自体を見やすくする」なら → 外部整形ツールや自作VBAを活用。

AccessのUNIONクエリを「読む用」に自動整形するVBA(Excel出力つき)

UNION入りはデザインビュー不可なので、SQLテキストを**自動整形(改行・インデント)**して読みやすくするのが現実的です。下のVBAをAccessに貼るだけで、選んだクエリ(または全クエリ)のSQLを整形し、**Excelに1行ずつ出力(折返し・等幅)**します。
※レファレンスで Microsoft VBScript Regular Expressions 5.5 を有効にしてください。


使い方(3手順)

  1. AccessのVBAエディタ(Alt+F11)で標準モジュールを作成
  2. 参照設定:Microsoft VBScript Regular Expressions 5.5 をチェック
  3. マクロ実行:
  • あるクエリだけ → FormatOneQueryToExcel "クエリ名"
  • すべて → FormatAllQueriesToExcel

コード(貼り付け用)

Option Compare Database
Option Explicit

'==============================
' modSQLFormatter
'==============================

' ▼整形の入り口:単一クエリをExcelへ
Public Sub FormatOneQueryToExcel(ByVal queryName As String)
    Dim qd As DAO.QueryDef
    On Error GoTo EH

    Set qd = CurrentDb.QueryDefs(queryName)
    ExportSqlPartsToExcel queryName, SplitUnionParts(qd.SQL)
    MsgBox "整形完了:" & queryName, vbInformation
    Exit Sub
EH:
    MsgBox "クエリが見つかりません: " & queryName, vbExclamation
End Sub

' ▼整形の入り口:すべてのSELECT系クエリをExcelへ
Public Sub FormatAllQueriesToExcel()
    Dim qd As DAO.QueryDef, parts As Collection
    Dim cnt As Long: cnt = 0
    For Each qd In CurrentDb.QueryDefs
        If IsSelectLike(qd.SQL) Then
            Set parts = SplitUnionParts(qd.SQL)
            ExportSqlPartsToExcel qd.Name, parts, True  '追記モード
            cnt = cnt + 1
        End If
    Next
    MsgBox "整形完了:" & cnt & " 件", vbInformation
End Sub

' ==============================
' 判定・分割・整形
' ==============================
Private Function IsSelectLike(ByVal sqlText As String) As Boolean
    Dim s As String: s = LCase$(Trim$(sqlText))
    IsSelectLike = (Left$(s, 6) = "select" Or Left$(s, 9) = "transform" Or InStr(s, " union ") > 0)
End Function

' ▼UNION/UNION ALLで分割し、各パートを整形して返す
Private Function SplitUnionParts(ByVal sqlText As String) As Collection
    Dim re As New RegExp, m As Match, arr As Variant, i As Long
    Dim cleaned As String: cleaned = NormalizeWhitespace(sqlText)
    Dim result As New Collection

    ' 「) UNION (」のようなケースも壊さないように、改行を一律正規化した上で区切る
    ' 正規表現:大文字小文字無視で "UNION ALL" または "UNION" を区切りとして保持
    Dim breaker As String
    breaker = "(\sUNION\sALL\s|\sUNION\s)"

    Set re = New RegExp
    re.IgnoreCase = True
    re.Global = True
    re.Pattern = breaker

    Dim tokens As Collection: Set tokens = RegexSplitWithDelimiter(cleaned, re)
    ' tokens は [part, delimiter, part, delimiter, part, ...] の交互配列
    Dim current As String
    current = ""
    For i = 1 To tokens.Count
        If LCase$(Trim$(tokens(i))) = "union" Or LCase$(Trim$(tokens(i))) = "union all" Then
            ' デリミタ単体で来ることはないように調整済みだが保険
            ' ここでは無視
        ElseIf LCase$(Left$(Trim$(tokens(i)), 5)) = "union" Then
            ' 例えば "UNION ALL" が一塊で来たケース
            ' ここでは無視(直前でパート確定済み)
        Else
            ' パート本文
            Dim formatted As String
            formatted = PrettyPrintSql(tokens(i))
            result.Add formatted
        End If
    Next

    ' 区切りがない場合(UNIONなし)でも1件返る
    Set SplitUnionParts = result
End Function

' ▼SQL整形本体(改行・インデント)
Private Function PrettyPrintSql(ByVal sqlText As String) As String
    Dim s As String
    s = NormalizeWhitespace(sqlText)
    s = AddLineBreaks(s)
    s = IndentSql(s)
    PrettyPrintSql = s
End Function

' ▼空白正規化(改行→スペース、タブ→スペース、2連続以上を1つ)
Private Function NormalizeWhitespace(ByVal s As String) As String
    Dim re As New RegExp
    s = Replace(s, vbCr, " ")
    s = Replace(s, vbLf, " ")
    s = Replace(s, vbTab, " ")

    re.IgnoreCase = True
    re.Global = True

    ' -- コメントは残す("--"以降は行頭に改行を入れると可読性UP)
    Dim i As Long: i = InStr(1, s, "--", vbTextCompare)
    Do While i > 0
        s = Left$(s, i - 1) & " " & Mid$(s, i)  ' 前にスペースを1つ足すだけ(壊さない)
        i = InStr(i + 2, s, "--", vbTextCompare)
    Loop

    ' 連続スペースを1つへ
    re.Pattern = "[ ]{2,}"
    s = re.Replace(s, " ")

    NormalizeWhitespace = Trim$(s)
End Function

' ▼主要キーワードの前に改行を入れる
Private Function AddLineBreaks(ByVal s As String) As String
    Dim keys As Variant
    keys = Array( _
        "SELECT", "FROM", "WHERE", "GROUP BY", "HAVING", "ORDER BY", _
        "INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN", "JOIN", _
        "ON", "UNION", "UNION ALL", "TRANSFORM", "PIVOT" _
    )

    Dim k As Variant
    For Each k In keys
        s = ReplaceCIKeepSpacing(s, " " & k & " ", vbCrLf & k & " ")
        s = ReplaceCIKeepSpacing(s, "(" & k & " ", "(" & vbCrLf & k & " ")
        s = ReplaceCIKeepSpacing(s, " " & k & "(", vbCrLf & k & " (")
    Next k

    ' AND / OR は条件の改行にも有効(ただしJOINのON直後は過剰改行になり過ぎない程度に)
    s = ReplaceCIKeepSpacing(s, " AND ", vbCrLf & "  AND ")
    s = ReplaceCIKeepSpacing(s, " OR ", vbCrLf & "  OR ")

    AddLineBreaks = Trim$(s)
End Function

' 大文字小文字無視で置換(周辺のスペースは壊さない)
Private Function ReplaceCIKeepSpacing(ByVal src As String, ByVal find As String, ByVal repl As String) As String
    Dim re As New RegExp
    re.IgnoreCase = True
    re.Global = True
    re.Pattern = EscapeRegex(find)
    ReplaceCIKeepSpacing = re.Replace(src, repl)
End Function

' 正規表現用エスケープ
Private Function EscapeRegex(ByVal s As String) As String
    Dim chars As Variant, c As Variant
    chars = Array("\", ".", "+", "*", "?", "(", ")", "[", "]", "{", "}", "^", "$", "|")
    For Each c In chars
        s = Replace(s, c, "\" & c)
    Next
    EscapeRegex = s
End Function

' ▼インデント付与(JOIN/ON/WHERE等で階層化の簡易版)
Private Function IndentSql(ByVal s As String) As String
    Dim lines() As String: lines = Split(s, vbCrLf)
    Dim i As Long, line As String, buf As String
    Dim indent As Long: indent = 0

    For i = LBound(lines) To UBound(lines)
        line = Trim$(lines(i))
        If line = "" Then GoTo AppendLine

        ' 閉じ括弧が先に来たらデデント
        If Left$(line, 1) = ")" Then
            indent = Application.Max(0, indent - 1)
        End If

        buf = buf & String$(indent * 2, " ") & line & vbCrLf

        ' 開き括弧やJOIN, WHERE, GROUP BY などでインデント微調整
        If Right$(line, 1) = "(" Then indent = indent + 1
        If LCase$(Left$(line, 9)) = "inner join" _
        Or LCase$(Left$(line, 9)) = "left join" _
        Or LCase$(Left$(line, 5)) = "join " Then
            indent = indent + 1
        End If
        If LCase$(Left$(line, 6)) = "where " Then indent = indent + 1
        If LCase$(Left$(line, 8)) = "group by" Then indent = indent + 1
        If LCase$(Left$(line, 8)) = "order by" Then indent = indent + 1

AppendLine:
    Next
    IndentSql = Trim$(buf)
End Function

' ▼正規表現で区切り文字も保持して分割([part, delimiter, part, ...])
Private Function RegexSplitWithDelimiter(ByVal text As String, ByVal re As RegExp) As Collection
    Dim result As New Collection
    Dim pos As Long: pos = 1
    Dim m As Match
    Dim lastEnd As Long: lastEnd = 1

    For Each m In re.Execute(text)
        ' 直前パート
        result.Add Mid$(text, lastEnd, m.FirstIndex + 1 - lastEnd)
        ' デリミタ("UNION"/"UNION ALL")は捨てる=ここでは追加しない
        lastEnd = m.FirstIndex + m.Length + 1
    Next
    ' 最終パート
    result.Add Mid$(text, lastEnd)

    Set RegexSplitWithDelimiter = result
End Function

' ==============================
' Excel出力
' ==============================
Private Sub ExportSqlPartsToExcel(ByVal queryName As String, ByVal parts As Collection, Optional ByVal appendMode As Boolean = False)
    Dim xl As Object, wb As Object, ws As Object
    Dim i As Long, ln As Variant, r As Long, part As Variant, lines() As String

    Set xl = CreateObject("Excel.Application")
    xl.Visible = True

    If appendMode And xl.Workbooks.Count > 0 Then
        Set wb = xl.ActiveWorkbook
    Else
        Set wb = xl.Workbooks.Add
    End If

    On Error Resume Next
    Set ws = wb.Worksheets("SQL_Formatted")
    On Error GoTo 0
    If ws Is Nothing Then
        Set ws = wb.Worksheets(1)
        ws.Name = "SQL_Formatted"
        ' ヘッダ
        ws.Range("A1:D1").Value = Array("QueryName", "UnionPart#", "Line#", "SQL")
        ws.Range("A1:D1").Font.Bold = True
        ws.Columns("A:D").ColumnWidth = 40
        ws.Columns("C:C").ColumnWidth = 8
        ws.Columns("D:D").ColumnWidth = 120
        ws.Columns("D:D").WrapText = True
        ws.Range("D:D").Font.Name = "Consolas"
    End If

    r = ws.Cells(ws.Rows.Count, "A").End(-4162).Row + 1 ' xlUp=-4162

    For i = 1 To parts.Count
        lines = Split(parts(i), vbCrLf)
        Dim j As Long
        For j = LBound(lines) To UBound(lines)
            If Trim$(lines(j)) <> "" Then
                ws.Cells(r, 1).Value = queryName
                ws.Cells(r, 2).Value = i
                ws.Cells(r, 3).Value = j + 1
                ws.Cells(r, 4).Value = lines(j)
                r = r + 1
            End If
        Next j
        ' 区切り行
        ws.Cells(r, 1).Resize(1, 4).Value = Array(queryName, i, "", "----")
        ws.Cells(r, 4).Font.Color = RGB(150, 150, 150)
        r = r + 1
    Next i

    ' テーブル風
    ws.Range("A1").CurrentRegion.Borders.LineStyle = 1 'xlContinuous
End Sub

補足(調整ポイント)

  • キーワード追加AddLineBreakskeys 配列に、よく使う句(DISTINCTROWCASE WHEN など)を足せます。
  • インデント規則IndentSql の if 条件を増やせば、社内流儀に合わせて細かく調整可能。
  • 大量のクエリFormatAllQueriesToExcelTRANSFORM(クロス集計)も対象に含めています。不要なら IsSelectLike で除外してください。
  • 書き戻しはしません(閲覧専用)。誤って元SQLを壊さない設計です。

これでできること

  • UNIONで分割表示 → 各パートを整形・行番号付きでExcelに出力
  • AND/ORの改行JOIN/ONの階層インデントで、条件の塊が追いやすい
  • Excel側で折返し+等幅フォントなので、レビュー配布・画面共有にそのまま使える
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?