Inserting NULL in an nvarchar fails in MSAccess - sql-server

I'm experiencing something a bit strange.
I have a table on SQL Server 2008, say StockEvent that contains a Description field defined as nVarchar(MAX).
The field is set to be Nullable, has no default value and no index on it.
That table is linked into an Access 2007 application, but if I explicitly insert a NULL into the field, I'm systematically getting:
Run-time Error '3155' ODBC--insert on a linked table 'StockEvent' failed.
So the following bits of code in Access both reproduce the error:
Public Sub testinsertDAO()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("StockEvent", _
dbOpenDynaset, _
dbSeeChanges + dbFailOnError)
rs.AddNew
rs!Description = Null
rs.Update
rs.Close
Set rs = Nothing
Set db = Nothing
End Sub
Public Sub testinsertSQL()
Dim db As DAO.Database
Set db = CurrentDb
db.Execute "INSERT INTO StockEvent (Description) VALUES (NULL);", _
dbSeeChanges
Set db = Nothing
End Sub
However, if I do the same thing from the SQL Server Management Studio, I get no error and the record is correctly inserted:
INSERT INTO StockEvent (Description) VALUES (NULL);
It doesn't appear to be machine-specific: I tried on 3 different SQL Server installations and 2 different PCs and the results are consistent.
I initially though that the problem may be in my Access application somewhere, but I isolated the code above into its own Access database, with that unique table linked to it and the results are consistent.
So, is there some known issue with Access, or ODBC and inserting NULL values to nvarchar fields?
Update.
Thanks for the answers so far.
Still no luck understanding why though ;-(
I tried with an even smaller set of assumptions: I created a new database in SQL Server with a single table StockEvent defined as such:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[StockEvent](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](max) NULL
) ON [PRIMARY]
GO
Then linked that table though ODBC into the test Access 2007 application.
That application contains no forms, nothing except the exact 2 subroutines above.
If I click on the linked table, I can edit data and add new records in datasheet mode.
Works fine.
If I try any of the 2 subs to insert a record, they fail with the 3155 error message.
(The table is closed and not referenced anywhere else and the edit datasheet is closed.)
If I try the SQL insert query in SQL Server Management Studio, it works fine.
Now for the interesting bit:
It seems that anything as big or bigger than nvarchar(256), including nvarchar(MAX) will fail.
Anything with on or below nvarchar(255) works.
It's like Access was considering nvarchar as a simple string and not a memo if its size is larger than 255.
Even stranger, is that varchar(MAX) (wihout the n) actually works!
What I find annoying is that Microsoft's own converter from Access to SQL Server 2008 converts Memo fields into nvarchar(MAX), so I would expect this to work.
The problem now is that I need nvarchar as I'm dealing with Unicode...

OK, I may have found a related answer: Ms Access linking table with nvarchar(max).
I tried using the standard SQL Server driver instead of the SQL Server Native Client driver and nvarchar(MAX) works as expected with that older driver.
It really annoys me that this seems to be a long-standing, unfixed, bug.
There is no valid reason why nvarchar should be erroneously interpreted as a string by one driver and as a memo when using another.
In both cases, they appear as memo when looking a the datatype under the table design view in Access.
If someone has any more information, please leave it on this page. I'm sure others will be glad to find it.

That should be legal syntax. Is it possible that the field you are try to give a null value is linked to other fields that don't allow null values?

Potential concurrency problem... Is the record open by another instance of Access on the same or a different machine, or does a form bound to the table have the record open in the same instance of Access on the same machine?
Renaud, try putting something in one of the other fields when you do the insert.
Also, try inserting an empty string ("") instead of a null.

Renaud,
Did you try running a SQL Profiler trace? If you look at the Errors and Warnings category it should kick out an error if your insert failed as a result of a SQL Server constraint.
If you don't see any errors, you can safely assume that the problem is in your application.
Also, are you sure you're actually connected to SQL Server? Is CurrentDB not the same variable you're using in your Access test loop?

i got annother issue (here my post: link text
In some very rare cases an error arises when saving a row with a changed memo field - same construct explained in my former post but driving sql2000-servers and it's appropriate odbc-driver (SQL SERVER).
The only weired fix is: to expand the table structure on sql-server with a column of datatype [timestamp] and refresh the odbc-links. That works and releases the show-stopper in this column on this one row ...
Maybe this info can help someone - for me it's history in going further to odbc with sql2008 in changing the datatypes [text] to [varchar(max)].

Related

SQL Server: Error converting data type varchar to numeric (Strange Behaviour)

I'm working on a legacy system using SQL Server in 2000 compatibility mode. There's a stored procedure that selects from a query into a virtual table.
When I run the query, I get the following error:
Error converting data type varchar to numeric
which initially tells me that something stringy is trying to make its way into a numeric column.
To debug, I created the virtual table as a physical table and started eliminating each column.
The culprit column is called accnum (which stores a bank account number, which has a source data type of varchar(21)), which I'm trying to insert into a numeric(16,0) column, which obviously could cause issues.
So I made the accnum column varchar(21) as well in the physical table I created and it imports 100%. I also added an additional column called accnum2 and made it numeric(16,0).
After the data is imported, I proceeded to update accnum2 to the value of accnum. Lo and behold, it updates without an error, yet it wouldn't work with an insert into...select query.
I have to work with the data types provided. Any ideas how I can get around this?
Can you try to use conversion in your insert statement like this:
SELECT [accnum] = CASE ISNUMERIC(accnum)
WHEN 0 THEN NULL
ELSE CAST(accnum AS NUMERIC(16, 0))
END

Avoid MS Access SQL insert bug

I am converting an Access ADP file to ACCDB in preparation for Access 2016.
It's currently Access 2013 while all the functional testing is done before we move that to Access 2016 for compatibility testing.
I have one form where inserting a new record displays #Deleted after the insert.
I have traced through the profiler that it does 2 sp_executesql calls
One does the insert
The second queries for the new identity column value by using all the column values.
Unfortunately, there are several column values that are NULL, and the text shows it is querying for Field = #P6 (where #P6 = NULL) rather than Field IS NULL.
I've checked, and replacing the NULL variable checks with IS NULL does return the correct key value.
Is there a known bug relating to this?
I'm assuming it would be either in Access or ODBC
If so, can someone point me to the appropriate documentation so I can arrange any required upgrades, etc.
ODBC Connection I am using:
ODBC;Driver={SQL Server};APP=TEST;SERVER=TEST_SQL;DATABASE=TestDB;Trusted_Connection=Yes;TABLE=dbo.Inspection

SQL Server varchar(MAX) datatype in delphi using RemObjects

Got a request to change comment field max size in application. Before had it set to varchar(500), so after reading documentation i have decided to change data type of the field from varchar(500) to varchar(max). Database accepted changes without any problems (using Microsoft SQL Server Management Studio 2005 and Microsoft SQL Server Management Studio 2008 for database management).
Then i went on changing the software. Software is written in Delphi with RemObjects to communication with database. So I changed the TDASchema for the server, it mapped my new varchar(max) field as String(65536) data type (got me a little worried there about such an explicit static size, but I went on). Then I Retrieved DataTable Schema for my TDAMemDataTable object, which updated all the fields.
I started the application and decided to see whether my database will accept changes on this specific changed field. I have edited one of the records and clicked the button to synchronize the DataSet with server and got such a fail message:
The data types varchar(max) and text are incompatible in the equal to operator
I interpret it as that my server object (the one that maps database fields with RemObjects objects) have mapped field data types to wrong data types in RemObjects.
How can this be resolved? What are the alternatives?
P.S. In this release Build .1267 logs from RemObjects it clearly states that:
fixed: DataSnap: fails to post updates to MSSQL 2005 VARCHAR(MAX)
I am using build version .1067. Wonder if update will fix the problem
P.P.S. After update to the latest version of RemObjects, the problem persists.
This error message usually happens when trying to compare a varchar(n) and text using an equality operator (usually in a where clause in sql but possible elsewhere). there was an article on MSDN which covered a few points which might relate to this.
when you store data to a VARCHAR(N) column, the values are physically stored in the same way. But when you store it to a VARCHAR(MAX) column, behind the screen the data is handled as a TEXT value. So there is some additional processing needed when dealing with a VARCHAR(MAX) value. (only if the size exceeds 8000)
You mentioned that the TDASchema had mapped your new field as String(65536) which, although never having used RemObjects before, i would assume somewhere in it's own code (or yours) is trying to do a comparison of some kind hence the error message.
Try using VARCHAR(8000) instead of MAX and see if that fixes the issue.
The other option if you can find where in the code it is doing this equality check, is to try doing a cast()
As you suspected, I think the root of your problems is that the fields haven't come into the TDASchema as the correct types. I've just tried it here and varchar(max) and nvarchar(max) fields come through to my schema as Memo and WideMemo respectively, not String(65536).
I'm using Delphi XE6 and SQL Server 2008 R2 via FireDAC.
This suggests an issue retrieving the metadata from the database. What database driver are you using? Can you try FireDAC (if available) or another driver to see if the problem persists?
Resolution for Delphi 7 and MS SQL Server 2008 R2 (SP2)
Delphi:
with TADOStoredProc.Create(Self) do
try
Connection := AConnection;
ProcedureName := ASPName;
Parameters.Refresh;
Parameters.ParamByName('#XML').Value := AXML;
try
ExecProc;
...
MS SQL Server:
ALTER PROCEDURE dbo.StoredProcName
#XML NVARCHAR(MAX)
,#ErrMsgOut NVARCHAR(MAX) = NULL OUT
AS BEGIN
SET NOCOUNT ON
DECLARE #RETURN INT = 0
,#idoc INT
BEGIN TRY
-- Prepare XML
DECLARE #XML_TEXT VARCHAR(MAX)
SET #XML_TEXT = CONVERT(VARCHAR(MAX), #XML)
EXEC sp_xml_preparedocument #idoc OUTPUT, #XML_TEXT
-- Open XML
SELECT *
FROM OPENXML (#idoc, '/ServicesList/ServicesItem', 2)
WITH
(
YourFields AndTypes
)
...

Update ADO Recordset for a Calculated Field

I'm using the following sql for an ADO recordset against a SQLServer backend in VB6:
select c.name, taxid=
case when exists(select 1 from sometable where fld='abc')
then c.SSN else null end
When I try to update the taxid field in a row within the recordset locally ADO complains with the error "Multiple-step operation generated errors. Check each status value." I assume it's bothered by the fact that the taxid field is coming from a calculated field and not a raw table column. For my purposes I'm never going to be persisting these changes back to the database so I'm looking for a way to tell ADO that have no intent to persist changes so that it will allow me to change the data locally.
I think that #HK1's suggestion is a good one, though I'm not sure what happens to your ability to alter any of the values in the recordset whether the column you're trying to update is computed or not. It's been a long time since I played with classic ADO but if the recordset is disconnected it may become read only at that point.
But if you have no interest in using the recordset to perform updates, and you need to alter values locally, perhaps you should consider storing the results in a local array first? That way you can minimize the locking and cursor options of the recordset, for example, and immediately close the recordset and free up those resources.
rs.Open cmd, conn, adOpenForwardOnly, adLockReadOnly
Dim MyArray
MyArray = rs.GetRows()
rs.Close: set rs = nothing
Now you can manipulate MyArray however you want...

Force SET IDENTITY_INSERT to take effect faster from MS Access

I'm working on upsizing a suite of MS Access backend databases to SQL Server. I've scripted the SQL to create the table schemas in SQL Server. Now I am trying to populate the tables. Most of the tables have autonumber primary keys. Here's my general approach:
For each TblName in LinkedTableNames
'Create linked table "temp_From" that links to the existing mdb'
'Create linked table "temp_To" that links to the new SQL server table
ExecutePassThru "SET IDENTITY_INSERT " & TblName & " ON"
db.Execute "INSERT INTO temp_To SELECT * FROM temp_From", dbFailOnError
ExecutePassThru "SET IDENTITY_INSERT " & TblName & " OFF"
Next TblName
The first insert happens immediately. Subsequent insert attempts fail with the error: "Cannot insert explicit value for identity column in table 'TblName' when IDENTITY_INSERT is set to OFF."
I added a Resume statement for that specific error and also a timer. It turns out that the error continues for exactly 600 seconds (ten minutes) and then the insert proceeds successfully.
Does MS Access automatically refresh its ODBC sessions every 10 minutes? Is there a way to force that to happen faster? Am I missing something obvious?
Background info for those who will immediately want to say "Use the Upsizing Wizard":
I'm not using the built-in upsizing wizard because I need to be able to script the whole operation from start to finish. The goal is to get this running in a test environment before executing the switch at the client location.
I found an answer to my first question. The ten minutes is a setting buried in the registry under the Jet engine key:
'Jet WinXP/ Win7 32-bit:'
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\ODBC\ConnectionTimeout
'Jet Win7 64-bit:'
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Jet\4.0\Engines\ODBC\ConnectionTimeout
'ACE WinXP/ Win7 32-bit:'
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Access Connectivity Engine\Engines\ODBC\ConnectionTimeout
'ACE Win7 64-bit:'
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\MicrosoftAccess Connectivity Engine\Engines\ODBC\ConnectionTimeout
It is documented here for ACE:
ConnectionTimeout: The number of seconds a cached connection can remain idle before timing out. The default is 600 (values are of type REG_DWORD).
This key was set to the default of 600. That's 600 seconds or 10 minutes. I reduced that to ten seconds and the code sped up accordingly.
This is by no means the full solution, because setting the default that low is sure to cause issues elsewhere. In fact, Tony Toews once recommended that the default might better be increased when using DSN-less connections.
I'm still hoping to find an answer to the second part of my question, namely, is there a way to force the refresh to happen faster.
UPDATE: The reason this is even necessary is that the linked tables use a different session than ADO pass-through queries. I ran a test using SQL Profiler. Here are some brief results:
TextData SPID
-------------------------------------------
SET IDENTITY_INSERT dbo.TblName ON 50
SET IDENTITY_INSERT "dbo"."TblName" ON 49
exec sp_executesql N'INSERT INTO "d... 49
SET IDENTITY_INSERT dbo.TblName OFF 50
SET IDENTITY_INSERT dbo.NextTbl ON 50
SET IDENTITY_INSERT "dbo"."NextTbl" ON 49
exec sp_executesql N'INSERT INTO "d... 49
What's going on here is that my ADO commands are running in a different session (#49) than my linked tables (#50). Access sees that I'm setting the value for an identity column so it helpfully sets IDENTITY_INSERT ON for that table. However, it never sets IDENTITY_INSERT OFF. I turn it off manually, but that's happening in a different session.
This explains why setting the ODBC session timeout low works. It's just an ugly workaround for the fact that Access never turns off IDENTITY_INSERT on a table once it turns it on. Since IDENTITY_INSERT is sessions-specific, creating a new session is like hitting the reset button on IDENTITY_INSERT. Access can then turn it on for the next table and the setting will take effect because it's a brand new session.
Two thoughts, though not sure either will be useful because this is unfamiliar territory for me.
"Does MS Access automatically refresh its ODBC sessions every 10 minutes? Is there a way to force that to happen faster? Am I missing something obvious?"
In the Access 2003 Options dialog, on the Advanced tab, there is a setting for "ODBC refresh interval" and also settings for retries. Does adjusting those help ... or have any effect at all?
I wonder if you could avoid this problem by creating the SQL Server columns as plain numbers rather than autonumber, INSERT your data, then ALTER TABLE ... ALTER COLUMN to change them after the data has been inserted.
Access won't let me convert a numeric column to an autonumber if the table contains data, but ISTR SQL Server is more flexible on that score.
I found a convenient whereas not so beautiful solution to export many access tables to sql server and avoid the identity_insert problem:
I open a local table-recordset which lists all tables to be exported and I loop through the records (each table). In each loop I...
create an access application object
use the transfer database method on application object
terminate / quit the application object and loop again
Here is the sample code:
Public Sub exporttables()
Dim rst As Recordset
Dim access_object
'First create a local access table which lists all tables to be exported'
Set rst = CurrentDb.OpenRecordset("Select txt_tbl from ####your_table_of_tables####")
With rst
While Not .EOF
'generate a new object to avoid identity insert problem'
Set access_object = CreateObject("Access.Application")
'with access object open the database which holds the tables to be exported'
access_object.OpenCurrentDatabase "####C:\yoursourceaccessdb####.accdb"
access_object.DoCmd.TransferDatabase acExport, "ODBC Database", "ODBC;DSN=####your connection string to target SQL DB;", acTable, .Fields("txt_tbl"), .Fields("txt_tbl"), False, False
Debug.Print .Fields("txt_tbl") & " exported"
access_object.CloseCurrentDatabase
access_object.Application.Quit
Set access_object = Nothing
.MoveNext
Wend
End With
Set rst = Nothing
End Sub

Resources