Answered by:
How to insert a NULL in a SQL_TYPE_TIMESTAMP column ?

Question
-
Hello,
I'm failing in inserting a NULL value to a TIMESTAMP data type column in MSSQL Server via SQLBindParameter. I tried all examples below. What am I doing wrong ?
Of course, column is NULLABLE in database and hEnv is set to ODBC 3.0:
CREATE TABLE [TEST_TABLE_RDD_ODBCRDD] ( [DATE_LIM] DATETIME NULL,
[SR_RECNO] NUMERIC (15,0) IDENTITY,
[SR_DELETED] CHAR (1) NOT NULL
)
SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
What I've tried:
-------------------------------------------------------------
SQLINTEGER nInd = SQL_NULL_DATA;
SQLRETURN res;
res = SQLBindParameter( hStmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, SQL_TIMESTAMP_LEN, 0, NULL, 0, &nInd );
Error diag: return code: -1, state: HY090, description: [Microsoft][ODBC SQL Server Driver]Invalid string or buffer length
-------------------------------------------------------------
SQLINTEGER nInd = SQL_NULL_DATA;
SQLRETURN res;
res = SQLBindParameter( hStmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 0, 0, NULL, 0, &nInd );
OR
res = SQLBindParameter( hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_TYPE_TIMESTAMP, 0, 0, NULL, 0, &nInd );
Error diag: return code: -1, state: HY104, description: [Microsoft][ODBC SQL Server Driver]Invalid precision value
-------------------------------------------------------------
SQLINTEGER nInd = SQL_NULL_DATA;
SQLRETURN res;
SQL_TIMESTAMP_STRUCT asTimestamp;
asTimestamp.year = 1900;
asTimestamp.month = 1;
asTimestamp.day = 1;
asTimestamp.hour = 0;
asTimestamp.minute = 0;
asTimestamp.second = 0;
asTimestamp.fraction = 0;
res = SQLBindParameter( hStmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, SQL_TIMESTAMP_LEN, 0, &(asTimestamp), 0, &nInd );
The above gives no error, but instead of a NULL it writes '1900-01-01 00:00:00.000'.. And if I do:
asTimestamp.year = 0;
asTimestamp.month = 0;
asTimestamp.day = 0;
SQLBindParameter works, but SQLExecute explodes:
Error diag: return code: -1, state: 22007, description: [Microsoft][ODBC SQL Server Driver]Invalid date format
-------------------------------------------------------------
Any help to write a NULL value to database would be very appreciated.
Thanks in advance,
Marcelo- Edited by Marcelo Lombardo Wednesday, April 29, 2009 8:33 PM Adding table structure
Wednesday, April 29, 2009 8:30 PM
Answers
-
That's because of the deferred parameter input &nInd. If you use BindOutsideCurrentFunction(), and nInd is declared inside the function, once you leave out of this function, the nInd does not exist any more, however, we will need to use it later in SQLExecute.
Below is the SDK documentation of SQLBindParameter last parameter:
StrLen_or_IndPtr Argument
The StrLen_or_IndPtr argument points to a buffer that, when SQLExecute or SQLExecDirect is called, contains one of the following. (This argument sets the SQL_DESC_OCTET_LENGTH_PTR and SQL_DESC_INDICATOR_PTR record fields of the application parameter pointers.)
So the solution is that if you declare nInd as global variable, it will work fine.
- Marked as answer by Marcelo Lombardo Thursday, May 7, 2009 9:47 AM
Thursday, May 7, 2009 2:03 AM
All replies
-
Hello,
Please,could you tell us what is the version ( 2000/2005/2008 ) ,the edition ( MSDE/Express/Workgroup/Standard/Entreprise ) and the Service Pack of your SQL Server ?
timestamp ( or rowversion in SQL Server 2008 ) does not correspond to a date ( on the contrary of timestamp defined in the SQL-2003 standard ).
the timestamp of the SQL-2003 standard is corresponding to the SQL Server data type datetime.
See these 2 links :
SQL Server 2005 : http://msdn.microsoft.com/en-us/library/ms182776(SQL.90).aspx
SQL Server 2008 : http://msdn.microsoft.com/en-us/library/ms182776.aspx
Also , could you have a look on this link ?
http://msdn.microsoft.com/en-us/library/ms174335.aspx
It explains shortly how to do an insert with timestamp
With your pasted code, i think you should replace timestamp by a datetime type
Don't hesitate to post again for more help or explanations
Have a nice day
PS : i suppose that you refer to :
http://msdn.microsoft.com/en-us/library/ms710963.aspx
Please, could you tell us with which language and IDE you are developing ?
( i am not sure to have well understood your question )
Mark Post as helpful if it provides any help.Otherwise,leave it as it is.- Edited by Papy Normand Thursday, April 30, 2009 5:14 PM post-scriptum added
Thursday, April 30, 2009 5:08 PM -
As Papy mentione, I'm not sure of the version of the product you're using, but the following sample using SQL Server 2008 should help you solve the issue you're facing:
SQLExecDirect("DROP TABLE t_U5CA64578B4B4458EA0FB4437104D73E5")
SQLExecDirect("CREATE TABLE t_U5CA64578B4B4458EA0FB4437104D73E5 (IntCol1 int, DT7Col2 datetime2(7))")
SQLPrepare("INSERT INTO t_U5CA64578B4B4458EA0FB4437104D73E5 VALUES(?, ?)")
SQLBindParameter(hStmt, iParam(=1), fParamType(=SQL_PARAM_INPUT), fCType(=SQL_C_SLONG), fSqlType(=SQL_INTEGER), nPrecision(=0), nScale(=0), pData, cbDataMaxSize(=4), pcbDataActualSize(*p=4))
SQLBindParameter(hStmt, iParam(=2), fParamType(=SQL_PARAM_INPUT), fCType(=SQL_C_BINARY), fSqlType(=SQL_SS_TIMESTAMPOFFSET), nPrecision(=34), nScale(=7), pData, cbDataMaxSize(=20), pcbDataActualSize(*p=-1))
SQLExecute()
SQLExecDirect("SELECT * FROM t_U5CA64578B4B4458EA0FB4437104D73E5 WHERE IntCol1 = 1")
SQLFetch()
SQLGetData(column '2', fCType(=1='SQL_C_CHAR'), pData, cbDataMaxSize(=1024), pcbDataActualSize)
...SQLGetData(nCol(=2) returned cbValueFetched = -1)
Found expected Integer value of '-1'
SQLFreeStmt(SQL_CLOSE)
~Warren- Proposed as answer by Warren Read - MSFT Thursday, April 30, 2009 5:44 PM
Thursday, April 30, 2009 5:44 PM -
Papy,
Thank you for your reply :-)
About the version used, it's SQL Server 2000. The select @@version says:
Microsoft SQL Server 2000 - 8.00.2039 (Intel X86) May 3 2005 23:18:38 Copyright (c) 1988-2003 Microsoft Corporation Developer Edition on Windows NT 5.1 (Build 2600: Service Pack 3)
I understand the difference to 2008 timestamp (auto update on any row change) to datetime. I'm using datetime as you can see in table structure from my post:
"CREATE TABLE [TEST_TABLE_RDD_ODBCRDD] ( [DATE_LIM] DATETIME NULL,..."
Using plain INSERT I can add NULLs to database, as described in the article you pointed, but the fact is that I must use a bound parameter to an already prepared INSERT statement.
I tried to use SQL_DATETIME but it does not work. SqlBindParameter returns: return code: -1, state: HYC00, description: [Microsoft][ODBC SQL Server Driver]Optional feature not implemented.
Also, when reading from this table, SqlDescribeCol reports it as SQL_TYPE_TIMESTAMP (93) and using this datatype I can correctly store any valid date in database. Problem is just to add a NULL... It seems driver is completely ignoring the parameter SQL_NULL_DATA I pass in last SqlBindParameter parameter.
About the language, I'm using VC2005, MSVC++ and ODBC API. I got the instructions from:
http://support.microsoft.com/default.aspx/kb/248799
http://msdn.microsoft.com/en-us/library/ms710963.aspx
Funny that the code from MS article above, "How To Insert NULL Data with ODBC API Functions" gives the same errors I'm experiencing.
Thank you,
Marcelo
Thursday, April 30, 2009 6:02 PM -
Warren,
I'm using SQL2000 but I tried the 2008 sample and it does not work as well. It seems there is no support for SQL_SS_TIMESTAMPOFFSET (-155) data type. SqlBindParameter returns:
return code: -1, state: HY004, description: [Microsoft][ODBC SQL Server Driver]Invalid SQL data type
Any other idea ? I tried to fool the driver using as many data types combination my creativity allows me in both C and SQL data types (4th and 5th SqlBindParameter params) with no success.
Any other idea ?
Thank you,
MarceloThursday, April 30, 2009 8:10 PM -
Hello,
I think you will find an explanation of your error message HY004 here :
http://msdn.microsoft.com/en-us/library/bb677302.aspx
at the end of the page
Have a nice day
Mark Post as helpful if it provides any help.Otherwise,leave it as it is.Thursday, April 30, 2009 9:33 PM -
Hello,
I think you will find an explanation of your error message HY004 here :
http://msdn.microsoft.com/en-us/library/bb677302.aspx
at the end of the page
Have a nice day
Mark Post as helpful if it provides any help.Otherwise,leave it as it is.
Papy,
Yes, that's exactly what I supposed. The sample may work in 2008 but my mission is to have a cross-database-version product. I'm stuck in this silly problem from 5 days and I can't believe it's an ODBC bug - for sure I am missing something.. does someone have any example of writing NULL to a column using ODBC 3.0 API's SqlBindParameter in SQL Server 2000 ?
Thank you,
Marcelo
Thursday, April 30, 2009 11:18 PM -
Hello Marcello,
I have a problem : i have no SQL Server 2000, only SQL Server Dev/Express 2008, an instance of SQL Server 2005 Express which works when it wants.
I will try to deter something but i am not sure that i will succeed
Have a nice day
Mark Post as helpful if it provides any help.Otherwise,leave it as it is.Sunday, May 3, 2009 3:32 PM -
Hello Marcello,
I have a problem : i have no SQL Server 2000, only SQL Server Dev/Express 2008, an instance of SQL Server 2005 Express which works when it wants.
I will try to deter something but i am not sure that i will succeed
Have a nice day
Mark Post as helpful if it provides any help.Otherwise,leave it as it is.
Papy,
If you can make it on 2005 it's fine for me. Any enlightenment would be very appreciated :-)
BTW I realized that problem is not restricted to SQL_TYPE_TIMESTAMT. Driver seems to ignores SQL_NULL_DATA with SQL_CHAR and SQL_NUMERIC as well. I didn't try other data types.
Thank you,
Marcelo
- Edited by Marcelo Lombardo Monday, May 4, 2009 5:59 PM typos
Monday, May 4, 2009 4:15 PM -
As you are suggesting this may be a driver issue, have you tried the same scenario under SQL Server Native Client? Your MDAC driver version would be tied to the OS, not the SQL Server SKU and may be fairly old depending on the operating system you are using. If this works better under SQL Server Native Client or SQL Server Native Client 10.0, then we can perhaps scope down the problem somewhat or use SQL Native Client as a workaround.
Thanks,
John
This post is provided 'as is' and confers no express or implied warranties or rights.Monday, May 4, 2009 5:47 PM -
Could you post your whole piece of code here? I tried on Vista SP1 with SQL 2000 backend, and it works well on my side.
The script to create table is:
CREATE TABLE TEST_TABLE_RDD_ODBCRDD (
[SR_RECNO] [numeric](15, 0) IDENTITY (1, 1) NOT NULL,
[DATE_LIM] [datetime] NULL
)
The code to insert NULL is:
#include "stdafx.h" #include <Windows.h> #include <sql.h> #include <sqlext.h> #include <sqltypes.h> #include <stdio.h> #include <ODBCSS.h> #include <iostream> #include <odbcinst.h> using namespace std; // display error message when an error occurs void displayErrorMessage(SQLSMALLINT handleType, SQLHANDLE handle) { RETCODE rc; const int NAME_LEN = 300; SQLTCHAR state[NAME_LEN]; SQLTCHAR errorInfo[NAME_LEN]; SQLINTEGER nativeError; SQLSMALLINT cbLength; TCHAR szMsg[1000]; SQLINTEGER numRecords; rc = SQLGetDiagRec(handleType, handle, 1, state, &nativeError, errorInfo, NAME_LEN, &cbLength); _stprintf_s(szMsg, TEXT("the error message is: %s"), errorInfo); cout << szMsg << TEXT("\n"); SQLGetDiagField(handleType, handle, 0, SQL_DIAG_NUMBER, &numRecords, SQL_IS_INTEGER, NULL); for (int i=1; i<=numRecords; i++) { rc = SQLGetDiagRec(handleType, handle, i, state, &nativeError, errorInfo, NAME_LEN, &cbLength); _stprintf_s(szMsg, TEXT("the error message is: %s"), errorInfo); cout << szMsg << TEXT("\n"); _stprintf_s(szMsg, TEXT("the error state is: %s"), state); cout << szMsg << TEXT("\n"); _stprintf_s(szMsg, TEXT("the nativeError state is: %d"), nativeError); cout << szMsg << TEXT("\n"); } } int _tmain(int argc, _TCHAR* argv[]) { RETCODE rc; HENV henv = NULL; HDBC hdbc = NULL; HSTMT hstmt = NULL; // allocate environment handle rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } // set the environment attribute rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } // allocate connection handle rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } #pragma region SQLDriverConnect -- active // using SQLDriverConnect to connect to the database rc = SQLDriverConnect( hdbc, NULL, //(SQLTCHAR *) TEXT("Driver={SQL Server};Server=xxxx\\SHILOH;UID=sa;PWD=xxxx"), (SQLTCHAR *) TEXT("Driver={SQL Server};Server=xxxx\\SHILOH;UID=sa;PWD=xxxx"), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT ); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { displayErrorMessage(SQL_HANDLE_DBC, hdbc); return -1; } #pragma endregion // allocate statement handle rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } rc = SQLPrepare(hstmt, (SQLTCHAR *)"INSERT INTO TEST_TABLE_RDD_ODBCRDD (DATE_LIM) VALUES (?)", SQL_NTS); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } SQLINTEGER nInd = SQL_NULL_DATA; rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 19, 0, NULL, 0, &nInd); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { displayErrorMessage(SQL_HANDLE_STMT, hstmt); return -1; } rc = SQLExecute(hstmt); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } SQLFreeStmt(hstmt, SQL_CLOSE); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); SQLFreeHandle(SQL_HANDLE_ENV, henv); return 0; }
- Edited by Ya Yu Tuesday, May 5, 2009 9:43 AM
Tuesday, May 5, 2009 7:55 AM -
Ronggang,
Your code works fine here, so it discards any old MDAC issue. Funny that all involved API calls are exactly the same, but it does not work as expected when running from within my application.
I'm trying to recreate the same circumstances of my application in reduced, self contained sample cos program is really huge to be posted here.
I'll let you know what I find.
Thank you,
MarceloTuesday, May 5, 2009 5:25 PM -
Ronggang,
Ok, I finally have a conclusion (a very very odd one!). I came trying to replicate my application behavior and reducing it to isolate the problem, than I found this weird issue... Sorry if it has a simple solution, but I do not know how to fix...
The point is: If the call to SQLBindParameter() is not in same function, it does not work to bind NULL value (works to bind any other value). The reduced sample below can be used to simulate the problem. The table structure is the same from your sample.
#include <Windows.h> #include <sql.h> #include <sqlext.h> #include <stdio.h> #include <iostream> #include <assert.h> #ifdef _UNICODE #undef _UNICODE #endif // display error message when an error occurs void displayErrorMessage(SQLSMALLINT handleType, SQLHANDLE handle) { RETCODE rc; const int NAME_LEN = 300; SQLTCHAR state[NAME_LEN]; SQLTCHAR errorInfo[NAME_LEN]; SQLINTEGER nativeError; SQLSMALLINT cbLength; char szMsg[4096]; SQLINTEGER numRecords; rc = SQLGetDiagRec(handleType, handle, 1, state, &nativeError, errorInfo, NAME_LEN, &cbLength); printf((const char *)errorInfo); printf( "\n" ); SQLGetDiagField(handleType, handle, 0, SQL_DIAG_NUMBER, &numRecords, SQL_IS_INTEGER, NULL); for (int i=1; i<=numRecords; i++) { rc = SQLGetDiagRec(handleType, handle, i, state, &nativeError, errorInfo, NAME_LEN, &cbLength); printf((const char *)errorInfo); printf( "\n" ); printf((const char *)state); printf( "\n" ); } } void BindOutsideCurrentFunction( HSTMT hstmt ) { SQLRETURN res; SQLLEN nInd = SQL_NULL_DATA; res = SQLBindParameter( hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, SQL_TIMESTAMP_LEN, 0, NULL, 0, &nInd ); assert( res == SQL_SUCCESS ); } int main(int argc, char* argv[]) { RETCODE rc; HENV henv = NULL; HDBC hdbc = NULL; HSTMT hstmt = NULL; // allocate environment handle rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } // set the environment attribute rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } // allocate connection handle rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } // using SQLDriverConnect to connect to the database rc = SQLDriverConnect( hdbc, NULL, (SQLTCHAR *) ("DSN=xHB001;UID=sa;PWD=;"), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT ); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { displayErrorMessage(SQL_HANDLE_DBC, hdbc); return -1; } SQLSetConnectOption( hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF ); // allocate statement handle rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } rc = SQLPrepare(hstmt, (SQLTCHAR *)"INSERT INTO [TEST_TABLE_RDD_ODBCRDD] ( [DATE_LIM] ) VALUES ( ? ); SELECT @@IDENTITY ;", SQL_NTS); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } /* // Note: uncomment this block and comment call to BindOutsideCurrentFunction() below // and SQLExecute will work fine. Note that BindOutsideCurrentFunction() does the SAME // that this block does. SQLRETURN res; SQLLEN nInd = SQL_NULL_DATA; res = SQLBindParameter( hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, SQL_TIMESTAMP_LEN, 0, NULL, 0, &nInd ); assert( res == SQL_SUCCESS ); */ BindOutsideCurrentFunction( hstmt ); rc = SQLExecute(hstmt); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { return -1; } SQLFreeStmt(hstmt, SQL_CLOSE); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); SQLEndTran( SQL_HANDLE_DBC, (SQLHANDLE) hdbc, SQL_COMMIT ); SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); SQLFreeHandle(SQL_HANDLE_ENV, henv); return 0; }
I'm using:
SQL Server 2000
Microsoft Visual Studio 2005
Version 8.0.50727.42 (RTM.050727-4200)
Installed Edition: Professional
Microsoft Visual C++ 2005 77626-009-0000007-41361
Security Update for Microsoft Visual Studio 2005 Professional Edition - ENU (KB925674)
BTW I tested with Postgres and MySQL backends with same results.
Any clue ?
Thank you,
Marcelo
- Edited by Marcelo Lombardo Thursday, May 7, 2009 1:39 AM text improvments
Wednesday, May 6, 2009 9:43 PM -
That's because of the deferred parameter input &nInd. If you use BindOutsideCurrentFunction(), and nInd is declared inside the function, once you leave out of this function, the nInd does not exist any more, however, we will need to use it later in SQLExecute.
Below is the SDK documentation of SQLBindParameter last parameter:
StrLen_or_IndPtr Argument
The StrLen_or_IndPtr argument points to a buffer that, when SQLExecute or SQLExecDirect is called, contains one of the following. (This argument sets the SQL_DESC_OCTET_LENGTH_PTR and SQL_DESC_INDICATOR_PTR record fields of the application parameter pointers.)
So the solution is that if you declare nInd as global variable, it will work fine.
- Marked as answer by Marcelo Lombardo Thursday, May 7, 2009 9:47 AM
Thursday, May 7, 2009 2:03 AM -
Ronggang,
Correct, problem fixed, thank you so much!!
Best Regards,
MarceloThursday, May 7, 2009 9:47 AM