Unable to decrypt Microsoft Access *.mdb database with user security - sql-server

I have old .mdb database with a lot of tables/forms/queries and external connections (was created in Access 2003) with User Level security.
Now I have decided to move to SQL Server, but I can't decrypt database (in Access 2010 & 2013) to use with Access to SQL tool.
Decryption process is working but in the end I get an error: "you can't access this table".
I've tried repair & compress.
Can you help me?
Thank you.

You don't have a lot of choices as far as I know:
you can reconstruct the .mdw file by creating a new database and using the exact same username and PID of the original user, then use the new .mdw with the old database.
You can use some of the tools from Serge Gavrilov to help you find the username and PID from a database.
Have a look at the forums on UtterAccess.com. There seem to be a lot of discussions regarding lost mdw.
you can use some data repair services like at EverythingAccess. Cost some money, but I'm sure they can recover your data.
Once you can access the DB, you can write code to extract all the forms, queries and such from the DB. Here's a snippet of code I wrote that does this. You're going to need to tweak it heavily, because it's based on a form that allows you to choose the database name. You're going to have to repeat this for queries, forms, etc...
' Database.
Dim dbRep As DAO.Database
Dim dbNew As DAO.Database
' For copying tables and indexes.
Dim tblRep As DAO.TableDef
Dim tblNew As DAO.TableDef
Dim fldRep As DAO.Field
Dim fldNew As DAO.Field
Dim idxRep As DAO.Index
Dim idxNew As DAO.Index
' For copying data.
Dim rstRep As DAO.Recordset
Dim rstNew As DAO.Recordset
Dim rec1 As DAO.Recordset
Dim rec2 As Recordset
Dim intC As Integer
' For copying table relationships.
Dim relRep As DAO.Relation
Dim relNew As DAO.Relation
' For copying queries.
Dim qryRep As DAO.QueryDef
Dim qryNew As DAO.QueryDef
' For copying startup options.
Dim avarSUOpt
Dim strSUOpt As String
Dim varValue
Dim varType
Dim prpRep As DAO.Property
Dim prpNew As DAO.Property
' For importing forms, reports, modules, and macros.
Dim appNew As New Access.Application
Dim doc As DAO.Document
' Open the database, not in exclusive mode.
Set dbRep = OpenDatabase(Forms!CMDB_frmUpgrade.TxtDatabase, False)
' Open the new database
Set dbNew = CurrentDb
DoEvents
' Turn on the hourglass.
DoCmd.Hourglass True
'********************
Debug.Print "Copy Tables"
'********************
If Forms!CMDB_frmUpgrade.CkTables = True Then
Forms!CMDB_frmUpgrade.LstMessages.addItem "Copying Tables:"
' Loop through the collection of table definitions.
For Each tblRep In dbRep.TableDefs
Set rec1 = dbRep.OpenRecordset("SELECT MSysObjects.Name FROM MsysObjects WHERE ([Name] = '" & tblRep.Name & "') AND ((MSysObjects.Type)=4 or (MSysObjects.Type)=6)")
If rec1.EOF Then
XF = 0
Else
XF = 1
End If
' Ignore system tables and CMDB tables.
If InStr(1, tblRep.Name, "MSys", vbTextCompare) = 0 And _
InStr(1, tblRep.Name, "CMDB", vbTextCompare) = 0 And _
XF = 0 Then
'***** Table definition
' Create a table definition with the same name.
Set tblNew = dbNew.CreateTableDef(tblRep.Name)
Forms!CMDB_frmUpgrade.LstMessages.addItem "--> " & tblRep.Name & ""
' Set properties.
tblNew.ValidationRule = tblRep.ValidationRule
tblNew.ValidationText = tblRep.ValidationText
' Loop through the collection of fields in the table.
For Each fldRep In tblRep.Fields
' Ignore replication-related fields:
' Gen_XXX, s_ColLineage, s_Generation, s_GUID, s_Lineage
If InStr(1, fldRep.Name, "s_", vbTextCompare) = 0 And _
InStr(1, fldRep.Name, "Gen_", vbTextCompare) = 0 Then
'***** Field definition
Set fldNew = tblNew.CreateField(fldRep.Name, fldRep.Type, _
fldRep.Size)
' Set properties.
On Error Resume Next
fldNew.Attributes = fldRep.Attributes
fldNew.AllowZeroLength = fldRep.AllowZeroLength
fldNew.DefaultValue = fldRep.DefaultValue
fldNew.Required = fldRep.Required
fldNew.Size = fldRep.Size
' Append the field.
tblNew.Fields.Append fldNew
'On Error GoTo Err_NewShell
End If
Next fldRep
'***** Index definition
' Loop through the collection of indexes.
For Each idxRep In tblRep.Indexes
' Ignore replication-related indexes:
' s_Generation, s_GUID
If InStr(1, idxRep.Name, "s_", vbTextCompare) = 0 Then
' Ignore indices set as part of Relation Objects
If Not idxRep.Foreign Then
' Create an index with the same name.
Set idxNew = tblNew.CreateIndex(idxRep.Name)
' Set properties.
idxNew.Clustered = idxRep.Clustered
idxNew.IgnoreNulls = idxRep.IgnoreNulls
idxNew.Primary = idxRep.Primary
idxNew.Required = idxRep.Required
idxNew.Unique = idxRep.Unique
' Loop through the collection of index fields.
For Each fldRep In idxRep.Fields
' Create an index field with the same name.
Set fldNew = idxNew.CreateField(fldRep.Name)
' Set properties.
fldNew.Attributes = fldRep.Attributes
' Append the index field.
idxNew.Fields.Append fldNew
Next fldRep
' Append the index to the table.
tblNew.Indexes.Append idxNew
End If
End If
Next idxRep
' Append the table.
dbNew.TableDefs.Append tblNew
End If
DoEvents
Next tblRep
'********************
Debug.Print "Copy Data"
'********************
' Loop through the list of table definitions.
For Each tblRep In dbRep.TableDefs
Set rec1 = dbRep.OpenRecordset("SELECT MSysObjects.Name FROM MsysObjects WHERE ([Name] = '" & tblRep.Name & "') AND ((MSysObjects.Type)=4 or (MSysObjects.Type)=6)")
If rec1.EOF Then
XF = 0
Else
XF = 1
End If
' Ignore system tables and CMDB tables.
If InStr(1, tblRep.Name, "MSys", vbTextCompare) = 0 And _
InStr(1, tblRep.Name, "CMDB", vbTextCompare) = 0 And _
XF = 0 Then
' Open a recordset for the new table.
Set rstNew = dbNew.OpenRecordset(tblRep.Name, dbOpenTable)
' Open a recordset for the old table.
Set rstRep = dbRep.OpenRecordset(tblRep.Name, dbOpenTable)
' Continue if there are records.
If Not rstRep.BOF Then
' Move to the first record.
rstRep.MoveFirst
' Loop through all the old table records.
Do Until rstRep.EOF
' Add a record to the new table.
rstNew.AddNew
' For each field in the new table, set the value
' to the value in the related field of the old table.
For intC = 0 To rstNew.Fields.count - 1
rstNew.Fields(intC).Value = _
rstRep.Fields(rstNew.Fields(intC).Name).Value
Next
' Update the new table.
rstNew.Update
' Move to the next old table record.
rstRep.MoveNext
Loop ' rstRep
End If
' Close the new recordset.
rstNew.Close
' Close the old recordset.
rstRep.Close
End If
DoEvents
Next tblRep
End If

Related

Compare two sheets and highlight differences on each sheet - is looping the only way?

I am seeking advice in relation to improving performance for a large data set (roughly 175k lines on each sheet and 39 columns A:AM (comparing sheet1 vs sheet2). These sheets are exported from access and my VBA is written in Access. I have coding that employs a "for" loop that checks cell by cell and highlights if there is a mismatch in each relative cell.
My question - will using an array or dictionary function speed up the process? If yes, can you provide bread crumbs to shed some light on how to execute? This code currently takes approximately 3 hours to complete. Roughly 2 minutes for the export from Access to Excel and the rest of the time represents looping and highlighting.
As a note - I have written code for conditional formatting and that works incredibly fast. The main problem is that I am unable to copy/paste the sheets with highlighted cells into new sheets while leaving the conditions behind. I would be interested to hear if any have found a way to maneuver that mine field.
Code below:
DoCmd.SetWarnings False
Dim xlapp As Excel.Application
Dim xlbook As Excel.Workbook
Dim xlSheet, xlSheetPre, xlSheetPost As Excel.Worksheet
Dim SQL As String
Dim rs1 As DAO.Recordset
Dim iSheet As Long, iRow As Long, iCol As Long, cols As Long
Dim MaxLastRow As Long, MaxLastCol As Long
Dim LastRow1 As Range, LastRow2 As Range
Dim LastCol1 As Range, LastCol2 As Range
Dim i As Integer
SQL = "SELECT * From Pre"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set xlapp = Excel.Application
xlapp.Visible = True
Set xlbook = xlapp.Workbooks.Add
i = 1
Do
Set xlSheet = Sheets.Add(after:=Sheets(Sheets.Count))
i = i + 1
Loop Until i = 2 ' the number 2 represents how many sheets you want to add to the
workbook
Set xlSheet = xlbook.Worksheets(1) ' Finds worksheet (1) and begins loading data from SQL
table above
With xlSheet
.Name = "Pre" ' Name the worksheet
.Range("a1:am1").Font.Bold = True 'Converts headers in row 1 to
bold font
.Range("A2").CopyFromRecordset rs1 'Copies all data from selected
table (SQL)into your worksheet
.Range("a1").AutoFilter ' Adds filter to your columns
.Cells.Columns.AutoFit ' Adjust worksheet column width to autofit
your data
.Range("a1:am1").Interior.ColorIndex = 37 ' Changes color of cell
' This loop reads all headers in your access table and places
them on worksheet
For cols = 0 To rs1.Fields.Count - 1
.Cells(1, cols + 1).Value = rs1.Fields(cols).Name
Next
End With
SQL = "SELECT * From Post"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set xlSheet = xlbook.Worksheets(2)
With xlSheet
.Name = "Post" ' Name the worksheet
.Range("a1:am1").Font.Bold = True 'Converts headers in row 1 to
bold font
.Range("A2").CopyFromRecordset rs1 'Copies all data from selected
table (SQL)into your worksheet
.Range("a1").AutoFilter ' Adds filter to your columns
.Cells.Columns.AutoFit ' Adjust worksheet column width to autofit
your data
.Range("a1:am1").Interior.ColorIndex = 37 ' Changes color of cell
' This loop reads all headers in your access table and places
them on worksheet
' This loop reads all headers in your access table and places them on worksheet
For cols = 0 To rs1.Fields.Count - 1
.Cells(1, cols + 1).Value = rs1.Fields(cols).Name
Next
End With
Set xlSheetPre = xlbook.Worksheets(1)
Set xlSheetPost = xlbook.Worksheets(2)
Set LastRow1 = xlSheetPre.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
Set LastRow2 = xlSheetPost.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
If Not LastRow1 Is Nothing Then
If Not LastRow2 Is Nothing Then
If LastRow1.Row > LastRow2.Row Then
MaxLastRow = LastRow1.Row
Else
MaxLastRow = LastRow2.Row
End If
Else
MaxLastRow = LastRow1.Row
End If
Else
MaxLastRow = LastRow2.Row
End If
Set LastCol1 = xlSheetPre.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
Set LastCol2 = xlSheetPost.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
If Not LastCol1 Is Nothing Then
If Not LastCol2 Is Nothing Then
If LastCol1.Column > LastCol2.Column Then
MaxLastCol = LastCol1.Column
Else
MaxLastCol = LastCol2.Column
End If
Else
MaxLastCol = LastCol1.Column
End If
Else
MaxLastCol = LastCol2.Column
End If
For iRow = 2 To MaxLastRow 'starting loop on row 2
For iCol = 4 To MaxLastCol 'starting loop on column 4
If xlSheetPre.Cells(iRow, iCol).Value <> xlSheetPost.Cells(iRow, iCol).Value Then
xlSheetPre.Cells(iRow, iCol).Interior.ColorIndex = 4
xlSheetPost.Cells(iRow, iCol).Interior.ColorIndex = 4
End If
Next iCol
Next iRow
SubExit:
On Error Resume Next
rs1.Close
Set rs1 = Nothing
DoCmd.SetWarnings True
Exit Sub
Try and reduce the number of records you have to compare by only extracting those with differences. There are several ways you could do that in SQL but as a proof of concept this compares each column in turn creating a temporary table of keys which is used to filter the records extracted.
Option Compare Database
Option Explicit
Sub DumpToExcel()
Dim n As Integer, SQL As String, fname
' field names
fname = Array("", "F1", "F2", "F3", "F4", "F5", _
"F6", "F7", "F8", "F9", "F10")
' identify diff records
Debug.Print UBound(fname)
DoCmd.SetWarnings False
For n = 1 To UBound(fname)
If n = 1 Then ' create table
SQL = " SELECT post.ID, """ & n & """ AS Col INTO tmp"
Else
SQL = " INSERT INTO tmp" & _
" SELECT post.ID, """ & n & """ AS Col"
End If
SQL = SQL & _
" FROM Post LEFT JOIN pre ON Post.id = pre.id" & _
" WHERE NZ([pre].[" & fname(n) & "],"")<>NZ([post].[" & fname(n) & "],"");"
DoCmd.RunSQL SQL
Next
DoCmd.SetWarnings True
' extract data
Dim rs1 As DAO.Recordset
SQL = " SELECT * FROM pre" & _
" WHERE (((pre.[ID]) In " & _
" (SELECT DISTINCT(ID) FROM tmp )));"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
' create excel
Dim xlapp As Excel.Application, xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
Set xlapp = Excel.Application
xlapp.Visible = True
Set xlBook = xlapp.Workbooks.Add
'add sheets as required
Do While xlBook.Sheets.Count < 2
xlBook.Sheets.Add
Loop
' copy recordset to sheet
xlBook.Sheets(1).Range("A2").CopyFromRecordset rs1
MsgBox "Done"
End Sub
"My question - will using an array or dictionary function speed up the process?"
Speaking from experience, the answer is: No, it will not. The reason is that you will have to read the cells in the worksheet to populate an array or a dictionary in the first place, so... Looping is it, really, and you need to organize the data (usually by proper sorting of the lists, tables, ranges, whatever) to minimize searching for the matching records (rows) to make your loops run faster.
If you are in Access then you can do that directly with the recordsets, providing your company's network security does not interfere with movement withing recordset objects (mine does interfere, and very severely at that--Tanium is a real menace!)
Here's an array-based comparison.
Compiled but not tested:
Sub Tester()
Dim xlapp As Excel.Application
Dim xlbook As Excel.Workbook
Dim xlSheet, wsPre As Excel.Worksheet, wsPost As Excel.Worksheet
Dim rowsPost As Long, rowsPre As Long, rowsMax As Long
Dim colsPre As Long, colsPost As Long, colsMax As Long, flag As Boolean
Dim r As Long, c As Long, rngPre As Range, rngPost As Range, arrPre, arrPost
DoCmd.SetWarnings False
Set xlapp = New Excel.Application 'forgot "New" here?
xlapp.Visible = True
Set xlbook = xlapp.Workbooks.Add()
Do While xlbook.Worksheets.Count < 2 'how many sheets you need in the Workbook
xlbook.Sheets.Add
Loop
Set wsPre = xlbook.Worksheets(1)
Set wsPost = xlbook.Worksheets(2)
PutInWorksheet "SELECT * From Pre", wsPre, "Pre"
PutInWorksheet "SELECT * From Post", wsPost, "Post"
Set rngPre = wsPre.Range("A1").CurrentRegion 'data ranges
Set rngPost = wsPost.Range("A1").CurrentRegion
arrPre = rngPre.Value 'read data to arrays
arrPost = rngPost.Value
rowsPre = UBound(arrPre, 1) 'compare array bounds...
rowsPost = UBound(arrPost, 1)
rowsMax = xlapp.Max(rowsPre, rowsPost)
colsPre = UBound(arrPre, 2)
colsPost = UBound(arrPost, 2)
colsMax = xlapp.Max(colsPre, colsPost)
For r = 2 To rowsMax
flag = (r > rowsPre) Or (r > rowsPost) 'flag whole row if have run out of data in one set...
If flag Then
FlagRanges rngPre.Cells(r, 1).Resize(1, colsMax), _
rngPost.Cells(r, 1).Resize(1, colsMax)
Else
'have two rows to compare
For c = 1 To colsMax
flag = (c > colsPre) Or (c > colsPost) 'run out of cols in one dataset?
If Not flag Then
flag = arrPre(r, c) <> arrPost(r, c) 'compare data
End If
If flag Then
'no data to compare, or data does not match
FlagRanges rngPre.Cells(r, c), rngPost.Cells(r, c)
End If
Next c
End If
Next r
End Sub
Sub FlagRanges(rng1 As Excel.Range, rng2 As Excel.Range)
Const CLR_INDX = 4
rng1.Interior.ColorIndex = CLR_INDX
rng2.Interior.ColorIndex = CLR_INDX
End Sub
'run a query and put the results on a worksheet starting at A1
Sub PutInWorksheet(SQL As String, ws As Excel.Worksheet, _
Optional newName As String = "")
Dim f, c As Excel.Range, rs As dao.Recordset
If Len(newName) > 0 Then ws.Name = newName
Set rs = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set c = ws.Range("A1")
For Each f In rs.Fields
c.Value = f.Name
c.Font.Bold = True
Next f
ws.Range("A2").CopyFromRecordset rs
rs.Close
End Sub

Import EXCEL data to SQL Server without data conversion

I am trying to insert excel data into sql server tables. Each column should be imported with the exact same format that the user wrote in the source Excel.
I am using following query to fetch data
SELECT * FROM OPENROWSET( 'Microsoft.ACE.OLEDB.12.0', 'Excel 12.0 Xml;HDR=YES;IMEX=1;Database=H:\Loadloandata\Test\K3.xlsx',
'SELECT * FROM [Sheet1$]')
But now in the date column of excel we are receiving some float values( format issues from the users) as shown below
Because of the invalid data, the OLE provider convert the all other dates to float values in corresponding SQL table colums (float values corresponding to each date).If a date column is automatically cast to float I won't be able to know the original format of the data in the excel file, so all columns should be imported as varchar.
How can i prevent this datatype conversion? Based on google search i have used IMEX=1 in connection string to retrieve data for mixed data columns.
But it is not working !!
I think that you should get the data types from the SQL server table first to create the recordset, rather than letting Excel decide the datatypes from the Sheet. I believe Excel decides the data types by the first row, so in your case, it assumed Funded data was an integer, then casts any following strings into that data type.
Here is a full function that I use
This code is modified by me from the original source, which is mentioned in the comment. I made changes to deal with errors better.
Function ExportRangeToSQL(ByVal sourcerange As Range, _
ByVal conString As String, ByVal table As String, _
Optional ByVal beforeSQL = "", Optional ByVal afterSQL As String) As String
'https://www.excel-sql-server.com/excel-sql-server-import-export-using-vba.htm
' Object type and CreateObject function are used instead of ADODB.Connection,
' ADODB.Command for late binding without reference to
' Microsoft ActiveX Data Objects 2.x Library
' ADO API Reference
' https://msdn.microsoft.com/en-us/library/ms678086(v=VS.85).aspx
' Dim con As ADODB.Connection
On Error GoTo Finalise ' throw friendly user connection error
Dim con As Object
Set con = CreateObject("ADODB.Connection")
con.ConnectionString = conString
con.Open
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
' BeginTrans, CommitTrans, and RollbackTrans Methods (ADO)
' http://msdn.microsoft.com/en-us/library/ms680895(v=vs.85).aspx
Dim level As Long
level = con.BeginTrans
cmd.CommandType = 1 ' adCmdText
If beforeSQL > "" Then
cmd.CommandText = beforeSQL
cmd.ActiveConnection = con
cmd.Execute
End If
' Dim rst As ADODB.Recordset
Dim rst As Object
Set rst = CreateObject("ADODB.Recordset")
With rst
Set .ActiveConnection = con
.Source = "SELECT * FROM " & table
.CursorLocation = 3 ' adUseClient
.LockType = 4 ' adLockBatchOptimistic
.CursorType = 0 ' adOpenForwardOnly
.Open
' Column mappings
Dim tableFields(100) As Integer
Dim rangeFields(100) As Integer
Dim exportFieldsCount As Integer
exportFieldsCount = 0
Dim col As Integer
Dim index As Variant
For col = 0 To .Fields.Count - 1
index = 0
index = Application.Match(.Fields(col).Name, sourcerange.Rows(1), 0)
If Not IsError(index) Then
If index > 0 Then
exportFieldsCount = exportFieldsCount + 1
tableFields(exportFieldsCount) = col
rangeFields(exportFieldsCount) = index
End If
End If
Next
If exportFieldsCount = 0 Then
Err.Raise 513, , "Column mapping mismatch between source and destination tables"
End If
' Fast read of Excel range values to an array
' for further fast work with the array
Dim arr As Variant
arr = sourcerange.Value
' The range data transfer to the Recordset
Dim row As Long
Dim rowCount As Long
rowCount = UBound(arr, 1)
Dim val As Variant
For row = 2 To rowCount
.AddNew
For col = 1 To exportFieldsCount
val = arr(row, rangeFields(col))
If IsEmpty(val) Then
Else
.Fields(tableFields(col)) = val
End If
Next
Next
.UpdateBatch
End With
rst.Close
Set rst = Nothing
If afterSQL > "" Then
cmd.CommandText = afterSQL
cmd.ActiveConnection = con
cmd.Execute
End If
Finalise:
If con.State <> 0 Then
con.CommitTrans
con.Close
End If
Set cmd = Nothing
Set con = Nothing
' Raise appropriate custom errors
Select Case Err.Number
Case -2147217843
Err.Raise 513, , "Issue connecting to SQL server database - please check login credentials"
Case -2147467259
If InStr(1, Err.Description, "Server does not exist") <> 0 Then
Err.Raise 513, , "Could not connect to SQL server, please check you are connected to the local network (in the office or on VPN)"
Else
Err.Raise 513, , "Issue connecting to SQL server database" & vbNewLine & Err.Description
End If
Case -2147217900
If InStr(1, Err.Description, "'PK_XL_Eng_Projects_QuoteRef'") <> 0 Then
Err.Raise 513, , "Quote already uploaded for this QuoteRef and Upload Time, please wait a minute before trying again" & vbNewLine & vbNewLine & Err.Description
Else
Err.Raise Err.Number, , Err.Description
End If
Case 0
' do nothing no error
Case Else
' re raise standard error
Err.Raise Err.Number, , Err.Description
End Select
End Function
Is there a reason why you using SSIS? I think that is best suited for the job.
Anyways, back to your issue. IMEX=1 is not enough. What you need is to check the registry entries
You need set TypeGuessRows and ImportMixedTypes within this registry path (this is for 32-bit office!):
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Office\12.0\Access Connectivity Engine\Engines\Excel
TypeGuessRows = 0 (the default is 8)
ImportMixedTypes = Text
What does TypeGuessRows do?
It tries to guess data type based on the number of rows defined. The default values is 8. That means it will check 8 rows to see what data type should be used. If you want to the engine to scan all the rows put 0 there. There is a catch, however, if your spreadsheet large, you could pay heavy performance penalty for such setting.
What does ImportMixedTypes do?
This is where your IMEX setting comes into the game. There are 3 possible values for IMEX setting 0, 1, 2:
0 is Export mode
1 is Import mode
2 is Linked mode (full update capabilities)
Only when setting IMEX=1 the registry value is honored. - with the default setting being ImportMixedTypes=Text. In any other value (0, 2) the value in registry is checked, if it is valid, but it does not influence the outcome. (you will get an error if invalid)
There are two valid values for ImportMixedTypes:
ImportMixedTypes=Text
ImportMixedTypes=Majority Type
The Majority Type is rarely used. What it does it counts type of each column and the majority type is then used for the whole column. The Text type will limit the row size to 255 characters, if you want to use more characters then Majority Type must be used and the majority must use more than 256 characters.

EXCEL VBA to SQL Index and Seek

I am exporting data in Excel table to SQL Server Database, If exists UPDATE else INSERT.
The following VBA code works well for exporting to ACCESS Database,
BUT NOT TO SQL SERVER DATABASE TABLE.
Error Message appear :Invalid Use of Property for .Index and .Seek.
Please Help !!! Toh
Sub ExcelDataToSql ()
Dim cn As ADODB.Connection, rs As ADODB.Recordset, r As Long
Dim lastrow As Long, o As Long
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
cn.Open "Provider=SQLNCLI11;Server=***;Database=****;Trusted_Connection=yes;"
rs.CursorLocation = adUseServer
rs.Open "InventorySQL", cn, 1, 3, adCmdTableDirect
' Get Lastrow
Worksheets("InventoryEXCEL").Select
lastrow = Worksheets("InventoryEXCEL").Cells(rows.Count, 1).End(xlUp).Row
r = 2 ' the start row in the worksheet
For o = 2 To lastrow
'Check For Duplicate In Database SQL
With rs
.Index = "PrimaryKey"
.Seek Range("A" & r).Value
If .EOF Then
.AddNew
'If No Duplicate insert New Record
rs.Fields("oPartno") = Range("A" & r).Value
rs.Fields("oDesc") = Range("B" & r).Value
rs.Fields("oCost") = Range("C" & r).Value
.update
Else
' If Duplicate Found Update Existing Record
rs.Fields("oDesc") = Range("B" & r).Value
rs.Fields("oCost") = Range("C & r).Value
.Update
End If
End With
Next o
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
MsgBox "Posting Completed"
End Sub
. Index = "PrimaryKey" --- Sysntax Error : Invalid Use of Property
.Seek Range ("A" & r).Value Sysntax Error :
Reference:Seek Method and Index Property Example (VB)
The MSDN example passes an Array as the first parameter.
rstEmployees.Seek Array(strID), adSeekFirstEQ
The first parameter's name os KeyValues which also implies an array
I would try this first
.Seek Array(Range("A" & r).Value)
It might also be beneficial to use one of the SeekEnum value
Update: TOH the OP found that this was the relavent code snippet
MSDN also suggest checking if the Provider supports .Index and .Seek
If rstEmployees.Supports(adIndex) And rstEmployees.Supports(adSeek) Then
My Problem is resolved by work around.
Many resources indicated that Sql Providers do not support the index and seek function. So I avoid Index and Seek
I work around by importing the excel worksheet into Sql server as source table... thereafter Merge the target table with source table... if match UPDATE, if Not Match INSERT.
select * from InventoryTableSQL
select * from InventoryTableFromExcel
Merge InventoryTableSQL as T
using InventoryTableFromExcel as S
on t.oPartno = s.oPartno
when matched then
update set t.oPartno = s.oPartno,
t.oDesc = s.oDesc,
t.oCost = s.oCost
when not matched by target then
insert(oPartno, oDesc, oCost) values(s.oPartno, s.oDesc, s.oCost));

MS Access database grow issue

I have created below piece of code in order to amend some data in an Access table:
Dim Ways As DAO.Recordset
Dim Keys As DAO.Recordset
Dim Recordcount As Double
Dim Records As Double
Dim ID_Old As String
Dim ID_New As String
Dim STArray() As String
Dim SaveTime As String
Set Ways = CurrentDb.OpenRecordset("Ways_Sorted")
Recordcount = 1
Records = 3724755
Ways.MoveFirst
Dim word As Variant
While Not Ways.EOF
DoCmd.SetWarnings (False)
DoCmd.OpenQuery "KeyFind:DEL"
DoCmd.SetWarnings (True)
Set Keys = CurrentDb.OpenRecordset("KeyFind")
STArray = Split(Ways!Veld4, ";")
For Each word In STArray
If Len(word) > 0 Then
Keys.AddNew
Keys!IDOld = CDbl(word)
Keys!IDNew = DLookup("[New ID]", "ID Keys", "[Old ID]=" & CDbl(word))
Keys.Update
End If
Next
Keys.MoveFirst
While Not Keys.EOF
ID_Old = " " + Trim(Str$(Keys!IDOld))
ID_New = " " + Trim(Str$(Keys!IDNew))
Ways.Edit
Ways!Veld4 = Replace(Ways!Veld4, ID_Old, ID_New)
Keys.MoveNext
Wend
Keys.Close
Me.Tekst1 = Recordcount
Me.Tekst3 = Records - Recordcount
Me.Tekst5 = FileLen(Application.CurrentProject.Path & "\Map_Convert_2.mdb")
If FileLen(Application.CurrentProject.Path & "\Map_Convert_2.mdb") > 1977142784 Then
' Exit Sub
End If
DoEvents
Ways!Done = True
Ways.Update
Ways.MoveNext
Recordcount = Recordcount + 1
'CommandBars("Menu Bar").Controls("Tools").Controls("Database utilities").Controls("Compact and repair database...").accDoDefaultAction
'Stop
Wend
DoCmd.SetWarnings (False)
DoCmd.OpenQuery "Ways_Amend ID"
DoCmd.SetWarnings (True)
MsgBox "New Map created"
Actually what the code is doing is replacing the data in field "Veld4" in table "Ways_Sorted". This field holds a string with ID's, which is splitted with STArray = Split(Ways!Veld4, ";") into an array.
This array is stored in a table called "KeysFound".
Another table in my database is containing the old ID and the new ID.
As said the rest of the code will replace the old id in "Veld4"with the new ID.
It is looping through 3.7 million records this way.
My problem is that after 250 loops or so my database has grown with 1mB, meaning that my database will be above the 2gB way before the code has finished.
I can not explain why the growth is happening and how I can stop this or at leas reduce the growth
Your code has lots of potential for optimization.
Main issue: you are constantly writing into and deleting from the Keys table. I guess this is also the cause of the growth issue.
This table is unnecessary. Just do the replacement right after reading each key. Build the new Veld4 as string NewVeld, only write it into the table once you are finished for the current Ways row.
STArray = Split(Ways!Veld4, ";")
NewVeld = ""
For Each word In STArray
If Len(word) > 0 Then
NewKey = DLookup("[New ID]", "ID Keys", "[Old ID]=" & CDbl(word))
' you will have to adapt this to your exact "veld" structure
' If there is a specific reason, you can also continue to use Replace(),
' but I don't think it's necessary.
NewVeld = NewVeld & ";" & NewKey
End If
Next
' remove leading ";"
NewVeld = Mid(NewVeld, 2)
Ways.Edit
Ways!Veld4 = NewVeld
Ways!Done = True
Ways.Update
Ways.MoveNext
Further optimization: DLookup is a rather expensive operation for your row count.
Consider loading the whole ID Keys table into a Dictionary object at the beginning, then reading the new IDs from there.

Settting .RefreshPeriod for an ADOBD SQL connection

I have an Excel spreadsheet that connects to SQL and pulls data from a table. I used the macro recorder and SQL import wizard to do this, however I now need to be able to write the data back to SQL so I came across this post and have been trying to make the below code work. It works fine, however I need to tweak it so it refreshes the data every minute so users are seeing data in near real time.
In the macro I recorded I was able to set a .RefreshPeriod = 1 parameter so the data would update, how can I do that here?
(Note: there are other functions dependent on the variables in here so I need to keep it somewhat the same - here is write up with full code).
' General variables we'll need
Public con As ADODB.Connection
Public bIgnoreChange As Boolean
Dim pk As New Collection
Dim oldValue As Variant
Dim nRecordCount As Integer
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
' Let's retrieve the data from the SQL Server table with the same name as the sheet
bIgnoreChange = True
Set con = New ADODB.Connection
con.Provider = "sqloledb"
sConnectionString = "Server=CONDO-HTPC;Database=Strat_sample;Trusted_Connection=yes;" ';UID="";Pwd="" "
con.Open sConnectionString
' Clean up old Primary Key
While (pk.Count > 0)
pk.Remove 1
Wend
' Try to retrieve the primary key information
On Error GoTo NoCon
Set rs = con.Execute("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY' AND tc.TABLE_NAME = '" & Sh.name & "'")
'Disable eventchange trigger in Workbook_SheetChange sub while this runs
Application.EnableEvents = False
' Fill up the primary key infomration
While (Not rs.EOF)
pk.Add CStr(rs(0))
rs.MoveNext
Wend
' Clean up the sheet's contents
Sh.UsedRange.Clear
' Now get the table's data
Set rs = con.Execute("SELECT * FROM " & Sh.name)
' Set the name of the fields
Dim TheCells As Range
Set TheCells = Sh.Range("A1")
For i = 0 To rs.Fields.Count - 1
TheCells.Offset(0, i).Value = rs.Fields(i).name
Next i
' Get value for each field
nRow = 1
While (Not rs.EOF)
For i = 0 To rs.Fields.Count - 1
TheCells.Offset(nRow, i).Value = rs(i)
Next
rs.MoveNext
nRow = nRow + 1
Wend
nRecordCount = nRow - 1
bIgnoreChange = (pk.Count = 0) And (nRecordCount > 0)
'Enable Workbook_SheetChange sub
Application.EnableEvents = True
Exit Sub
NoCon:
con.Close
Set con = Nothing
'Enable Workbook_SheetChange sub
Application.EnableEvents = True
End Sub
This is easy to do. As described in the answer for following Stackoverflow question, you can set a macro to run at intervals using Application.OnTime. After the macro runs, you set another wait period so it runs again.
VBA Macro On Timer style to run code every set number of seconds, i.e. 120 seconds
Also, make sure to turn off screen updating while the data is being refreshed.
Application.ScreenUpdating = False
Last, you should still be able to run this query if you set it up as a query table using the connection string. Then you just set the refresh rate and call it a day.

Resources