複数Excelから特定文字を含む行を抽出

使い方

  1. 結果貼り付け用にシート名 「結果」 を作る
  2. VBAに上のコードを貼る
  3. ExtractRowsContainingABC_InColumnB を実行
  4. フォルダを選ぶ → 抽出結果が「結果」シートに出る
Option Explicit

Public Sub ExtractRowsContainingABC_InColumnB()

    Dim targetFolder As String
    Dim outWs As Worksheet
    Dim outRow As Long

    Dim fso As Object
    Dim folderObj As Object
    Dim fileObj As Object

    Dim wb As Workbook
    Dim ws As Worksheet

    Dim lastRow As Long
    Dim lastCol As Long
    Dim r As Long

    Dim bText As String
    Dim hit As Boolean

    '=== 設定 ===
    '結果貼り付け先シート名
    Set outWs = ThisWorkbook.Worksheets("結果")

    '開始行(1行目は見出しにしたいなら 2 に)
    outRow = 2

    'フォルダ選択(ダイアログ)
    targetFolder = PickFolder()
    If Len(targetFolder) = 0 Then Exit Sub

    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    Application.EnableEvents = False

    '出力シートを初期化(必要に応じてコメントアウト)
    outWs.Cells.Clear
    outWs.Cells(1, 1).Value = "ファイル名"
    outWs.Cells(1, 2).Value = "抽出行(B列ヒット行の全列)"

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set folderObj = fso.GetFolder(targetFolder)

    '=== フォルダ内のファイルを走査 ===
    For Each fileObj In folderObj.Files

        If IsExcelFile(fileObj.Name) Then

            On Error Resume Next
            Set wb = Workbooks.Open(Filename:=fileObj.Path, ReadOnly:=True)
            If Err.Number <> 0 Then
                Err.Clear
                On Error GoTo 0
                GoTo NextFile
            End If
            On Error GoTo 0

            '各シートを対象(特定シートだけならここを調整)
            For Each ws In wb.Worksheets

                '空シート対策
                If Application.WorksheetFunction.CountA(ws.Cells) = 0 Then
                    GoTo NextSheet
                End If

                lastRow = ws.Cells(ws.Rows.Count, "B").End(xlUp).Row
                lastCol = GetLastUsedCol(ws)

                'B列を走査(1行目がヘッダなら 2 からに変えてOK)
                For r = 1 To lastRow

                    bText = CStr(ws.Cells(r, "B").Value2) '改行含んでもOK
                    hit = (InStr(1, bText, "ABC", vbTextCompare) > 0)

                    If hit Then
                        'A列にファイル名(必要ならシート名も付ける)
                        outWs.Cells(outRow, 1).Value = wb.Name & " / " & ws.Name

                        'B列以降に「その行の全列」を貼り付け
                        '※値として貼る(書式は不要ならこれが安全)
                        outWs.Cells(outRow, 2).Resize(1, lastCol).Value = ws.Cells(r, 1).Resize(1, lastCol).Value

                        outRow = outRow + 1
                    End If

                Next r

NextSheet:
            Next ws

            wb.Close SaveChanges:=False
        End If

NextFile:
        DoEvents
    Next fileObj

    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
    Application.EnableEvents = True

    MsgBox "完了: " & (outRow - 2) & " 行 抽出しました。", vbInformation

End Sub

'--- フォルダ選択ダイアログ ---
Private Function PickFolder() As String
    Dim fd As FileDialog
    Set fd = Application.FileDialog(msoFileDialogFolderPicker)

    With fd
        .Title = "対象フォルダを選択してください"
        .AllowMultiSelect = False
        If .Show <> -1 Then
            PickFolder = ""
        Else
            PickFolder = .SelectedItems(1)
        End If
    End With
End Function

'--- Excelファイル判定 ---
Private Function IsExcelFile(ByVal fileName As String) As Boolean
    Dim ext As String
    ext = LCase$(Mid$(fileName, InStrRev(fileName, ".") + 1))
    IsExcelFile = (ext = "xlsx" Or ext = "xlsm" Or ext = "xls")
End Function

'--- シートの最終使用列(UsedRangeベース) ---
Private Function GetLastUsedCol(ByVal ws As Worksheet) As Long
    On Error GoTo EH
    If ws.UsedRange Is Nothing Then
        GetLastUsedCol = 1
    Else
        GetLastUsedCol = ws.UsedRange.Columns(ws.UsedRange.Columns.Count).Column
        If GetLastUsedCol < 1 Then GetLastUsedCol = 1
    End If
    Exit Function
EH:
    GetLastUsedCol = 1
End Function

SQLフォーマッタ

  1. Alt + F11 で VBA エディタを開く
  2. 挿入 → 標準モジュール → 上記コードを貼り付け
  3. Excel に戻って、整形したい SQL が入ったセル(または範囲)を選択
  4. Alt + F8 → FormatSqlInSelection を実行
Option Explicit

' ==== 公開関数 ============================================

' 単一文字列の SQL を整形
Public Function FormatSql(ByVal sql As String) As String
    Dim tokens As Collection
    Dim result As String
    
    Set tokens = TokenizeSql(sql)
    result = BuildFormattedSql(tokens)
    
    FormatSql = result
End Function

' 選択セル内の SQL をまとめて整形
Public Sub FormatSqlInSelection()
    Dim c As Range
    
    If Selection Is Nothing Then Exit Sub
    
    For Each c In Selection.Cells
        If Not IsEmpty(c.Value) Then
            On Error Resume Next
            c.Value = FormatSql(CStr(c.Value))
            On Error GoTo 0
        End If
    Next c
End Sub

' ==== トークナイザ ========================================

' SQL をトークン列に分解(文字列リテラルも考慮)
' SQL をトークン列に分解(文字列リテラル & ブロックコメントも考慮)
Private Function TokenizeSql(ByVal sql As String) As Collection
    Dim tokens As New Collection
    Dim i As Long
    Dim ch As String
    Dim token As String
    Dim inSingle As Boolean
    Dim inDouble As Boolean
    Dim j As Long
    
    i = 1
    Do While i <= Len(sql)
        ch = Mid$(sql, i, 1)
        
        ' ==== ブロックコメント /* ... */ の検出 ====
        If Not inSingle And Not inDouble Then
            If ch = "/" And i < Len(sql) And Mid$(sql, i + 1, 1) = "*" Then
                ' 直前までのトークンを確定
                If token <> "" Then
                    tokens.Add token
                    token = ""
                End If
                
                j = i + 2
                Do While j <= Len(sql) - 1
                    If Mid$(sql, j, 2) = "*/" Then
                        j = j + 2
                        Exit Do
                    End If
                    j = j + 1
                Loop
                If j > Len(sql) Then j = Len(sql) + 1   ' 万一 */ が無かった場合の保険
                
                ' コメント全体を 1 トークンとして追加
                tokens.Add Mid$(sql, i, j - i)
                
                i = j
                GoTo NextChar
            End If
        End If
        
        ' ==== ここから元々のロジック ====
        If inSingle Then
            ' 単一引用符内('...')
            token = token & ch
            If ch = "'" Then
                ' '' のエスケープを考慮
                If i < Len(sql) And Mid$(sql, i + 1, 1) = "'" Then
                    token = token & "'"
                    i = i + 1
                Else
                    tokens.Add token
                    token = ""
                    inSingle = False
                End If
            End If
        
        ElseIf inDouble Then
            ' 二重引用符内("...")
            token = token & ch
            If ch = """" Then
                ' "" のエスケープを考慮
                If i < Len(sql) And Mid$(sql, i + 1, 1) = """" Then
                    token = token & """"
                    i = i + 1
                Else
                    tokens.Add token
                    token = ""
                    inDouble = False
                End If
            End If
        
        Else
            ' 通常領域
            Select Case ch
                Case " ", vbTab, vbCr, vbLf
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                Case "("
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                    tokens.Add ch
                Case ")"
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                    tokens.Add ch
                Case ","
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                    tokens.Add ch
                Case "'"
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                    inSingle = True
                    token = "'"
                Case """"
                    If token <> "" Then
                        tokens.Add token
                        token = ""
                    End If
                    inDouble = True
                    token = """"
                Case Else
                    token = token & ch
            End Select
        End If
        
NextChar:
        i = i + 1
    Loop
    
    If token <> "" Then
        tokens.Add token
    End If
    
    Set TokenizeSql = tokens
End Function


' ==== フォーマッタ本体 ====================================

Private Function BuildFormattedSql(tokens As Collection) As String
    Dim sb As String
    Dim indentLevel As Long          ' () のネストレベル
    Dim nextIndentLevel As Long      ' キーワード行の次の行のインデント
    Dim i As Long
    Dim tok As String
    Dim lowerTok As String
    Dim dispTok As String
    
    indentLevel = 0
    nextIndentLevel = 0
    sb = ""
    
    i = 1
    Do While i <= tokens.Count
        tok = tokens(i)
        dispTok = tok
        lowerTok = LCase$(tok)
        
        ' -------------------------------------------------
        ' 1. 構造トークン(カッコとカンマ)
        ' -------------------------------------------------
        If tok = "(" Then
            ' "(" を書いて改行 → インデントレベル+1
            If EndsWithNewline(sb) Then
                sb = sb & IndentString(indentLevel)
            ElseIf Len(sb) > 0 Then
                If Right$(sb, 1) <> " " Then
                    sb = sb & " "
                End If
            End If
            sb = sb & "(" & vbCrLf
            indentLevel = indentLevel + 1
            nextIndentLevel = indentLevel
        
        ElseIf tok = ")" Then
            ' 閉じカッコの前にインデントレベルを戻す
            indentLevel = IIf(indentLevel > 0, indentLevel - 1, 0)
            sb = RTrim$(sb) & vbCrLf & IndentString(indentLevel) & ")"
            nextIndentLevel = indentLevel
        
        ElseIf tok = "," Then
            ' カンマの後は改行+同じインデント
            sb = sb & ","
            sb = sb & vbCrLf
            ' nextIndentLevel はそのまま(SELECT句のカラムを揃える)
        
        Else
            ' -------------------------------------------------
            ' 2. キーワード結合(GROUP BY / ORDER BY / UNION ALL など)
            ' -------------------------------------------------
            If (lowerTok = "order" Or lowerTok = "group") And i < tokens.Count Then
                If LCase$(tokens(i + 1)) = "by" Then
                    dispTok = tok & " " & tokens(i + 1)
                    lowerTok = lowerTok & " by"
                    i = i + 1
                End If
            ElseIf lowerTok = "union" And i < tokens.Count Then
                If LCase$(tokens(i + 1)) = "all" Then
                    dispTok = tok & " " & tokens(i + 1)
                    lowerTok = lowerTok & " all"
                    i = i + 1
                End If
            End If
            
            ' -------------------------------------------------
            ' 3. キーワード行かどうか
            ' -------------------------------------------------
            If IsSqlKeyword(lowerTok) Then
                ' 直前の余分な空白カット
                sb = RTrim$(sb)
                If Len(sb) > 0 Then
                    sb = sb & vbCrLf
                End If
                
                ' キーワード自体を表示
                sb = sb & IndentString(indentLevel) & dispTok & vbCrLf
                
                ' 次の行から一段深くインデント
                nextIndentLevel = indentLevel + 1
            
            Else
                ' -------------------------------------------------
                ' 4. 通常トークン
                ' -------------------------------------------------
                If EndsWithNewline(sb) Then
                    ' 改行直後ならインデントを入れる
                    sb = sb & IndentString(nextIndentLevel) & dispTok
                ElseIf Len(sb) = 0 Then
                    sb = dispTok
                Else
                    Dim lastChar As String
                    lastChar = Right$(sb, 1)
                    If lastChar = " " Or lastChar = "(" Then
                        sb = sb & dispTok
                    Else
                        sb = sb & " " & dispTok
                    End If
                End If
            End If
        End If
        
        i = i + 1
    Loop
    
    BuildFormattedSql = RTrim$(sb)
End Function

' ==== 補助関数 ============================================

Private Function IndentString(ByVal level As Long) As String
    If level < 0 Then level = 0
    IndentString = String(level * 4, " ") ' 4スペースインデント
End Function

Private Function EndsWithNewline(ByVal s As String) As Boolean
    If Len(s) >= 2 Then
        EndsWithNewline = (Right$(s, 2) = vbCrLf)
    Else
        EndsWithNewline = False
    End If
End Function

' SQL キーワード判定
Private Function IsSqlKeyword(ByVal key As String) As Boolean
    Select Case key
        Case "select", "from", "where", "group", "group by", "order", "order by", _
             "having", "join", "inner", "left", "right", "full", "cross", _
             "union", "union all", "on", "and", "or", _
             "case", "when", "then", "else", "end", _
             "update", "insert", "into", "values", "delete", "set"
            IsSqlKeyword = True
        Case Else
            IsSqlKeyword = False
    End Select
End Function

汎用CLP表→CSV化

<# 
Reflow CLP-style fixed-width table into TSV with lookahead continuation.

仕様(重要な変更点):
- 列境界: 最初に出る "-----" 罫線からダッシュの塊で列範囲を決定(固定幅スライス)
- 先読み束ね: 物理行 i が「日付セル = 時刻」にマッチしたら新規レコード開始。
              以降、次行 j を“先読み”し、j が日付で始まらない限り、テキスト列へ連結し続ける。
- 継続行の取り方(スマート):
    1) テキスト列の範囲スライス
    2) 空なら テキスト列の開始位置から行末まで(列幅越え対策)
    3) まだ空なら その行の最長非空セル
    4) それでも空なら 行全体の Trim
- 改行: CRLF / LF / CR / NEL(0x85) / LS(0x2028) / PS(0x2029) を LF に正規化
- 出力: TSV(タブ区切り)。PS7+ なら UseQuotes Always 指定
#>

param(
  [Parameter(Position=0)]
  [string]$InPath  = (Join-Path $PSScriptRoot 'clp_output.txt'),

  [Parameter(Position=1)]
  [string]$OutPath = (Join-Path $PSScriptRoot 'reflow.tsv'),

  # Db2 っぽい時刻/日付。必要に応じて調整。
  [string]$TimeRegex = '^(?:\d{4}-\d{2}-\d{2}[-\s]?\d{2}[:\.]\d{2}[:\.]\d{2}(?:\.\d{1,6})?|\d{8}|\d{2}:\d{2}:\d{2}|\d{14})$',

  # どの列を「時刻列」として見るか(0始まり)。既定=先頭列
  [int]$DateColumnIndex = 0,

  # どの列に継続を連結するか(テキスト列)
  [string]$TextColumnName = '',
  [int]$TextColumnIndex = -1,

  # 連結時の区切り
  [ValidateSet('space','newline','literal-n')]
  [string]$JoinMode = 'space',

  # 空行も区切りだけ入れて保持するか
  [bool]$KeepBlankContinuation = $true
)

# ----- Join token -----
switch ($JoinMode) {
  'space'     { $JoinToken = ' ' }
  'newline'   { $JoinToken = "`n" }
  'literal-n' { $JoinToken = '\n' }
}

# ----- 入力 & 改行正規化 -----
if (-not (Test-Path -LiteralPath $InPath)) {
  $alt = Join-Path (Get-Location) (Split-Path $InPath -Leaf)
  if (Test-Path -LiteralPath $alt) { $InPath = $alt }
}
if (-not (Test-Path -LiteralPath $InPath)) { throw "Input file not found: $InPath" }

$raw = Get-Content -LiteralPath $InPath -Raw
$raw = $raw -replace "`r`n", "`n"                        # CRLF -> LF
$raw = $raw -replace "`r",   "`n"                        # CR   -> LF
$raw = $raw -replace ([string][char]0x0085), "`n"       # NEL  -> LF
$raw = $raw -replace ([string][char]0x2028), "`n"       # LS   -> LF
$raw = $raw -replace ([string][char]0x2029), "`n"       # PS   -> LF

$lines = $raw -split "`n"
if (-not $lines -or $lines.Count -eq 0) { throw "No input lines found." }

# ----- Helpers -----
function IsSepLine([string]$s){
  if ([string]::IsNullOrWhiteSpace($s)) { return $false }
  return [bool]([regex]::IsMatch($s, '^(?=.*-{3,})[ \t\-\+\|]+$'))
}
function Get-ColRanges([string]$sep){
  $ranges = @(); $chars = $sep.ToCharArray(); $i = 0
  while ($i -lt $chars.Length){
    if ($chars[$i] -eq '-') {
      $start = $i
      while ($i -lt $chars.Length -and $chars[$i] -eq '-') { $i++ }
      $end = $i - 1
      $ranges += ,@($start, $end)
    } else { $i++ }
  }
  return $ranges
}
function Slice-Trim([string]$line, [int]$start, [int]$end){
  if ($null -eq $line) { return "" }
  $len = [math]::Max(0, [math]::Min($line.Length, $end+1) - $start)
  if ($len -le 0 -or $start -ge $line.Length) { return "" }
  return $line.Substring($start, $len).Trim()
}
function Slice-From([string]$line, [int]$start){
  if ($null -eq $line) { return "" }
  if ($start -ge $line.Length) { return "" }
  return $line.Substring($start).Trim()
}
function Test-IsTime([string]$s, [string]$pattern){
  if ([string]::IsNullOrWhiteSpace($s)) { return $false }
  return [bool]([regex]::IsMatch($s, $pattern))
}

# ----- ヘッダ/罫線の検出 -----
$sepIdx = -1
for ($i=0; $i -lt $lines.Count; $i++){
  if (IsSepLine $lines[$i]) { $sepIdx = $i; break }
}
if ($sepIdx -lt 1) { throw "Header separator (-----) not found." }

$headerLine = $lines[$sepIdx - 1]
$sepLine    = $lines[$sepIdx]

# ----- 列範囲 & ヘッダ -----
$ranges = Get-ColRanges $sepLine
if (-not $ranges -or $ranges.Count -lt 2) { throw "Failed to detect column ranges." }

$headers = @()
foreach($r in $ranges){ $headers += (Slice-Trim $headerLine $r[0] $r[1]) }

# ヘッダ正規化
$seen = @{}
for($i=0;$i -lt $headers.Count;$i++){
  if ([string]::IsNullOrWhiteSpace($headers[$i])) { $headers[$i] = "Col$($i+1)" }
  if ($seen.ContainsKey($headers[$i])) {
    $n = 2; while ($seen.ContainsKey("$($headers[$i])_$n")) { $n++ }
    $headers[$i] = "$($headers[$i])_$n"
  }
  $seen[$headers[$i]] = $true
}

# インデックス決定
if ($DateColumnIndex -lt 0 -or $DateColumnIndex -ge $headers.Count) { $DateColumnIndex = 0 }

$TextCol = -1
if ($TextColumnIndex -ge 0 -and $TextColumnIndex -lt $headers.Count) {
  $TextCol = $TextColumnIndex
} elseif (-not [string]::IsNullOrWhiteSpace($TextColumnName)) {
  for($i=0;$i -lt $headers.Count;$i++){ if ($headers[$i] -ieq $TextColumnName){ $TextCol = $i; break } }
}
if ($TextCol -lt 0) {
  for($i=0;$i -lt $headers.Count;$i++){
    if ($headers[$i] -match '(?i)^(STMT_TEXT|SQL_TEXT|DYNAMIC_SQL_TEXT|TEXT|SQL|STATEMENT)$'){ $TextCol = $i; break }
  }
}
if ($TextCol -lt 0) { $TextCol = $headers.Count - 1 }  # 最終列をテキスト列に

# テキスト列のスライス開始位置をキャッシュ
$rText = $ranges[$TextCol]
$txtStart = $rText[0]; $txtEnd = $rText[1]

# ----- データ部(先読みで束ね) -----
$dataLines = @()
for($i = $sepIdx + 1; $i -lt $lines.Count; $i++){ $dataLines += $lines[$i] }

# ライン属性を先に評価(isDate / isSep / isBlank)
$attrs = @()
for($i=0; $i -lt $dataLines.Count; $i++){
  $ln = $dataLines[$i]
  $isBlank = [string]::IsNullOrWhiteSpace($ln)
  $isSep   = (-not $isBlank) -and (IsSepLine $ln)
  $isDate  = $false
  if (-not $isBlank -and -not $isSep) {
    # 「現在の行」の日付セルで判定
    $cellsTmp = @()
    foreach($r in $ranges){ $cellsTmp += (Slice-Trim $ln $r[0] $r[1]) }
    $dateCell = $cellsTmp[$DateColumnIndex]
    $isDate   = Test-IsTime $dateCell $TimeRegex
  }
  $attrs += ,@($isBlank, $isSep, $isDate)
}

$rows = New-Object System.Collections.Generic.List[Object]

for ($i=0; $i -lt $dataLines.Count; $i++) {
  $ln = $dataLines[$i]
  $isBlank = $attrs[$i][0]; $isSep = $attrs[$i][1]; $isDate = $attrs[$i][2]

  if ($isBlank -or $isSep) { continue }

  # ---- 新規レコードは「現在が日付行」だけで開始し、その後は“先読み”で連結 ----
  if ($isDate) {
    # 行 i を固定幅スライスして初期レコード作成
    $cells = @()
    foreach($r in $ranges){ $cells += (Slice-Trim $ln $r[0] $r[1]) }
    $obj = [ordered]@{}
    for($c=0; $c -lt $headers.Count; $c++){ $obj[$headers[$c]] = $cells[$c] }

    # 先読み:i+1 以降、次の「日付行 or 罫線 or 空行」直前までをテキスト列に連結
    $j = $i + 1
    while ($j -lt $dataLines.Count) {
      $n_isBlank = $attrs[$j][0]; $n_isSep = $attrs[$j][1]; $n_isDate = $attrs[$j][2]
      if ($n_isDate) { break }           # 次が日付行 → ここで束ね終了
      if ($n_isSep)  { $j++; continue }  # 余計な罫線はスキップ
      if ($n_isBlank){
        if ($KeepBlankContinuation) { $obj[$headers[$TextCol]] = ($obj[$headers[$TextCol]] + $JoinToken) }
        $j++; continue
      }

      $nLine = $dataLines[$j]

      # 継続行の“スマート掴み”
      $frag = Slice-Trim $nLine $txtStart $txtEnd           # 1) テキスト列範囲
      if ([string]::IsNullOrWhiteSpace($frag)) {
        $frag = Slice-From $nLine $txtStart                  # 2) テキスト開始から行末
      }
      if ([string]::IsNullOrWhiteSpace($frag)) {
        # 3) 行内で最長の非空セル
        $cellsN = @(); foreach($r in $ranges){ $cellsN += (Slice-Trim $nLine $r[0] $r[1]) }
        $longest = ""; foreach($cc in $cellsN){ if ($null -ne $cc -and $cc.Trim().Length -gt $longest.Length){ $longest = $cc.Trim() } }
        $frag = $longest
      }
      if ([string]::IsNullOrWhiteSpace($frag)) {
        # 4) 行全体
        $t = $nLine.Trim(); if ($t -ne "") { $frag = $t }
      }

      if (-not [string]::IsNullOrWhiteSpace($frag)) {
        $obj[$headers[$TextCol]] = ( @($obj[$headers[$TextCol]], $frag) -join $JoinToken ).Trim()
      } else {
        if ($KeepBlankContinuation) { $obj[$headers[$TextCol]] = ($obj[$headers[$TextCol]] + $JoinToken) }
      }

      $j++
    }

    # 完成レコードを追加
    $rows.Add([pscustomobject]$obj) | Out-Null

    # 先読みで消費したぶん i を進める
    $i = $j - 1
  }
  else {
    # 日付行以前の前置きやノイズは無視(先頭が日付行になるまでスキップ)
    continue
  }
}

if ($rows.Count -eq 0) { throw "No data rows (reflow failed)." }

# ----- TSV 出力 -----
try {
  $parent = Split-Path -Parent $OutPath
  if ($parent -and -not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }

  $quoteParam = @{}
  if ($PSVersionTable.PSVersion.Major -ge 7) { $quoteParam.UseQuotes = 'Always' }

  $rows | Export-Csv -LiteralPath $OutPath -Delimiter "`t" -NoTypeInformation -Encoding UTF8 -Force -ErrorAction Stop @quoteParam
  Write-Host ("DONE: {0}" -f (Resolve-Path $OutPath))
}
catch {
  Write-Warning ("Export-Csv failed. Fallback writer: {0}" -f $_.Exception.Message)
  if ($PSVersionTable.PSVersion.Major -ge 7) {
    $csv = $rows | ConvertTo-Csv -NoTypeInformation -Delimiter "`t" -UseQuotes Always
  } else {
    $csv = $rows | ConvertTo-Csv -NoTypeInformation -Delimiter "`t"
  }
  $utf8bom = New-Object System.Text.UTF8Encoding($true)
  [System.IO.File]::WriteAllLines($OutPath, $csv, $utf8bom)
  Write-Host ("DONE (fallback): {0}" -f (Resolve-Path $OutPath))
}


Db2の移行

## SAMPLEデータベース再作成
$ db2 drop db SAMPLE
$ db2 disconnect al
$ db2sampl -force

## ディレクトリ再作成
rm -fr ./logs ./data
rm -fr ./ikou

## 準備
$ mkdir ikou
$ cd ikou
$ mkdir logs data

## 元データベース(SAMPLE)接続
$ db2 connect to SAMPLE

## DDL取得
$ db2look -d SAMPLE -e -x -o sample.ddl 1> ./logs/db2look.out 2> ./logs/db2look.err

## 確認結果
[db2inst1@localhost logs]$ cat db2look.err
-- No userid was specified, db2look tries to use Environment variable USER
-- USER is: DB2INST1
-- Creating DDL for table(s)
-- Output is sent to file: sample.ddl
-- Binding package automatically ... 
-- Bind is successful
-- Binding package automatically ... 
-- Bind is successful

正常。バインド成功メッセージだけであればエラーではない
Bind is successful が 2 回出ていて、単なる情報メッセージ(stderr側に出る仕様)
DDL 抽出自体は正常完了している

## 元データベース(SAMPLE)のデータエクスポート
$ db2move SAMPLE export -l $(pwd)/data  1> ./logs/db2move_export.out 2> ./logs/db2move_export.err

## 結果確認
[db2inst1@localhost logs]$ cat db2move_export.err
Binding package automatically ... /home/db2inst1/sqllib/bnd/db2common.bnd ... successful!
Binding package automatically ... /home/db2inst1/sqllib/bnd/db2move.bnd ... successful!

正常。db2move はバインド系メッセージを .err に出すだけで、エクスポートの成否は .out 側に出

## 準備
$ db2 disconnect all
$ db2 connect

## 移行先データベース(SAMPLE2)作成
db2 create db SAMPLE2 using codeset UTF-8 territory JP pagesize 8192 1> ./logs/create_db.out 2> ./logs/create_db.err

## 結果確認
[db2inst1@localhost logs]$ cat create_db.out
DB20000I  The CREATE DATABASE command completed successfully.

$ db2 connect to SAMPLE2

## DDL実行
db2 -tvf ./sample.ddl 1> ./logs/apply_ddl.out 2> ./logs/apply_ddl.err

## データインポート
db2move SAMPLE2 import -l $(pwd)/data 1> ./logs/db2move_import.out 2> ./logs/db2move_import.err

## 結果確認
[db2inst1@localhost logs]$ cat db2move_import.err
Binding package automatically ... /home/db2inst1/sqllib/bnd/db2common.bnd ... successful!
Binding package automatically ... /home/db2inst1/sqllib/bnd/db2move.bnd ... successful!

正常。バインド成功メッセージだけであればエラーではない


pivot用vbaマクロ

Sub PivotWithMultiCharts_FileSelect()

    Dim f As Variant
    Dim wsData As Worksheet, wsP As Worksheet
    Dim pc As PivotCache, pt As PivotTable
    Dim srcRange As Range
    Dim timeCol As Range, seriesCol As Range, valueCol As Range
    Dim answer As String, chartTypes() As String
    Dim i As Long, co As ChartObject
    
    ' --- データファイルを選択 ---
    f = Application.GetOpenFilename("CSV/Excel Files,*.csv;*.xlsx;*.xls")
    If f = False Then Exit Sub
    
    ' --- 読み込みシート作成 ---
    Set wsData = ThisWorkbook.Sheets.Add
    wsData.Name = "Source" & wsData.Index
    
    ' CSV読み込み(カンマ区切り前提)
    wsData.QueryTables.Add "TEXT;" & f, wsData.Range("A1")
    With wsData.QueryTables(1)
        .TextFileParseType = xlDelimited
        .TextFileCommaDelimiter = True
        .Refresh
    End With
    
    Set srcRange = wsData.UsedRange
    
    ' --- 列を選択(時間・系列・値)---
    Set timeCol = Application.InputBox("時間列を選択してください", "列指定", Type:=8)
    Set seriesCol = Application.InputBox("系列列を選択してください", "列指定", Type:=8)
    Set valueCol = Application.InputBox("値列を選択してください", "列指定", Type:=8)
    
    ' --- ピボット用シート ---
    Set wsP = ThisWorkbook.Sheets.Add
    wsP.Name = "Pivot" & wsP.Index
    
    ' --- ピボット作成 ---
    Set pc = ThisWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:=srcRange)
    Set pt = pc.CreatePivotTable(TableDestination:=wsP.Range("A3"), TableName:="MyPivot")
    
    With pt
        .PivotFields(timeCol.Cells(1, 1).Value).Orientation = xlRowField
        .PivotFields(seriesCol.Cells(1, 1).Value).Orientation = xlColumnField
        .AddDataField .PivotFields(valueCol.Cells(1, 1).Value), "合計", xlSum
    End With
    
    ' --- グラフ種類を入力(カンマ区切り)---
    answer = InputBox("作りたいグラフの種類をカンマ区切りで入力してください。" & vbCrLf & _
                      "例: Line,ColumnClustered,AreaStacked")
    If answer = "" Then Exit Sub
    chartTypes = Split(answer, ",")
    
    ' --- グラフを複数作成 ---
    For i = LBound(chartTypes) To UBound(chartTypes)
        Set co = wsP.ChartObjects.Add(Left:=300, Top:=20 + i * 320, Width:=500, Height:=300)
        co.Chart.SetSourceData Source:=pt.TableRange2
        co.Chart.HasTitle = True
        co.Chart.ChartTitle.Text = "Chart: " & Trim(chartTypes(i))
        
        Select Case Trim(chartTypes(i))
            Case "Line": co.Chart.ChartType = xlLineMarkers
            Case "ColumnClustered": co.Chart.ChartType = xlColumnClustered
            Case "AreaStacked": co.Chart.ChartType = xlAreaStacked
            Case "BarClustered": co.Chart.ChartType = xlBarClustered
            Case "Pie": co.Chart.ChartType = xlPie
            Case Else: co.Chart.ChartType = xlLine
        End Select
    Next i
    
End Sub

ファイルを開いているプロセスを調べる

ファイルを用意してviで開く

[user@localhost ~]$ touch test.txt
[user@localhost ~]$ vi test.txt

fuserで開いているプロセスを確認する

viで開いた状態で別ターミナルからfuserで確認する。
viはファイルそのものではなく、.swpの方を掴んでいる。

[user@localhost ~]$ fuser test.txt
[user@localhost ~]$ 
[user@localhost ~]$ fuser .test.txt.swp
/home/user/.test.txt.swp: 493139

psで確認する

-e: 全ユーザの実行中の全プロセスを表示

[user@localhost ~]$ ps -ef | grep 493139
user      493139  439474  0 16:56 pts/0    00:00:00 /usr/libexec/vi test.txt
user      493200  468583  0 16:57 pts/1    00:00:00 grep --color=auto 493139

Db2 インスタンスの作成/起動

ユーザを作成する

  • インスタンスユーザ: インスタンスを管理する
  • 分離ユーザ (Fence User): ストアドプロシージャとユーザ定義関数を実行する
[root@localhost ~]# adduser myinst
[root@localhost ~]# adduser myfenc

ポートを確認する

使用されていないポート番号を確認する。

[root@localhost instance]# cat /etc/services

インスタンスを作成する

[root@localhost instance]# cd /opt/ibm/db2/V11.5/instance
[root@localhost instance]# ./db2icrt -p 50010 -u myfenc myinst
DBI1446I  The db2icrt command is running.


DB2 installation is being initialized.

 Total number of tasks to be performed: 4 
Total estimated time for all tasks to be performed: 309 second(s) 

Task #1 start
Description: Setting default global profile registry variables 
Estimated time 1 second(s) 
Task #1 end 

Task #2 start
Description: Initializing instance list 
Estimated time 5 second(s) 
Task #2 end 

Task #3 start
Description: Configuring DB2 instances 
Estimated time 300 second(s) 
Task #3 end 

Task #4 start
Description: Updating global profile registry 
Estimated time 3 second(s) 
Task #4 end 

The execution completed successfully.

For more information see the DB2 installation log at "/tmp/db2icrt.log.334967".
DBI1070I  Program db2icrt completed successfully.

作成したインスタンスユーザに切り替える

[root@localhost instance]# su - myinst
Last login: Wed Sep  4 17:25:14 JST 2024 on pts/0

インスタンスを起動する

[myinst@localhost ~]$ db2start
09/04/2024 17:25:33     0   0   SQL1063N  DB2START processing was successful.
SQL1063N  DB2START processing was successful.

grepで該当行の前後を表示する

対象を確認

[root@localhost ~]# ps -aux | head -10
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.3 173860 10100 ?        Ss   Aug12   0:16 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2  0.0  0.0      0     0 ?        S    Aug12   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_gp]
root           4  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_par_gp]
root           5  0.0  0.0      0     0 ?        I<   Aug12   0:00 [slub_flushwq]
root           6  0.0  0.0      0     0 ?        I<   Aug12   0:00 [netns]
root           8  0.0  0.0      0     0 ?        I<   Aug12   0:00 [kworker/0:0H-events_highpri]
root          10  0.0  0.0      0     0 ?        I<   Aug12   0:00 [mm_percpu_wq]
root          12  0.0  0.0      0     0 ?        I    Aug12   0:00 [rcu_tasks_kthre]

前後2行を一緒に表示

[root@localhost ~]# ps -aux | grep -2 rcu_par_gp
root           2  0.0  0.0      0     0 ?        S    Aug12   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_gp]
root           4  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_par_gp]
root           5  0.0  0.0      0     0 ?        I<   Aug12   0:00 [slub_flushwq]
root           6  0.0  0.0      0     0 ?        I<   Aug12   0:00 [netns]
--
省略

前2行を一緒に表示

[root@localhost ~]# ps -aux | grep -B 2 rcu_par_gp
root           2  0.0  0.0      0     0 ?        S    Aug12   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_gp]
root           4  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_par_gp]
--
省略

後ろ2行を一緒に表示

[root@localhost ~]# ps -aux | grep -A 2 rcu_par_gp
root           4  0.0  0.0      0     0 ?        I<   Aug12   0:00 [rcu_par_gp]
root           5  0.0  0.0      0     0 ?        I<   Aug12   0:00 [slub_flushwq]
root           6  0.0  0.0      0     0 ?        I<   Aug12   0:00 [netns]
--省略

Db2 表スペース

表スペース

表やインデックスを格納する領域

表スペースの使用量を見るためのモニターエレメント

  • TBSP_FREE_PAGES::表スペースの空きページ数
  • TBSP_USABLE_PAGES:表スペースの容量

MON_GET_TABLESPACEを使った取得方法

  • Db2 V9.7以降で可能
  • TBSP_FREE_PAGES,TBSP_USABLE_PAGESは、表スペースタイプが「DMS」の場合のみ機能
  • FREE_RATIO:空き容量のパーセンテージを計算した結果
[db2inst1@localhost ~]$ db2 "select varchar(TBSP_NAME,20) as NAME, TBSP_TYPE, TBSP_FREE_PAGES,TBSP_USABLE_PAGES, 100.0*TBSP_FREE_PAGES/TBSP_USABLE_PAGES as FREE_RATIO from table(SYSPROC.MON_GET_TABLESPACE('',-2)) as T"

NAME                 TBSP_TYPE  TBSP_FREE_PAGES      TBSP_USABLE_PAGES       FREE_RATIO                       
-------------------- ---------- -------------------- --------------------    ----------------
SYSCATSPACE          DMS                        3220                20476        15.725727681
TEMPSPACE1           SMS                           0                    1         0.000000000
USERSPACE1           DMS                        2240                 4064        55.118110236
IBMDB2SAMPLEREL      DMS                        3328                 4064        81.889763779
IBMDB2SAMPLEXML      DMS                        2624                 4064        64.566929133
SYSTOOLSPACE         DMS                        3980                 4092        97.262952101

検索結果をヘッダ付きで表示

awkを使用する

  • awk ‘パターン { アクション }’ [ 入力ファイルのパス ]
  • /xxx/: 正規表現はスラッシュの間に入れる
  • NR: 行番号
  • ||: または
  • { print $0 }: その行を表示($0は省略可)
  • $1: 1列目を表示
[root@localhost ~]# ps -u | awk '{print $0}'
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         881  0.0  0.0   3044   256 tty1     Ss+  Aug12   0:00 /sbin/agetty -o -p -- \u --noclear - linux
root      139614  0.0  0.2  13180  6400 pts/0    S    Aug16   0:00 su - root
root      139618  0.0  0.1   5020  3968 pts/0    S    Aug16   0:00 -bash
root      224777  0.0  0.1   7616  3200 pts/0    R+   14:15   0:00 ps -u
root      224778  0.0  0.1   6528  3584 pts/0    S+   14:15   0:00 awk {print $0}

1行目がヘッダの場合

[root@localhost ~]# ps -u | awk 'NR==1 || /bash/ {print}'
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      139618  0.0  0.1   5020  3968 pts/0    S    Aug16   0:00 -bash
root      224590  0.0  0.1   6528  3584 pts/0    S+   14:08   0:00 awk NR==1 || /bash/ {print}

{ action } を省略すると、{ print $0 }と同じアクションとして実行する。

  • /xxx/: /xxx/ { print $0 } と同じ
[root@localhost ~]# ps -u | awk 'NR==1 || /bash/'
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      139618  0.0  0.1   5020  3968 pts/0    S    Aug16   0:00 -bash
root      224279  0.0  0.1   6528  3584 pts/0    S+   13:55   0:00 awk NR==1 || /bash/