I'm querying an Excel 2010 workbook using the following ADO:
Function WorksheetRecordsetSQL(workbookPath As String, sheetName As String, selectSQL As String) As ADODB.Recordset
Dim objconnection As New ADODB.Connection
Dim objrecordset As New ADODB.Recordset
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adCmdText = &H1
objconnection.CommandTimeout = 99999999
objconnection.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & workbookPath & ";" & _
"Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
objrecordset.Open selectSQL, _
objconnection, adOpenStatic, adLockOptimistic, adCmdText
Set WorksheetRecordsetSQL = objrecordset
End Function
When I call it with this SQL:
Select * FROM [someWorksheet$]
the function executes successfully. But if I call it with:
Select *,cdate(someField) FROM [someWorksheet$]
then I get this error:
Too many fields defined.
I found that the select * query was producing a recordset with 255 fields (the maximum that the Access engine can have in a query), even though there are only 58 columns in the sheet. So that one extra cdate() column overloaded the engine and produced the error.
Is there a way that I can keep a select * query from picking up blank columns from an Excel sheet? Or some parameters that I can set that will allow more than 255 fields?
You can specify the range which should be read and so reduce the number of columns entering into the query. Here from cell A1 to column BF (58) all rows:
FROM [Source_sheet$A1:BF]
Sub main()
Dim reultingRecordset As ADODB.Recordset
Set reultingRecordset = WorksheetRecordsetSQL( _
"C:\Temp\VBA\ReadWithADOSource.xlsx", _
"Source_sheet", _
"Select * FROM [Source_sheet$]")
Debug.Print "Select * FROM [Source_sheet$] >"
Debug.Print "Fields: " & reultingRecordset.Fields.Count & " Records: " & reultingRecordset.RecordCount
Set reultingRecordset = WorksheetRecordsetSQL( _
"C:\Temp\VBA\ReadWithADOSource.xlsx", _
"Source_sheet", _
"Select *,cdate(Col2) FROM [Source_sheet$A1:BF]")
Debug.Print "Select *,cdate(Col2) FROM [Source_sheet$A1:BC] > "
Debug.Print "Fields: " & reultingRecordset.Fields.Count & " Records: " & reultingRecordset.RecordCount
End Sub
Output:
Select * FROM [Source_sheet$] >
Fields: 255 Records: 8
Select *,cdate(Col2) FROM [Source_sheet$A1:BC] >
Fields: 59 Records: 8
Related
The following 2 queries are taken from tables on different databases
MyQuery = "Select * from " & "T1"
MyQuery2 = "Select * from " & "T2"
I'd like to nest these in the following query
Dim rrst As New ADODB.Recordset
mkQry = "SELECT x.*" _
& "FROM (" & MyQuery & ") x LEFT JOIN (" & MyQuery2 & ") y ON " _
& "(x.F1 = y.F2) AND " _
& "(x.F1 = y.F2) AND " _
& "(x.F1 = y.F2) AND " _
& "(x.F1 = y.F2) AND " _
& "(x.F1 = y.F2)" _
& "WHERE (((y.F2) Is Null))"
rrst.Open mkQry
Worksheets("TST").Range("A1").CopyFromRecordset rrst
However, I am getting an error:
The connection cannot be used to perform this operation
On the following line: rrst.Open mkQry
I guess it has to do with MyQuery and MyQuery2, both being from a different database.
Is there a way to make this work?
Learned something new - Excel CAN pull data from multiple Access files via one SQL statement. You were on the right path with nesting. Have to set a connection, which can be the workbook or one of the Access files, then other data sources must be nested with embedded filepath. Examples:
connection to workbook using ADODB objects with early binding so would need reference to Microsoft ActiveX Data Objects x.x Library.
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
cn.Open "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & ThisWorkbook.FullName & ";HDR=Yes';"
rs.Open "SELECT H.*, P.* FROM (SELECT * FROM Holidays IN 'C:\Users\Owner\June\Umpires.accdb') AS H " & _
"INNER JOIN (SELECT * FROM Projects IN 'C:\Users\Owner\June\LabData.accdb') AS P " & _
"ON H.HolID = P.ProjRecID", cn, adOpenStatic, adLockReadOnly
connection to one Access file
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0; Data Source='C:\Users\Owner\June\LL\Umpires.accdb'"
rs.Open "SELECT Holidays.*, Pjt.* FROM Holidays INNER JOIN (SELECT * FROM Projects IN 'C:\Users\Owner\June\DOT\Lab\Data\LabData.accdb') AS Pjt ON Holidays.HolID = " & _
"Pjt.ProjRecID ", cn, adOpenStatic, adLockReadOnly
DAO with early binding so reference Microsoft DAO 3.6 Object Library
Dim ws As DAO.Workspace
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set ws = DBEngine.Workspaces(0)
Set db = ws.OpenDatabase("C:\Users\Owner\June\LL\Umpires.accdb")
Set rs = db.OpenRecordset("SELECT Holidays.*, Pjt.* FROM Holidays " & _
"INNER JOIN (SELECT * FROM Projects IN 'C:\Users\Owner\June\DOT\Lab\Data\LabData.accdb') AS Pjt " & _
"ON Holidays.HolID = Pjt.ProjRecID ")
I did a quick test with PowerQuery add-in and it was able to pull from both Access files and save dataset to worksheet. This does allow for a 'live' link to both data sources as a merged dataset.
Background
I'm trying to use Excel VBA to load data from Microsoft Office Access database.
The code was worked fine and I am now trying to add an extra column Position drawn from the datebasetable named EqBucket into the final result table
The SQL works find in Access but it doesn't parse through to VBA.
The code break when I add in
SUM(Eq_Buckets.Position) AS PositionOfSum
I'm guess it has to do with the aggregation sum wrapped around the column because this issue has never come up with other direct referenced columns.
Appreciate for any pointers. Thanks
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Info:
1. SQL string is stored in Sheets("SQL").Range("A1").value
2. Database tables Eq_SingleName_LBU, Eq_Buckets << this is where the position data are stored
3. Eq_Portfolio_Ref is just a reference table which could be ignored
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
**IF I remove "Sum(Eq_Buckets.Position) AS PositionOfSum" the code works in VBA
Here is the FULLY working SQL code in MS Access:
SELECT Eq_SingleName_LBU.Identifier AS Identifier, Eq_SingleName_LBU.Issuer AS Issuer, Eq_SingleName_LBU.MV_USD AS MV, Sum(Eq_Buckets.Position) AS PositionOfSum, Eq_SingleName_LBU.Issuer_Weight AS [Issuer Weight], Eq_SingleName_LBU.Test_Limit AS Limit, Eq_SingleName_LBU.Room_Limit AS [Remaining Limit], Eq_SingleName_LBU.Data_Date
FROM Eq_SingleName_LBU INNER JOIN (Eq_Buckets INNER JOIN Eq_Portfolio_Ref ON Eq_Buckets.Composite_Portfolio = Eq_Portfolio_Ref.BBG_Account_Codes) ON Eq_SingleName_LBU.Identifier = Eq_Buckets.BB_UniqueID
Where Eq_Buckets.Data_Date = (#03/12/2020#) and Eq_SingleName_LBU.UnderTest="Y"
GROUP BY Eq_SingleName_LBU.Identifier, Eq_SingleName_LBU.Issuer, Eq_SingleName_LBU.MV_USD, Eq_SingleName_LBU.Issuer_Weight, Eq_SingleName_LBU.Test_Limit, Eq_SingleName_LBU.Room_Limit, Eq_SingleName_LBU.Data_Date
HAVING (((Eq_SingleName_LBU.Data_Date) In (#03/12/2020#)))
ORDER BY Eq_SingleName_LBU.Data_Date;
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Here is the VBA code that the SQL string needs to fit through
Sub ADOImportFromAccessTable()
'On Error GoTo ErrorHandler
Application.ScreenUpdating = False
Application.Calculation = xlManual
Sheets("EQ1_SQL").Visible = True
Dim con As Object
Dim rst As Object
Dim dbPath As String
dbPath = "\\Db\Asset_db.accdb"
Set con = CreateObject("ADODB.Connection")
con.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
con.Open
Set rst = CreateObject("ADODB.Recordset")
'This is where the SQL code will be referenced.
strSql = ThisWorkbook.Sheets("SQL").Range("A1").Value
Debug.Print strSql
strSql = Replace(strSql, "{date1}", Date_1)
Debug.Print strSql
strSql = Replace(strSql, "{date2}", Date_2)
rst.Open strSql, con, adOpenDynamic, adLockOptimistic
rst.Close
Set rst = Nothing
con.Close
Set con = Nothing
End sub
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Here is the error message I get from Excel VB editor
Here is the error I get from VB editor.
Run-tme error '-2147467259 (80004005);:
Method 'Open' of object' _ Recordset' failed
Try adding brackets around Position ie Sum(B.[Position]),
You can shorten the SQL by using table name aliases, for example
strSQL = " SELECT A.Identifier AS Identifier, A.Issuer AS Issuer, A.MV_USD AS MV," & _
" Sum(B.[Position]) AS PositionOfSum, " & _
" A.Issuer_Weight AS [Issuer Weight]," & _
" A.Test_Limit AS Limit, " & _
" A.Room_Limit AS [Remaining Limit]," & _
" A.Data_Date" & _
" FROM Eq_SingleName_LBU AS A " & _
" INNER JOIN Eq_Buckets AS B" & _
" ON A.Identifier = B.BB_UniqueID" & _
" WHERE B.Data_Date = #2020/12/03# " & _
" AND A.UnderTest = 'Y' " & _
" GROUP BY A.Identifier, A.Issuer," & _
" A.MV_USD, A.Issuer_Weight, A.Test_Limit," & _
" A.Room_Limit, A.Data_Date" & _
" HAVING A.Data_Date IN (#2020/12/03#) " & _
" ORDER BY A.Data_Date"
First off, I truly apologize if someone promptly points me to a post in which this question was answered. I'm not great at sifting through the boards, but have been searching for about a week. Many threads are similar to my problem, but none exactly mirror what I'm trying to do or the problem I'm having. The closest I've found was posted here. The solution reached there did not solve my issue.
I am trying to update records in an ACCESS 2007 database from an update Excel worksheet using VBA. I have accomplished getting information from ACCESS into Excel, and from Excel into my recordset. Now, I need to update ACCESS with the populated recordset.
Public Sub Read_Spreadsheet()
Dim strSql As String, target_fields As String
Dim fuel_table As String, new_values As String
Dim roww As Integer, coll As Integer
Dim i As Integer, n As Integer, mbrs(32) As Integer
Call Load_Globals
' Configure ADODB connection, command, recordset objects
With cn1
.Provider = "Microsoft.JET.OLEDB.4.0"
.ConnectionString = "Data Source = " & Src_WB_nm & "; " & _
"Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'"
.Open
End With
Set cmd1.ActiveConnection = cn1
cmd1.CommandType = adCmdText
cmd1.CommandText = "SELECT * FROM [" & Src_WS_nm & "$]"
With rs1
.CursorLocation = adUseClient ' used 3 previously
.CursorType = adOpenDynamic ' used 1 previously
.LockType = adLockOptimistic
.Open cmd1
End With
Debug.Print "Excel Connection established; recordset created."
Debug.Print "Fields: " & rs1.Fields.count
Debug.Print rs1.Fields(0).name
Debug.Print rs1.Fields(1).name
'--------------------------------------------------------------------------
With cn2
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source = " & Dest_DB
.Open
End With
With rs2
.CursorLocation = adUseClient ' used 3 previously
.CursorType = adOpenDynamic ' used 1 previously
.LockType = adLockOptimistic
End With
Debug.Print "Access connection established."
'--------------------------------------------------------------------------
' NOTE to S.O. readers, Two nested loops are commented out below
' These will eventually loop through an uncertain number of fields
' (~10) and records (~2000) to make all the SQL updates. For debugging,
' I'm just trying to get 1 pass to be successful.
'
' For n = 1 To rs1.RecordCount
' strSql = "SELECT ID, FSERIAL FROM TESTTABLE WHERE ID = 1"
strSql = ""
i = 1
' For i = 1 To rs1.Fields.count - 1
If i <> 1 Then strSql = strSql & ", "
strSql = strSql & " SET [" & rs1.Fields(i).name & "] = " & Chr(39) & rs1.Fields(i).Value & Chr(39)
' Next i
strSql = "UPDATE " & Dest_Table & strSql & " WHERE [ID] = " & rs1.Fields(0).Value
strSql = "UPDATE TESTTABLE SET BATCH = 'B' WHERE ID = 11"
Debug.Print strSql
Set cmd2 = New ADODB.Command
With cmd2
.ActiveConnection = cn2
.CommandType = adCmdText
.CommandText = strSql
.Execute , , adCmdText + adExecuteNoRecords
End With
' CP.Cells(27 + n, 4) = rs1(0)
' CP.Cells(27 + n, 5) = rs1(1)
rs1.MoveNext
Set cmd2 = Nothing
' Next n
' cmd2.CommandText = "SELECT ID, FSERIAL FROM TESTTABLE WHERE ID = 1"
' cmd2.CommandText = "UPDATE TESTTABLE SET BATCH = B WHERE ID = 1"
' Debug.Print cmd2.CommandText
' rs2.Open cmd2
' CP.Cells(28, 4).CopyFromRecordset rs2
Call Close_Connections
End Sub
Both Access & Excel are 2007, and I'm in Windows 7, 32 Bit OS. I'm using the following VBA references: MS ADO Ext. 6.0 for DDL and Security, MS ActiveX Data Ojects Recordset 6.0 Lib, MS ActiveX Objeects 6.1 Lib, MS Access 12.0 Object Lib, OLE Automation. (sorry, I cannot post images yet)
Everything works fine up until the cmd2.execute command (That is the line highlighted by the debugger). If I replace the SQL query with a simple static SELECT and dump it into rs2, it works fine. It's when I try to update only that I get the problem.
The debug.print strSQL command yields"'UPDATE TESTTABLE SET BATCH = 'B' WHERE ID = 11"
I've also tried "UPDATE TESTTABLE SET [BATCH] = 'B' WHERE [ID] = 11" and other permutations, with no success.
The error is : "Run-time error '-2147217904 (80040e10)': No value given for one or more required parameters."
Thank you for your help! I appreciate it very much, and will be sure to rank/flag the solution.
,Mike Shanahan
Your query is ill-formed. I think what you want is:
For i = 1 To rs1.Fields.count - 1
if i<>1 Then strsql = strsql & ", "
strSql = strSql & "[" & rs1.Fields(i).name & "] = " & rs1.Fields(i).Value
Next i
strsql = "UPDATE " & Dest_Table & " SET " & strSql & " WHERE [ID] = " & rs1.Fields(0).Value
Still, this supposes all values are numeric. You'll need to still work it out so that values corresponding to strings are enclosed with single quotes. For example, your test query should be:
.CommandText = "UPDATE TESTTABLE SET BATCH = 'B' WHERE ID = 1"
' ^^^
As suggested in comments, a simple Debug.Print strsql is very helful to debug your queries.
I have two data tables in excel that I wish to join into a single set in my vba code. I have identified the ADO connector as the best way to do this, however using the query below, I get the following error
"Run time error -2147217904
No value given for one or more required parameters"
SELECT components.[name], InputData.Datatype
FROM [Rules$A5:F30] components
INNER JOIN [Rules$O5:R17] InputData ON components.[name] = InputData.[name]
WHERE components.RowId = 0 GROUP BY components.[name], InputData.Datatype
EDIT: The full code:
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim dataRows As Integer
strFile = ThisWorkbook.FullName
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strsql = "SELECT components.[name], InputData.Datatype " _
+ " FROM [" + GetTableAddress("componentTable") _
+ "] components INNER JOIN [" + GetTableAddress("DataLocations") + "] InputData" _
+ " ON components.[name] = InputData.[name] " _
+ " WHERE components.RowId = " + CStr(RowId) + " GROUP BY components.[name], InputData.Datatype"
rs.Open strsql, cn
If Not rs.EOF Then
dataRows = rs.GetRows
and the GetTableAddress function
Private Function GetTableAddress(tableName)
Dim oSh As Worksheet
Dim oLo As ListObject
For Each oSh In ThisWorkbook.Worksheets
For Each oLo In oSh.ListObjects
If oLo.Name = tableName Then
GetTableAddress = Replace(oSh.ListObjects(tableName).Range.AddressLocal, "$", "")
GetTableAddress = oSh.Name + "$" + GetTableAddress
End If
Next
Next
End Function
If both data sets are in Excel, you should use vLookup to create the final table. It'll be easier for you and the benefit is that you can use syntax that you're already familiar with.
vLookup is essentially a table join. You can even use it with Application.WorksheetFunctions if you wish to do it that way.
Also, RecordSet.GetRows can return an array. You should probably use CInt(rs.GetString) if you're not expecting more than one value to be returned.
I know that I must set HDR=NO so the headers to be included in the results. But when I set it I cannot use the where in the SQL statement. Any ideas how to fix it;
Sub adoExcel()
Set objConnection = CreateObject("ADODB.Connection")
Set objrecordset = CreateObject("ADODB.Recordset")
'*************************************************************************************
objConnection.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\Users\sp\Desktop\test ado excel\test.xls;" & _
"Extended Properties=""Excel 8.0;HDR=No;IMEX=1"";"
'*************************************************************************************
'where Number =2
objrecordset.Open "Select * FROM [Sheet1$] where Number =1", _
objConnection, adOpenStatic, adLockOptimistic, adCmdText
ActiveSheet.Cells(2, 1).CopyFromRecordset objrecordset
'*************************************************************************************
' Loop through the recordset and send data to the Immediate Window
'**************************************************************************************
'objrecordset.MoveFirst
'Do
' Debug.Print objrecordset![Name] & " " & objrecordset![Number]
' objrecordset.MoveNext
'Loop Until objrecordset.EOF
'**************************************************************************************
'ActiveSheet.Cells(1, 1).CopyFromRecordset objrecordset![Name] & " " & objrecordset![Number]
'*************************************
End Sub
When you use HDR=NO, your fields are implicitly referred using F1, F2 etc. So, if your data looked like this:
A B
1 Name Number
2 John 100
3 Matt 200
Your previous query:
Select ... from [Sheet1$] where Number = "200"
will look like
Select ... from [Sheet1$] where [F2] = "200"
Your code will look like this:
Sub adoExcel()
Set objConnection = CreateObject("ADODB.Connection")
Set objrecordset = CreateObject("ADODB.Recordset")
objConnection.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\Users\sp\Desktop\test ado excel\test.xls;" & _
"Extended Properties=""Excel 8.0;HDR=No;IMEX=1"";"
' Notice that I have placed 200 in quotations assuming Text information
' Import Export Mode (IMEX) is set to mixed-type (value of 1)
' Also, field name used in WHERE clause is F2
objrecordset.Open "Select * FROM [Sheet1$] where [F2]= ""200""", objConnection
objrecordset.MoveFirst
Do
' Notice the use of Fields F1 and F2
Debug.Print objrecordset![F1] & " " & objrecordset![F2]
objrecordset.MoveNext
Loop Until objrecordset.EOF
End Sub
References: Implicit field name when HDR=0, and IMEX properties