VBA function in Excel ADODB query - vba

I'm opening an ADODB connection in Excel 2007 to query one of the worksheets of the current workbook. When trying to add a custom VBA function, an error raises "Undefined function name". The connection:
Dim connection As String
Dim records As ADODB.Recordset
Dim query As String
Dim fileName As String
fileName = ThisWorkbook.FullName
connection = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & fileName & ";Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
query = "select t.[Col1] from [Sheet1$] As t"
Set records = New ADODB.Recordset
records.Open query, connection
Sheets(2).Range("A1").CopyFromRecordset records
What I would like to achieve is to have another column in the select, like
query = "select t.[Col1], myFunc() from [Sheet1$] As t"
where myFunc is a Function defined in the workbook.
I know that something like this is possible in Access (to have custom VBA functions in a query). Is this possible in Excel too?
What's the best practice or workaround for this scenario?

I think you need some basic explanations here, and maybe an answer to this question:
Where do the functions in SQL come from?
If you're sending queries to any database server that supports ANSI-Standard SQL, the database engine will parse and run 'inline' functions that are native to SQL: LEFT(), SUBSTRING(), SIN(), SQR(), etc. There's a short list here:
http://www.smallsql.de/doc/sql-functions/index.html
Oracle servers will expose additional functions that extend ANSI SQL, as will Microsoft SQL Servers; both PL-SQL and T-SQL have functions that are not available in standard SQL. Both of them allow the DB Owner to create their own functions, which reside on the server and are also available to SQL queries.
Microsoft Jet SQL, which isn't quite the same as ANSI SQL, has a rather limited set of native functions; but nobody minds when they are running Jet SQL in an MS-Access environment, because almost all of the Visual basic for Applications functions are made available to the SQL engine by MS-Access.
Furthermore, all of the globally-declared VBA functions that they've written and made visible in the local MS-Access project are made available to the SQL engine, too.
As long as you're running the query from Microsoft Access.
That's the easy part...
Once you move outside the MS-Access environment, you can't see the VBA.
You can query the data using Jet SQL (and the Microsoft.ACE.OLEDB.12.0 provider is doing exactly that) but, if you're running it from Excel, you're not going to enjoy the MS-Access database engine's ability to use VBA: you've got the native Jet SQL function list, and nothing else
The functions are listed here, and very few other places:
MS Access: Functions - Listed by Category
That list now includes IIF() - the inline 'IF' function - which is all you've got in Jet SQL if you want a CASE statement in your SELECT clause; you'll find that useful if your first lesson in native Jet-SQL is that all the VBA NZ() functions in your query have stopped working.
Many of these functions look like the familiar VBA functions: and this is a source of confusion, because you're not running VBA, you're running SQL.
Very few people understand that this list of native Jet functions is not the same as the list of native VBA functions made available to SQL by MS-Access, and Microsoft do not make this explicit in their documentation.
This is a complete PITA because everyone querying a SQL server or an Oracle DB expects that the server will run all and any functions in their SQL query that are declared, imported or 'native' to the dialect of SQL running on that server. You've declared VBA functions in the Access mdb, and you expected they would be visible to SQL too!
How not to fix this:
I have seen a Sybase server where the brilliant but misguided database owner had imported functions from an external library that you have definitely used without realising that it's there in every MS-Access database: vbaen32.dll, the VBA function enumeration dll. This required quite a lot of work, and never quite worked: please do not attempt to replicate this feat of genius.
So the short answer is 'No'.
Now for the useful answer:
Recordset.GetRows() will return your recordset as a 2-Dimensional VBA array, and you can run your VBA functions on that after the SQL engine has done the heavy lifting of sorting, filtering and aggregation.
You can do this efficiently on a large data set, without an excessive memory footprint, if you run your vba sequentially in chunks on a Forward-Only cursor, calling Recordset.GetRows(Rows:=1024) until you hit the end of the data.
Although you might want to ask: "Why am I doing this?", as it's very difficult to think of a process where better analysis and design wouldn't reveal a better solution. Me, I had to implement that hack for a team whose Excel-dependent process ran on csv files that grew, and grew... And grew to terabyte sizes that could only be read by a database driver. And they work in an environment where getting a proper database server takes 2-3 years of sustained managerial effort.
If you are the fortunate son who inherited that particular process after I quit, I recommend trying my GetRows 'solution' after nuking the site from orbit.
Footnote: Do, please, expand this answer if you find a better online listing for Jet SQL functions.
Meanwhile, I would urge any other 'Stack contributor who reads this to add their own links to a good listing of Jet SQL native functions - if you can find one. Most are wrong and none are comprehensive, and very few are explicit in stating that there's a difference between native functions and imported VBA. For all I know, the Jet Engine is importing and running a restricted set of functions from VBAEN32.dll - but Jet under ADODB definitely isn't a fully-featured VBA host application, and that limitation needs to be clearly understood when you're using it outside MS-Access.

I see only 1 option here:
As the "myFunc()" function does not have any parameters you can input the function to a separate worksheet (e.g. to Sheet2 in A2, in A1 put a header like "A") and reference the cell in the SQL query like:
select t.[Col1], myFunc.A from [Sheet1$] As t, [Sheet2$] as myFunc

Related

Alternate codes for ExecuteReader

It is my first time to post here. My question is what code can we use instead of ExecuteReader? We're currently doing a total production program and we want to input the productions, Projects 1-4 (I label it P1-P4) into the text box, but i have read that ExecuteReader is only for SQL, we're only using Access DB. Our code for displaying the projects to textboxes
You apparently don't know what "SQL" is. SQL (structured query language) is not a database. It is a language that databases use to define and manipulate data. Just about all databases use SQL for this purpose, including Access.
There are a number of databases with "SQL" in the name, e.g. SQL Server, MySQL and SQLite, but there's no database named "SQL". Some people lazily refer to Microsoft's SQL Server as just SQL or maybe MS SQL but its name is SQL Server.
As for using ExecuteReader, it's for any database. If you were connecting to SQL Server then you'd use a System.Data.SqlClient.SqlCommand object and call ExecuteReader on that. As you're using Access, you need to use a System.Data.OleDb.OleDbCommand and call ExecuteReader on that. They both work the same way but the different providers are for different data sources.

Query References Another - Access to SQL Server

Good morning,
I just received a new assignment and I am struggling with finding an appropriate solution. I have searched through the SO Forums, and through Google, but have not found a workable solution. Below is my scenario:
We are working out of Microsoft Access to connect to an SQL Server Database via an ODBC Connection.
I wasgiven an incredibly large pass-through SQL query, larger than is able to be processed in MS Access. In this pass-through query, there is a subquery in a WITH...AS method.
I am hoping to be able to split this one, singularly large, SQL pass through query into two: Query One (the subquery), and Query Two (which references the results of the subquery)
I know that by using general Access queries, I can write a Macro like follows...
Sub myQuery()
' Edited from http://www.dbforums.com/showthread.php?1667831-Run-multiple-queries-in-sequence-on-click
' On Error GoTo ErrHandler
' Run the first query
MsgBox "Starting first query"
DoCmd.OpenQuery "first_Query"
DoEvents
' Run the second query
MsgBox "Done. Now starting second query"
DoCmd.OpenQuery "second_Query"
DoEvents
MsgBox "Done!"
End Sub
However, these need to be pass-through queries. I believe that the enormously large SQL String is created via a number of user inputs. Regardless, I don't have the ability to change the pass-through SQL that I was given.
Is there anyway I can write a macro that calls the first pass-through query, and then calls the second pass-through query that REFERENCES the result of the first?
Here is an example with what I am working with...
WITH queryOne AS
(
SELECT fooID
FROM tblFoo
WHERE foodate > ...
)
SELECT foo, fooone, footwo, foothree
FROM tblOtherFoo
WHERE fooID = OtherFooID
However, the query is 50000+ characters, exceeding that ~37k limit.
Please feel free to ask any questions. I am stumped by this and would appreciate any feedback or alternative resources.
Thank you!
It not clear what you mean by something that references the first or previous? Why break up something you been given that supposed works just fine?
So just place that existing t-sql you been given into a stored procedure. In T-SQL you can easy have some SQL operate on some previous SQL, but why break up such a HUGE massive monster slew of code and introduce bugs? It will take you YEARS AND YEARS to break up a KNOWN working huge T-SQL that been built and developed for you (something that long likely took a few years and a team of developers to create).
A conservative estimate would be such a routine cost $50,000, or even $100,000 to develop.
No question that the working T-SQL you been given might reference previous data, or even do selects into #Temp tables that additional T-SQL can work on.
If you ALREADY have a working PT code and query given to you?
Simply take that T-SQL query, and simply paste it into a stored procedure. You will do this in SQL Server and NOT even touch or bother with Access.
So don't create some macro in Access that calls multiple separate queries, but place all of the T-SQL in a stored procedure, and simply call that huge mess one time from Access.
It possible that the T-SQL you been given is incorrect, but assuming that the T-SQL is correct, then simply place all that long mess into a working stored procedure. You do not place this SQL in MS Access and you don’t need to have that mess inside of Access.
So get that T-SQL working in SQL Server – don’t bother with MS Access until this long query mess is working in SQL Server. ONLY THEN do you fire up Access.
So you THEN create a simple PT query in Access that calls the huge long T-SQL mess you been given. But that “mess” is to be placed in SQL Server – not in Access.
So create a PT query in Access that calls your “supposed” working T-SQL you been given. The SQL you save in the Access PT query will be this
Exec my_StupidLongSQLProc
Save the above as a PT query. Then in VBA code go:
Currentdb.QueryDefs("MyPTQuery").Execute
If you need to pass some values from Access, then go:
With CurrentDb.QueryDefs("MyPTQuery")
.sql = "exec My_StupidLongSQLproc " & p1 & "," p2
.Execute
End with
In above we pass two VBA values from Access to the big mess of sql you have – the stored procedure in above is just an example that access two parameters passed from Access VBA. If the T-SQL you been given does not require values from Access, then the first single .execute will do the job.
And if you REALLY did get such a long routine that is correct T-SQL, then it likely already has parameters in the working T-SQL (and again you don’t want to mess with or change such a huge long working T-SQL that you been given).
So you only need one line of code in Access, and your existing long T-SQL you been given if written correctly can be placed in a stored procedure (assuming that you actually been given a correctly working PT query).
So if you REALLY did get a huge massive working T-SQL statement, then simply place that KNOWN AND WORKING T-SQL in SQL Server as a stored procedure and call it with one line of code as per above.
So trying to split this up from Access will only server to cause world-wide poverty and ANY tiny miss step or breaking up of that huge long routine will cause world-wide poverty and starving children as you try and “fix” this great working T-SQL that you been given. As noted, something that long to create would take a teams of developers HUGE resources. If you touch or break up one line of code and mess it up, then you need that team of developers to spend several months trying to fix what you broke.
So the INSTANT you start breaking up such a huge long mess is the instant you lost this battle and will waste several years of your life trying to fix this crazy long T-SQL that you been given that is already claimed to be proper working code.

Generic SQL that both Access and ODBC/Oracle can understand

I have a MS Access query that is based on a linked ODBC table (Oracle).
I'm troubleshooting the poor performance of the query here: Access not properly translating TOP predicate to ODBC/Oracle SQL.
SELECT ri.*
FROM user1_road_insp AS ri
WHERE ri.insp_id = (
select
top 1 ri2.insp_id
from
user1_road_insp ri2
where
ri2.road_id = ri.road_id
and year(insp_date) between [Enter a START year:] and [Enter a END year:]
order by
ri2.insp_date desc,
ri2.length desc,
ri2.insp_id
);
The documentation says:
When you spot a problem, you can try to resolve it by changing the local query. This is often difficult to do successfully, but you may
be able to add criteria that are sent to the server, reducing the
number of rows retrieved for local processing.
In many cases you will find that, despite your best efforts, Office Access still retrieves some entire tables unnecessarily and
performs final query processing locally.
However, it's occurred to me that I don't really understand what sort of SQL I should be writing to make both Access and ODBC/Oracle happy.
Should I be writing some sort of generic SQL that Access can understand in a local query AND that can be easily translated to ODBC/Oracle SQL? Is generic SQL a real thing?
What kind of SQL does the ODBC driver use? It depends as typically MS Access has three types of external data connections that interfaces with different SQL dialects each with the ODBC API.
Linked tables that acts like local tables but are ODBC connected data sources and not stored locally. Once they are incorporated in an Access app, these tables can only use MS Access' SQL dialect. They can be joined with local or even other backend tables from other sources.
Hence, why TOP is available in MS Access and not Oracle. You are essentially using Access SQL to manipulate Oracle data. ODBC serves as the origin point of data while Access' Jet/ACE SQL engine does the processing and resultset viewing in cached memory.
Pass-through queries that do not see local tables or anything else in local app's environment. Such queries use the SQL dialect of the connected database here being Oracle.
Hence, why TOP is NOT available in Oracle and double quotes are allowed in column identifiers. Such quoting would fail in MS Access. Essentially, you are using Oracle SQL to manipulate Oracle data in an Access app. You can take the output of the sqlout.txt log and run it in a pass-through query ODBC-connected to your Oracle database.
ADO/DAO Recordsets that are run entirely via code such as VBA and are direct connections to data sources and uses the connecting database's dialect.
Here, you using Oracle SQL to manipulate Oracle data in an Access app via the ODBC API.
In each one of these types, you will have to connect to a backend ODBC data source. You do not even need to use the GUI but can use Access' object library to create linked tables (see DoCmd.TransferDatabase) and pass through querydefs (see QueryDef.Connect or .Execute).
I suspect the sqlout.txt log you see are translations of the ODBC calls to its native dialect.
To build on #Parfait's point #1:
From Microsoft Access Developer's Guide to SQL Server by Mary Chipman and Andy Baron:
Optimizing Access Queries:
There's a common misconception that the Jet engine always retrieves all the data in linked SQL Server tables and then processes the data locally. This is not usually true. Jet is perfectly capable of sending efficient queries to SQL Server over ODBC and retrieving only the rows required. However, in some cases, Jet will in fact be forced to fetch all the data in certain tables first and then process it. You should be aware of when you are forcing Jet to do this and be sure that it is justified. The following are some general guidelines to follow when creating your Access queries:
Using expressions that can't be evaluated by the server will cause Jet to retrieve all the data required to evaluate those expressions locally. The impact of using Access-specific expressions, such as domain aggregate functions, Access financial functions, or custom VBA functions will vary depending on where in your query the expressions are used. Using such an expression in the SELECT clause will usually not cause a problem because no extra data will be returned. However, if the expression is in the WHERE clause, that criterion cannot be applied on the server, and all the data evaluated by the expression will have to be returned.
With multiple criteria, as many as possible will be processed on the server. This means that even if you use criteria that you know include functions that will need to be processed by Jet, adding other criteria that can be handled by the server will reduce the number of records that Jet has to process. Adding criteria on indexed columns is especially helpful.
Query syntax that includes an Access-specific extension to SQL, not supported by the ODBC driver, may force processing to be done on the client by Access. For example, even though SELECT TOP 5 PERCENT is now supported by SQL Server, it is not supported by the ODBC driver. If you use that syntax in an Access query, Jet will need to retrieve all the records and calculate which ones are in the top 5 percent. On the other hand, even though crosstab queries are specific to Access, Jet will translate them into simple GROUP BY queries and fetch just the required data in one trip to the server unless problematic criteria is used.
Heterogeneous joins between local and remote tables or between remote tables that are in different data sources will, of course, have to be processed by Jet after the source data is retrieved. However, if the remote join field is indexed and the table is large, Jet will often use the index to retrieve only the required rows by making multiple calls to the remote table, one fore each row required.
Jet allows you to mix data types within [typo - fix later] of UNION queries and within expressions, but SQL server doesn't. Such mixing of data types will force processing to be done locally.
Multiple outer joins in one query will be processed locally.
The most important factor is reducing the total number of records being fetched. Jet will retrieve multiple batches of records in the background until the result set is complete, so even though you may seem to get results back immediately, a continuing load is being placed on the server for large result sets.
Note: this book is quite old (published in 2000) and is in reference to Jet Engine. I imagine things might be slightly different in newer versions of Access which use ACE, although I don't have a source to back this up.

Join (INNER JOIN) local Excel table in Oracle SQL Query - VBA

I'm having trouble performing a query on a remote Oracle SQL Server via Excel VBA while trying to perform an INNER JOIN in the same query with a local table in a Excel file sheet.
Example:
Excel Sheet with local table ["LTE_Cells$LTE_Cells_Tmp"]:
Sheet "LTE_Cells"
Oracle SQL Query
SELECT a.STARTDATE, a.ENODEB, a.EUTRANCELLFDD, (a.COUNTER_1/8/1024)+
a.COUNTER2/8/1024) AS Total_Total_Traffic_TB FROM »»»LOCAL_EXCEL_TABLE««««
INNER JOIN REMOTE_DATABASE.LTE_KPI_1 a ON
((»»»LOCAL_EXCEL_TABLE««««.EUTRANCELLFDD =
REMOTE_DATABASE.LTE_KPI_1.EUTRANCELLFDD) AND
(»»»LOCAL_EXCEL_TABLE««««.ENODEB = REMOTE_DATABASE.LTE_KPI_1.ENODEB)) WHERE
(((REMOTE_DATABASE.LTE_KPI_1.STARTDATE)>=sysdate-3));`
Thanks in advance for the help!
This doesn't answer your question directly, and I may be all wet on this but I don't know that either Excel or Oracle explicitly handles what you are trying to accomplish.
However, MS Access will out of the box. Short answer: I think you are using the wrong tool for this task. You are using the proverbial hammer to saw a board in half. Link the spreadsheet and the Oracle table as linked objects in Excel, and your query should be easy-peazy.
Longer answer: while Access does this simply and easily, it can and probably will leave a path of destruction behind it on the DBMS. Specifically, you can expect to thrash the shared pool in Oracle, as Access will be issuing one query (using literals, no less) for every line in Excel. For 1,000 lines, it probably doesn't matter that much, but if you're going to do this on really large datasets, you will make a fast enemy out of your DBA.
Extended answer: really, the best way to do this is to load the contents of those Excel spreadsheets in Oracle tables and let the DBMS do the heavy lifting. This is bread and butter for the RDBMS.

Field types available for use with "CREATE TABLE" in Microsoft Access

I have the displeasure of generating table creation scripts for Microsoft Access. I have not yet found any documentation describing what the syntax is for the various types. I have found the documentation for the Create Table statement in Access but there is little mention of the types that can be used. For example:
CREATE TABLE Foo (MyIdField *FIELDTYPE*)
Where FIELDTYPE is one of...? Through trial and error I've found a few like INTEGER, BYTE, TEXT, SINGLE but I would really like to find a page that documents all to make sure I'm using the right ones.
I've found the table in the link below pretty useful:
http://allenbrowne.com/ser-49.html
It lists what Access's Gui calls each data type, the DDL name, DAO name and ADO name (they are all different...).
Some of the best documentation from Microsoft on the topic of SQL Data Definition Language (SQL DDL) for ACE/Jet can be found here:
Intermediate Microsoft Jet SQL for Access 2000
Of particular interest are the synonyms, which are important for writing portable SQL code.
One thing to note is that the Jet 4.0 version of the SQL DDL syntax requires the interface to be in ANSI-92 Query Mode; the article refers to ADO because ADO always uses ANSI-92 Query Mode. The default option for the MS Access interface is ANSI-89 Query Mode, however from Access2003 onwards the UI can be put into ANSI-92 Query Mode. All versions of DAO use ANSI-89 Query Mode. I'm not sure whether SQL DDL syntax was extended for ACE for Access2007.
For more details about query modes, see
About ANSI SQL query mode (MDB)
This has it all. It's direct from MS, and actually tells you what the SQL datatype is that correlates to the GUI name.