Using Excel as an ODBC database - database

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!

Related

Application.CalculateUntilAsyncQueriesDonecode prevents the login mask of an SQL query in Excel

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.

EXCEL SQL SELECT won't recognize field names in ThisWorkBook

I'm a self taught Excel VBA and SQL user. I'm testing out some simple queries before I add complexity. I must be missing something blindingly obvious here...
I am using an ADO connection to run a SQL SELECT statement on a table in the activeworkbook (ThisWorkBook). The Excel Table is named "tbl_QDB" and is on worksheet "MyQDB". The table starts on cell A1, so there are no blank or populated cells above the Table HeaderRowRange.
I have set up an ADO connection to ThisWorkBook and this is working fine. Here's the code:
Sub ConnectionOpen2()
'### UNDER DEVELOPMENT
Dim sconnect As String
Const adUseClient = 3
Const adUseServer = 2
Const adLockOptimistic = 3
Const adOpenKeyset = 1
Const adOpenDynamic = 2
'used to connect to this workbook for SQL runs
On Error GoTo err_OpenConnection2
Set cn2 = CreateObject("ADODB.Connection")
Set rec2 = CreateObject("ADODB.Recordset")
rec2.CursorLocation = adUseClient
rec2.CursorType = adOpenStatic
rec2.LockType = adLockOptimistic
datasource = ThisWorkbook.FullName
sconnect = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & datasource & ";" & _
"Extended Properties=""Excel 12.0;HDR=YES;ReadOnly=False;Imex=0"";"
cn2.Open sconnect
'etc, etc...
End Sub
I can run this simplest basic SELECT query:
SQLSTR="SELECT * FROM [MYQDB$]"
rec2.open SQLSTR, cn2
This works and produces 10 records i.e. rec2.recordcount=10.
However, if I try this, it errors:
SQLSTR="SELECT QID_1 FROM [MYQDB$]"
QID_1 is a valid field in the table on worksheet "MyQDB".
It doesn't change the error if I enclose QID_1 in () or [] or ``
I can even replace the field name with a made up field e.g. DonaldDuck and I get the same error.
Why would the SELECT statement work if I use "*" but not if I use any of the field names in the table? This seems so basic that I feel I must have missed a simple but key point.
I really will appreciate if someone can point out the mistake!
The SQL should work - if the field exists. Execute the Select * and dump the field list:
For i = 0 To rec2.Fields.Count - 1
Debug.Print rec2.Fields(i).Name
Next i
Thank you all for your comments.
That suggestion #FunThomas was an eye opener! The results were F1, F2, F3 etc, so the field names (or column names if you prefer) were not being recognised.
This would explain why, after days of trying to join this table with another in a closed, external workbook, it was not working. SQL error messages can be quite obtuse and were not saying it didn't recognise the field name.
I have now fixed that issue. Here's what I can tell / warn others:
I started this table with rows above the header. In 2 of those cells
above I recorded the last connection time and status to another
workbook table. I realised before that these extra rows, with data
populated in ANY cell above the headers, were causing problems with
SQL. Despite having my data in an Excel Table, the SQL "engine" for
Excel looks at the sheet, i.e. [MYQDB$] where the data is stored
(although I am aware that you can specify a sheet and range, but
cannot use the actual table name as the range).
It is ok to have blank rows above the table headerrowrange. So, I
deleted the cells containing the data above the table
headerrowrange. Instead, I placed a Text Box and used a formula to
look at another sheet where the last connection time and status were
now stored to supply the text for the text box.
I can now see that even this Text Box, which occupies no cell, causes a problem for Excel SQL.
Before posting my question here, I made a copy of the workbook and removed the text box and the rows above the table headerrowrange. I still got errors. I still got F1, F2, F3 etc as field names (per #FunThomas's suggestion).
Only after deleting these rows and the text box and then resizing the table (actually, the same range as before) did the Excel SQL recognise the proper field names. I was then even able (just for curiosity) to insert a blank row above the table headerrowrange, and the SQL still worked.
It seems to me that Excel retained in memory the old table definition and only by removing all data above the table headerrowrange and then resizing the table did it refresh that. Perhaps I should be less lazy in future and call the sheetname and range (table address) in the sql: maybe that would ignore data in cells above the headerrowrange?
#PanagiotisKanavos: I was originally trying to compare two tables (actual Excel Tables, not just ranges, hence they have Field Names), one in ThisWorkBook and another in a closed Excel workbook. SQL is the best way to do this. Having failed to get a left join to work between these tables (and this Question might now have revealed why that wouldn't work!) I decided to bring the data from the external workbook into ThisWorkBook and compare there. Then I was going to find the differences, store in a recordset (hence SQL) and then INSERT INTO the external workbook.
Thanks for your help guys!

How to change database for linked tables in Access MDB

I have many Access database files that contain linked tables to a SQLServer database. Now the database server has changed and I have to edit the links, possibly without recreating them.
it's possible to do it? I use Access 2013.
Yes it is possible to do with VBA but the way you'll do it really depends on how you linked the tables.
Here are 2 example of connection strings I use for SQL server tables
Direct connection:
Driver=SQL Server Native Client 10.0;Server=server_name;Address=server_address,1433;Network=network_name;Database=database_name;Trusted_Connection=Yes
DSN connection (with an entry in the ODBC control panel)
ODBC;Provider=SQLNCLI10;Server=server_name;DSN=name_of_DSN_entry_in_ODBC_control_panel;Database=database_name;Trusted_Connection=Yes
So the first thing to do is to determine how your tables are linked.
You can use this code (pay attention to the comments):
Public Sub Check_ODBC_tables()
Dim tdef As TableDef
' First loop on all tables to determine the connection strings
For Each tdef In CurrentDb.TableDefs
' only print the constring if the database name is your old database (adapt accordingly)
If InStr(tdef.Connect, "Database=old_database_name") Then
Debug.Print tdef.Connect
End If
Next
End Sub
Run this code (F5) and check the output in the immediate window. You'll find how the table are linked and what are the connection strings.
You should prepare a connection string based on that, and adapt in them the database name to use your new DB.
Once you are ready, you can adapt and run the following code that will delete the old table and recreate them with the new database names (some tweaks might be necessary), so go thought it in debug mode !
Public Sub Change_ODBC_tables()
Dim tDef As TableDef
Dim tDefNew As TableDef
Dim strTable As String
Dim strCOnString As String
' examples of constrings - ADAPT!!!!
strCOnString = "Driver=SQL Server Native Client 10.0;Server=server_name;Address=server_address,1433;Network=network_name;Database=database_name;Trusted_Connection=Yes"
strCOnString = "ODBC;Provider=SQLNCLI10;Server=server_name;DSN=name_of_DSN_entry_in_ODBC_control_panel;Database=database_name;Trusted_Connection=Yes"
For Each tDef In CurrentDb.TableDefs
If InStr(tDef.Connect, "Database=old_database_name") Then
' We find a match, store the table name
strTable = tDef.Name
' delete the linked table
DoCmd.DeleteObject acTable, strTable
' recreate the linked table with new DB name
Set tDef2 = CurrentDB.CreateTableDef(strTable)
tDef2.Connect = strCOnString
tDef2.SourceTableName = strTable
tDef2.Name = strTable
CurrentDb.TableDefs.Append tDef2
End If
Next
End Sub
If you don't fully understand the second piece of code I posted, I urge you to backup your mdb prior to run it because it will delete objects which can cause serious issues.

Searching for Field in ODBC Machine Data Sources - MS Access

Yesterday I had to run a query in MS Access 2010. One field I needed was not in the tables I usually use (already linked through the ODBC Database) and I didn't know what table it was a part (there are several hundred tables in the Machine Data Sources). Aside from manually importing all the tables and looking in each one for this field is there a way I can search for a field without knowing the table either 1. without importing any tables from the ODBC Databases, or if not 2. importing a handful of possible tables and searching once those tables have been linked into my active MS Access 2010 session?
Install Access Dependency Checker, link all tables and search for column name (enable checkbox for search in linked tables)
You could do this in a Function using ADO schema's.
Try this function in a standard module:
Function ListTablesContainingField(SelectFieldName) As String
Dim cn As New ADODB.Connection
Dim rs As ADODB.Recordset
Dim strTempList As String
Set cn = CurrentProject.Connection
'Get names of all tables that have a column called <SelectFieldName>
Set rs = cn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, Empty, SelectFieldName))
'List the tables that have been selected
While Not rs.EOF
'Exclude MS system tables
If Left(rs!Table_Name, 4) <> "MSys" Then
strTempList = strTempList & "," & rs!Table_Name
End If
rs.MoveNext
Wend
ListTablesContainingField = Mid(strTempList, 2)
rs.Close
Set cn = Nothing
End Function

ASP DataConnectivity Error

I am a Student and ASP is my subject this year. I am trying to do the Database Connectivity for the First time. It gave me this Error while i was connecting my ASP file with MSAccess.
Code:
<%
Dim objConn, strConn, objRS
Set objConn = Server.CreateObject("ADODB.Connection")
strConn = "PROVIDER=Microsoft.ACE.OLEDB.12.0;DATA SOURCE =" & _
"C:\demo.accdb"
objConn.Open strConn
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.Open "Student", objConn, 2, 2
objRS.AddNew
objRS("idnum") = Request.Form("idnum")
objRS("firstname") = Request.Form("firstname")
objRS("lastname") = Request.Form("lastname")
objRS.Update
objRS.close
%>
**The Above code Gives the Following Error:*
ADODB.Recordset error '800a0cc1'
Item cannot be found in the collection corresponding to the requested name or ordinal.
/MyWeb/choice1.asp, line 12*
.. I also tried doing this..
..
..
Dim objConn, strConn, objRS
Set objConn = Server.CreateObject("ADODB.Connection")
strConn = "DSN=Stud"
objConn.Open strConn
and it gives me the same error.
My Database name is demo.accdb
My Table name is Student.
ApplicationPool Settings for IIS is set to "true" for using Windows 32bit.
I have also installed OLEDB ACE 12.
Please help as am totally in mess.. All I want is to insert a record in an Access Database.
Help would be appreciated.
That error has nothing to do with your connection, setup, IIS settings, or anything esoteric, and everything to do with what columns exist (or rather, don't exist) in the recordset you're opening.
What is in line 12 of your code? (In the snippet you've posted, line 12 is the "lastname" field, but I don't know if that's true for your actual code.) Check the setup of the Student table: did you spell that column name correctly? If the table column is LastN, then your code should have objRS("LastN") = Request.Form("LastName")1, not objRS("LastName").... Thankfully, neither VBScript nor SQL are case-sensitive, so you don't need to be anal, but you do need to spell things correctly.
Note that it may help you "see" what you're doing better if you write an explicit SELECT statement to return just the columns (and rows) you want, instead of opening the entire table. Also, when you're working with actual databases (which tend to have many thousands or even millions of records, rather than the half a dozen you probably have in your test database), opening entire tables is A Very Bad Idea. Well, unless you like timeout errors.
objRS.Open "SELECT TOP 0 id, firstname, lastname FROM Student", objConn, 2, 2
(Since all you're doing is adding a row, you don't actually need to return any records; hence the TOP 0.2)
1 All you "OMG! Your code is vulnerable to SQL injection!!1!" types can insert your customary rant here.
2 It's been a while since I've worked with Access; if it chokes on TOP 0 with no ORDER BY clause, try SELECT ... WHERE 1 = 2.

Resources