SqlBulkCopy では、Decimal 型で桁落ちをする

SqlBulkCopy のスピードは 20 倍ぐらい早い | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2234

なところで、SQL Server へのインサートを高速化したのですが、実はちょっとした落とし穴があります(落とし穴、というよりも「仕様」っぽいのですが)。

SQL Server で Decimal 型を使うと、Oracle の nameric 型のように精度を指定できます。たとえば decimal(18,4) と設定すると、小数点以下第4桁までの精度になります。

この場合、「0.12345」という値を入れようとすると、クエリでは「0.1235」のように最後の桁で四捨五入されるのですが、SqlBulkCopy を使うと「0.1234」のように切り捨てになる、という違いがあります。

CREATE TABLE [dbo].[t_dec](
	[dec] [decimal](18, 4) NULL,
	[dbl] [float] NULL
) ON [PRIMARY]

のようにテーブルを作成しておいて、データベースに書き込みます。

''' <summary>
''' BulkCopy で書き込み
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click

	Dim dec As Decimal = 0.1234567890123456789012345679D
	Dim dbl As Decimal = 0.12345678901234559
	Dim dt As New DataTable

	dt.Columns.Add("dec", GetType(Decimal))
	dt.Columns.Add("dbl", GetType(Double))
	Dim row As DataRow = dt.NewRow
	row("dec") = dec
	row("dbl") = dbl
	dt.Rows.Add(row)

	Dim cn As New SqlConnection(CNSTR)
	Dim bc As New SqlBulkCopy(cn)
	cn.Open()
	bc.DestinationTableName = "t_dec"
	bc.WriteToServer(dt)
	cn.Close()
	bc.Close()

	' decimal(18,4) で指定していると、dec = 0.1234 となり切り捨てとなる

	Dim dt2 As New DataTable
	Dim da As New SqlDataAdapter("SELECT * FROM t_dec", cn)
	da.Fill(dt2)
	DataGridView1.DataSource = dt2


End Sub

''' <summary>
''' SqlCommand で書き込み
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Button4_Click(sender As System.Object, e As System.EventArgs) Handles Button4.Click

	Dim dec As Decimal = 0.1234567890123456789012345679D
	Dim dbl As Decimal = 0.12345678901234559

	Dim cn As New SqlConnection(CNSTR)
	Dim cmd As New SqlCommand("INSERT INTO t_dec values ( @DEC, @DBL )", cn)
	cmd.Parameters.Add(New SqlParameter("@DEC", dec))
	cmd.Parameters.Add(New SqlParameter("@DBL", dbl))

	cn.Open()
	cmd.ExecuteNonQuery()
	cn.Close()

	' decimal(18,4) で指定していると、dec = 0.1235 となり四捨五入される

	Dim dt2 As New DataTable
	Dim da As New SqlDataAdapter("SELECT * FROM t_dec", cn)
	da.Fill(dt2)
	DataGridView1.DataSource = dt2
End Sub

こうすると、SqlCommand の場合は「0.1235」のように四捨五入されるのですが、SqlBulkCopy の場合は「0.1234」のように切り捨てられます。

なので、仕方がないので(?)、四捨五入されるように「0.00005D」を加算します(末尾の「D」は、decimal 型の印です)

''' <summary>
''' decimal の精度に合わせて 0.00005 を加算する
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Button5_Click(sender As System.Object, e As System.EventArgs) Handles Button5.Click

	Dim dec As Decimal = 0.1234567890123456789012345679D
	Dim dbl As Decimal = 0.12345678901234559
	Dim dt As New DataTable

	dt.Columns.Add("dec", GetType(Decimal))
	dt.Columns.Add("dbl", GetType(Double))
	Dim row As DataRow = dt.NewRow
	row("dec") = dec + 0.00005D	' 四捨五入させる
	row("dbl") = dbl
	dt.Rows.Add(row)

	Dim cn As New SqlConnection(CNSTR)
	Dim bc As New SqlBulkCopy(cn)
	cn.Open()
	bc.DestinationTableName = "t_dec"
	bc.WriteToServer(dt)
	cn.Close()
	bc.Close()

	' BulkCopy だと切り捨てになるので、
	' decimal(18,4) の場合は 0.00005D を加えて、
	' dec = 0.1235 のように四捨五入にする

	Dim dt2 As New DataTable
	Dim da As New SqlDataAdapter("SELECT * FROM t_dec", cn)
	da.Fill(dt2)
	DataGridView1.DataSource = dt2

End Sub

さて、これを組み込みかどうかを思案中。

カテゴリー: 開発, VB パーマリンク