Inserting into SQL Server using MS Access - sql-server

I know this will be a common problem, nevertheless I didn't find any solution.
I migrated my access database into SQL Server Express. SELECT and UPDATE work well, actually better that with the Access engine. But I can't get standard insertions running...
This does not work:
Set rc = CurrentDb.openRecordset("SELECT * FROM DBLog WHERE false")
rc.AddNew
rc.update '-->ERROR
rc.close
This does not work either:
DoCmd.RunSQL("INSERT INTO [test2].dbo.DBLog (type) VALUES (6)")
This does work:
sending the above SQL with pass through. So its not the SQL Servers problem.
I have set IDENTITY and PRIMARY in the Database. Access also knows the primary. In design view although there is no "Autonumber", and this may be the problem. But how to resolve that?

As a general rule, after migrating to SQL server for the back end, then a typical and common open reocrdset of
Set rst = CurrentDb.OpenRecordset("Contacts")
Needs to become
Set rst = CurrentDb.OpenRecordset("Contacts", dbOpenDynaset, dbSeeChanges)
So, in your case:
Set rc = CurrentDb.openRecordset("SELECT * FROM DBLog WHERE false" dbOpenDynaset, dbSeeChanges)

Since you have a linked table named DBLog, I would execute an INSERT statement, similar to the one which worked in SQL Server itself, against that linked table.
Dim db As DAO.Database
Dim strInsert As String
strInsert = "INSERT INTO DBLog ([type]) VALUES (6)"
Set db = CurrentDb
db.Execute strInsert, dbFailonerror
I enclosed the field name type in square brackets because it is a reserved word.

Related

Microsoft Access to SQL Server Transition - Invalid use of null on insert

I'm migrating a legacy Microsoft Access application to use SQL Server as the backend. I've noticed since switching to SQL Server all the inserts are now coming up with invalid use of null errors.
From some testing I've found this is due to how the VBA is written. The primary key is referenced before the record is created and not surprisingly it nulls.
Dim recordSet As DAO.Recordset
Dim newID As Long
Set recordSet = dbLocal.OpenRecordset("Select * FROM tblUser", dbOpenDynaset, dbSeeChanges)
With recordSet
.AddNew
newID = !UserID
.Update
End With
recordset.Close
Now this works fine in the old Access to Access application. How is it able to do so and how can i make this work with the new SQL Server back end?
(I am aware that I can change the code to not reference it however this occurs multiple times within the application and as such I would prefer not to).
I'm surprised this ever worked. The correct way to do it is like this:
With myRecordSet
.AddNew
' Set all data columns (not the IDENTITY column)
!Data = someData
' Save new record
.Update
' Move the recordset pointer to the new record to read its ID
.Bookmark = .LastModified
newID = !UserID
' Done
.Close
End With
https://learn.microsoft.com/en-us/office/client-developer/access/desktop-database-reference/recordset-lastmodified-property-dao

Determining if a SQL Server table has IDENTITY column from MS Access VBA over ODBC

I have an MS Access program that constructs a SQL INSERT statement that inserts rows from an Access table into a linked SQL Server table with the same table structure. The SQL Server table may or may not have an IDENTITY primary key.
The MS Access source table has its corresponding primary key pre-populated, and these values must be inserted into the SQL Server table for referential reasons. SQL Server won't allow this unless I toggle the IDENTITY_INSERT property, but to decide if I need to toggle I need to determine if there is an IDENTITY column in the first place.
So in VBA from MS Access I need to determine if the linked SQL Server table has an IDENTITY column.
This question is covered here but for Transact-SQL.
I actually can get what I need using a pass-thru query, so it is feasible.
Create a pass-thru query to the SQL Server database.
In the SQL view enter
SELECT OBJECTPROPERTY(OBJECT_ID(N'table_name'), 'TableHasIdentity') AS 'HasIdentity'
and execute, it returns a 1-row datasheet with column HasIdentity that has value 1 if the table has an IDENTITY column.
The problem is that in VBA I cannot get at this datasheet. Given the query runs OK from MS Access I think the datasheet must correspond to a recordset and the following code should work.
Public Function metaODBCExecuteSQL(ByVal pstrConn As String) As Recordset
Dim qdf As QueryDef
Dim rst As Recordset
Set qdf = CurrentDb.CreateQueryDef("ODBC Execute")
qdf.ReturnsRecords = True
qdf.Connect = pstrConn
qdf.SQL = "SELECT OBJECTPROPERTY( OBJECT_ID(N'table_name'), 'TableHasIdentity') AS 'HasIdentity'"
Set rst = qdf.OpenRecordset(, dbSeeChanges)
qdf.Close
Set metaODBCExecuteSQL = rst
END Function
Instead the set rst line returns a run-time error 3219 invalid operation (with or without dbSeeChanges, which I believe is necessary for working with tables with IDENTITY).
Can anyone point out how I can retrieve the result set from the SELECT OBJECTPROPERTY query? What is wrong with my code, or is there another way.
After all if the MS Access UI can do it there must be a way.
If I create and save the query, this works great here:
? metaODBCExecuteSQL("")(0)
' Returns 0 or 1.
using this function and adjusting table_name to some valid table name:
Public Function metaODBCExecuteSQL(ByVal pstrConn As String) As Recordset
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
Set qdf = CurrentDb.QueryDefs("ODBC Execute")
qdf.SQL = "SELECT OBJECTPROPERTY( OBJECT_ID(N'table_name'), 'TableHasIdentity') AS 'HasIdentity'"
Set rst = qdf.OpenRecordset()
qdf.Close
Set metaODBCExecuteSQL = rst
End Function

Send SQL Server command to move data (on server) from Access

I would like to have an Access VBA sub that sends an instruction to a SQL Server database to copy some data from one of the remote tables to another table (at least one of the tables is not linked to the local Access database). I want this to all happen on the server, as this is a lot of data.
I'm trying something like the following, but it returns an run-time error 3065 (Cannot execute a select query). Any insight into how to fix? Is there some reason I couldn't do this from Access?
Also, I'm using a DAO approach, but is there a better approach (ADO?)? Somewhat new to this so not always sure I understand the nuances of the different approaches.
Public Sub myTest()
Setup:
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb()
Set qdf = dbs.CreateQueryDef("")
qdf.SQL = "INSERT INTO tmp SELECT [Applicant], [CaseName], [DecisionDate], [Filed], [Docket] " _
& "FROM Cases WHERE [DecisionDate] >= '01/01/2018';"
qdf.Connect = "ODBC;Driver={SQL Server};server=myServer;database=myDB;"
qdf.Execute
End Sub
What you have looks ok. I would “test” the sql first by running the exact same command by using SQL management studio.
As for your code? It also looks ok, but I find it MUCH better to save a PT query and thus you don’t have to mess with the connection string in code. And thus your code becomes:
With CurrentDB.querydefs("MyPassR")
.SQL = "INSERT INTO tmp SELECT [Applicant], [CaseName], [DecisionDate], [Filed], [Docket] " & _
"FROM Cases WHERE [DecisionDate] >= '01/01/2018';"
.ReturnsRecords = False
.Execute
End With

dbFailOnError SQL Server datetime bug for time-only values

I have a linked table in SQL Server with a datetime column where I store a time-only value*. If I execute an UPDATE query without dbFailOnError it translates that command into a SELECT followed by individual UPDATE statements that execute one row at a time:
exec sp_executesql N'UPDATE "dbo"."Appeals" SET "HearingTime"=#P1
WHERE "AppealID" = #P2'
,N'#P1 datetime,#P2 int','1899-12-30 09:00:00',1
...
exec sp_executesql N'UPDATE "dbo"."Appeals" SET "HearingTime"=#P1
WHERE "AppealID" = #P2'
,N'#P1 datetime,#P2 int','1899-12-30 09:00:00',4
If I execute the exact same UPDATE query, but with dbFailOnError, I get this translation:
UPDATE "dbo"."Appeals" SET HearingTime={t '09:00:00'}
It is interesting that the dbFailOnError forces a more efficient UPDATE on the back end, but my real concern is with the time value itself.
In the first example, Access correctly sets the datetime to 12/30/1899 (MS Access's magic "zero" day). In the second case, that does not happen. The end result is that the first example "works" and the second one does not.
What I mean by that, is that if I view the HearingTime field in datasheet view, Access shows the first one as:
9:00:00 AM
And the second one shows as (as of this writing, 9/3/16 is today's date):
9/3/2016 9:00:00 AM
I have to assume this is a bug on Microsoft's part. Or am I missing something here? Do I have a better option than reporting the bug and just hoping that Microsoft fixes it someday?
*Yes I am aware there is a time datatype in SQL Server. It is not compatible with the MS Access datetime type, so it's of little use to me in an MS Access linked table.
I was able to reproduce your issue using queries with Access SQL Date/Time literals. Both
Dim cdb As DAO.Database
Set cdb = CurrentDb
cdb.Execute "UPDATE dbo_Appeals SET HearingTime=#10:00:00#", dbFailOnError
and
Dim cdb As DAO.Database
Set cdb = CurrentDb
cdb.Execute "UPDATE dbo_Appeals SET HearingTime=#1899-12-30 11:00:00#", dbFailOnError
resulted in [HearingTime] values with the current date (2016-09-03), not the "zero" date.
However, passing the full date/time as a string seems to work
Dim cdb As DAO.Database
Set cdb = CurrentDb
cdb.Execute "UPDATE dbo_Appeals SET HearingTime='1899-12-30 12:00:00'", dbFailOnError
(Omitting the date part in the string value results in [HearingTime] values with the SQL Server DATETIME "zero" date of 1900-01-01.)
And, even better, a parameterized query with a DAO.QueryDef object also appears to work correctly
Dim cdb As DAO.Database
Set cdb = CurrentDb
Dim qdf As DAO.QueryDef
Set qdf = cdb.CreateQueryDef("", _
"PARAMETERS prmHearingTime DateTime;" & _
"UPDATE dbo_Appeals SET HearingTime=[prmHearingTime]")
qdf!prmHearingTime = #10:00:00 AM#
qdf.Execute dbFailOnError
That's a good question, and I can reproduce it.
It seems to be a bug in the ODBC driver, because the behaviour you demonstrate is that of SQL Server which the ODBC driver should compensate for, as it does in your first example by providing the full date-time string that will translate correctly when read back in Access.
I can see no way to work around it other than, when you read back the data, to apply TimeValue to the date-time values. This, however, will cease any use of an index of your time field.
And data type Time of SQL Server cannot be used for many things as the ODBC driver reads these as text.

Get Connection string for Pass through queries

I have just learned about the pass through queries in MS-ACCESS.
I have a SQL SERVER backend and
if I'm right, for a query access loads all records before to do the where clause... so what would be the point of having a SQL SERVER backend?
That's why I want to try using pass through queries as much as possible but is there a way I can get the connection string from my linked tables for my pass through queries?
I tried CurrentDb.TableDefs("One of my table name").Connect in the ODBC Connect Str property but I got the error saying it's an invalid connection string.
It would be nice because I know I will have to change the connection soon so I wouldn't have to edit the connection string at many places.
Thank you.
I'm not sure what you meant here: "for a query access loads all records before to do the where clause"
If the WHERE clause can be applied at the server, ODBC will translate it to the server's language, and only the matching rows will be sent back to Access:
WHERE date_field >= #2011-01-01# AND date_field < #2012-01-01#
That WHERE clause would limit the rows sent to Access to only those whose date_field values are from 2011.
However, if a WHERE clause includes functions which must be evaluated by Access, ODBC must retrieve all candidate rows and hand them over to the Access db engine so it can perform the evaluation.
WHERE Format(date_field, 'yyyy') = '2011'
But for your actual question ... connection string for pass through queries ... consider the following code example. I have an ODBC link named dbo_foo whose source table in SQL Server is [dbo].[foo]. So I can grab the .Connect property from dbo_foo and use it for the .Connect property of a pass through query based on the same server table.
Public Sub CreatePassThruQuery()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim strConnect As String
Set db = CurrentDb
strConnect = db.TableDefs("dbo_foo").Connect
Set qdf = db.CreateQueryDef("qryDbo_Foo")
qdf.Connect = strConnect
qdf.SQL = "SELECT * FROM [dbo].[foo];"
qdf.Close
Set qdf = Nothing
Set db = Nothing
End Sub
Still when you change the .Connect property of the table, you will also need to do it for the query. If you have many of them and/or change the connections frequently, it may be worth the effort to create a VBA procedure to update them. Alternatively, you might use a DSN for the .Connect property of the table and matching query. Then revise the DSN as needed. A pitfall with that approach is that, if other people will be using your application, you would need to manage the DSNs on multiple machines.

Resources