VBA Excel - Saving column names to spreadsheet from MS Access - vba

I am using VBA in Visual Studio Express. What I am trying to do is give the top row of my excel spreedsheet that i have created by exporting an MS Access DB through VB, column names, i.e. the names that i have in my DB.
There are 10 columns the 9th is skipped, i have also spaced the spreedsheet out to allow for the first row to have headers, how would i fill the first row of my spreedsheet with the column names of my DB?
Also it is fine if to assign the names directly through the code rather than passing the column headers from the DB as well.
My Code:
Public Sub ExportEx()
Dim strSQL_Query As String
Dim oCN As ADODB.Connection
Dim oCMD As ADODB.Command
Dim oRecords As ADODB.Recordset
Dim strDBPath As String
Dim varValues As Object
Dim lngRows As Long
Dim lngCols As Long
Dim strCN As String
strDBPath = Application.StartupPath & "\SCO_Leaderboard.accdb"
strCN = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strDBPath & ";" & _
"Persist Security Info=False;"
strSQL_Query = "Select top 10 Rank, Username, Time_Played, Lv, EXP, Floor, Col, Logins, Status FROM tblUsers ORDER BY Rank ASC"
Dim oExcel As Object
Dim oBook As Object
Dim oSheet As Object
oExcel = CreateObject("Excel.Application")
oBook = oExcel.Workbooks.Add
oSheet = oBook.Worksheets(1)
oCN = New ADODB.Connection
oCN.ConnectionString = strCN
oCN.Open()
oCMD = New ADODB.Command
oCMD.ActiveConnection = oCN
oCMD.CommandText = strSQL_Query
oRecords = oCMD.Execute
varValues = oRecords.GetRows
lngCols = UBound(varValues, 2)
lngRows = UBound(varValues, 1)
oSheet.Range("A2", oSheet.Range("A2").Offset(lngRows, lngCols)) = varValues
oBook.SaveAs(Application.StartupPath & "\Top_10_All_Time.xls")
oExcel.Quit()
MsgBox("An Excel spreadsheet has been created under:" & vbNewLine & vbNewLine & Application.StartupPath & "\Top_10_All_Time.xls")
'' Clean up...
oCMD = Nothing
oCN.Close()
oCN = Nothing
On another note how would I space the fields out in Excel so that all the data fit in the column?
Thanks for any help,
Andy

In VBA, there are two methods to exporting Access table/query data to an Excel spreadsheet:
1) TransferSpreadsheet method
This command will export all fields and records. So, save your VBA string as a stored query object and reference it in below command:
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, _
"yourtableorqueryname", "fullpathtoExcelFile", True
2) CopyFromRecordset method
This command will export only records. However, you can use the recordset's Fields property to fill in first row. Do note the code below assumes you create an ADO recordset named rst using your ADODB connection.
Dim rst As ADODB.Recordset
Set rst = New ADODB.Recordset
rst = "Select top 10 Rank, Username, Time_Played, Lv, EXP, Floor, Col, Logins, Status" _
& " FROM tblUsers ORDER BY Rank ASC", oCN
oSheet.Range("A1").Select
For Each fld In rst.Fields
oExcel.ActiveCell = fld.Name
oExcel.ActiveCell.Offset(0, 1).Select
Next
'REMOVE BELOW IF YOU WANT ONLY COLUMN HEADERS NOT DATA
oSheet.Range("A2").CopyFromRecordset rst
'TO AUTO FIT (SPACE OUT) COLUMNS
osheet.Range("A1:I" & rst.RecordCount + 1).Columns.AutoFit

This works in Access, I am not sure if it works in your case:
Select top 10 Rank As Header1, Username As Header2, Time_Played As Header3 ...

You would have to retrieve the fields Collection from the scheam of oRecords:
oRecords = oCMD.Execute
or just parse the field names from strSQL ...
or - very easy, as you define the field names and build your SQL from the field names - put these in an array and then build the first row of your range from this array.

Related

Push Excel Range to SQL Table via VBA

I am in need of pushing a range in Excel to a new row in an SQL table each time an associate executes a VBA macro. So far, I have segregated the data into a single row and multiple columns (110 cells of data in total) in Excel. My problem right now is stemming from how to insert each one of these individual cells from the Excel sheet into the corresponding column and the first empty row in an SQL table. I've done some pretty extensive searches of the internet and have found nothing remotely close to what I am trying to do.
Is there a correct procedure that would allow me to dump a 110-column row into the first empty row in an SQL table?
I have the tables written and I have the range set:
Set DataBaseData = ActiveSheet.Range("DO1:HT1")
Beyond this I have no idea in which manner to open a connection with the Server, Database and Table. This is what I've winged so far:
Sub Connection()
Dim Conn As ADODB.Connection
Dim Command As ADODB.Command
Set Conn = New ADODB.Connection
Set Command = New ADODB.Command
Dim i As Integer
Dim columnnumber As Integer
i = 0
Conn.ConnectionString = "Provider=SQLOLEDB; Data Source=[Nope];Initial Catalog=[NopeNope];User ID=[NopeNopeNope];Password=[AbsolutelyNot]; Trusted_Connection=no;"
Conn.Open
Command.ActiveConnection = Conn
End Sub
Any help would be greatly appreciated.
If you have the curiosity as to what I'm trying to do: I'm pushing a series of data from a CMM to the Database so I can store the data for the needed amount of time, and call that data back to PowerBI and Minitab.
I was able to successfully write an entire row from Excel to an SQL Database using the following:
Sub Connection()
Const Tbl As String = "NEIN"
Dim InsertQuery As String, xlRow As Long, xlCol As Integer
Dim DBconnection As Object
Set DBconnection = CreateObject("ADODB.Connection")
DBconnection.Open "Provider=SQLOLEDB.1;Password=NEIN" & _
";Persist Security Info=false;User ID=NEIN" & _
";Initial Catalog=NEIN;Data Source=NEIN"
InsertQuery = ""
xlRow = 1 'only one row being used *as of now*, and that is the top row in the excel sheet
xlCol = 119 'First column of data
While Cells(xlRow, xlCol) <> ""
InsertQuery = InsertQuery & "INSERT INTO " & Tbl & " VALUES('"
For xlCol = 119 To 229 'columns DO1 to HT1
InsertQuery = InsertQuery & Replace(Cells(xlRow, xlCol), "'", "''") & "', '" 'Includes mitigation for apostrophes in the data
Next xlCol
InsertQuery = InsertQuery & Format(Now(), "M/D/YYYY") & "')" & vbCrLf 'The last column is a date stamp, either way, don't forget to close that parenthesis
Wend
DBconnection.Execute InsertQuery
DBconnection.Close
Set DBconnection = Nothing
End Sub

Read CSV/Excel file into array

I am trying to make a macro which copies emails when I receive them, and saves them in specific windows folders on a network drive based on the domain name.
The list of domains I have will be large and subject to change by users without coding experience, so I am looking to develop a text, CSV, or excel file that someone can update which lists my company's relationship to them (client, vendor, sub-contractor, etc...) and their name (both of which controls the file path), the domain name (#example.com).
I think I can figure out how to do most of that (a clever combination of nested if and for statements), but I can't figure out how to read the file into an array, and my google-fu has failed me.
I don't think it really helps, but here is the code that I shamelessly copied from the web and am planning to work off of.
Option Explicit
Private WithEvents InboxItems As Outlook.Items
Sub Application_Startup()
Dim xNameSpace As Outlook.NameSpace
Set xNameSpace = Outlook.Application.Session
Set InboxItems = xNameSpace.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub InboxItems_ItemAdd(ByVal objItem As Object)
Dim FSO
Dim xMailItem As Outlook.MailItem
Dim xFilePath As String
Dim xRegEx
Dim xFileName As String
Dim SenderAddress As String
On Error Resume Next
' Define SenderAddress as sender's email address or domain
xFilePath = PathCreator(SenderAddress)
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FolderExists(xFilePath) = False Then
FSO.CreateFolder (xFilePath)
End If
Set xRegEx = CreateObject("vbscript.regexp")
xRegEx.Global = True
xRegEx.IgnoreCase = False
xRegEx.Pattern = "\||\/|\<|\>|""|:|\*|\\|\?"
If objItem.Class = olMail Then
Set xMailItem = objItem
xFileName = xRegEx.Replace(xMailItem.Subject, "")
xMailItem.SaveAs xFilePath & "\" & xFileName & ".html", olHTML
End If
Exit Sub
End Sub
Function PathCreator(SenderAddress)
' [needs to read the file and create the path based on the values]
End Function
You can use ADODB to connect to the source file, and read it into a 2-dimensional array. Add a reference to Microsoft ActiveX Data Objects from Tools -> References.... For example, if you want to use an Excel file:
Dim excelPath As String
excelPath = "C:\path\to\excel\file.xlsx" ' Replace with the path to the Excel file
Dim connectionString As String
connectionString = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=""" & excelPath & """;" & _
"Extended Properties=""Excel 12.0;HDR=Yes"""
'This assumes the Excel file contains column headers -- HDR=Yes
Dim sql As String
sql = "SELECT Relationship, LastName, FirstName, DomainName FROM [Sheet1$]"
'Assumes the relevant worksheet is named Sheet1
'Also assumes the first row of the sheet has the following labels: Relationship, LastName, FirstName, Domain (in no particular order)
Dim rs As New ADODB.Recordset
rs.Open sql, connectionString
Dim arr As Variant
arr = rs.GetRows 'Puts the data from the recordset into an array
rs.Close
Set rs = Nothing
Dim row As Variant, column As Variant
For row = 0 To UBound(arr, 2)
For column = 0 To UBound(arr, 1)
Debug.Print arr(column, row)
Next
Next
Using a text file or CSV is just a matter of slightly changing the connection string and the SQL. But I think using an Excel file will force the users to keep the data in columns, where in a CSV users would have to insert field- and row-separators manually; the same for any other text format -- users would have to remember the format's rules and apply them correctly.
But I question if an array is the best data structure for you to use; in this case you could use the recordset directly. In order to make sure the file is not held open, you could use a disconnected recordset. (If your intention is to find the appropriate domain name and use that to get other details, then I would suggest you load the data from a recordset into a Scripting.Dictionary.)
Also note that you probably only need to load the data from the file once, unless you expect it to change while the code is running.
I would write something like this
Dim rs As ADODB.Recordset
Function PathCreator(SenderAddress) As String
If rs Is Nothing Then
Dim excelPath As String
excelPath = "C:\path\to\excel\file.xlsx" ' Replace with the path to the Excel file
Dim connectionString As String
connectionString = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=""" & excelPath & """;" & _
"Extended Properties=""Excel 12.0;HDR=Yes"""
Dim sql As String
sql = "SELECT Relationship, LastName, FirstName, DomainName FROM [Sheet1$]"
Set rs As New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.CursorType = adOpenStatic
rs.Open sql, connectionString, adOpenStatic, adLockBatchOptimistic
'Disconnect the recordset
rs.ActiveConnection = Nothing
'Now the data will still be available as long as the code is running
'But the connection to the Excel file will be closed
End If
'build the path here, using the recordset fields
PathCreator = rs!Relationship & "_" & rs!LastName & "_" & rs!FirstName & "_" & rs!Domain
End Function
NB. By the same token, you can add a reference to Microsoft Scripting Runtime; then you can write the code that uses the FileSystemObject as follows:
Dim FSO As New Scripting.FileSystemObject
If Not FSO.FolderExists(xFilePath) Then
FSO.CreateFolder xFilePath
End If
and with a reference to the Microsoft VBScript Regular Expressions 5.5 libary:
Set xRegEx As New VBScript_RegExp_55.RegExp
xRegEx.Global = True
xRegEx.IgnoreCase = False
xRegEx.Pattern = "\||\/|\<|\>|""|:|\*|\\|\?"
If objItem.Class = olMail Then
Set xMailItem = objItem
xFileName = xRegEx.Replace(xMailItem.Subject, "")
xMailItem.SaveAs xFilePath & "\" & xFileName & ".html", olHTML
End If

How to create a new sheet/table in an .xlsx file using ADO in excel vba

Hi I'm trying to create a function that will store a range of data selected by a user along with a user's custom name and then using ADO the data will be stored in a new Excel sheet with the user's custom name as the sheet name. So far I've gotten an ADO connection working and can read and write data to the .xlsx file but when I try and create a new sheet by creating a new table I get an error that my sheet name is not correct. I've used test and testName and after digging around I am stumped. Here is a chunk of my code:
Sub AddSheet()
Dim DataName As String, SRange As Variant, qry As String, SCols As Integer, SRows As Integer
DataName = InputBox("Enter Your Data Name:")
Set SRange = Application.Selection
Set SRange = Application.InputBox("Select your data to be saved:", xTitleId, SRange.Address, Type:=8)
SCols = SRange.Columns.Count 'new
SRows = SRange.Rows.Count 'new
'creates the query to create a new sheet/table for the data
qry = "CREATE TABLE [" & DataName & "$] ("
For i = 1 To SCols
qry = qry & "[Col" & i & "] Float"
If i <> SCols Then
qry = qry & ", "
End If
Next i
qry = qry + ")"
SQLUpdateData qry
End Sub
'function that executes the SQL query
Function SQLUpdateData(qry As String) As Variant
Dim FileName As String, sconnect As String
Dim cnn As New ADODB.Connection
Dim objMyCmd As ADODB.Command
Set cnn = New ADODB.Connection
Set objMyCmd = New ADODB.Command
FileName = "c:\Users\" & Environ("Username") & "\AppData\Roaming\Microsoft\AddIns\DataStorage.xlsx"
sconnect = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & FileName & ";HDR=Yes';"
cnn.Open sconnect
objMyCmd.CommandType = adCmdText
objMyCmd.CommandText = qry
objMyCmd.ActiveConnection = cnn
MsgBox qry
objMyCmd.Execute
Set objMyCmd = Nothing
Set cnn = Nothing
End Function
So far I've printed out the query and it looks ok before execution. For example if the user chooses the name test I get the following query output:
CREATE TABLE [test$] ([Col1] Float, [Col2] Float)
and then a runtime error stating
[Microsoft] [ODBC Excel Driver] 'test$' is not a valid name
I've searched that error also but still can find out why this isn't working. Any help is really appreciated!
The below example shows how to create a workbook and add worksheets using ADOX:
Option Explicit
Sub Test()
' Add reference
' Microsoft ADO Ext. 6.0 for DDL and Security
Dim cat As ADOX.Catalog
Dim tbl As ADOX.Table
Dim col As ADOX.Column
Set cat = New ADOX.Catalog
cat.ActiveConnection = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & ThisWorkbook.Path & "\test.xlsx;Extended Properties=Excel 12.0 Xml"
Set tbl = New ADOX.Table
tbl.Name = "TestTable"
Set col = New ADOX.Column
With col
.Name = "Col1"
.Type = adVarWChar
End With
tbl.Columns.Append col
cat.Tables.Append tbl
End Sub
Some useful links:
About ADOX
Using ADOX with Excel Data
Microsoft ACE OLEDB 12.0 connection strings

VBA ADODB- Select query using the excel sheet of the same workbook as Database

I am novice in VBA so please don't mind if the question is of low level.I am trying to run a SQL query where the data has to be extracted from one of the sheets of the same workbook.
SQL = "Select ProductNumber from [sData$] where ProductSource = " & pSource & "
'pSource is a string that stores Product Source
'sdata is a sheet named as Data in the workbook
dataPath = ThisWorkbook.Fullname
'Not sure if this is the value I shall send as datapath in getData function
Set rst = getData(dataPath,SQL)
rst.Open
The getData function is defines as below
Public funtion getData(path as String, SQL as string) as ADODB.Recordset
Dim rs as ADODB.Recordset
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
cn.Open ("Provider= Microsoft.Jet.OLEDB.4.0;" & _
"DataSource= " & path & ";"&_
"Extended Properties=""Excel 8.0;HDR=Yes;FMT=Delimited;IMEX=1;""")
rs.ActiveConnection =cn
rs.Source= SQL
Set getData =rs
End Function
Now after I get the numbers from Data sheet, I need to find the corresponding
ProductCompany from Relation sheet. 9 is for Amul, 5 is for Nestle and so on.
Relation:
I am not sure how to do that. The numbers corresponds to their respective Product company in order.
Take a look at the below example showing how to create ADODB connection to this workbook, get ADODB recordset from SQL query, retrieve key - value pairs from relation sheet, create and populate a dictionary, and output the values from the recordset and the corresponding values from the dictionary:
Option Explicit
Sub Test()
Dim oCn As Object
Dim oRs As Object
Dim aKeys
Dim aItems
Dim i As Long
Dim oDict As Object
Dim dProdNum
' create ADODB connection to this workbook
Set oCn = CreateObject("ADODB.Connection")
oCn.Open _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"DataSource='" & ThisWorkbook.FullName & "';" & _
"Extended Properties=""Excel 8.0;HDR=Yes;FMT=Delimited;IMEX=1;"";"
' get ADODB recordset from SQL query
Set oRs = oCn.Execute("SELECT DISTINCT ProductNumber FROM [Data$] WHERE ProductSource = 'A1'")
' retrieve key - value pairs from relation sheet
With ThisWorkbook.Sheets("Relation")
aKeys = Split(.Range("B1"), ",")
aItems = Split(.Range("B2"), ",")
End With
' create and populate a dictionary
Set oDict = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(aKeys)
oDict(Trim(aKeys(i)) + 0) = Trim(aItems(i))
Next
' output the values from the recordset and the corresponding values from the dictionary
oRs.MoveFirst
Do Until oRs.EOF
dProdNum = oRs.Fields(0).Value
Debug.Print dProdNum & " - " & oDict(dProdNum)
oRs.MoveNext
Loop
End Sub
The output for me is as follows:
4 - Britanica5 - Nestle9 - Amul
Note, connection string in the above code shown for .xls file. In case .xlsm you should use:
oCn.Open _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source='" & ThisWorkbook.FullName & "';" & _
"Extended Properties=""Excel 12.0 Macro;HDR=Yes;FMT=Delimited;IMEX=1;"";"

Looking up Access database in Excel

I want to do something very simple: I have an Access database with one table mapping thousands of product IDs to product information fields. In an Excel worksheet, the user types in perhaps 100 product IDs in the first column. I need for the remaining columns to pull in information from the Access database for the corresponding IDs. Specifically:
if I use MS-Query, it seems to insist on the output being a table. I simply want the output to be inside a single cell. Preferably, a formula that involves a SQL-type query.
I don't want any of the values to be updated automatically, but rather want all the columns updated only on user demand (the user could either choose refresh through a menu, or a VBA-based refresh button on the sheet is fine as well).
I'm thinking this would be a straightforward use case, but it seems surprisingly hard to find a solution. Thank you in advance!
Working from Excel, you can use ADO to connect to a database. For Access and Excel 2007/2010, you might:
''Reference: Microsoft ActiveX Data Objects x.x Library
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
''Not the best way to refer to a workbook, but convenient for
''testing. it is probably best to refer to the workbook by name.
strFile = ActiveWorkbook.FullName
''Connection string for 2007/2010
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 12.0 xml;HDR=Yes;"";"
cn.Open strCon
''In-line connection string for MS Access
scn = "[;DATABASE=Z:\Docs\Test.accdb]"
''SQL query string
sSQL = "SELECT a.Stuff, b.ID, b.AText FROM [Sheet5$] a " _
& "INNER JOIN " & scn & ".table1 b " _
& "ON a.Stuff = b.AText"
rs.Open sSQL, cn
''Write returned recordset to a worksheet
ActiveWorkbook.Sheets("Sheet7").Cells(1, 1).CopyFromRecordset rs
Another possibility returns a single field from MS Access. This example uses late binding, so you do not need a library reference.
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim s As String
Dim i As Integer, j As Integer
strFile = "z:\docs\test.accdb"
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile
''Late binding, so no reference is needed
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
''Select a field based on a numeric reference
strSQL = "SELECT AText " _
& "FROM Table1 a " _
& "WHERE ID = " & Sheets("Sheet7").[A1]
rs.Open strSQL, cn, 3, 3
Sheets("Sheet7").[B1] = rs!AText
OK, this may seem a bit lengthy - Create an Excel-table - in the first row (from column two) you have the Fieldnames Exactly as you have them in the access-table, in the first column you have the desired key-values (e.g. CustomerIDs).
When you run the macro it fills in what it finds...
Sub RefreshData()
Const fldNameCol = 2 'the column with the first fieldname in it'
Dim db, rst As Object
Set db = DBEngine.workspaces(0).OpenDatabase("C:\path\to\db\name.accdb")
Set rst = db.openrecordset("myDBTable", dbOpenDynaset)
Dim rng As Range
Dim showfields() As Integer
Dim i, aRow, aCol As Integer
ReDim showfields(100)
Set rng = Me.Cells
aRow = 1 'if you have the fieldnames in the first row'
aCol = fldNameCol
'***** remove both '' to speed things up'
'On Error GoTo ExitRefreshData'
'Application.ScreenUpdating = False'
'***** Get Fieldnames from Excel Sheet'
Do
For i = 0 To rst.fields.Count - 1
If rst.fields(i).Name = rng(aRow, aCol).Value Then
showfields(aCol) = i + 1
Exit For
End If
Next
aCol = aCol + 1
Loop Until IsEmpty(rng(aRow, aCol).Value)
ReDim Preserve showfields(aCol - 1)
'**** Get Data From Databasetable'
aRow = 2 'startin in the second row'
aCol = 1 'key values (ID) are in the first column of the excel sheet'
Do
rst.FindFirst "ID =" & CStr(rng(aRow, aCol).Value) 'Replace ID with the name of the key field'
If Not rst.NoMatch Then
For i = fldNameCol To UBound(showfields)
If showfields(i) > 0 Then
rng(aRow, i).Value = rst.fields(showfields(i) - 1).Value
End If
Next
End If
aRow = aRow + 1
Loop Until IsEmpty(rng(aRow, aCol).Value)
ExitRefreshData:
Application.ScreenUpdating = True
On Error GoTo 0
End Sub
And if you dont want your fieldnames in the excel sheet replace the paragraph "Get Fieldnames From Excelsheet" with this:
fieldnames = Split("field1name", "", "", "field3name")
For j = 0 To UBound(fieldnames) - 1
For i = 0 To rst.fields.Count - 1
If rst.fields(i).Name = fieldnames(j) Then
showfields(j + fldNameCol) = i + 1
Exit For
End If
Next
Next
ReDim Preserve showfields(UBound(fieldnames) - 1 + fldNameCol)
and add this at the top
dim j as integer
dim fieldnames