SETUP:
I am using a SQL Server database linked to a MS Access file so I can use Access forms.
I am using unbound forms with custom queries since I am dealing with several tables (If there is a more efficient way I am all for it).
In SQL Server I have setup a database role with permissions for the table, I have double checked that I have allowed the role to update the table.
ISSUE:
Whenever I use my update query in Access using a QueryDef (shown below) it runs successfully, but does not actually update the table.
DETAILS:
I was having an issue with my insert query as well, however that was only because I had not updated the query with new columns I had added in the database. I also made sure that I did the same for the update query.
Additionally I know that the update query works since I am able to update the entry directly from SSMS.
Since this seemed similar to another access/sql-server question I had, Found Here. I made sure to try the solution for that one, refreshing the table link. However, it made no difference.
CODE:
Query:
UPDATE con_people
SET people_first_name = #firstName,
people_last_name = #lastName,
people_title = #title,
people_group = #group,
people_email = #email,
people_shift = #shift,
people_hiredate = #hireDate,
people_location = #location,
people_reportsTo = #reportsTo,
people_versionCount = people_versionCount + 1,
people_datelastupdated = #dateUpdated,
people_isActive = #isActive
WHERE people_employeeID = #empID;
QueryDef:
Public Function UpdatePeople(firstName As String, _
lastName As String, _
title As Integer, _
group As Integer, _
Email As Variant, _
isActive As Boolean, _
Shift As Integer, _
Location As Integer, _
HireDate As Variant, _
ReportsTo As Variant, _
employeeID As Integer)
OtherFunctions.Initialize
Dim QDF As DAO.QueryDef
If FindQuery("UpdatePeople") = True Then OtherFunctions.dbs.QueryDefs.Delete "UpdatePeople"
Set QDF = OtherFunctions.dbs.CreateQueryDef("UpdatePeople", SQLUpdatePeople)
QDF.Parameters("#firstName").Value = firstName
QDF.Parameters("#lastName").Value = lastName
QDF.Parameters("#title").Value = title
QDF.Parameters("#group").Value = group
QDF.Parameters("#email").Value = Email
QDF.Parameters("#isActive").Value = isActive
QDF.Parameters("#empID").Value = employeeID
QDF.Parameters("#shift").Value = Shift
QDF.Parameters("#hireDate").Value = HireDate
QDF.Parameters("#location").Value = Location
QDF.Parameters("#reportsTo").Value = ReportsTo
QDF.Parameters("#dateUpdated").Value = ConvertTimeUnix.ConvertDateToUnix(Now())
QDF.Execute
If FindQuery("UpdatePeople") = True Then OtherFunctions.dbs.QueryDefs.Delete "UpdatePeople"
End Function
Any help is appreciated,
Thanks.
Thanks to a comment by #Andre I was able to find the source of the issue.
I was using the wrong data type when I was updating the entry. The SQL-Server was expecting an INT (for a foreign key) when I was providing it a boolean (SQL-Server BIT).
Details About the solution:
Links provided by Andre: Error Object - Data Access Object and Determine real cause of ODBC failure (error 3146) with ms-access?.
For more information on the DAO.Error object please reference those.
Here is an example of how it is used:
Dim myerror As DAO.Error
For Each myerror In DBEngine.Errors
With myerror
If .Number <> 3146 Then 'prevents the system from returning the basic error.
MsgBox "Error #:" & .Number & ", Description: " & .Description & ", Source: " & .Source
End If
End With
Next
Once I ran it, this was returned, allowing me to find the root cause:
Error #:547,
Description: [Microsoft][ODBC SQL Server Driver][SQL Server]The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_people_isActive". The conflict occurred in database "continuousimprovement", table "dbo.con_isactive", column 'isActive_id'.,
Source: ODBC.QueryDef
Again, Thank you Andre.
Related
I created a new view in my SQL backend. I would like to view it in Access front end. When I open the linked table manager to add it to Access, it does not appear. I refreshed/closed/reopened. For clarity, the database where the new view exists is already linked and many of its tables/views do show in the linked table manager but not the new one I just made.
I opened the linked table manager, expanded all tables in the linked sql server, and my table does not show.
Verify that the new view has the same permissions as the views that already show up in Access. If permissions are different, modify the permissions on your view that is not showing up.
Run a small function:
Public Function LinkNewTable( _
ByVal TableName As String, _
ByVal SourceTableName As String) _
As Boolean
Const Master As String = "SomeCurrentlyLinkedTable"
Const Schema As String = "dbo"
Dim Database As DAO.Database
Dim Table As DAO.TableDef
Dim Connect As String
Dim Success As Boolean
Set Database = CurrentDb
Set Table = Database.TableDefs(Master)
Connect = Table.Connect
Table.Close
Set Table = Database.CreateTableDef(TableName)
Table.SourceTableName = Schema & "." & SourceTableName
Table.Connect = Connect
Database.TableDefs.Append Table
Database.TableDefs.Refresh
Success = Not CBool(Err.Number)
Set Table = Nothing
LinkNewTable = Success
End Function
I'm trying to selectively import data from SQL Server tables to MS Access tables.
As far as I understand, import full tables can be done quite easy with DoCmd commands.
Unfortunately, the data I need are stored in large tables and I only need a parcel of them.
So far, I was able to import the data and store them at a recordset.
Now I want to create a table to store at MS Access, but VBA is complaining of type mismatch (run-time error 3421).
So far my code is like that - first part: connection to SQL Server database and data acquisition
Sub Test()
Dim Table As String, SQL As String, SQLTable As String
Dim server As String, Database As String, GetClientDB As String
'CaseID = 0
'SolutionID = 498232069
Table = "PrSale"
SQL = PrSale(SolutionID, CaseId)
SQLTable = TableSQL(Table, SolutionID, CaseId)
server = ServerID
Database = DatabaseID
GetClientDB = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=" + Database + ";Data Source=" + server
Set DBConnection = New ADODB.Connection
DBConnection.CursorLocation = adUseServer
DBConnection.ConnectionString = GetClientDB
DBConnection.Open
Dim myview As ADODB.Recordset
'myview.Open SQLTable, DBConnection, adOpenStatic
Set myview = DBConnection.Execute(SQLTable)
Second part - table creation and fields definition:
Call DeleteIfExists(Table)
'Cria a tabela na base de dados
Dim tdf As DAO.TableDef
Set tdf = CurrentDb.CreateTableDef(Table)
'Lê os campos da tabela original e replica na tabela local
For Each viewfld In myview.Fields
Dim fld As DAO.Field
Dim mytype As Integer
Select Case viewfld.Type
Case 200 'VarChar
mytype = dbText
Case 3 'integer
mytype = dbInteger
Case 202 'VarWChar
mytype = dbText
Case 5 'Double
mytype = dbDouble
Case 2 'smallint
mytype = dbInteger
Case Else
mytype = dbText
End Select
If Table = "PMPERIOD" Then
If viewfld.Name = "StartDate" Or viewfld.Name = "StopDate" Then
mytype = dbDate
End If
End If
If Table = "PMSELL" Then
If viewfld.Name = "GRPNUM" Then
mytype = dbText
End If
End If
Set fld = tdf.CreateField(viewfld.Name, mytype)
tdf.Fields.Append fld
Next
CurrentDb.TableDefs.Append tdf
Third part - table fulfillment using data stored at myview recordset:
If myview.EOF Then
GoTo Skip_
Else
End If
myview.MoveFirst
Do While Not myview.EOF
'Preenche as tabelas com os dados do Recordset
Dim myrecordset As DAO.Recordset
Set myrecordset = CurrentDb.OpenRecordset(Table)
myrecordset.AddNew
For Each viewfld In myview.Fields
myrecordset.Fields(viewfld.Name).Value = viewfld.Value
Dim j As Integer
j = 10
Next
myrecordset.Update
myview.MoveNext
Loop
Skip_:
End Sub
As far as I was able to debug, until the second part things seems to be right, although I was not able to verify the fields type. The table is created at the MS Access database with correct labels, but than it crashes with the error 3421! :(
How can I verify the type of the fields in tdf?
Do you have any clue what is going on?
Best regards
Just setup a table link to the sql server table.
then fire up the Access query builder.
Now create a blank new query, drop in the linked table to sql server.
Now in the ribbon, change the query to a make table query.
You Have this:
So, you can pick the field names, set critera, and when you run that query, it will create a local table.
So, you don't need code to filter
You don't need code to pick some columns
You don't need code to re-name the colum name(s) in the target table.
Just setup a linked table to sql server.
then drop in that linked table into the query builder.
On ribbon, choose "make table"
At this point, you can pick some, or all of the columns.
At this point you can type in criteria for some of the columns to limit the data.
run the query - you are done. There is no need to write code, or even mess with connection strings here.
I'm trying to use Access's upsizing wizard to move the data from Access to SQL Server. I'm currently on this error but can't figure out where the property is coming from;
Property 'Attributes' already exists for 'table'.
The SQL its trying to run is;
EXEC sp_addextendedproperty N'Attributes', N'2', N'user', N'dbo', N'table', N'table', N'column', N'ID'
But the table in Access doesn't include an ID column and I can't see anything in the properties on the table to indicate why it's trying to add the property for SQL Server.
In the wizard guide I chose not to import any extras like indexes, triggers etc.
Any ideas why the wizard is doing this and how to stop it trying to create the properties?
Alternatively, are there any other tools which would moved the data from Access to MSSQL while keeping Access front-end objects in place and working?
The upsizing wizard had its deficiencies from the start, and has been removed from recent versions of Access. I recommend not using it.
Personally, I have a form that handles upsizing for me. It's a form with on it two text boxes named ConStr and adoString (the first containing the connection string for Access to use including the ODBC; prefix, the second containing either an ODBC or OLEDB string for ADO to use), a button named ToSQL, and a listbox named lstTables. It contains the following code:
To populate the local tables on load:
Private Sub Form_Load()
lstTables.RowSourceType = "Value List"
Dim iterator As Variant
For Each iterator In CurrentDb.TableDefs
If Not iterator.NAME Like "MSys*" And Not iterator.NAME Like "~*" Then
lstTables.AddItem iterator.NAME
End If
Next iterator
End Sub
To move the tables over to SQL server:
Private Sub ToSQL_Click()
Dim i1 As Variant
Dim td As DAO.TableDef
Dim NewTd As DAO.TableDef
Dim db As DAO.Database
Set db = CurrentDb
'Iterate through all selected tables
With lstTables
For Each i1 In .ItemsSelected
Set td = db.TableDefs(.ItemData(i1))
'Add a primary key if none exist
'AddPK td 'Not providing this one as it's not part of normal upscaling
'Move the table to SQL server
DoCmd.TransferDatabase acExport, "ODBC Database", _
conStr _
, acTable, .ItemData(i1), .ItemData(i1)
'Rename the local table to name_local
td.NAME = .ItemData(i1) & "_local"
'Change the remote table to the schema specified
'ADOChangeSchema GetDefaultSchema(), "mySchema", .ItemData(i1) 'Not providing this one as it's not part of normal upscaling
'Set the primary key in SQL server
ADOAddPrimaryKey GetDefaultSchema(), .ItemData(i1), GetPKName(td)
'Create a new linked table, linking to the remote table
Set NewTd = db.CreateTableDef(.ItemData(i1), 0, GetDefaultSchema() & .ItemData(i1), conStr)
db.TableDefs.Append NewTd
Next i1
End With
End Sub
And some helper functions:
Public Sub ADOAddPrimaryKey(SchemaName As String, tableName As String, FieldName As String)
On Error GoTo SetNotNull
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
conn.Open adoString
cmd.ActiveConnection = conn
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Exit Sub
SetNotNull:
If Err.Number = -2147217900 Then
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ALTER COLUMN [" & FieldName & "] INTEGER NOT NULL"
cmd.Execute
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Else
Err.Raise Err.Number
End If
End Sub
Public Function GetDefaultSchema() As String
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim rs As Object
conn.Open adoString
Set rs = conn.Execute("SELECT SCHEMA_NAME()")
GetDefaultSchema = rs.Fields(0)
End Function
Public Function GetPKName(td As DAO.TableDef) As String
'Returns the name of the first field included in the primary key (WARNING! Doesn't return all fields for composite primary keys!)
Dim idx As DAO.Index
For Each idx In td.Indexes
If idx.Primary Then
GetPKName = idx.Fields(0).NAME
Exit Function
End If
Next idx
End Function
This form only preserves data and the primary key, and makes several assumptions (too lazy to avoid them), such as: table names don't contain square brackets, there are no composite primary keys, the table schema is safe for use in SQL statements, there are no attachment fields or multivalued fields, and there are no relationships (had a version that preserved relationships, but... I honestly don't know where it is now).
It also leaves a renamed copy of the local table. The original version then tests 1K random rows to check if the content is identical, but I've omitted that for brevity.
You can use this as a starting point, since it might need tuning to suit your specific needs.
I seem to be having troubles with my stored procedure call here. I'm trying to get all my information from a stored procedure, which returns a single column into a datatable. I've followed along for most solutions here and can't seem to figure out why it is not working. My data table has a primary key constraint on it, due to having multiple values in it. But, from what I've read, this isn't necessary as the Fill on the SqlDataAdapter will just merge those changes if there is any?
Here is my code so far:
Dim dtValues As New DataTable()
Dim dtCol As New DataColumn()
dtCol.ColumnName = "ReferenceID"
dtCol.DataType = GetType(SqlInt32)
dtCol.AllowDBNull = False
dtValues.Columns.Add(dtCol)
dtValues.PrimaryKey = {dtCol}
Public Shared Sub ExecStoredProc(ByVal ProcName As String, ByRef connection As SqlConnection, ByRef dtValues As DataTable, ByVal ParamArray args() As Object)
Dim sqlCommand As New SqlCommand()
For i = 0 To args.Length - 1 Step 2
sqlCommand.Parameters.AddWithValue(CStr(args(i)), args(i + 1))
Next
sqlCommand.CommandText = ProcName
sqlCommand.CommandType = CommandType.StoredProcedure
sqlCommand.Connection = connection
Dim sqlAdp As New SqlDataAdapter(sqlCommand)
' Fill the table to mess around with.
sqlAdp.Fill(dtValues)
End Sub
However, the error that I get is:
Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
My stored procedure I'm calling is a simple
Select ID
From TableName
Where Reference = #Reference
with no conditions really, and I've tested and it returns about 19 unique records.
Any help would be appreciated, thanks!
You are defining a not null primary key data column in your table called "ReferenceID":
Dim dtCol As New DataColumn()
dtCol.ColumnName = "ReferenceID"
dtCol.DataType = GetType(SqlInt32)
dtCol.AllowDBNull = False
However, your sql query is not selecting a 1ReferenceID1 column, it is selecting a column named ID:
Select ID
From TableName
Where Reference = #Reference
Therefore, when you go to fill your table you get the error:
Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
Because the not-null, unique constraint you set on "ReferenceID" is being violated.
Change your query to:
Select ID as [ReferenceId]
From TableName
Where Reference = #Reference
And you should be set (assuming that that ID is unique and not null)
This code currently is using a DROP TABLE to transfer data from Access to SQL Server, that is when it was intended for one person to use. Now it is going to be used by multiple people so I need to alter the code to APPEND. I am trying to figure out how to get it to check the destination table to see if the record already exists, if it does it will skip over it, if not it will write that employees data to the table. The SQL table is prebuilt and it has a ROW ID column and TIME STAMP column that the access table does not have in order to keep track of the records being entered.
The code currently looks like:
Public Function Update()
Dim cdb As DAO.Database, qdf As DAO.QueryDef
Dim err As DAO.Error
Const DestinationTableName = "AC_CDData"
Const ConnectionString = _
"ODBC;" & _
"Driver={SQL Server Native Client 10.0};" & _
"Server=SERVER;" & _
"Database=DB;" & _
"UID=ID;" & _
"PWD=PW;"
Set cdb = CurrentDb
Set qdf = cdb.CreateQueryDef("")
qdf.Connect = ConnectionString
qdf.SQL = _
"IF EXISTS " & _
"(" & _
"SELECT * FROM INFORMATION_SCHEMA.TABLES " & _
"WHERE TABLE_NAME='" & DestinationTableName & " '" & _
") " & _
"DROP TABLE [" & DestinationTableName & "]"
qdf.ReturnsRecords = False
On Error GoTo Update_qdfError
qdf.Execute dbFailOnError
On Error GoTo 0
Set qdf = Nothing
Set cdb = Nothing
DoCmd.TransferDatabase _
acExport, _
"ODBC Database", _
ConnectionString, _
acTable, _
"CDData", _
DestinationTableName, _
False
Exit Function
Update_qdfError:
For Each err In DAO.Errors
MsgBox err.Description, vbCritical, "Error " & err.Number
Next
End Function
The DoCmd.TransferDatabase does a complete transfer of the data from the Access table, to the SQL Server database. This function can not do partial inserts based on existing records.
What you can do, however, is export the data into a temporary new table (without dropping the existing table), followed by executing an SQL MERGE statement, to incorporate the new records of the temporary table, with the existing table.
You'll need to know how to perform SQL queries from VBA against your SQL database, and you'll need to know how to use the SQL MERGE statement. A google search will quickly give you the answer to both.
Just setup a simple standared linked table to SQL server in Access.
Since the column of the existing record is a primary key (or simply an index set = unique), then you only need ONE LINE of code to append your data.
This will work:
CurrentDb.Execute "INSERT INTO AC_CDData SELECT * FROM CDData;"
Any row that already exists will be ignored due to key (or index) violation. Thus only new non existing records will be appended with the above one line of code.
Edit:
As for the target table being a SQL linked table? Your application on startup does not care if you have some linked tables. Your application does not use nor care about such linked tables unless you use them.
The assuming here is you have a local data table. The linked table ONLY comes into play when you going to do the export. As noted you cannot export unless you are connected to SQL server. The “code” or testing if you are connected is not dealt with in this question but even if it was part of the question in both cases you still have to test for a connection and once again when you do finally determine you have a connect and you decide to export then again the linked table as per above will work.
So there is really no reason I can think of as to why having a linked table that is ONLY used during the append to the SQL table will be any kind of issue or problem.
As stated, then the one line of VBA code should suffice here.
In case of concurrent usage the process need to be changed:
DestinationTableName must be unique per session (thread) and need to be changed (from constant to variable)
Tabel with DestinationTableName neame need and need to be dropped before the export data (as it was in current solution).
DoCmd.TransferDatabase will not change - the data will be exported into the unique temp table.
For simplifying the Merge process the stored procedure can be written on the SQL Server side (with all needed to APSERT logic) with the source table name as parameter. This SP need to be called after the DoCmd.TransferDatabase completion
In this case the flow will be the following:
DestinationTableName unique name generation
Dropping this table on the SQL Server side (qdf.Execute ...)
Export data to the temp table (DoCmd.TransferDatabase ...)
Merge data in the SQL Server side (call to created SP with needed logic of update / insert)