I have an SQL query in Excel. After the query a message should appear. This works well as an existing user, but for new users the password query is hidden.
I was looking for an alternative to Application.CalculateUntilAsyncQueriesDone but couldn't find a solution. The Excel login mask is always not displayed.
ThisWorkbook.RefreshAll
Application.CalculateUntilAsyncQueriesDone
MsgBox "The new data has been imported.", , "Import done"
How can I get Excel to wait for the end of the query and get the login displayed before (if I have never entered the password as a new user)?
Ok, so this not the solution you're looking for, but I don't think it exists I'm afraid. Embedded query objects in Excel are horrible to work with, and they expect you to be using Active Directory for your SQL Server password, hence the exceptionally poor handling of the "login" popup box.
My advice would be to move away from embedded workbook tables / queries and script out your queries using an ADODB connection, purely in VBA.
If the user's password is a simple username / password combo, then you can read it from a cell in your sheet before you send the query to the server.
If the login is based on Active Directory, then the user doesn't need to enter their password at all, ADODB will just log them on automatically.
Here's my simple SQL function I use to query SQL Server databases using ADODB. The long string in the middle after "conn.open" is the connection string. In that, replace "DatabaseHostName" with the Host name of your DB, and "DatabaseName" with the name of the DB you want to query by default:
Sub ReadSQL(SQLQuery As String, DestinationRecordSet As Object)
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Set DestinationRecordSet = CreateObject("ADODB.Recordset")
conn.Open "Provider=SQLOLEDB;Data Source=DatabaseHostName;Initial Catalog=DatabaseName;Integrated Security=SSPI;"
conn.CommandTimeout = 0
Set DestinationRecordSet = conn.Execute(SQLQuery)
End Sub
then I call it like this:
Sub GetData()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim OutputRecordSet As Object
Dim SQLQuery As String
SQLQuery = "SELECT TOP 10 * FROM dbo.SomeTable"
ReadSQL SQLQuery, OutputRecordSet
'Clear output sheet'
wb.Sheets("Output").Cells.ClearContents
'Write Headers from query in row 1'
For x = 0 To OutputRecordSet.Fields.Count - 1
wb.Sheets("Output").Cells(1, x + 1).Value = OutputRecordSet.Fields(x).Name
Next x
'Copy the data from the query to the worksheet, under the headers'
wb.Sheets("Output").Range("A2").CopyFromRecordset OutputRecordSet
End Sub
When I call my "ReadSQL" sub from this sub, by default it waits until the query is complete before doing anything else.
Again I would strongly recommend active directory logins for your DB, then you never have to worry about passwords, but If you wanted to embed a user's password into the connection string, you could, or you could build the connection string based on something the user entered, or had entered into a cell in the workbook.
In any case, you will find once you move away from Excel's embedded tables which work with the dreaded "Refresh All" button, things will become much easier to control, and your queries will run about 10 times faster.
Related
My ultimate goal is to run sql queries against sql-server and capture the returned data in a spreadsheet. The following code roughly reflects my current set-up and it works. The design allows me to read sql codes from text files and submit it to a sql-server. "Sub ExecuteCRUD" submits a first sql script to prepare data and dumps the result into a temp table. "Function loadRecordset" submits a relatively simple select query and captures the returned data in a recordset, which I then use to populate a spreadsheet.
There are a couple "variables" in my setup that could potentially be relevant for discussion.
My 4 set of Sql Codes
The ConnectionString (Part of my vba code)
The rest of my vba codes
a. Dbo.ConnectionString = "Provider=MSDASQL;DRIVER=SQL
Server;SERVER=myserver;UID=id;PWD=password;DATABASE=database;"
b. Dbo.ConnectionString = "Provider=SQLOLEDB;Data
Source=myserver;Initial Catalog=database;User
ID=id;Password=password;"
Initially, all I changed was the connection string. The immediate result was connection string version a works perfectly. Using b version, my setup would fail without any errors from sql-server.
Using Connection string version b, "Sub ExecuteCRUD" (data preparation step) would still work smoothly. I can verify that the temp table is created in tempdb as a result of my first sql script.
"Function loadRecordset" would run through the lines without any errors up to and including "rs.open". (I checked for errors, none whatsoever from the ado connection).
Only subsequent codes, when using the recordset to copy out the data would get an error: "Operation is not allowed when the object is closed."
Through some testing, I narrowed down the issue to the sql codes, sort of.
I have to reiterate here. My initial set of sql codes worked completely fine when using the ODBC provider. Things only went sideways using the OLEDB provider.
Using the OLEDB provider, the "offending" sql code was Use databaseABC. Furthermore, using ado, my setup submits 4 sets of sql codes to the sql server. The first set of sql codes prepares data (creating tables, inserting data, creating index, using while loops to populate data, using recursive ctes, etc). In this first set of sql codes, Use databaseABC was also included, and it would execute successfully. The other 3 set of sql codes submitted were only select queries aimed at obtaining data. When Use databaseABC was included in the select query sql codes, the operation failed without any errors. After I took out the Use databaseABC, everything would run correctly in the OLEDB provider world.
Use databaseABC is not a necessary part of the select queries. Using it saves me the trouble of specifying database names all the time in the join clauses.
At this point, my curiosity is two fold
why Use databaseABC causes failures, specifically only when using OLEDB provider, more specifically only when running select queries.
When the failure occurred, should sql-server or the driver generate any errors? What would be the proper way of checking and verifying that?
Private Dbo As New ADODB.Connection
Private rs As ADODB.Recordset
Public Sub ConnectServer()
If Dbo.State = adStateClosed Then
Dbo.ConnectionString = "Provider=SQLOLEDB;Data Source=*server*;" _
& "Initial Catalog=*database*;User ID=*id*;Password=*pwd*;"
Dbo.Open
End If
End Sub
Public Sub ExecuteCRUD(ByVal Sql As String)
On Error GoTo PANIC
Dbo.Execute Sql
Exit Sub
PANIC:
Debug.Print Err.Descript
Stop
End Sub
Public Function loadRecordset(ByVal Sql As String) As Long
On Error GoTo PANIC
Set rs = New ADODB.Recordset
With rs
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockReadOnly
.Source = Sql
Set .ActiveConnection = Dbo
End With
rs.Open
loadRecordset = rs.RecordCount
Exit Function
PANIC:
Debug.Print Err.Description
loadRecordset = 0
Stop
End Function
First I created a table in a SQL Server database with columns like document id, docname and docdata.
Second I managed to upload a document into that table using MS Access and ole object and now I have a word document there, by double click the ole object in the MS Access form interface I could open the document.
What is interesting about this is that I can edit the document and get the changes saved to the document inside the server.
The idea is that I want to use the SQL Server over our LAN-network as an alternative to OneDrive and make collaborative working possible to my coworkers with no internet service.
Now I am trying to open that document using using VBA in MS Word.
Here is my vague attempt:
Option Explicit
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Private Sub CommandButton1_Click()
' my own connection string
cn.Open "provider=sqloledb;server=127.0.0.1,1433;database=accessdb;UID=sa;PWD=111111"
With rs
Set .ActiveConnection = cn
.Source = "select * from docs where did=1"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
Dim WordObj As Word.Application
'rs![docdata].Verb = -2
'rs![docdata].Action = 7 ' Activates the application.
Dim obdoc As Word.Document
Set obdoc = rs![docdata] ' what property to attach here ??
obdoc.SaveAs2 "D:\thisdoc.doc"
WordObj.Documents.Open obdoc
WordObj.Activate
End Sub
A mismatch error pops up and I did not know how to deal with this, what type of property should retrieve the data from that field and assign it to the newly made document.
Update 20-2-2020
Um, Now I recognized that the Ms-word document isn't a simple RTF file.
It is a zipped folder ! see this !
and in order to verify this I removed the .docx extension and replaced it with .zip,
then I unzipped it and got some folders inside of them a few .xml files..
This means that the upper approach is not compatible with the nature of this file. type.
Needed to refresh data connection (SQL server connection consisting of multiple tables) in my excel worksheet on a regular basis. However, when I perform a "Refresh All" excel basically retrieves all data from the SQl server from scratch and hence takes a long time to retrieve data. I was hoping to speed this by pulling in only the required data and then appending this to my tables in my connection. Is there a way to do this using VBA?
I have an Excel sheet with a connection to a SQL server from which I bring in multiple tables to build dynamic reports. While refreshing the data to bring in new rows to all different tables, excel takes a really long time to bring in the data as it retrieves all rows from the sql server from scratch. I was hoping I could just bring in the new data and then feed it into the tables in my data connection and refresh the data this way using a VBA script. I did come across code to get the required data from the SQL server but didn't find a way to then feed this data back into the tables and refresh the tables this way instead of using the "refresh all" option and avoid having to wait a long time.
Dim sConnString As String
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
sConnString = "Provider=SQLOLEDB.1;Initial Catalog=WELLTHY_ALL;" & _
"User ID=username;Password=password;Data Source=NEWINSTANCE;"
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
conn.Open sConnString
query = "SELECT * FROM table where date >" & " '2019-05-03'"
Set rs = conn.Execute(query)
I want to then append this data that I got above in rs into the table bought into excel via the connection and avoid the time-consuming refresh all method
So I have this application and I moved all local tables to SQL Server using upsizing, now they are currently linked tables. I'm able to access tables and forms related to tables can be accessed with no problems. But when I programmatically fetch a record, or perform a sql operation in VBA script, a SQL Server Login prompt pops up asking me to enter in the SQL Authentication login to access the database.
I followed this link here:
https://support.microsoft.com/en-us/kb/177594
Where this is my end code:
Dim db1 As Database
Dim db2 As Database
Dim rs As Recordset
Dim strConnect As String
Set db1 = OpenDatabase("C:\Workspace\ms1.mdb")
strConnect = UCase(db1.TableDefs("dbo_TableA").Connect) & ";UID=User1;PWD=Password1"
Set db2 = OpenDatabase("", False, False, strConnect)
db2.Close
Set db2 = Nothing
Set rs = db1.OpenRecordset("dbo_TableA")
rs.Close
db1.Close
Set rs = Nothing
Set db1 = Nothing
DoCmd.RunCommand acCmdSaveRecord
'Sql Server login prompt pops up after running the below code;'
If DCount("*", "TableA", "[ColA] = [forms]![FRM_LOGS]![USER]") = 0 Then
MsgBox "User ID not found - contact HelpDesk", vbCritical
DoCmd.Quit
Exit Sub
End If
The DCount is triggering the SQL Server Login Prompt. I need this prompt to go away. If I open up a form, query, report, anything where the access object is bound to the data, I get no message. It ONLY happens in VBA when I'm trying to access the data object.
Edit! I did find the culprit. I deleted the linked table to the TableA in sql server, and I relinked it again, and clicked the Save password checkbox. I did this before, and it didn't work. Did it again, and it fixed everything. Not sure why this didn't work the first time. I marked the below as an answer because that did solve the problem given the circumstances.
Not sure what you're doing here with two database connections and using DCOUNT on an internal table?
It looks like your database connection has linked tables that have stored passwords
Why not just use your recordset that works to check for a valid user?
Set db1 = OpenDatabase("C:\Workspace\ms1.mdb")
Set rs = db1.OpenRecordset("SELECT [ColA] FROM [dbo_TableA] WHERE [ColA] = """ & [forms]![FRM_LOGS]![USER] & """")
if rs.EOF Then
MsgBox "User ID not found - contact HelpDesk", vbCritical
DoCmd.Quit
Exit Sub
End If
rs.Close
db1.Close
Set rs = Nothing
Set db1 = Nothing
I'd like to know, how to create a database table in Excel, so that it may be used with ODBC
I want to use ODBC, and I have two options, either MS Access or Excel,
As you probably know, in order to indicate some MS Access file or Excel file as an ODBC source, you need to follow:
Administrative Tools -> Data Sources (ODBC) -> Choose User DSN -> Choose either 'Excel Files' or 'MS Access Database' from the list -> Press 'Configure' -> finally choose the file (MS Access or Excel) as ODBC source
Well, it works fine with MS Access, I can connect to the file and see all tables that I've created inside
But when it comes to Excel, although I can connect to the file, I can't see the table that I've created inside
I just used 'Table' in 'Insert' tab, added some headers as column names, and gave the table
a meaningful name. Is that the way to do it?
There are several ways you can reference "table" data in an Excel workbook:
An entire worksheet.
A named range of cells on a worksheet.
An unnamed range of cells on a worksheet.
They are explained in detail in the "Select Excel Data with Code" section of the Microsoft Knowledge Base article 257819.
The most straightforward way is to keep the data on a separate sheet, put column names in the first row (starting in cell A1), and then have the actual data start in row 2, like this
To test, I created a User DSN named "odbcFromExcel" that pointed to that workbook...
...and then ran the following VBScript to test the connection:
Option Explicit
Dim con, rst, rowCount
Set con = CreateObject("ADODB.Connection")
con.Open "DSN=odbcFromExcel;"
Set rst = CreateObject("ADODB.Recordset")
rst.Open "SELECT * FROM [Sheet1$]", con
rowCount = 0
Do While Not rst.EOF
rowCount = rowCount + 1
If rowCount = 1 Then
Wscript.Echo "Data row 1, rst(""LastName"").Value=""" & rst("LastName").Value & """"
End If
rst.MoveNext
Loop
Wscript.Echo rowCount & " data rows found."
rst.Close
Set rst = Nothing
con.Close
Set con = Nothing
The results were
C:\Users\Gord\Documents\__tmp>cscript /nologo excelTest.vbs
Data row 1, rst("LastName").Value="Thompson"
10 data rows found.
I hope that helps your Excel connection issue.
As a final comment I have to say that if you are doing something that takes "several seconds" to do in Excel but "takes around 20-25 min" to do in Access then I strongly suspect that you are using Access in a very inefficient way, but that's a topic for another question (if you care to pursue it).
EDIT
If you want to INSERT data into an Excel workbook then that is possible, but be aware that the default setting for an Excel ODBC connection is "Read Only" so you have to click the "Options>>" button and clear that checkbox:
Once that's done, the following code...
Option Explicit
Dim con
Set con = CreateObject("ADODB.Connection")
con.Open "DSN=odbcFromExcel;"
con.Execute "INSERT INTO [Sheet1$] (ID, LastName, FirstName) VALUES (11, 'Dumpty', 'Humpty')"
con.Close
Set con = Nothing
Wscript.Echo "Done."
...will indeed append a new row in the Excel sheet with the data provided.
However, that still doesn't address the problem of no "Tables" being available for selection when you point your "sniffer" app at an Excel ODBC DSN.
One thing you could try would be to create an Excel sheet with column headings in row 1, then select those entire columns and create an Excel "Defined Name". Then, see if your "sniffer" app recognizes that as a "table" name that you can select.
FWIW, I defined the name myTable as =Sheet1!$A:$C in my Excel workbook, and then my original code sort of worked when I used SELECT * FROM [myTable]:
C:\Users\Gord\Documents\__tmp>cscript /nologo excelTest.vbs
Data row 1, rst("LastName").Value="Thompson"
1048576 data rows found.
As you can see, it retrieved the first "record" correctly, but then it didn't recognize the end of the valid data and continued to read the ~1 million rows in the sheet.
I doubt very much that I will be putting any more effort into this because I agree with the other comments that using Excel as an "ODBC database" is really not a very good idea.
I strongly suggest that you try to find out why your earlier attempts to use Access were so unsatisfactory. As I said before, it sounds to me like something was doing a really bad job at interacting with Access.
I had a similar problem with some data recently. The way I managed to get around it was to select the data as a range A1:XY12345, then use the Define Name tool to name the range. When you connect to the Excel workbook via ODBC, this named range will appear as a "table," while ranges that you actually defined (per Excel) as a table, do not.
You just need to select as many as required columns from first row of your excel file and then give a name to it on the edit box left to the formula bar. Of course you give a name to each column of the file too!