0

I am doing a bulk copy of program variables to a SQL Server 2005 table and I use bcp_bind and bcp_sendrow in a C++ application

However, some of the integer columns in the table allow null values. I'm not quite sure how I should be sending null values at runtime using bcp_sendrow when the need arises to send a null value for one of the columns.

Documentation on bcp_bind says:

To bulk copy a row to the server containing a NULL value for the bound column, the value of the instance's iIndicator member should be set to SQL_NULL_DATA

However this still doesn't make things quite clear. Am I supposed to have 2 different calls to bcp_sendrow: one for the not null case and another for the null case?

pb2q
  • 58,613
  • 19
  • 146
  • 147
dingo
  • 1
  • 4

1 Answers1

0

The SQL server bulk copy interface has two distinct methods for declaring null values:

  1. You can specify SQL_NULL_DATA as the cbData parameter to bcp_bind. This indicates that all rows sent to the server will be NULLs

  2. You can use an indicator variable to say when the column contains an integer and when it contains NULL. To do this, you need to set aside space immediately before your int for a 64-bit indicator variable. The documentation shows this in a small struct, like this:

    typedef struct tagBCPBOUNDINT
    {
    __int64 iIndicator;
    int Value;
    } BCPBOUNDINT;
    

    (I've changed the iIndicator to an explicit 64-bit integer here; it's important that it not be shorter.)

    When you call bcp_bind, you point to the struct, rather than the value, and you specify that you are using an 8-byte indicator, and that the data length is variable.

    bcp_bind(hdbc, &local_struct, 8, SQL_VARLEN_DATA, NULL, 0, SQLINT4, colIndex);
    

    To send a row with data in this column, set iIndicator to the size of the integer field (4), and put the value in Value. If the column should be null, then set iIndicator to SQL_NULL_DATA and don't worry about the value:

    if (my_int_is_null) {
        local_struct.iIndicator = SQL_NULL_DATA;
    } else {
        local_struct.iIndicator = sizeof(int);
        local_struct.Value = my_int_value;
    }
    /* Fill the rest of the columns in a similar way */
    /* ... */
    /* finally, queue the row */
    bcp_sendrow(hdbc);
    
Ian Clelland
  • 43,011
  • 8
  • 86
  • 87
  • thanks Ian. I used an SQLUSMALLINT and had to cast the address to a BYTE* `bcp_bind(m_hdbc, (BYTE*)&mystruct, 8, SQL_VARLEN_DATA, NULL, (INT)NULL, SQLINT1, colno)` Haven't tested it as yet but I hope it will work – dingo Oct 10 '12 at 01:42
  • I'm a bit skeptical about the __int64, given the fact that I have to cast the structure to a unsigned char* as required by bcp_bind – dingo Oct 10 '12 at 02:20
  • Yes, but you have to cast everything to `unsigned char *`, regardless of the data types. The important thing is that you reserve 8 bytes for the indicator value, if you want to be able to use `SQL_NULL_DATA` in it. – Ian Clelland Oct 10 '12 at 05:20
  • I did reserve 8 bytes but for some reason bcp_sendrow() failed. Let me try and post the code – dingo Oct 11 '12 at 01:28
  • markdown is not working and I just can't grapple with it now since I have to meet a deadline. If you can copy out and take a look it would be great. There are 2 for loops one for binding and the other for the actual inserting before I call bcp_sendrow() – dingo Oct 11 '12 at 01:44
  • typedef struct tBCPBINDINT { __int64 iIndicator; SQLUSMALLINT Value; } BCPBINDINT; BCPBINDINT mystruct[MAX]; for(int colno=0;colno – dingo Oct 11 '12 at 01:46
  • First, columns are numbered from 1, not 0. You might want to use `SQLColumns` to see what order they're in in the DB. Second, you need to do more than `break` if a call to `bcp_bind` fails -- at least you should call `SQLGetDiagRecW` on your connection handle to see what failed, and don't continue with the `bcp_sendrow` call. – Ian Clelland Oct 11 '12 at 06:21
  • Thanks for your suggestions. The problem lay in the wrong datatype being bound but a different datatype being cast and sent. I fixed it and I didn't use the __int64. An int sufficed since all the indicator needs to store is the sizeof my value and in my case the value was an unsigned short – dingo Oct 11 '12 at 09:11