Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sql Bulk Copy Truncating Decimal

When I insert decimal values into Sql Server 2005 from a C# DataTable using bulk copy the values get truncated instead of rounded.

  • The data type in the DataTable is Decimal.
  • The data type in the database is Decimal(19,3)
  • The value in the DataTable is 1.0005
  • The value insert in the database is 1.000 (I expected 1.001)

The code I'm using is pretty simple:

var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, null) { DestinationTableName = tableName};
bulkCopy.WriteToServer(dataTable);

Does anyone know how to fix this?

like image 279
Keith Harrison Avatar asked May 09 '14 13:05

Keith Harrison


People also ask

How do you get rid of extra decimals in SQL?

The TRUNCATE() function truncates a number to the specified number of decimal places.

How do you store decimals in SQL?

Standard SQL requires that DECIMAL(5,2) be able to store any value with five digits and two decimals, so values that can be stored in the salary column range from -999.99 to 999.99 . In standard SQL, the syntax DECIMAL( M ) is equivalent to DECIMAL( M ,0) .

What is SqlBulkCopy in SQL?

The SqlBulkCopy class lets you write managed code solutions that provide similar functionality. There are other ways to load data into a SQL Server table (INSERT statements, for example), but SqlBulkCopy offers a significant performance advantage over them.

Can integers be decimals in SQL?

As we know from before, integers are whole numbers, or numbers with no fractions (i.e. no decimal places). This is going to be in the test later, so pay attention. In other words, the numbers 0 through 9 are integers, but a floating point or decimal / numeric value is not an integer.


1 Answers

According to the reference source, SqlBulkCopy always truncates decimal values instead of rounding, which unfortunately differs from the behavior of the BULK INSERT statement.

The private ConvertValue method calls TdsParser.AdjustSqlDecimalScale if the scale of the source value differs from the scale of the destination column:

switch(type.NullableType) {
    case TdsEnums.SQLNUMERICN:
    case TdsEnums.SQLDECIMALN:
        // ...

        if (sqlValue.Scale != metadata.scale) {                            
            sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, metadata.scale);  
        }

AdjustSqlDecimalScale in turn calls SqlDecimal.AdjustScale, passing false for fRound:

static internal SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) {
    if (d.Scale != newScale) {
        return SqlDecimal.AdjustScale(d, newScale - d.Scale, false /* Don't round, truncate.  MDAC 69229 */);
    }

    return d;
}

There's apparently no way to override this behavior and pass true to AdjustScale, so if you want to use SqlBulkCopy, you will need to round the values in the DataTable yourself before calling WriteToServer.

Alternatively, you could write the data to a file and execute BULK INSERT directly, forgoing SqlBulkCopy.

like image 59
Michael Liu Avatar answered Oct 06 '22 00:10

Michael Liu