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.
Related
When I create a DSN connection to SQL Server using the wizard, I am able to link it to a view. In this case, Access recognizes it as an editable table.
But if I use a DSN-less connection to a view using vba code (Method 1 from https://support.microsoft.com/en-us/kb/892490), it is linked as a table that is NOT updatable.
I don't know why there is a difference, but how can I make a connection to a view in SQL Server (either as a table or a query in Access) and have it be updatable?
Edit: When I make a DSN-less connection using a table in SQL Server rather than a view, it is updatable in Access. I would have guessed my problem has to do with views not having a unique ID, but I'm confused why a DSN connection can be updatable while DSN-less cannot.
It's not because it's DSN-less, but because you created it via VBA. If you link the view via the Access GUI, it asks you for the primary key.
But via VBA, it doesn't know the primary key, so the linked view is not updateable. With a table, Access gets the primary key automatically via ODBC, so the table works.
Solution: set the primary key after linking the view via VBA:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
If you have many views, and re-link them regularly (e.g. going from dev to production database), it becomes impractical to hardcode their names and PKs. I wrote a function to retrieve all primary key indexes from linked views, and re-create them after linking.
If you want, I can dig it up.
Edit:
This is what I do:
' This function returns the full DSN-less connect string
Private Function ODBC_String() As String
' In the real world there are several constants and variable in there
ODBC_String = "ODBC;DRIVER={SQL Server};SERVER=aaa;DATABASE=bbb;UID=ccc;PWD=ccc;LANGUAGE=us_english;TRUSTED_CONNECTION=No"
End Function
To link a table or view the first time, I use this (strTable is the table/view name):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
For tables, the primary key (PK) is determined automatically. For a view, I get the Access dialog window to specify the PK, same as if I link the view manually.
The PK information is stored in the TableDef object for the linked view, so I never have to hardcode it anywhere.
To store the PK information for all linked views, I have this table (it's a local table in the Access frontend for simplicity):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
and this function. All Views (and only Views) are called "v_*", so I can list them by name.
I'm actually not sure if you can determine from a TableDef object whether it points to a table or view.
Private Sub StoreViewPKs()
Dim TD As TableDef
Dim idx As index
Dim FD As Field
Dim RS As Recordset
Dim S As String
' DB is a global Database object, set to CurrentDB
DB.Execute "Delete * From t_LinkedViewPK"
Set RS = DB.OpenRecordset("t_LinkedViewPK")
For Each TD In DB.TableDefs
If TD.Name Like "v_*" Then
' Views must have exactly one index. If not: panic!
If TD.Indexes.Count <> 1 Then
MsgBox "View " & TD.Name & " has " & TD.Indexes.Count & " Indizes.", vbCritical
Stop
End If
Set idx = TD.Indexes(0)
' Build field list (the index may contain multiple fields)
S = ""
For Each FD In idx.Fields
If S <> "" Then S = S & ", "
S = S & FD.Name
Next FD
RS.AddNew
RS!ViewName = TD.Name
RS!IndexFields = S
RS.Update
End If
Next TD
RS.Close
End Sub
When I make changes to table or view structures, or change the source database (this is done by changing the output of ODBC_String()), I call this function:
Public Function Sql_RefreshTables()
Dim TD As TableDef
Dim S As String
Dim IdxFlds As String
DB.TableDefs.Refresh
' save current Indizes for Views (recreated after .RefreshLink)
Call StoreViewPKs
For Each TD In DB.TableDefs
If Len(TD.Connect) > 0 Then
If Left(TD.Connect, 5) = "ODBC;" Then
Debug.Print "Updating " & TD.Name
TD.Connect = ODBC_String()
TD.RefreshLink
' View?
If TD.Name Like "v_*" Then
IdxFlds = Nz(DLookup("IndexFields", "t_LinkedViewPK", "ViewName = '" & TD.Name & "'"))
If IdxFlds = "" Then Stop
' Create PK
S = "CREATE INDEX PrimaryKey ON " & TD.Name & " (" & IdxFlds & ") WITH PRIMARY"
DB.Execute S
End If
End If
End If
Next TD
DB.TableDefs.Refresh
End Function
Note:
Instead of the table t_LinkedViewPK, a dictionary object could be used. But while developing this, it was very useful to have it as an actual table.
Andre's answer is correct. I am using a little bit more complex code to create the index - it is just cosmetic change:
Public Function RefreshIndexes()
On Error Resume Next
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName (MyPrimaryKeyField) WITH PRIMARY;", dbFailOnError
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName2 (MyPrimaryKeyField2) WITH PRIMARY;", dbFailOnError
End Function
I call this command when user opens Access and only for views that do not have index.
I'm in the process of moving our DB from an Access backend to a SQL Server backend while keeping the Access front end. One of the tables was set up with a replication ID for its PK. As an access DB, we could insert values into this table without referencing the PK field, as Access automatically generates a new GUID.
We have a function that builds a record to insert into this table that no longer works with the linked SQL Server version of the table and I'm pretty sure it must has something to do with this replcationID field. In SQL Server, it imported as data type 'uniqueidentifier'. When the function runs, it return no errors, but also inserts nothing.
I'd like to know if there is a way to get this working without changing it to a passthrough. How do I get MS Access to tell SQL Server to generate a new GUID when inserting a new record.
As a side note, the PK field is called PurchaseID and is not referenced currently in the vba (since it used to auto generate for this field)
Public Sub BuildReorderRecord(Market As String, MPID As String, FloorsetID As String)
Dim values As String
Dim sql As String
Dim P3ID As String
Dim Username As String
Dim dt As String
Dim db As Database
Dim rs As Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT ID FROM tblAttributes WHERE MPID = """ & MPID & """")
'Get P3ID and auditing values
dt = Now()
Username = Environ("USERNAME")
P3ID = rs![ID]
'build record values string
values = """" & P3ID & """, """ & Market & """, """ & MPID & "', """ & FloorsetID & """"
values = values & ", #" & dt & "#, """ & Username & """, #" & dt & "#, """ & Username & """"
'build sql string
sql = "INSERT INTO tblReorders (P3ID, country, mpid, floorsetID"
sql = sql & ", CreateDate, CreatorUsername, ChangeRecord_Timestamp, ChangeRecord_Username)" & vbNewLine
sql = sql & "VALUES(" & values & ")"
'insert the new record
db.Execute sql
Set db = Nothing
Set rs = Nothing
End Sub
You could add a default constraint on the table for that column in sql server like so:
alter table t
add constraint [df_t_guid]
default newid() for [guid];
Or you could use a custom function in access to generate a guid like stguidgen()
insert into ... (guid...
values (stguidgen(), ...)
With a pass-through query, you could use newid() which generates a new uniqueidentifier in sql server.
insert into ... (guid...
values (newid(), ...)
When I create a DSN connection to SQL Server using the wizard, I am able to link it to a view. In this case, Access recognizes it as an editable table.
But if I use a DSN-less connection to a view using vba code (Method 1 from https://support.microsoft.com/en-us/kb/892490), it is linked as a table that is NOT updatable.
I don't know why there is a difference, but how can I make a connection to a view in SQL Server (either as a table or a query in Access) and have it be updatable?
Edit: When I make a DSN-less connection using a table in SQL Server rather than a view, it is updatable in Access. I would have guessed my problem has to do with views not having a unique ID, but I'm confused why a DSN connection can be updatable while DSN-less cannot.
It's not because it's DSN-less, but because you created it via VBA. If you link the view via the Access GUI, it asks you for the primary key.
But via VBA, it doesn't know the primary key, so the linked view is not updateable. With a table, Access gets the primary key automatically via ODBC, so the table works.
Solution: set the primary key after linking the view via VBA:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
If you have many views, and re-link them regularly (e.g. going from dev to production database), it becomes impractical to hardcode their names and PKs. I wrote a function to retrieve all primary key indexes from linked views, and re-create them after linking.
If you want, I can dig it up.
Edit:
This is what I do:
' This function returns the full DSN-less connect string
Private Function ODBC_String() As String
' In the real world there are several constants and variable in there
ODBC_String = "ODBC;DRIVER={SQL Server};SERVER=aaa;DATABASE=bbb;UID=ccc;PWD=ccc;LANGUAGE=us_english;TRUSTED_CONNECTION=No"
End Function
To link a table or view the first time, I use this (strTable is the table/view name):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
For tables, the primary key (PK) is determined automatically. For a view, I get the Access dialog window to specify the PK, same as if I link the view manually.
The PK information is stored in the TableDef object for the linked view, so I never have to hardcode it anywhere.
To store the PK information for all linked views, I have this table (it's a local table in the Access frontend for simplicity):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
and this function. All Views (and only Views) are called "v_*", so I can list them by name.
I'm actually not sure if you can determine from a TableDef object whether it points to a table or view.
Private Sub StoreViewPKs()
Dim TD As TableDef
Dim idx As index
Dim FD As Field
Dim RS As Recordset
Dim S As String
' DB is a global Database object, set to CurrentDB
DB.Execute "Delete * From t_LinkedViewPK"
Set RS = DB.OpenRecordset("t_LinkedViewPK")
For Each TD In DB.TableDefs
If TD.Name Like "v_*" Then
' Views must have exactly one index. If not: panic!
If TD.Indexes.Count <> 1 Then
MsgBox "View " & TD.Name & " has " & TD.Indexes.Count & " Indizes.", vbCritical
Stop
End If
Set idx = TD.Indexes(0)
' Build field list (the index may contain multiple fields)
S = ""
For Each FD In idx.Fields
If S <> "" Then S = S & ", "
S = S & FD.Name
Next FD
RS.AddNew
RS!ViewName = TD.Name
RS!IndexFields = S
RS.Update
End If
Next TD
RS.Close
End Sub
When I make changes to table or view structures, or change the source database (this is done by changing the output of ODBC_String()), I call this function:
Public Function Sql_RefreshTables()
Dim TD As TableDef
Dim S As String
Dim IdxFlds As String
DB.TableDefs.Refresh
' save current Indizes for Views (recreated after .RefreshLink)
Call StoreViewPKs
For Each TD In DB.TableDefs
If Len(TD.Connect) > 0 Then
If Left(TD.Connect, 5) = "ODBC;" Then
Debug.Print "Updating " & TD.Name
TD.Connect = ODBC_String()
TD.RefreshLink
' View?
If TD.Name Like "v_*" Then
IdxFlds = Nz(DLookup("IndexFields", "t_LinkedViewPK", "ViewName = '" & TD.Name & "'"))
If IdxFlds = "" Then Stop
' Create PK
S = "CREATE INDEX PrimaryKey ON " & TD.Name & " (" & IdxFlds & ") WITH PRIMARY"
DB.Execute S
End If
End If
End If
Next TD
DB.TableDefs.Refresh
End Function
Note:
Instead of the table t_LinkedViewPK, a dictionary object could be used. But while developing this, it was very useful to have it as an actual table.
Andre's answer is correct. I am using a little bit more complex code to create the index - it is just cosmetic change:
Public Function RefreshIndexes()
On Error Resume Next
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName (MyPrimaryKeyField) WITH PRIMARY;", dbFailOnError
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName2 (MyPrimaryKeyField2) WITH PRIMARY;", dbFailOnError
End Function
I call this command when user opens Access and only for views that do not have index.
When I create a DSN connection to SQL Server using the wizard, I am able to link it to a view. In this case, Access recognizes it as an editable table.
But if I use a DSN-less connection to a view using vba code (Method 1 from https://support.microsoft.com/en-us/kb/892490), it is linked as a table that is NOT updatable.
I don't know why there is a difference, but how can I make a connection to a view in SQL Server (either as a table or a query in Access) and have it be updatable?
Edit: When I make a DSN-less connection using a table in SQL Server rather than a view, it is updatable in Access. I would have guessed my problem has to do with views not having a unique ID, but I'm confused why a DSN connection can be updatable while DSN-less cannot.
It's not because it's DSN-less, but because you created it via VBA. If you link the view via the Access GUI, it asks you for the primary key.
But via VBA, it doesn't know the primary key, so the linked view is not updateable. With a table, Access gets the primary key automatically via ODBC, so the table works.
Solution: set the primary key after linking the view via VBA:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
If you have many views, and re-link them regularly (e.g. going from dev to production database), it becomes impractical to hardcode their names and PKs. I wrote a function to retrieve all primary key indexes from linked views, and re-create them after linking.
If you want, I can dig it up.
Edit:
This is what I do:
' This function returns the full DSN-less connect string
Private Function ODBC_String() As String
' In the real world there are several constants and variable in there
ODBC_String = "ODBC;DRIVER={SQL Server};SERVER=aaa;DATABASE=bbb;UID=ccc;PWD=ccc;LANGUAGE=us_english;TRUSTED_CONNECTION=No"
End Function
To link a table or view the first time, I use this (strTable is the table/view name):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
For tables, the primary key (PK) is determined automatically. For a view, I get the Access dialog window to specify the PK, same as if I link the view manually.
The PK information is stored in the TableDef object for the linked view, so I never have to hardcode it anywhere.
To store the PK information for all linked views, I have this table (it's a local table in the Access frontend for simplicity):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
and this function. All Views (and only Views) are called "v_*", so I can list them by name.
I'm actually not sure if you can determine from a TableDef object whether it points to a table or view.
Private Sub StoreViewPKs()
Dim TD As TableDef
Dim idx As index
Dim FD As Field
Dim RS As Recordset
Dim S As String
' DB is a global Database object, set to CurrentDB
DB.Execute "Delete * From t_LinkedViewPK"
Set RS = DB.OpenRecordset("t_LinkedViewPK")
For Each TD In DB.TableDefs
If TD.Name Like "v_*" Then
' Views must have exactly one index. If not: panic!
If TD.Indexes.Count <> 1 Then
MsgBox "View " & TD.Name & " has " & TD.Indexes.Count & " Indizes.", vbCritical
Stop
End If
Set idx = TD.Indexes(0)
' Build field list (the index may contain multiple fields)
S = ""
For Each FD In idx.Fields
If S <> "" Then S = S & ", "
S = S & FD.Name
Next FD
RS.AddNew
RS!ViewName = TD.Name
RS!IndexFields = S
RS.Update
End If
Next TD
RS.Close
End Sub
When I make changes to table or view structures, or change the source database (this is done by changing the output of ODBC_String()), I call this function:
Public Function Sql_RefreshTables()
Dim TD As TableDef
Dim S As String
Dim IdxFlds As String
DB.TableDefs.Refresh
' save current Indizes for Views (recreated after .RefreshLink)
Call StoreViewPKs
For Each TD In DB.TableDefs
If Len(TD.Connect) > 0 Then
If Left(TD.Connect, 5) = "ODBC;" Then
Debug.Print "Updating " & TD.Name
TD.Connect = ODBC_String()
TD.RefreshLink
' View?
If TD.Name Like "v_*" Then
IdxFlds = Nz(DLookup("IndexFields", "t_LinkedViewPK", "ViewName = '" & TD.Name & "'"))
If IdxFlds = "" Then Stop
' Create PK
S = "CREATE INDEX PrimaryKey ON " & TD.Name & " (" & IdxFlds & ") WITH PRIMARY"
DB.Execute S
End If
End If
End If
Next TD
DB.TableDefs.Refresh
End Function
Note:
Instead of the table t_LinkedViewPK, a dictionary object could be used. But while developing this, it was very useful to have it as an actual table.
Andre's answer is correct. I am using a little bit more complex code to create the index - it is just cosmetic change:
Public Function RefreshIndexes()
On Error Resume Next
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName (MyPrimaryKeyField) WITH PRIMARY;", dbFailOnError
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName2 (MyPrimaryKeyField2) WITH PRIMARY;", dbFailOnError
End Function
I call this command when user opens Access and only for views that do not have index.
Im trying to open a SQL stored procedure, which contains a Select top 5* form table, and load the data into an Access table called Table3.
How do I set the Command Object ActiveConnection property to use the current Access DB?
when I provide it with an actual connection string, it says that the user has locked it.
At the moment, it runs and prints prints out the results but it does not insert the values. It does not give me an error either.
'Use this code to run the SP and extract all the records
Public Sub ExecuteSPAsMethod2()
Dim rsData As ADODB.Recordset
Dim sConnectSQL As String 'to create connection to SQL Server
Dim sConnectAccess As String 'to create connection with Access DB (may not be neccessary)
Dim objCommand As ADODB.Command 'for INSERT results of SP into Access table
'Creating the connection string to SQL server
sConnectSQL = "Provider=SQLOLEDB;Data Source=MYSERVER; " & _
"Initial Catalog=SQLDatabase;Integrated Security=SSPI"
'Creating the Connection object and Recordset object
Set objConn = New ADODB.Connection
Set rsData = New ADODB.Recordset
'Opening the connection
objConn.Open sConnectSQL
'Execute the SP and give the results to the Recordset
objConn.SurveyDataSP "4", rsData
Do While Not rsData.EOF
Debug.Print rsData!Stratum
rsData.MoveNext
Loop
'Now write the data into Access Table
'Create connection string to Access DB table
'sConnectAccess = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=C:\Databse1.accdb;" & _
"Mode = Share Exclusive"
'Command object to be used for Access SQL query
Set objCommand = New ADODB.Command
'objCommand.ActiveConnection = sConnectAccess
'Insert new record in the DB
'Load the SQL string into the command object
Do While Not rsData.EOF
objCommand.CommandText = "INSERT INTO table3 (" & rsData!Stratum & ")"
objCommand.Execute
rsData.MoveNext
Loop
End Sub
There is no need to write such large amounts of code and create world poverty. Save the execute command as a pass through query.
Eg:
Exec 4
Assuming the above is called sp1, then this code will append all data from the above into the local table:
CurrentDb.Execute "INSERT INTO sp1Local select sp1.* from sp1"
So all of this code can be done with ONE line of VBA code.