自分用のメモとして。
PowerShellよりSQLite3を呼び出す関数を作成し、PowerShellを機能強化してみる。
内容は随時追加予定。
事前準備
1.SQLite3のインストール
※詳細は「SQLite 3.8.7.1のダウンロードとインストール」を参考
ちなみに本記事では、SQLite3は「version 3.9.2」を使用
2.sqlite3.exeの格納フォルダを環境変数PATHに登録
ヘルパー関数
【InvokeSQLite3Script関数(自作関数)】
・SQLite3スクリプトを実行する
・実行はファイル経由(コマンド直接実行でINSERTすると文字化けした為)
・後述のサンプルで使う為、宣言しておく必要がある
# string -> string
filter InvokeSQLite3Script($DatabasePath = ''){
$readfile = [System.IO.Path]::GetTempFileName()
$outputfile = [System.IO.Path]::GetTempFileName()
;[System.IO.File]::WriteAllText($readfile,$_) #UTF-8
@"
.mode csv
.header on
.output $($outputfile.replace('\','/'))
.read $($readfile.replace('\','/'))
"@ | sqlite3 $DatabasePath
;[System.IO.File]::ReadAllText($outputfile) #UTF-8
del $readfile
del $outputfile
}
# 例1:test.dbにて実行
$testdata = 'temp\test.db'
"create table test(id,name);" | InvokeSQLite3Script $testdata | Out-Null
"insert into test values(1,'太郎');" | InvokeSQLite3Script $testdata | Out-Null
"insert into test values(2,'花子');" | InvokeSQLite3Script $testdata | Out-Null
"insert into test values(10,'ジョン');" | InvokeSQLite3Script $testdata | Out-Null
"select * from test where id < 10;" | InvokeSQLite3Script $testdata
# -> id,name
# 1,"太郎"
# 2,"花子"
# 例2:オンメモリで実行
@"
create table test(id,name);
insert into test values(1,'太郎さん');
insert into test values(2,'花子さん');
insert into test values(10,'ジョンさん');
select * from test where id < 10;
"@ | InvokeSQLite3Script
# -> id,name
# 1,"太郎さん"
# 2,"花子さん"
【SplitLine関数(自作関数)】
・行を分割し配列で返す
・改行コードがCR+LF(Windows)とLF(Linux)の両方の場合を考慮
・Firstスイッチ:1行目とそれ以降で分ける
・他のスイッチは、今後、必要に応じて追加する予定
・後述のサンプルで使う為、宣言しておく必要がある
# string -> string,string
filter SplitLine{
param(
[switch]$First
)
if($First.IsPresent){
$_.Substring(0,$_.IndexOfAny("`r`n")),$_.Substring($_.IndexOf("`n") + 1)
}
}
$head,$tail = [System.IO.File]::ReadAllText((Resolve-Path filelist.csv)) | SplitLine -First
$head
# -> "Name","LastWriteTime","Extension"
$tail
# -> "cmd","2015/11/16 0:21:02",""
# "html","2015/11/08 8:57:54",""
# "php","2015/11/14 21:32:57",""
# "powershell","2015/11/23 14:52:29",""
# "svn","2015/11/08 12:27:15",""
# "vba","2015/11/18 22:51:29",""
# "wsh","2015/11/17 22:20:53",""
# "data_win.csv","2015/11/23 1:26:15",".csv"
# "filelist.csv","2015/11/23 17:53:32",".csv"
# "utf8text.txt","2015/11/23 13:47:40",".txt"
"Name","LastWriteTime","Extension"
"cmd","2015/11/16 0:21:02",""
"html","2015/11/08 8:57:54",""
"php","2015/11/14 21:32:57",""
"powershell","2015/11/23 14:52:29",""
"svn","2015/11/08 12:27:15",""
"vba","2015/11/18 22:51:29",""
"wsh","2015/11/17 22:20:53",""
"data_win.csv","2015/11/23 1:26:15",".csv"
"filelist.csv","2015/11/23 17:53:32",".csv"
"utf8text.txt","2015/11/23 13:47:40",".txt"
【MeasureCsv関数(自作関数)】
・CSV形式の文字列から、行数や列数の情報を取得する
・「"」(ダブルクオーテーション)で囲まれている「,」(カンマ)や改行コード等も考慮
・列数は1行目の列数を取得(処理速度を優先する為)
・後述のサンプルで使う為、宣言しておく必要がある
# string -> object
filter MeasureCsv{
$rows = ($_.Trim() -replace '"(([^"]|"")*)"','"X"') -split "`n"
@{
RowsCount = $rows.Length;
ColumnsCount = ($rows[0] -split ',').Length;
}
}
$testdata = 'data\KEN_ALL.CSV'
$encoding = [System.Text.Encoding]::GetEncoding("shift_jis")
;[System.IO.File]::ReadAllText((Resolve-Path $testdata),$encoding) | MeasureCsv
# -> Name Value
# ---- -----
# ColumnsCount 15
# RowsCount 123864
$testdata = 'data\KEN_ALL_dummy.CSV'
$encoding = [System.Text.Encoding]::GetEncoding("shift_jis")
;[System.IO.File]::ReadAllText((Resolve-Path $testdata),$encoding) | MeasureCsv
# -> Name Value
# ---- -----
# ColumnsCount 15
# RowsCount 4
テストデータその1
(日本郵便株式会社のサイトより入手)
01101,"060 ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
01101,"064 ","0640941","ホッカイドウ","サッポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ケ丘",0,0,1,0,0,0
~ 中略 ~
47382,"90718","9071800","オキナワケン","ヤエヤマグンヨナグニチョウ","イカニケイサイガナイバアイ","沖縄県","八重山郡与那国町","以下に掲載がない場合",0,0,0,0,0,0
47382,"90718","9071801","オキナワケン","ヤエヤマグンヨナグニチョウ","ヨナグニ","沖縄県","八重山郡与那国町","与那国",0,0,0,0,0,0
テストデータその2
(その1より抜粋し、「"」(ダブルクオーテーション)内に「,」(カンマ)や改行コード等を混ぜてみた)
01101,"060 ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY1,"060 ","0600000","ホッカイドウ","サッポロシチュウ
オウク","イカニケイサイガ
ナイバアイ","北海道""","札幌市,中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY2,"060 ","0600000","ホッカイドウ","サッホ
゚ロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
01101,"060 ","0600031","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウヒガシ","北海道","札幌市中央区","北一条東",0,0,1,0,0,0
ファイル操作系
【ReadCsv関数(自作関数)】
ReadCsv関数は少し仕様が濃いので、詳しくは後日、別記事にて解説予定。
今回は一部のみ解説。
・CSVファイルをSQLite3データベース(DatabasePathで指定)に取り込む
・DatabasePath未指定の場合は、オンメモリ実行
※CSV取り込み後の実行クエリをQuery引数にて指定することで結果取得可能
・CSVファイルは複数指定可能
・テーブル名は自動取得(ファイル名の「.」(ドット)より前部分)
・列名については以下のように取得/生成される
・ヘッダありモード(デフォルト)→ CSVファイルの1行目より取得
・ヘッダなしモード → 自動採番(_1,_2 ... )
・CSV取り込み後の実行クエリをQuery引数にて複数指定可能
・CSVファイルの文字コードを指定可能(ファイル毎に指定可能)
省略時はUTF-8が指定される
# string[] -> string
function ReadCsv{
param(
$DatabasePath = '', #単一指定
$Query = '', #複数指定可能
$Encoding = '', #複数指定可能
[switch]$NoHeader
)
$defaultEncoding = 'utf-8'
$tempFileList = @()
$tableNameList = @()
$createSql = ''
$importCmd = ''
# SQL文生成
$csv_i = 0
$input | foreach{
# CSVファイルの文字コードの指定
$objEncoding = `
switch($Encoding.GetType().Name){
# 個別指定
'Object[]' {
if($csv_i -lt $Encoding.Length){
[System.Text.Encoding]::GetEncoding($Encoding[$csv_i])
} else {
[System.Text.Encoding]::GetEncoding($defaultEncoding)
}
# 一括指定
} 'String' {
if($Encoding -eq ''){
[System.Text.Encoding]::GetEncoding($defaultEncoding)
} else {
[System.Text.Encoding]::GetEncoding($Encoding)
}
}
}
# .NetクラスのファイルI/Oではフルパスの指定が必要
$absolutePath = Resolve-Path $_
$tableName = (Split-Path $_ -Leaf).split('.')[0]
# ヘッダなしモード
if($NoHeader.IsPresent){
# CSVファイルの読み込み
$data = [System.IO.File]::ReadAllText($absolutePath,$objEncoding)
# 列名の自動採番
$col_n = ($data | MeasureCsv).ColumnsCount
$colNames = (1..$col_n | foreach{'_' + $_}) -join ','
# ヘッダありモード
} else {
# CSVファイルの内容を列名とデータに分割
$colNames,$data = [System.IO.File]::ReadAllText($absolutePath,$objEncoding) | SplitLine -First
}
# インポートファイル生成
$importfile = [System.IO.Path]::GetTempFileName()
$tempFileList += $importfile
[System.IO.File]::WriteAllText($importFile,$data) #UTF-8
# CREATE文生成
# テーブル名が被った場合は先勝ち(初めのCSVの定義を優先する)
if(!$tableNameList.Contains($tableName)){
$tableNameList += $tableName
$createSql += "CREATE TABLE $tableName ($($colNames.Replace('"','')));" + "`n"
}
# importコマンド生成
$importCmd += ".import $($importFile.replace('\','/')) $tableName" + "`n"
$csv_i++
}
# SQLite3の実行
@"
$createSql
$importCmd
$($Query -join "`n")
"@ | InvokeSQLite3Script $DatabasePath
# 一時ファイルの削除
$tempFileList | foreach{del $_}
}
$testdata1_1 = 'data\KEN_ALL_dummy.CSV'
$testdata1_2 = 'data\KEN_ALL_dummy_euc.CSV'
$testdata2 = 'temp\test.db'
# 実行
$result = & {
$testdata1_1,$testdata1_2 | ReadCsv -DatabasePath $testdata2 `
-NoHeader `
-Encoding @('shift_jis','euc-jp') `
-Query @'
select
euc._1,
euc._3,
euc._5 as euc_5,
euc._7 as euc_7,
sjis._5 as sjis_5,
sjis._7 as sjis_7
from KEN_ALL_dummy_euc as euc,
KEN_ALL_dummy as sjis
where euc._1 = sjis._1
and euc._3 = sjis._3
;
'@
}
# 確認
$result | ConvertFrom-Csv | Format-List
# -> _1 : 01101
# _3 : 0600000
# euc_5 : サッポロシチュウオウク
# euc_7 : 北海道
# sjis_5 : サッポロシチュウオウク
# sjis_7 : 北海道
#
# _1 : DUMMY1
# _3 : 0600000
# euc_5 : サッポロシチュウ
# オウク
# euc_7 : 北海道"
# sjis_5 : サッポロシチュウ
# オウク
# sjis_7 : 北海道"
#
# _1 : DUMMY2
# _3 : 0600000
# euc_5 : サッホ
# ゚ロシチュウオウク
# euc_7 : 北海道
# sjis_5 : サッホ
# ゚ロシチュウオウク
# sjis_7 : 北海道
#
# _1 : 01101
# _3 : 0600031
# euc_5 : サッポロシチュウオウク
# euc_7 : 北海道
# sjis_5 : サッポロシチュウオウク
# sjis_7 : 北海道
テストデータ
01101,"060 ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY1,"060 ","0600000","ホッカイドウ","サッポロシチュウ
オウク","イカニケイサイガ
ナイバアイ","北海道""","札幌市,中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY2,"060 ","0600000","ホッカイドウ","サッホ
゚ロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
01101,"060 ","0600031","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウヒガシ","北海道","札幌市中央区","北一条東",0,0,1,0,0,0
01101,"060 ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY1,"060 ","0600000","ホッカイドウ","サッポロシチュウ
オウク","イカニケイサイガ
ナイバアイ","北海道""","札幌市,中央区","以下に掲載がない場合",0,0,0,0,0,0
DUMMY2,"060 ","0600000","ホッカイドウ","サッホ
゚ロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
01101,"060 ","0600031","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウヒガシ","北海道","札幌市中央区","北一条東",0,0,1,0,0,0