views:

1388

answers:

6

Hey I just sort of learned how to put my SQL statements into VBA (or atleast write them out), but I have no idea how to get the data returned?

I have a couple forms (chart forms) based on queries that i run pretty regular parameters against, just altering timeframe (like top 10 sales for the month kinda of thing). Then I have procedures that automatically transport the chart object into a powerpoint presentation. So I have all these queries pre-built (like 63), and the chart forms to match (uh, yeah....63...i know this is bad), and then all these things set up on "open/close" events triggering the next (its like my very best attempt at being a hack....or dominos; whichever you prefer).

So I was trying to learn how to use SQL statements in VBA, so that eventually I can do all this in there (I may still need to keep all those chart forms but I don't know because I obviously lack understanding).

So aside from the question that I asked at the top, can anyone offer advice? thanks

+3  A: 

It's a bit dated, so you might want to grab a book on the subject. But, here's a ton of access resources and some tutorials and examples as well. But, basically ...

Dim dbs As Database
Dim rs As Recordset
Dim strSQL As String
Set dbs = CurrentDb

strSQL = 'your query here

Set rs = dbs.OpenRecordset(strSQL)

If Not (rs.EOF And rs.BOF) Then
  rs.MoveFirst
  'get results using rs.Fields()
Else

'Use results

Per comment: Take a look at the recordset class. It contains a collection called Fields that are the columns that are returned from your query. Without knowing your schema, it's hard to say, but something like ...

rs.MoveFirst
Do While Not rs.EOF
   'do something like rs("SomeFieldName") 
   rs.MoveNext
Loop

Like I said, your best bet is to grab a book on this subject, they have tons of examples.

JP Alioto
not all cursor types will return the RecordCount. It's safer to do NOT .EOF ...
Mitch Wheat
thanks and thanks for the sites!
Justin
The code example is DAO and all recordset types return a recordcount of at least 1 if there are records, so there is no need to check EOF and BOF, as checking that RecordCount<>0 is sufficient. If you're porting your code to some other data interface, your point is relevant, but it is certainly completely off base in the present code example, which is quite clearly DAO.
David-W-Fenton
the 'get results using re.fields()...how does that work?
Justin
i will JP, but i understand the surface (as with everything). thanks again man!
Justin
You're welcome, glad to help!
JP Alioto
If you have an accurate recordcount then it will be more efficient to use it rather than test EOF on every iteration. Just how many milliseconds of efficiency could be involved you would have to test for ;)
onedaywhen
+1  A: 

Although convoluted, your way is easier than trying to build the SQL statements in VBA. Remember, saving the queries individually allows you to manipulate them visually. Also, there are some performance benefits.

A better understanding of SQL would help you consolidate and simplify your existing queries (if they even need to be simplified. It sounds like they are doing a lot of work, so you may need all 64 queries anyway).

That said, it's pretty easy to execute SQL queries in code:

Dim strSQL as String
strSQL = "UPDATE Table MyTable SET fieldname = 1 WHERE fieldname = 0;"
DoCmd.RunSQL strSQL
Robert Harvey
thanks. the only thing that i am nervous/confused about is all these objects in this thing, and when I need to dump it to the front end/mde. This will be thie first time I am ever doing that actually because my limited experience with access has always been simply kept in a back end state
Justin
It's easy enough to separate the tables from everything else when the time comes, using the Database Splitter in Tools/Database Utilities.
Robert Harvey
+2  A: 

Use a parameterized querydef and invoke it from vba.
The query is easier to design...easily testable..and easily accessible from VBA or a form.

dim qd as querydef
set qd = currentdb.querydefs!myquerydef

qd.parameters!parm1=val1

....

either qd.execute

or

dim rs as recordset
set rs = qd.openrecordset()

YMMV ...

CMB
thanks. i will try
Justin
BTW... this uses the DAO Libraries.Insure that in 'References', you've selected DAO higher than ADO.
CMB
One limitation is that you can't parameterize the N in a TOP N SQL statement.
David-W-Fenton
+2  A: 

Here's a function that you might consider refactoring to take in a string, and you'll be able to re-use anywhere in your code.

So have a CONST or build a string for your SQL statement, and pop in your SANITIZED, NON SQL INJECTED string as an argument :)

i.e.

strSQL = "SELECT * FROM Customer WHERE ID = " & EnsureParamIsNotSQLInjection(customerID)

... then call the function/sub from anywhere you need to get a recordset/data/execute a statement. Consider creating a handful of data-access functions/subs where you can simply run an UPDATE statement, or retrieve a single value, or retreive a full blown recordset.

The key here is to have these functions all living in one place and reuse them all over the place. Here's a sample in VBScript.

Sub DoStuff(strSQL)

   Set adoCon = Server.CreateObject("ADODB.Connection")


    strConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("db\Database.mdb") 
    'strConnString = "DRIVER={Microsoft Access Driver (*.mdb)}; DBQ=" & Server.MapPath("db\Database.mdb")

   adoCon.Open strConnString

   Set rsMain = Server.CreateObject("ADODB.Recordset")

   rsMain.Open strSQL, adoCon

   Do While NOT rsMain.EOF
      customerName = rsMain("CustomerName") 'silly example
      RsMain.MoveNext
   Loop

   rsMain.Close

   Set adoCon = Nothing
End Sub
p.campbell
yeah that sounds cool. not a bad idea. i know how to call a public function into a form's sub (event), but what about functions...do they work the same way?
Justin
onedaywhen
...and I'm not sure what this 'Server' object is you are using to serve up ADO objects (more usual to use the local machine's registry) and resolve the path to your database file. Consider most folk here won't have the Server object referenced and will only get 'Object required' errors on these lines.
onedaywhen
@oneday: Thanks for the comments. It's just a snippet, really. People should feel free to modify code they find on StackOverflow as they feel might fit their situation.
p.campbell
+1  A: 

Another way to do this that it seems no one has mentioned is to bind your graph to a single saved QueryDef and then at runtime, rewrite the QueryDef. Now, I don't recommend altering saved QueryDefs for most contexts, because it causes front-end bloat and is usually not even necessary (most contexts where you use a saved QueryDef can be filtered in one way or other in the context in which they are used, e.g., as a form's Recordsource, you just pass one argument in the DoCmd.OpenForm).

Graphs are different because the SQL driving the graphs can't be altered at runtime.

Some have suggested parameters, but opening a form with a graph on it that uses a SQL string with parameters is going to pop the default parameter dialogs. One way to avoid that is to use a dialog form to collect the criteria and then set the references to the controls on the dialog form as parameters, e.g.:

PARAMETERS [Forms]![MyForm]![ID] Long;

If you're using form references, it's crucial that you do this, because from Access 2002 on, the Jet Expression Service doesn't always correctly process these when the controls are Null. Defining them as parameters rectifies that problem (which was not present before Access XP).

One situation in which you must rewrite the QueryDef for a graph is if you want to allow the user to choose the N in a TOP N SQL statement. In other words, if you want them to be able to choose TOP 5 or TOP 10 or TOP 20, you will have to alter the saved QueryDef, as the N can't be parameterized.

David-W-Fenton
+1  A: 

"if you want to allow the user to choose the N in a TOP N SQL statement" -- well, you could use a correlated subquery (instead of dynamic SQL) e.g. (ANSI-92 Query Mode syntax):

CREATE PROCEDURE GetOrdersTopN
(
 :N INTEGER
)
AS 
SELECT O1.OrderDate, O1.CustomerID
  FROM Orders AS O1
 WHERE :N >= (SELECT COUNT(*) + 1
               FROM Orders AS O2
              WHERE O1.OrderDate < O2.OrderDate);

...but last time I checked the Access engine wasn't well optimized (to put it mildly) for this kind of construct.

onedaywhen
thanks! not sure I am quite there yet, but this is how I learn!
Justin