Options 1 & 2 are comparable since in both cases SQL Server establishs an OLE-DB connection to Access. Option 2 is the easiest to work with from the application since the Access table is linked on the sql server side it looks to your access program just like an ADO connection to another sql server table. The downside is that the sql server dba needs to set up the access table as a linked server and the dba might not want to do it. This is one more thing for them to consider in the sql server maintenance.
The OpenRowSet is still done on the sql server side but asks sql server to make the connection at runtime. This is probably more dynamic and flexible for your needs. I will paste in an example using the Northwind database which you should have on your system. First on option 4, it is still all done on the client side but the update to sql server is done in 1 batch. For 20K rows, I would go with options 1 or 2. In the example the access mdb is on the sql server pc.
Public Function rowset()
Dim cn As New ADODB.Connection, sqlString As String
Dim rs As New ADODB.Recordset, connString As String
connString = "provider=SQLOLEDB.1;" & _
"User ID=sa;Initial Catalog=northwind;" & _
"Data Source=bigtuna;" & _
"Persist Security Info=False"
cn.ConnectionString = connString
cn.Open connString
sqlString = "SELECT c.*, o.* "
sqlString = sqlString & "FROM Northwind.dbo.Customers AS c INNER JOIN "
sqlString = sqlString & "OPENROWSET('Microsoft.Jet.OLEDB.4.0', "
'--- could put variables in at this point for location and database name
sqlString = sqlString & "'c:\program files\microsoft office\office\Samples\northwind.mdb';'admin';, Orders) "
sqlString = sqlString & "AS o ON c.CustomerID = o.CustomerID "
rs.Open sqlString, cn, adOpenForwardOnly, adLockReadOnly
If Not (rs.EOF = True) Then
Debug.Print "field name = "; rs.Fields(0).Name
Debug.Print "field value = "; rs.Fields(0).Value
End If
End Function