概要

  • MS SQL Server のテーブルを CSV ファイル(K3フォーマット)としてインポート/エクスポートする。
  • 第1フィールドをプライマリフィールドとして、レコードが未登録か登録済みかを判別し、INSERT または UPDATE を行なう。
  • NULLは「NULL」として書き出される。文字列としてのNULLは「"NULL"」になる。
  • INDEX(オートナンバー)制約があるフィールドには未対応(ハングアップする)。(2011/09/28)

ソースコード

DB_Connect.yaml

# DB接続情報
Driver:   ODBC
Server:   localhost\SQLEXPRESS,1433 # <サーバ名>\<インスタンス名>[,<ポート番号>]
User:     TestUser
Password: "TestPass"  # 記号を含む場合は""で括る。
DB:       TestDB
Options:
    LongTruncOk: 1
    LongReadLen: 8192
DSN:      _Driver_:Driver={SQL Server}; Server={_Server_}; Database=_DB_;

getSchema.pl

#!/usr/bin/perl
# MSSQLサーバからスキーマを読み出す

use strict;
use warnings;
use utf8;
use Encode;
use YAML::Syck;
use FindBin::libs;
use DBIx::NamedParams;

$YAML::Syck::ImplicitUnicode = 1;

my $charsetConsole	= 'CP932';
my $charsetFile		= 'UTF-8';

binmode( STDIN,  ":encoding($charsetConsole)" );
binmode( STDOUT, ":encoding($charsetConsole)" );
binmode( STDERR, ":encoding($charsetConsole)" );

my $DB_Connect_yaml = './DB_Connect.yaml';
my $DB_Schema_yaml  = './DB_Schema.yaml';

my @SchemaColumns = qw( 
	COLUMN_NAME DATA_TYPE CHARACTER_MAXIMUM_LENGTH COLUMN_DEFAULT IS_NULLABLE
);

$| = 1;

my $Schemas = {};

my $DB_Connect = YAML::Syck::LoadFile( $DB_Connect_yaml ) 
	or die( "$DB_Connect_yaml: $!" );
foreach( keys( %{$DB_Connect} ) ){
	$DB_Connect->{'DSN'} =~ s/_${_}_/$DB_Connect->{$_}/;
}
#print Dump( $DB_Connect );
#exit;

my $dbh = DBI->connect( 
	"DBI:" . $DB_Connect->{'DSN'}, 
	$DB_Connect->{'User'}, 
	$DB_Connect->{'Password'}, 
	$DB_Connect->{'Options'} 
) or die( "$DBI::errstr\n" );
#print Dump( $dbh );
#exit;

my $DBName = $DB_Connect->{'DB'};
my $sql_getTables = qq{
	SELECT		[TABLE_NAME]
	FROM		[INFORMATION_SCHEMA].[TABLES]
	WHERE		[TABLE_CATALOG] = '$DBName' 
				and [TABLE_TYPE]='BASE TABLE' 
	ORDER BY	[TABLE_NAME];
};
my $sth = $dbh->prepare( $sql_getTables ) or die( "$DBI::errstr\n" );
$sth->execute() or die( "$DBI::errstr\n" );

do {
	while( my @a = $sth->fetchrow_array ){
		#printf( "%s\n", join( "\t", @a ) );
		$Schemas->{ $a[0] } = [];
	}
}while( $sth->{odbc_more_results} );
$sth->finish;

my @TableNames = sort( keys( %{ $Schemas } ) );
#print "Tables: " . scalar(@TableNames) ."\n";
#print join( "\n", DBIx::NamedParams::all_sql_types() ) . "\n";
#exit;

$_ = '[' . join( "], [", @SchemaColumns ) . ']';
my $sql_getColumns = qq{
	SELECT	$_
	FROM	[INFORMATION_SCHEMA].[COLUMNS]
	WHERE	[TABLE_CATALOG] = '$DBName' 
			and [TABLE_NAME] = :TableName-WVARCHAR
};
$sth = $dbh->prepare_ex( $sql_getColumns ) or die( "$DBI::errstr\n" );

my $count = 0;
foreach my $tbl ( @TableNames ){
	printf( "%d/%d\t%s\n", ++$count, scalar(@TableNames), $tbl );
	my @columns = ();
	$sth->bind_param_ex( { 'TableName' => $tbl } );
	$sth->execute() or die( "$DBI::errstr\n" );
	do {
		while( my @a = $sth->fetchrow_array ){
			#printf( "%s\n", join( "\t", @a ) );
			my $ColumnsInfo = {};
			for( my $i=0; $i<@SchemaColumns; ++$i ){
				$ColumnsInfo->{ $SchemaColumns[ $i ] } = $a[ $i ];
			}
			push( @columns, $ColumnsInfo );
		}
	}while( $sth->{odbc_more_results} );
	$Schemas->{ $tbl } = [ @columns ];
}
$sth->finish;

$dbh->disconnect;

open( my $fhout, ">:utf8", encode( $charsetConsole, $DB_Schema_yaml ) ) 
	or die( "$DB_Schema_yaml: $!\n" );
print $fhout YAML::Syck::Dump( $Schemas );
#print dump( $Schemas );
close( $fhout );

# EOF

getTbl.pl

#!/usr/bin/perl
# MSSQLサーバからデータ読み出し

use strict;
use warnings;
use utf8;
use Encode;
use YAML::Syck;
use FindBin::libs;
use DBIx::NamedParams;
use EscapeSlash;

$YAML::Syck::ImplicitUnicode = 1;

my $charsetConsole	= 'CP932';
my $charsetFile		= 'UTF-8';

binmode( STDIN,  ":encoding($charsetConsole)" );
binmode( STDOUT, ":encoding($charsetConsole)" );
binmode( STDERR, ":encoding($charsetConsole)" );

my $DB_Connect_yaml = './DB_Connect.yaml';
my $DB_Tables_yaml  = './DB_Schema.yaml';

# ""で括る文字列型
my $StrType = join( "|", qw( 
	CHAR GUID UNKNOWN_TYPE VARCHAR WCHAR WLONGVARCHAR WVARCHAR
) );

my $DB_Connect = YAML::Syck::LoadFile( $DB_Connect_yaml ) 
	or die( "$DB_Connect_yaml: $!\n" );
foreach( %{$DB_Connect} ){
	$DB_Connect->{'DSN'} =~ s/_${_}_/$DB_Connect->{$_}/;
}
#print dump( $DB_Connect );

my $DB_Tables = YAML::Syck::LoadFile( $DB_Tables_yaml ) 
	or die( "$DB_Tables_yaml: $!\n" );
#print dump( $DB_Tables );
my @DB_TableNames = sort( keys( %{$DB_Tables} ) );

my $dbh = DBI->connect( 
	"DBI:" . $DB_Connect->{'DSN'}, 
	$DB_Connect->{'User'}, 
	$DB_Connect->{'Password'}, 
	$DB_Connect->{'Options'} 
) or die( "$DBI::errstr\n" );

my %DrvTypeToSQLType = $dbh->driver_typename_map();

my $count=0;
my $TableNum = scalar(@DB_TableNames);

foreach my $tbl ( @DB_TableNames ){
	printf STDERR ( "%d/%d\t%s\n", ++$count, $TableNum, $tbl );
	my @FieldName = map { ${$_ }{'COLUMN_NAME'}; } @{ $DB_Tables->{ $tbl } };
	my @DataType = map { ${$_ }{'DATA_TYPE'}; } @{ $DB_Tables->{ $tbl } };

	my $sql = "SELECT [" . join( "],[", @FieldName ) . "] FROM [$tbl];";
	my $sth = $dbh->prepare( $sql ) or die( "$DBI::errstr\n" );
	$sth->execute() or die( "$DBI::errstr\n" );

	my $fout = './_DB/' . $tbl . '.txt';
	open( my $fhout, ">:encoding($charsetFile)", encode( $charsetConsole, $fout ) ) 
		or die( "$fout: $!\n" );

	printf $fhout ( "\"%s\"\n", join( "\"\t\"", @FieldName ) );
	do {
		while( my @a = $sth->fetchrow_array ){
			for( my $i=0; $i<@a; ++$i ){
				no warnings 'uninitialized';
				$a[$i] =~ s/\x00//g;
				#$a[$i] =~ s/[\r\n\t]+$//;
				$a[$i] = escapeslash( $a[$i] );
				if ( !defined($a[$i]) ){
					# NULL だったら
					$a[$i] = "NULL";
				} elsif ( $DrvTypeToSQLType{ $DataType[$i] } =~ /$StrType/ ){
					# 文字列ならば
					$a[$i] = '"' . $a[$i] . '"';
				}
			}
			printf $fhout ( "%s\n", join( "\t", @a ) );
		}
	}while( $sth->{odbc_more_results} );
	$sth->finish;

	close( $fhout );
}

$dbh->disconnect;

# EOF

setTbl.pl

#!/usr/bin/perl
# MSSQLサーバへデータ書込み
# 2011/09/28	オートナンバー型非対応

use strict;
use warnings;
use utf8;
use Encode;
use YAML::Syck;
use FindBin::libs;
use DBIx::NamedParams;
use EscapeSlash;

$YAML::Syck::ImplicitUnicode = 1;

my $charsetConsole	= 'CP932';
my $charsetFile		= 'UTF-8';

binmode( STDIN,  ":encoding($charsetConsole)" );
binmode( STDOUT, ":encoding($charsetConsole)" );
binmode( STDERR, ":encoding($charsetConsole)" );

my $DB_Connect_yaml = './DB_Connect.yaml';
my $DB_Tables_yaml  = './DB_Schema.yaml';

my $DB_Connect = YAML::Syck::LoadFile( $DB_Connect_yaml ) 
	or die( "$DB_Connect_yaml: $!" );
foreach( %{$DB_Connect} ){
	$DB_Connect->{'DSN'} =~ s/_${_}_/$DB_Connect->{$_}/;
}
#print Dump( $DB_Connect );

my $DB_Tables = YAML::Syck::LoadFile( $DB_Tables_yaml ) 
	or die( "$DB_Tables_yaml: $!" );
#print Dump( $DB_Tables );
my @DB_TableNames = keys( %{$DB_Tables} );

my $dbh = DBI->connect( 
	"DBI:" . $DB_Connect->{'DSN'}, 
	$DB_Connect->{'User'}, 
	$DB_Connect->{'Password'}, 
	$DB_Connect->{'Options'} 
) or die( "$DBI::errstr\n" );

#DBIx::NamedParams::debug_log( 'MSSQL_ImpExp.log' );

my %DrvTypeToSQLType = $dbh->driver_typename_map();

my $count=0;
my $TableNum = scalar(@DB_TableNames);
my $fNoLocalize = 0;

foreach my $tbl ( @DB_TableNames ){
	++$count;
	print "$count/$TableNum\t$tbl\n";
	my( @field_org, @field_len, @field_type, @field_name_type, @field_prm );
	foreach ( @{ $DB_Tables->{ $tbl } } ){
		my $column_name = ${ $_ }{'COLUMN_NAME'};
		my $data_type = ${ $_ }{'DATA_TYPE'};
		my $name_type = ":$column_name-" . $DrvTypeToSQLType{ $data_type };
		push( @field_org,	$column_name );
		push( @field_type,	$data_type );
		push( @field_len,	${ $_ }{'CHARACTER_MAXIMUM_LENGTH'} );
		push( @field_name_type, $name_type );
		push( @field_prm, "[$column_name]=$name_type" );
	}
	my $i = 0;
	my %FN2I = map{ s/^"(.*)"$/$1/; $_ => $i++; } @field_org;
	#print Dump( \%FN2I );

	my $FieldList  = "[" . join( "],[", @field_org ) . "]";
	my $ValueList  = join( ",", @field_name_type );
	my $UpdateList = join( ",", @field_prm );

	my $PrimaryFieldName		= $field_org[0];
	my $PrimaryFieldNameType	= $field_name_type[0];
	my $sql_InsOrUpd = qq{
		IF NOT EXISTS (
			SELECT	$FieldList 
			FROM	[$tbl] 
			WHERE	[$PrimaryFieldName] = $PrimaryFieldNameType
		)
		BEGIN
			INSERT INTO [$tbl]
			($FieldList)
			VALUES ($ValueList)
		END
		ELSE
		BEGIN
			UPDATE	[$tbl]
			SET		$UpdateList
			WHERE	[$PrimaryFieldName] = $PrimaryFieldNameType
		END;
	};
	#warn "$sql_InsOrUpd\n";
	#exit();

	my $sth = $dbh->prepare_ex( $sql_InsOrUpd ) 
		or die( "$DBI::errstr\n" );

	my $fin = './_DB/' . $tbl . '.txt';
	open( my $fhin, "<:encoding($charsetFile)", encode( $charsetConsole, $fin ) ) 
		or die( "$fin: $!\n" );
	<$fhin>;	# ヘッダ行を捨てる
	my @DB_Body = <$fhin>;
	close( $fhin );
	chomp( @DB_Body );

	foreach my $row ( @DB_Body ){
		#warn $row."\n";
		my @param = split( "\t", $row );
		map{ if( /^NULL$/i ){ undef($_) } else { s/^"(.*)"$/$1/; } } @param;
		for( my $i=0; $i<@param; ++$i ){
			if ( 
				$field_len[ $i ] && ( $field_len[ $i ] > 0 ) 
				&& $param[ $i ] && ( length( $param[ $i ] ) > $field_len[ $i ] ) 
			){
				print "Too long. ID:$param[0], $field_org[$i]: $param[$i]\n";
				$param[ $i ] = substr( $param[ $i ], 0, $field_len[ $i ] );
			}
		}
		my %bind = ();
		for( my $i=0; $i<@field_org; ++$i ){
			$bind{ $field_org[ $i ] } = unescapeslash( $param[ $i ] );
		}
		#print Dump( \%bind ) ."\n";
		$sth->bind_param_ex( \%bind );
		#exit();
		$sth->execute() or die( "$DBI::errstr\n" );
	}
	$sth->finish;
}
$dbh->disconnect;

# EOF

makeTable.sql

USE [TestDB]
GO

/****** Object:  Table [dbo].[会員Table]    Script Date: 08/15/2009 04:13:13 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[会員Table](
	[ID] [int] NOT NULL,
	[] [nvarchar](50) NOT NULL,
	[] [nvarchar](50) NOT NULL,
	[誕生日] [date] NOT NULL,
	[性別] [int] NOT NULL,
	[Email] [nvarchar](50) NULL,
	[電話番号] [nvarchar](50) NULL,
	[郵便番号] [nvarchar](8) NULL,
	[住所1] [nvarchar](max) NULL,
	[住所2] [nvarchar](max) NULL,
	[クラスレベル] [int] NOT NULL,
	[前回ログイン] [datetime] NULL,
	[備考] [nvarchar](max) NULL,
 CONSTRAINT [PK_会員Table] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

/****** Object:  Table [dbo].[性別Table]    Script Date: 08/15/2009 04:13:59 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[性別Table](
	[ID] [int] NOT NULL,
	[性別] [nvarchar](10) NOT NULL,
 CONSTRAINT [PK_性別Table] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

/****** Object:  Table [dbo].[クラスレベルTable]    Script Date: 08/15/2009 04:14:09 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[クラスレベルTable](
	[ID] [int] NOT NULL,
	[クラスレベル] [nvarchar](30) NOT NULL,
 CONSTRAINT [PK_クラスレベルTable] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Link