I need to move data from Excel sheet to database. To do this, I create ADODB Connection and I am able to execute such an SQL query:
INSERT INTO myTable SELECT * FROM [Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].[Shee1$A1:C100]
My problem is that the range cannot point further than 255 columns, i.e. column IU. I want to try using named range instead, but I cannot find suitable notation. All examples I found connect directly to the workbook, and use either SELECT * FROM [Sheet1$] reference, or SELECT * FROM myRange as an example of named range. I tried things like
[Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].[myRange]
[Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].[myRange$]
[Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].myRange
[Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb;Name=myRange]
, but without success.
What is the proper way to use named range here? Would it even help working around column number limitation?
I expected [Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].[myRange] to work, but it throws the following error: 'The Microsoft Access database engine could not find the object 'myRange'. Make sure the object exists (...)'
I can work it around by copying data from source sheet to temporary one, and have it within 255 column limit, but it would be great do it right way.
Not sure if you are going to find a solution for connecting to a named range. I took a look at getting this to work, and I had no luck either, I suspect it's not included in the schema past 255 column, but could be wrong.
I thought you might as well have an efficient solution that doesn't rely on looping for adding data to Access. It's more code than just doing an insert, but I hope it fits your specific problem.
I was able to do an insert of ~2500 records (all integers) in about 3 seconds, so it is fairly quick.
Option Explicit
Private Function GetDisconnectedRecordset(TableName As String) As ADODB.Recordset
Dim conn As ADODB.connection: Set conn = getConn()
Dim rs As ADODB.Recordset: Set rs = New ADODB.Recordset
With rs
.CursorLocation = adUseClient ' <-- needed for offline processing
'Get the schema of the table, don't return anything
.Open "Select * from " & TableName & " where false", conn, adOpenDynamic, adLockBatchOptimistic
End With
rs.ActiveConnection = Nothing
conn.Close
Set conn = Nothing
Set GetDisconnectedRecordset = rs
End Function
'Do an update batch of the data
'Portion used from: https://stackoverflow.com/questions/32821618/insert-full-ado-recordset-into-existing-access-table-without-loop
Sub PopulateDataFromNamedRange()
Dim conn As ADODB.connection
Dim ws As Excel.Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet2") 'Update to your sheet/wb
Dim NamedRange As Excel.Range: Set NamedRange = ws.Range("Test") ' Update to your named range
Dim NamedItem As Excel.Range
Dim rs As ADODB.Recordset: Set rs = GetDisconnectedRecordset("[TestTable]") 'Specify your table name in access
Dim FieldName As String
Dim Row As Long
Dim AddRow As Long
'Add Data to the disconnected recordset
For Each NamedItem In NamedRange
If Not NamedItem.Row = 1 Then
Row = NamedItem.Row
If Not Row = AddRow Then rs.AddNew
AddRow = NamedItem.Row
FieldName = ws.Cells(NamedItem.Row - (NamedItem.Row - 1), NamedItem.Column).Value
rs.Fields(FieldName).Value = NamedItem.Value
End If
Next
'Connect again
Set conn = getConn()
Set rs.ActiveConnection = conn
rs.UpdateBatch '<-- 'Update all records at once to Access
conn.Close
End Sub
Private Function getConn() As ADODB.connection
Dim conn As ADODB.connection: Set conn = New ADODB.connection
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Ryan\Desktop\Example.accdb"
Set getConn = conn
End Function
I had the same issue and the solution is pretty easy, even though it works only for named ranges at Workbook level.
The connection has to be done to the Workbook (i.e. [Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb]).
Then in the Query just type: SELECT * FROM [myRange] (Please note the square brackets and the absence of $ sign)
You need to use a construction:
[Excel 12.0 Macro;HDR=Yes;Database=C:\MyPath\MyFile.xlsb].[$myRange]
Related
Please help me. Excel VBA is throwing this error: Multiple-step OLE DB Operation generated errors
on the line : cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
I'm trying to run a query in access named "AGBA_Conversion" with an integer parameter. How do I resolve? thanks in advance :)
Sub RECT_MBTCLeads2_Extracts()
Dim cmd As New ADODB.Command, rs As ADODB.Recordset
Dim sht As Worksheet
Dim b As String
Dim d As Long
Dim a As String
Set sht = ActiveWorkbook.Sheets("AGBA_Conversion")
sht.Range("B1").ClearContents
cmd.ActiveConnection = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\nemberga\OneDrive - AXA\Documents\Automation\MBTC Conversion.accdb"
cmd.CommandType = adCmdText
cmd.CommandText = "AGBA_Conversion" '<<<--here is where you write your query sql
cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
Options = adCmdTable
Set rs = cmd.Execute
sht.Range("A1").CopyFromRecordset rs '--bulk copy to the Excel sheet
rs.Close
cmd.ActiveConnection.Close
MsgBox "All data were successfully retrieved from the queries!", vbInformation, "Done"
End Sub
Parameters is a collection of Parameter. You need first to create the object using CreateParameter, then to add it to the collection using Append.
Replace
cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
By
Dim p
Set p = cmd.CreateParameter("days", adInteger, adParamInput)
p.Value = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
cmd.Parameters.Append p
See https://learn.microsoft.com/fr-fr/sql/ado/reference/ado-api/append-and-createparameter-methods-example-vb?view=sql-server-ver15
Right now I am trying to export data from Excel to Access with VBA.
rst("2k2").Value = sProduct
This is an example of what I currently use which is putting the value sProduct into the column in Access called "2k2". How would I move to the next column in Access and put a value in that column? Besides the obvious using the name of that column.
Thanks!
You could set the fields to a variable and cycle through them.
Dim fld as DAO.Field
for each fld in rst.fields
'Do some stuff
next
Disclaimer: As mentioned in the comments, do not use this code in a production environment. Use it only for a DB, which you are using for a school project or anything similar. Databases are really different than an Excel Spreadsheet and the position of the columns is not always constant.
Having said this:
Option Explicit
Public Function GetColumnNameAfter(columnName As String) As String
Dim cnLogs As New ADODB.Connection
Dim rsHeaders As New ADODB.Recordset
Dim rsData As New ADODB.Recordset
Dim myCounter As Long
Dim myConn As String
Dim nameFound As Boolean
myConn = "Provider=SQLNCLI11;Server=(localdb)\MSSQLLocalDB;"
myConn = myConn & "Initial Catalog=Blog;Trusted_Connection=yes;timeout=30;"
cnLogs.Open myConn
With rsHeaders
.ActiveConnection = cnLogs
.Open "SELECT * FROM syscolumns WHERE id=OBJECT_ID('Posts')"
Do While Not rsHeaders.EOF
'Printing is fun...
'Debug.Print rsHeaders(0)
If nameFound Then
GetColumnNameAfter = rsHeaders(0)
Exit Function
End If
If columnName = rsHeaders(0) Then nameFound = True
myCounter = myCounter + 1
rsHeaders.MoveNext
Loop
.Close
End With
End Function
In the strConn change the Initial Catalog to your DB and the DB Table Posts here OBJECT_ID('Posts')" to your DB table. Then the function will return the String name of the column after the the one you have provided as a parameter.
The code loops through the Headers of a table, named "Posts", which is in a DB named Blog. Once it finds a header, named columnName, it sets the nameFound to True and returns the Name of the next column.
Once you know the name of the "Next" column, you can use the same logic as in your question.
Currently the following SQL query in Excel VBA shows the count data vertically
So the column header is in A1 and the count result is in B1
Const sqlconnection = "Provider=visoledb;"
Dim conn As New Connection
conn.ConnectionString = sqlconnection
conn.Open
Dim rs As Recordset
Dim r1 As String
r1 = "SELECT COUNT(*) FROM user"
Set rs = conn.Execute(r1)
With ActiveSheet.QueryTables.Add(Connection:=rs, Destination:=Range("A1"))
.Refresh
End With
rs.close
Is it possible to have it show the header text in A1 and the count value in A2?
So it shows vertically across the cells in a single row left to right
I took a look at your code and got it working, but I only had a local database to test it on so I used DAO- this wont work for you if you only have a connection string unfortunately, but if you have the full filepath of the DB then you can use this code directly, if not then hopefully the syntax helps you figure out the missing step in your OCDB code, they are very similar.
Second, I think there is something wrong with your SQL Query String- I don't think it does what you think it does. When you say you want the data 'across the cells in a single row left to right' but a SELECT COUNT(*) query is only going to return 1 field so you are only going to have 1 column...
Anyways, here is the code doing what you want it to- hopefully once you have tinkered with the SQL String you'll have all the pieces you need to get it working:
Public Sub sampleCode()
Dim targetWS As Worksheet
Dim db As DAO.Database
Dim SQLString As String
Dim rs As DAO.Recordset
Set targetWS = ThisWorkbook.Sheets(1)
Set db = Access.DBEngine.Workspaces(0).OpenDatabase("C:\DB File.accdb")
SQLString = "SELECT COUNT(*) FROM [user]"
Set rs = db.OpenRecordset(SQLString)
rs.MoveLast
rs.MoveFirst
With targetWS.QueryTables
.Add Connection:=rs, Destination:=Range("A1")
.Item(1).Refresh
End With
db.Close
End Sub
Hope this helps,
TheSilkCode
So I have an access database with multiple tables where I store data on companies. I have designed several queries in Access to pull some information.
From Excel VBA, I connect to the Access database using ADO connection and pull the stored query. I have done so several times already and it works fine. But one of my query doesn't return any result: the recordset returns "either EOF or BOF is true".
When I open the query in ACCESS, ACCESS prompts me for the parameter "GICS_SUB_INDUSTRY_NAME", and it does returns a table with values to me if I input a parameter. So the query works fine in ACCESS but doesn't from Excel VBA.
Here is the SQL statement of the query:
PARAMETERS GICS_SUB_INDUSTRY_NAME Text ( 255 );
SELECT STOCK_STATIC.NAME, Focus_MAXHistoryDate.MaxOfHistory_Date, Focus_MAXHistoryDate.Isin, STOCK_DYNAMIC.CUR_MKT_CAP, STOCK_HIST_IS_BS_CF.BS_TOT_LIAB2, STOCK_HIST_IS_BS_CF.PREFERRED_EQUITY__MINORITY_INT, STOCK_HIST_IS_BS_CF.BS_CASH_NEAR_CASH_ITEM, STOCK_STATIC.GICS_SUB_INDUSTRY_NAME
FROM ((Focus_MAXHistoryDate INNER JOIN STOCK_HIST_IS_BS_CF ON (Focus_MAXHistoryDate.Isin = STOCK_HIST_IS_BS_CF.Isin) AND (Focus_MAXHistoryDate.MaxOfHistory_Date = STOCK_HIST_IS_BS_CF.History_Date)) INNER JOIN STOCK_STATIC ON Focus_MAXHistoryDate.Isin = STOCK_STATIC.ISIN) INNER JOIN STOCK_DYNAMIC ON Focus_MAXHistoryDate.Isin = STOCK_DYNAMIC.Isin
WHERE (((STOCK_STATIC.GICS_SUB_INDUSTRY_NAME)=[GICS_SUB_INDUSTRY_NAME]));
And this is my code in Excel VBA:
Sub Pull_Stock_Peers_Leverage()
'define general variables
Dim Connection As ADODB.Connection
Dim Command As ADODB.Command
Dim GICS_SUB_INDUSTRY_NAME As ADODB.Parameter
Dim RecordSetL As ADODB.RecordSet
Dim iField As Integer
Dim Workbook As Excel.Workbook
Set Workbook = Excel.ActiveWorkbook
Dim Start As Excel.Worksheet
Set Start = ActiveWorkbook.Sheets("Start")
Dim GICS4_Range As Excel.Range
Set GICS4_Range = Excel.Range("GICS4_LookUp")
'Open connection to the AMSIR database
Set Connection = New ADODB.Connection
With Connection
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Open "N:\Switzerland\Others\_AMSIR_Company_Database\AMSIR_Company_Database.accdb"
End With
'Open command object and pass a parameter (criteria) to the query
'The parameter name should match the parameter clause in the SQL statement?
Set Command = New ADODB.Command
With Command
.ActiveConnection = Connection
.CommandText = "Pull_Peers_Leverage"
.CommandType = adCmdStoredProc
.Parameters.Append .CreateParameter("GICS_SUB_INDUSTRY_NAME", adVarWChar, adParamInput, Len(GICS4_Range))
.Parameters("GICS_SUB_INDUSTRY_NAME") = GICS4_Range
End With
'Create recordset by executing the command
Set RecordSetL = New ADODB.RecordSet
RecordSetL.CursorLocation = adUseClient
RecordSetL.CursorType = adOpenStatic
RecordSetL.Open Command
'Write fields from record set
Dim TargetRangeL As Excel.Range
Set TargetRangeL = Start.Range("PEERS_LEVERAGE")
Dim FieldCount As Integer
Dim i, j As Integer
FieldCount = RecordSetL.Fields.Count
i = 28
j = 37
For iField = 1 To FieldCount
Start.Cells(i + iField, j).Value = RecordSetL.Fields(iField - 1).Name
Next
'Transpose recordset values and paste into array
Dim recArray As Variant
Dim recCount As Long
recArray = RecordSetL.GetRows
recCount = UBound(recArray, 2) + 1
TargetRangeP.Resize(FieldCount, recCount).Value = recArray
Connection.Close
End Sub
So I do get the recordset in the code and I can actually loop into the recordset and extract field names but the fields have no value and come as "Either BOF or EOF is true or the current record has been deleted".
I have this code for all my other queries but only for this query the recordset comes empty. And it's the only query of that type I have (that does match a query and tables with inner joints).
EDIT:
I was wondering if the issue was the ADO or the way I pass my parameter so I decided to just pass the SQL statement to the ACCESS without putting any parameter; so I should normally get a recordset with all available values.
I have tried:
Sub Test1()
Dim Connection As New ADODB.Connection
Dim Command As ADODB.Command
Dim RecordSetUF As New ADODB.RecordSet
Dim Workbook As Excel.Workbook
Set Workbook = Excel.ActiveWorkbook
Dim Start As Excel.Worksheet
Set Start = ActiveWorkbook.Sheets("Start")
Dim GICS4_Range As Excel.Range
Set GICS4_Range = Excel.Range("GICS4_LookUp")
'Open connection to the AMSIR database
Set Connection = New ADODB.Connection
With Connection
' .Provider = "SQLOLEDB"
.Provider = "Microsoft.ACE.OLEDB.12.0"
' .Provider = "Microsoft.Jet.OLEDB.4.0"
.Properties("Data Source") = "N:\Switzerland\Others\_AMSIR_Company_Database\AMSIR_Company_Database.accdb"
.Open
End With
'Place sql statement and match it to newly created recordset
Set Command = New ADODB.Command
Command.ActiveConnection = Connection
Set RecordSetUF = New ADODB.RecordSet
Dim sql1 As String
'ACCESS accepts * for empty values but Excel VBA needs % as wild card in an SQL queryIf CompF.Text = "" Then CompF.Text = ""
sql1 = "SELECT STOCK_STATIC.NAME, Focus_MAXHistoryDate.MaxOfHistory_Date, Focus_MAXHistoryDate.Isin, STOCK_DYNAMIC.CUR_MKT_CAP, STOCK_HIST_IS_BS_CF.BS_TOT_LIAB2, STOCK_HIST_IS_BS_CF.PREFERRED_EQUITY__MINORITY_INT, STOCK_HIST_IS_BS_CF.BS_CASH_NEAR_CASH_ITEM, STOCK_STATIC.GICS_SUB_INDUSTRY_NAME FROM ((Focus_MAXHistoryDate INNER JOIN STOCK_HIST_IS_BS_CF ON (Focus_MAXHistoryDate.MaxOfHistory_Date = STOCK_HIST_IS_BS_CF.History_Date) AND (Focus_MAXHistoryDate.Isin = STOCK_HIST_IS_BS_CF.Isin)) INNER JOIN STOCK_STATIC ON Focus_MAXHistoryDate.Isin = STOCK_STATIC.ISIN) INNER JOIN STOCK_DYNAMIC ON Focus_MAXHistoryDate.Isin = STOCK_DYNAMIC.Isin;"
Command.CommandText = sql1
'Debug.Print sql1
'Debug.Print returns an SQL statement that works fine in Access!
Command.Execute
RecordSetUF.Open Command
So basically I have an sql statement that works fine in ACCESS, but not in excel VBA.
You were working in MS-Access, in a dialect of SQL called 'Jet-SQL'.
You are now working in Excel, with Jet-SQL, and all of the things that the MS-Access host application does for Jet are missing:
You don't have access to VBA native functions (just the Jet-SQL
native functions, which resemble some of the VBA string and
type-conversion functions)
You don't have access to VBA functions written by the Access
developer and globally-declared inside the MS-Access database;
The subqueries declared in your FROM clause had better be tables, or
queries with no VBA, too.
The asterisk wildcard won't be silently translated into '%' for you
(which I think you've got)
These things fail silently, or with misleading error messages - "Either BOF or EOF is true or the current record has been deleted" - instead of the 'missing parameter' or 'incorrect column name' errors you would expect...
But the place I'd start looking for an empty recordset and no explanation is the FROM clause: either it's one of the obvious 'No MS-Access' issues, or it's something more subtle involving a failure to match on NULL or Empty String - a failure which may well be another issue that the MS-Access wrapper manages in the background.
A quick note on Null-handling: you don't have IFNULL or NZ, so testing IIF(tbl.[Field] IS NULL is your best bet.
A sheet has several Pivot Tables. Currently, the source data for the Pivot Table is being retrieved from an Access Table using ADO and populated in a sheet. The Range in the sheet is then used as the source for the Pivot Table. I am trying to change the source of the Pivot Table to an SQL Query (i.e. Select * from a Table) directly from MS-Access. The following code runs fine (the Connectionstring is returned by a function and is shown below the code).
Sub PivotTableDataADO()
'Late Binding
Dim strConnectString As String
Dim cn As Object
Dim rs As Object
Dim qry As String
Dim ws As Worksheet
Dim pt As PivotTable
Dim pvc As PivotCache
Set ws = ThisWorkbook.Sheets("DashBoard")
'Connect Database
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
qry = "SELECT * FROM CallData;"
cn.Open AccessCode.strConnectString
rs.Open qry, cn
Set pvc = ActiveWorkbook.PivotCaches.Add(SourceType:=xlExternal)
Set pvc.Recordset = rs
For Each pt In ws.PivotTables
pt.PivotCache.SourceData = rs
pt.PivotCache.Refresh
pt.RefreshTable
Next pt
End Sub
The code fails at the line "pt.PivotCache.SourceData = rs" with the error message "Wrong number of arguments. Have experimented with other properties of the PivotCache without luck.
The ConnectionString is:
strConnectString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\rspai.mdb;Jet OLEDB:Database Password=xxxx;
How can I correct the issue and make the SQL Query as the SourceData for the existing Pivot Tables? I am okay with another approach too.
Thanks in Advance for the help.