I'm trying to automate the creation of queries in my access database using VBA. I currently have a table with over 1000 entries with four columns (id, name, last name & age) and i want it to automatically create queries for each number found in the "age" column. For example, if in the list of 1000 there are only 10 people that are 40 years old, then i want it to create a query named "Age 40" and only shown the name and last name of those 10 people and do the same for all the other ages.
New to access vba so i dont know where to start.
Firstly, you should not be storing the age in a table - it should be calculated as needed (what happens tomorrow - chances are some of the people will have a birthday)
Anyway, consider using a recordset to get a list of the unique ages, and then using that to create each query. Something like:
Sub sAgeQuery1()
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsSteer As DAO.Recordset
Dim qdf As DAO.QueryDef
Set db = CurrentDb
Set rsSteer = db.OpenRecordset("SELECT DISTINCT Age FROM tblAge ORDER BY Age ASC;")
If Not (rsSteer.BOF And rsSteer.EOF) Then
Do
Set qdf = db.CreateQueryDef("qryAge" & rsSteer!Age, "SELECT * FROM tblAge WHERE Age=" & rsSteer!Age & " ORDER BY LastName, [Name];")
rsSteer.MoveNext
Loop Until rsSteer.EOF
db.QueryDefs.Refresh
End If
sExit:
On Error Resume Next
Set qdf = Nothing
rsSteer.Close
Set rsSteer = Nothing
Set db = Nothing
Exit Sub
E_Handle:
Select Case Err.Number
Case 3012 ' query already exists
Resume Next
Case Else
MsgBox Err.Description & vbCrLf & vbCrLf & "sAgeQuery1", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Select
End Sub
However, this is probably not the way to do it - you have not said why you think you need to create all of these queries. Possibly a better way would be to have just one query, and alter the SQL in the recordset's loop before doing something with the query:
Sub sAgeQuery2()
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsSteer As DAO.Recordset
Dim qdf As DAO.QueryDef
Set db = CurrentDb
Set rsSteer = db.OpenRecordset("SELECT DISTINCT Age FROM tblAge ORDER By Age ASC;")
If Not (rsSteer.BOF And rsSteer.EOF) Then
Set qdf = db.CreateQueryDef("qryAge")
Do
qdf.SQL = "SELECT * FROM tblAge WHERE Age=" & rsSteer!Age
' do something with this query (export as an excel/csv file perhaps)
rsSteer.MoveNext
Loop Until rsSteer.EOF
End If
sExit:
On Error Resume Next
rsSteer.Close
Set rsSteer = Nothing
DoCmd.DeleteObject acQuery, "qryAge"
Set qdf = Nothing
Set db = Nothing
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "sAgeQuery2", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
Related
I have some code that I copied and modified from Export Query in VBA loop to select data based on String Value
The code works although the problem is that when it runs it creates a query in the database which is then deleted at the end. If the code breaks half way through, this query is still in the database. So when the code is run again it gives an error message saying it can't create the query as it already exists.
The query that is created within the database is named "Select * from SalesData"
The objective is that I have a query called "SalesData" which includes sales information for a number of countries. I want to export all the data for each country into an Excel file in a loop without creating any additional Access objects. Is it possible to just filter the existing query within the VBA without creating the temporary object?
Can anyone suggest any modifications to the below code to achieve this?
Sub TEST()
Dim db As DAO.Database
Set db = CurrentDb()
Dim rs1 As DAO.Recordset
Set rs1 = db.OpenRecordset("Select Distinct Country From SalesData")
Dim v As String
Dim strQry As String
Dim qdfTemp As DAO.QueryDef
Dim strQDF As String
strQDF = "select * from SalesData"
Do While Not rs1.EOF
v = rs1.Fields(0).Value
strQry = "SELECT * FROM SalesData WHERE Country = '" & v & "'"
Set qdfTemp = CurrentDb.CreateQueryDef(strQDF, strQry)
qdfTemp.Close
Set qdfTemp = Nothing
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, _
strQDF, "C:\Users\me\Desktop\VBA_TEST\" & v & ".xlsx", True
CurrentDb.QueryDefs.Delete strQDF
rs1.MoveNext
Loop
rs1.Close
End Sub
As far as I'm aware, it would not be possible to use the TransferSpreadsheet method to extract a parameterised version of your SalesData query without either modifying the SQL of the SalesData query itself or using an additional query with selection criteria applied to the data returned by SalesData.
However, you needn't delete & recreate such query with every iteration of the loop - instead, simply modify the SQL property of the query, e.g.:
Sub test()
Dim qry As String: qry = "salesdata_temp"
Dim sql As String: sql = "select * from salesdata where country = '{0}'"
Dim out As String: out = "C:\Users\me\Desktop\VBA_TEST\"
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
On Error Resume Next
DoCmd.DeleteObject acQuery, qry
On Error GoTo error_handler
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef(qry, sql)
With dbs.OpenRecordset("select distinct country from salesdata")
If Not .EOF Then
.MoveFirst
Do Until .EOF
qdf.sql = Replace(sql, "{0}", !country)
DoCmd.TransferSpreadsheet acExport, , qry, out & !country & ".xlsx", True
.MoveNext
Loop
End If
.Close
End With
exit_sub:
On Error Resume Next
DoCmd.DeleteObject acQuery, qry
Exit Sub
error_handler:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbExclamation + vbOKOnly, "Error"
Resume exit_sub
End Sub
Thanks to the input here, it seems that the only way to do it is to manipulate an existing query in the database or to create a query in the VBA script and then delete it at the end.
See below for an example of the first approach, the code uses a query already in the database called "blankquery".
Sub TEST()
Dim db As DAO.Database
Set db = CurrentDb()
Dim rs1 As DAO.Recordset
Set rs1 = db.OpenRecordset("Select Distinct Country From SalesData")
Dim qdfTemp As DAO.QueryDef
Dim v As String
Dim strQry As String
Dim strQDF As String
strQDF = "blankquery"
Do While Not rs1.EOF
v = rs1.Fields(0).Value
strQry = "SELECT * FROM SalesData WHERE Country = '" & v & "'"
db.QueryDefs(strQDF).sql = strQry
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, _
strQDF, "C:\Users\me\Desktop\VBA_TEST\" & v & ".xlsx", True
rs1.MoveNext
Loop
rs1.Close
End Sub
I have an Access 2016 database which use a form to select a time interval of 1 or more days.
A button let me to get the begin and end dates of the interval and do the follow 2 things:
a) build a query that, based on the dates, extracts a dataset from a table
b) open a pop-up form that show the dataset extracted by the query. There is no code on OpenForm event.
The magic is that everything works like a charm until I disable the Shift Bypass Key with the command
CurrentDb.Properties("AllowBypassKey") = False
After that the query still works well, but when the code try to open the form, 95% of the times, I get the error '2501 The OpenForm action was canceled', even if it worked well with Access 2013.
The code is quite simple, but after 3 days of hard work I still don't understand what is wrong. The only thing I got is that if I don't execute the CreateQueryDef instruction the error goes away and the form opens regoularly (even if it does not show the right dataset).
Therefore both the routine works alone, but they conflict if they run one after the other.
Below the code behind the button:
Private Sub Cmd_Meteo_Click()
On Error GoTo Err
Dim strFrmName As String
Dim datBegin As Date
Dim datEnd As Date
'Set the time interval
datBegin = Me.Txt_BeginTreatment 'Set the begin of the interval
datEnd = Me.Txt_Data 'Set tha end of the interval
'Build the query with meteo data
Call GetMetoData(Me.Txt_Region, Me.Cmb_MeteoStation, datBegin, datEnd, False)
'Set the form name
strFrmName = "Frm_DatiMeteoControllo"
'Check if the form is already open
If CurrentProject.AllForms(strFrmName).IsLoaded Then 'If the form is already open
DoCmd.Close acForm, strFrmName 'Close the form
End If
DoCmd.OpenForm strFrmName 'This line rise the 2501 error!
Exit_sub:
Exit Sub
Err:
MsgBox Err.Number & " " & Err.Description
Resume Exit_sub
End Sub
and the subroutine that build the query:
Public Sub GetMetoData(strRegion As String, intIdSM As Integer, datBegin As Date, datEnd As Date, bolTot As Boolean)
On Error GoTo Err
Dim db As DAO.Database
Dim strDbName As String
Dim qdf As DAO.QueryDef
Dim strSqlMeteo As String
Dim strLinkName As String
Dim strQryName As String
Set db = CurrentDb 'Set the db
strDbName = Application.CurrentProject.Name 'Get the db name
strTblName = GetMeteoTableName(strRegion, intIdSM) 'Get the name of the data table
strLinkName = "Tbl_DatiMeteo" 'Set the name of the linked table
strQryName = "TmpQry_DatiMeteoControllo" 'Set th name of the query
'SQL statement for the query
strSqlMeteo = "SELECT " & strLinkName & ".Data, ([" & strLinkName & "].[Precipitazione]) AS PrecTot, " & _
strLinkName & ".Tmin, " & strLinkName & ".Tmean, " & strLinkName & ".Tmax" & vbCrLf & _
"FROM " & strLinkName & vbCrLf & _
"WHERE (((" & strLinkName & ".Data) Between #" & Format(datBegin, "mm/dd/yyyy") & "# And #" & Format(datEnd, "mm/dd/yyyy") & "#));"
'Delete the previous query
If QueryEsiste(strDbName, strQryName) Then 'If the query already exist...
DoCmd.DeleteObject acQuery, strQryName 'delete the query.
End If
'Make the new query
Set qdf = db.CreateQueryDef(strQryName, strSqlMeteo)
Exit_sub:
qdf.Close
Set qdf = Nothing
db.Close
Set db = Nothing
Exit Sub
Err:
MsgBox Error$
Resume Exit_sub
End Sub
Does anyone has a hint or faced the same problem?
There should be no reason to delete the query:
If QueryEsiste(strDbName, strQryName) Then
' Modify the previous query.
Set qdf = db.QueryDef(strQryName)
qdf.SQL = strSqlMeteo
Else
' Create the new query.
Set qdf = db.CreateQueryDef(strQryName, strSqlMeteo)
End If
In Access 2007 I need to select all the short-text fields in a table.
VBA code should look like this:
Dim strClient As String
Set dbs = CurrentDb()
Debug.Print Me.ID
strClient = "Select * from ANG_CLIENTS where DATA_TYPE='TEXT' AND ID=" & Me.ID
Set rs = dbs.OpenRecordset(strClient)
I get "Runtime error 3061. Too few parameters. Expected 1" on the last assignment.
You need to define a custom function to loop through the recordset fields and extract the names of text fields only.
The names can then be added to your SQL script.
Public Function TextDataFileds(rs As DAO.Recordset) As String
Dim fld As DAO.Field, item As String
For Each fld In rs.Fields
If fld.Type = 10 Then 'dbText
item = IIf(Len(item) = 0, fld.Name, item & ", " & fld.Name)
End If
Next fld
TextDataFileds = item
End Function
You can then call it like this:
Sub Test()
On Error GoTo ErrProc
Dim rs As DAO.Recordset
Set rs = CurrentDb().OpenRecordset("SELECT TOP 1 * FROM ANG_CLIENTS;")
Dim sql_ As String
sql_ = "SELECT " & TextDataFileds(rs) & " FROM ANG_CLIENTS WHERE ID=" & Me!ID
rs.Close
Set rs = Nothing
Set rs = CurrentDb().OpenRecordset(sql_)
'....
Leave:
rs.Close
Set rs = Nothing
On Error GoTo 0
Exit Sub
ErrProc:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub
I want to log web site visits' IP, datetime, client and refferer data to access database but I'm planning to log every days log data in separate tables in example logs for 06.06.2010 will be logged in 2010_06_06 named table. When date is changed I'll create a table named 2010_06_07. But the problem is if this table is already created.
Any suggestions how to check if table exists in Access?
You can use the hidden system table MSysObjects to check if a table exists:
If Not IsNull(DlookUp("Name","MSysObjects","Name='TableName'")) Then
'Table Exists
However, I agree that it is a very bad idea to create a new table every day.
EDIT: I should add that tables have a type 1, 4 or 6 and it is possible for other objects of a different type to have the same name as a table, so it would be better to say:
If Not IsNull(DlookUp("Name","MSysObjects","Name='TableName' And Type In (1,4,6)")) Then
'Table Exists
However, it is not possible to create a table with the same name as a query, so if you need a look up to test for a name, it may be best to add 5, that is query, to the Type list.
Here's another solution, will be a bit faster than looping over all of the tables.
Public Function doesTableExist(strTableName As String) As Boolean
Dim db As DAO.Database
Dim td As DAO.TableDef
Set db = CurrentDb
On Error Resume Next
Set td = db.TableDefs(strTableName)
doesTableExist = (Err.Number = 0)
Err.Clear
End Function
I tested various methods for finding out if a table exists several years ago. Here is the code for all of them as I implemented, including my simple test routine.
Public Function TableExists(strTableName As String, Optional ysnRefresh As Boolean, Optional db As DAO.Database) As Boolean
' Originally Based on Tony Toews function in TempTables.MDB, http://www.granite.ab.ca/access/temptables.htm
' Based on testing, when passed an existing database variable, this is the fastest
On Error GoTo errHandler
Dim tdf As DAO.TableDef
If db Is Nothing Then Set db = CurrentDb()
If ysnRefresh Then db.TableDefs.Refresh
Set tdf = db(strTableName)
TableExists = True
exitRoutine:
Set tdf = Nothing
Exit Function
errHandler:
Select Case Err.Number
Case 3265
TableExists = False
Case Else
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Error in mdlBackup.TableExists()"
End Select
Resume exitRoutine
End Function
Public Function TableExists2(strTableName As String, Optional ysnRefresh As Boolean, Optional db As DAO.Database) As Boolean
On Error GoTo errHandler
Dim bolCleanupDB As Boolean
Dim tdf As DAO.TableDef
If db Is Nothing Then
Set db = CurrentDb()
bolCleanupDB = True
End If
If ysnRefresh Then db.TableDefs.Refresh
For Each tdf In db.TableDefs
If tdf.name = strTableName Then
TableExists2 = True
Exit For
End If
Next tdf
exitRoutine:
Set tdf = Nothing
If bolCleanupDB Then
Set db = Nothing
End If
Exit Function
errHandler:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Error in mdlBackup.TableExists1()"
Resume exitRoutine
End Function
Public Function TableExists3(strTableName As String, _
Optional db As DAO.Database) As Boolean
' Based on testing, when NOT passed an existing database variable, this is the fastest
On Error GoTo errHandler
Dim strSQL As String
Dim rs As DAO.Recordset
If db Is Nothing Then Set db = CurrentDb()
strSQL = "SELECT MSysObjects.Name FROM MSysObjects "
strSQL = strSQL & "WHERE MSysObjects.Name=" & Chr(34) & strTableName & Chr(34)
strSQL = strSQL & " AND MSysObjects.Type=6;"
Set rs = db.OpenRecordset(strSQL)
TableExists3 = (rs.RecordCount <> 0)
exitRoutine:
If Not (rs Is Nothing) Then
rs.Close
Set rs = Nothing
End If
Exit Function
errHandler:
MsgBox Err.Number & ": " & Err.Description, vbCritical, _
"Error in TableExists1()"
Resume exitRoutine
End Function
Public Sub TestTableExists(strTableName As String, intLoopCount As Integer)
Dim dteStart As Date
Dim i As Integer
Dim bolResults As Boolean
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists(strTableName, , CurrentDB())
Next i
Debug.Print "TableExists (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists2(strTableName, , CurrentDB())
Next i
Debug.Print "TableExists2 (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists3(strTableName, CurrentDB())
Next i
Debug.Print "TableExists3 (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
End Sub
I have found querying system tables or tabledefs to be unreliable and introduce unpredictable behaviour in scripts where tables get regularly created and dropped.
Based on my results, my hypothesis is that these tables aren't necessarily updated at the exact instant a CREATE or DROP is executed, or that concurrency issues are preventing me from getting an accurate result.
I've found the following method to be more reliable:
Public Function TableExists(theDatabase As Access.Application, _
tableName As String) As Boolean
' Presume that table does not exist.
TableExists = False
' Define iterator to query the object model.
Dim iTable As Integer
' Loop through object catalogue and compare with search term.
For iTable = 0 To theDatabase.CurrentData.AllTables.Count - 1
If theDatabase.CurrentData.AllTables(iTable).Name = tableName Then
TableExists = True
Exit Function
End If
Next iTable
End Function
There should be no runtime issue iterating unless there is an staggeringly enormous collection of tables.
This question is quite old but I found that no answer is satisfying, because:
they do not handle the case of "bad" linked tables, where the linked table points to a non existing db or table.
since linked tables are potentially huge, we must be able to check them with a fast query.
So here is my simple but more complete solution:
Function isTableOk(tblName As String) As Boolean
'works with local or linked tables
Dim db As DAO.Database, rs As DAO.Recordset
Dim sSql As String
sSql = "SELECT TOP 1 ""xxx"" AS Expr1 FROM [" & tblName & "]"
On Error Resume Next
Err.Clear
Set db = CurrentDb
Set rs = db.OpenRecordset(sSql)
isTableOk = (Err.Number = 0)
rs.Close
End Function
You can even check table in an external Access db with this version:
Function isTableOk(tblName As String, Optional dbName As String) As Boolean
'works with local or linked tables, or even tables in external db (if dbName is provided)
Dim db As DAO.Database, rs As DAO.Recordset
Dim sSql As String
Set db = CurrentDb
sSql = "SELECT TOP 1 'xxx' AS Expr1 FROM [" & tblName & "]"
If Len(dbName) > 0 Then 'external db
sSql = sSql & " IN '" & dbName & "'"
End If
Err.Clear
On Error Resume Next
Set rs = db.OpenRecordset(sSql)
isTableOk = (Err.Number = 0)
rs.Close
End Function
I'm trying to delete all the linked tables in my front end, but it fails on the first table with the error: 3125 "'attendeesSearch' is not a valid name. Make sure that it does not include invalid characters or punctuation and that it is not too long.".
This table contains a multi-valued field. I include this because it may be an issue.
I've tried using "Drop table..." SQL and DoCmd.DeleteObject method.
My routine:
Sub relinkRequestHelp()
Const csConnnectPrefix As String = ";DATABASE="
Dim bLenConnectPrefix As Byte
Dim db As dao.Database
Dim tbl As dao.TableDef
Dim s As String
On Error GoTo eh
bLenConnectPrefix = Len(csConnnectPrefix)
Set db = CurrentDb()
For Each tbl In db.TableDefs
If Left(tbl.Connect, bLenConnectPrefix) = csConnnectPrefix Then
' s = "DROP TABLE " & tbl.Name & ";"
' Debug.Print s
' db.Execute s, dbFailOnError
' Debug.Print , db.RecordsAffected
DoCmd.DeleteObject acTable, tbl.Name
End If
Next
Exit Sub
eh: Debug.Print Err.Number, Err.Description
End Sub
Any suggestions?
Delete the current object from the collection. That works for me:
For Each tbl In db.TableDefs
If Left(tbl.Connect, bLenConnectPrefix) = csConnnectPrefix Then
db.TableDefs.Delete tbl.Name
End If
Next
In this post, Daniel Pineault suggested I remove the troubled table from all relationships.
Once I did that, I was able to delete the linked table.