For those who don't know, it is fairly easy to add SQL functionality to VBA macros. The following article provides the code: http://analystcave.com/excel-using-sql-in-vba-on-excel-data/
I modified this a bit (happy to provide the code) so that it outputs nicely and put it in a Sub that I can call. This saves me from having to do multiple sorts, copy paste, etc. to find specific data from a large worksheet.
Note a problem, however, when working with the workbook from an online source e.g. Gmail:
.ConnectionString = "Data Source=" & ThisWorkbook.Path & "\" & ThisWorkbook.Name & ";"
This works fine when the file is saved to a drive, but from an online site, Excel can't connect. Any suggestions on modifying the connection string for when the file isn't saved anywhere?
For anyone who's interested, this code (based on the code from Analyst Cave) works great for using SQL in VBA. Save the following as a Sub:
Option Explicit
Sub QuerySQL(result_location As Range, query As String)
Dim ResultWS As Worksheet
Set ResultWS = ThisWorkbook.Sheets("Query Results")
ResultWS.Cells.ClearContents
If query = "" Then Exit Sub
Dim cn As Object, rs As Object
'Add to the workbook a database connection with itself
'Note other ConnectionString could be used to access a variety of media
Set cn = CreateObject("ADODB.Connection")
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & ThisWorkbook.Path & "\" & ThisWorkbook.Name & ";" & _
"Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
.Open
End With
'Build and execute the SQL query
Set rs = cn.Execute(query)
If rs.EOF = True Then Exit Sub
'Print column labels
Dim i As Long, j As Long
For i = 0 To rs.Fields.Count - 1
result_location.Offset(0, i).Value = rs.Fields(i).Name
Next i
'Print column contents
i = 0
Do
For j = 0 To rs.Fields.Count - 1
result_location.Offset(i + 1, j).Value = rs.Fields(j).Value
Next j
rs.MoveNext
i = i + 1
Loop Until rs.EOF
'Close the connections
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
End Sub
To use it, simply do the following:
Dim myQuery As String
myQuery = "SELECT * FROM [Sheet2$]"
Call QuerySQL(ThisWorkbook.Sheets("Sheet1").Range("A1"), myQuery)
It uses MS Access style SQL. The above will look to Sheet2 as the table and print the result starting in A1 on Sheet1.
Related
I have generated an Excel report with two fields, ETA (Col H), and Supplier Invoice Number (Col I).
The spread sheet runs out data, and, and my client simply needs to make changes within these two fields. Then in Col J, I have an Excel string formula that combines the various fields, and created the required SQL script.
Currently my client then copies all text from Col J, and pastes it into SQL Server Management Studio, then runs the queries, thus updating the SQL tables. Simple enough.
However, I would like to avoid my client having to run these scripts in SQL Server. Rather, I am looking for advice or a way that they can, from within Excel, click a button, and Col J to be automatically copied, and run in SQL Server, all from Excel (perhaps with a button, with some form of Macro).
Is this possible?
Below is a screen grab of the Excel report, showing to auto generated SQL Query in col J.
Please could I be assisted in building this feature in Excel, perhaps via VBA?
I hope I have explained my requirements correctly?
Thank you!
You could create a text file and run the query using the command line utility SQLCMD, or use an ADODB connection.
Option Explicit
Sub test()
Const METHOD = 1 '1=cmdsql 2=ADODB
Const SERVER = "test\sqlexpress"
Const DATABASE = "test"
Dim fso As Object, ts As Object, ar
Dim ws As Worksheet
Dim iLastRow As Long, i As Long
Dim sql As String, timestamp As String
Dim Folder As String, SQLfile As String, LOGfile As String
Dim t0 As String: t0 = Timer
' query file and log filenames
timestamp = Format(Now, "YYYYMMDD_HHMMSS")
Folder = ThisWorkbook.Path & "\"
SQLfile = timestamp & ".sql"
LOGfile = timestamp & ".log"
Set fso = CreateObject("Scripting.FileSystemObject")
' read data from sheet into array to build sql file
Set ws = ThisWorkbook.Sheets("Sheet1")
iLastRow = ws.Cells(Rows.Count, "J").End(xlUp).Row
If iLastRow = 1 Then
MsgBox "No data in Column J", vbCritical
Exit Sub
End If
ar = ws.Range("J2").Resize(iLastRow - 1).Value2
' connect to server and run query
If METHOD = 1 Then ' SQLCMD
' create sql file
Set ts = fso.CreateTextFile(SQLfile)
For i = 1 To UBound(ar)
sql = sql & ar(i, 1) & vbCr
Next
ts.write sql
ts.Close
' execute sql using slqcmd
Dim wsh As Object, sCommandToRun As String
Set wsh = VBA.CreateObject("WScript.Shell")
LOGfile = timestamp & ".log"
sCommandToRun = "sqlcmd -S " & SERVER & " -d " & DATABASE & _
" -i " & Folder & SQLfile & _
" -o " & Folder & LOGfile
wsh.Run sCommandToRun, 1, 1
MsgBox "See CMDSQL log file " & LOGfile, vbInformation, Format(Timer - t0, "0.0 secs")
ElseIf METHOD = 2 Then 'ADODB
Dim sConn As String, conn, cmd, n As Long
sConn = "Provider=SQLOLEDB;Server=" & SERVER & _
";Initial Catalog=" & DATABASE & _
";Trusted_Connection=yes;"
' open log file
Set ts = fso.CreateTextFile(LOGfile)
' make connection
Set conn = CreateObject("ADODB.Connection")
conn.Open sConn
' execute sql statements
Set cmd = CreateObject("ADODB.Command")
With cmd
.ActiveConnection = conn
For i = 1 To UBound(ar)
ts.writeLine ar(i, 1)
.CommandText = ar(i, 1)
.Execute
Next
End With
ts.Close
conn.Close
MsgBox UBound(ar) & " SQL queries completed (ADODB)", vbInformation, Format(Timer - t0, "0.0 secs")
End If
End Sub
i have an ADODB recordset to load data from a view in an Excel sheet. The view is located in a MS SQL database. It runs perfectly for months, but since a few days the recordset is always empty, so i did not get any results. After a long day searching the www for any reasons i found, that it can happen because i use a x32 Excel and there is so much data in the view. So i separated the procedure in two queries. This helped a lot and the macro run perfectly again. Yesterday the same error appears again, so i began to split the procedure again. But now the recordset is still empty. I don't know any reason for this. I tested it with a selection of only ten rows of the view and this runs but if i want to get 1000 rows the recordset is empty again. Does anybody know a reason for this problem? All queries give the result i want to have in the database so they are ok.
Here is my code:
Sub doStuff()
Dim sqlStatement(9) As String
Dim lrow As Long
'... other variables
sqlStatement(1) = "Select * from db.View1 where location like 'forest'"
'... other sqlStatements
For i = 0 To UBound(sqlStatement)
Call loadData(sqlStatement, lrow)
Next i
End Sub
Sub loadData(sqlStatement As String, lastrow As Long)
Dim sqlServer As String
Dim dbName As String
sqlServer = "MSSQLSERVER"
dbName = "database"
On Error Resume Next
Dim con As Object
Set con = CreateObject("ADODB.Connection")
con.Open _
"Provider = sqloledb;" & _
"Data Source=" & sqlServer & ";" & _
"Initial Catalog=" & dbName & ";" & _
"User ID=user1;" & _
"Password=abcde;"
Dim rst As Object
Set rst = CreateObject("ADODB.Recordset")
With rst
.ActiveConnection = con
.Open sqlStatement , con, adLockReadOnly
ws.Activate
If lastrow = 1 Then
For col = 0 To .Fields.Count - 1
ws.Cells(1, col + 1).Value = .Fields(col).Name
Next
End If
ws.Activate
ws.Cells(lastrow+1,1).CopyFromRecordset rst
.Close
End With
con.Close
Set rst = Nothing
Set con = Nothing
End Sub
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));
I am relatively experienced in VBA coding, but I am totally new in MS SQL server 2008.
I am trying to export an Excel table like below to a SQL server:
A B C D E
1 Name Year ID
2 Jill 2015 17
3 Jack 2012 13
4 Mike 1999 25
5
After activating ADO and DAO in the tools, it fixed the error "User Defined Type Not Defined" for the line Dim rs As New ADODB.Recordset. But now I get an error for the line Set dbclass = New clsDB. I don't understand what is wrong. The same definition structure works in another Workbook.
Private Sub Transtable()
Dim connOk As Boolean
Dim MyWorkBook As Workbook
Dim CurSheet As Worksheet
Dim listObj As ListObject
Dim rs As New ADODB.Recordset
' Dim dbclass As New clsDB ' both description leads to the same "User Undefined" error
' Dim dbclass As New ADODB.clsDB
Set dbclass = New clsDB
dbclass.Database = "Tables"
dbclass.ConnectionType = SqlServer
dbclass.DataSource = "E72b1783"
dbclass.UserId = Application.UserName
connOk = dbclass.OpenConnection(False, True)
If connOk = False Then
MsgBox "Connection not successfull"
Else
MsgBox "Connection successfull"
End If
tableName = "TableName1"
Set CurSheet = Sheet2
Set listObj = CurSheet.ListObjects(tableName) 'Table Name
'get range of Table
HeaderRange = listObj.HeaderRowRange.Address
DataRange = listObj.DataBodyRange.Address
dbclass.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & ThisWorkbook.FullName & ";" & _
"Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
strSQL = "SELECT * FROM [" & ws.Name & "$" & Replace(DataRange, "$", "") & "];"
rs.Open strSQL, dbclass, adOpenStatic, adLockReadOnly
arrData = rs.GetRows
rs.Close
dbclass.Close
Set rs = Nothing
Set dbclass = Nothing
Set listObj = Nothing
Set CurSheet = Nothing
End Sub
Make sure the Data Access Objects library (DAO) is checked in the Tools. Also look at the ActiveX Data Objects library (ADO) in the list. References list in the VBA Editor's menu/ribbon. (The specific name of the library can be different in different versions of Access)
I am trying to write an Exce-Vba code for SQL connection. The code, first will open the connection to the server, then it will copy a 4 columns of table (Range("C22:G81")) from my Excel-sheet to the SQL-server (I am only trying to send numerical table now as a test, I don't send any column name)
I have been trying to solve a "Application Defined or Object Defined" error quite long time. I get the error for the connection string strCon = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & strName & ";Extended Properties=""Excel 12.0;"
I even tried with another version with password option like strCon = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & strName & ";Extended Properties=""Excel 12.0; Jet OLEDB:Database Password='passwd';"
But I get the same error. I am quite new in SQL-coding. I wonder if I am missing something important.
Lasly, I don't know if it is related to this error, but I manually created 4 columns in the SQL server for my 4 columns in the Excel. Do I need to write something specific that those 4 columns in the Excel-sheet will find the right columns in the SQL-server?
Thanks in advance...
The code:
Private Sub inlasning()
Dim MyWorkBook As Workbook
Dim rs As New ADODB.Recordset
Dim conn As New ADODB.Connection
Dim ServerName As String, DataBaseName As String, strSQL As String
Set conn = New ADODB.Connection
ServerName = "E45c7642"
DataBaseName = "Tables"
' Specify the OLE DB provider
conn.Provider = "sqloledb"
' Set SQLOLEDB connection properties
conn.Properties("Data Source").Value = ServerName
conn.Properties("Initial Catalog").Value = DataBaseName
' Windows NT authentication.
conn.Properties("Integrated Security").Value = "SSPI"
conn.Open
Dim ValidSheet As Worksheet
Dim HeaderRange As Range
Dim DataRange As Range
Dim ColRange As Range
Dim LastRange As Range
Dim strName As String, strCon As String
strName = ThisWorkbook.FullName
Application.ScreenUpdating = False
Set ValidSheet = ThisWorkbook.Sheets("Sheet2") '
Set HeaderRange = ValidSheet.Range("C20:G21")
Set ColRange = HeaderRange.Find(TheHeader, , , xlWhole)
Set LastRange = ColRange.End(xlDown)
Set DataRange = ValidSheet.Range("C22:G81") ' This is what I am trying to transfer, only numeric values without column names
strCon = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & strName _
& ";Extended Properties=""Excel 12.0;"
conn.Open strCon
strSQL = "SELECT * FROM [" & ValidSheet.Name & "$" & Replace(DataRange, "$", "") & "];"
rs.Open strSQL, dbclass, adOpenStatic, adLockReadOnly
arrData = rs.GetRows
rs.Close
conn.Close
Set rs = Nothing
Set conn= Nothing
Set ValidSheet = Nothing
End Sub
After getting the same error for the "connection string", I changed the strategy, and I used dbclass procedure to open a connection. So the new code is like below. (I found this coding from a guy, but he is on vacation now, so I can't ask him).
It gets connection (dbclass) properties automatically, which are saved in the main ThisWorkbook. This code doesn't give any error at all, but it doesn't copy the column from the Excel to the database. I tried different versions for the sql-query, like SQL = .... VALUES('result') or SQL = .... VALUES(result), but there is no result again, without error.
Private Sub Testing_Click()
Dim FindColValues() As Double
Dim ValidBook As Workbook
Dim ValidSheet As Worksheet
Dim DataRange As Range
Dim dataa As Range
Application.ScreenUpdating = False
TheSheet = "Sheet2"
Set ValidSheet = Worksheets(TheSheet)
Set DataRange = ValidSheet.Range("C21:C81")
' Below creating an array "result(it)" from the seleced range.
For Each dataa In DataRange
ReDim Preserve result(it)
result(it) = dataa.Value
it = it + 1
Next
' Below is just an alternative array for "in case"
arrData = ValidSheet.Range("C22:G81").Value
SQL = "INSERT INTO Table_test (Column1) VALUES ('result()');"
dbclass.ExecuteSQL SQL
End Sub
Below is dbclass connection properties which is read automatically by the other function:
Private Sub Workbook_Open()
Dim connOk As Boolean
Dim rs As New ADODB.Recordset
Dim MyWorkBook As Workbook
Dim CurSheet As Worksheet
Set dbclass = New clsDB
dbclass.Database = "Tables"
dbclass.ConnectionType = SqlServer
dbclass.DataSource = "E45c7642"
dbclass.UserId = Application.UserName
connOk = dbclass.OpenConnection(False, True)
If connOk = False Then
MsgBox "Cannot connect"
Else
MsgBox "The server is connected"
End If
End Sub
Finally I found the problem for my second code. As I wrote before, in my alternative code (second code), I didn't get any error at all in VBA, but it didn't save my table into the server.
Now I know the reason, because my real value was in "comma" format, but the server saved the value in "dot" format. So I added Str(value) to convert the "comma" value to the "dot" value, and it works now:
....
SQL = "INSERT INTO Table_test (Column1) VALUES (" & Str(result(1)) & ")"
dbclass.ExecuteSQL SQL
End Sub