Function To Delete Table Or Query If Exists - ms-access

I am attempting to use a Function that checks if a table/query that is passed into the function then delete it. My issue is that when it hits this line of syntax
If TableExists(CStr(tableArray)) Then
I get a type mismatch error. However, if I change the syntax to If TableExists(CStr(amx)) Then the value is 0 so the table/query is still not deleted. What would be the appropriate syntax to pass an array of table names and query names into a function and delete them if they exists?
Function DeleteTables()
tableArray = Array("Table1", "Table2", "Table3", "Table4")
queryArray = Array("Query1", "Query2", "Query3")
For amx = LBound(tableArray) To UBound(tableArray)
If TableExists(CStr(tableArray)) Then
With db.TableDefs
.Delete CStr(tableArray)
.Refresh
End With
End If
Next
For qdi = LBound(queryArray) To UBound(queryArray)
If TableExists(CStr(queryArray)) Then
With db.QueryDefs
.Delete CStr(queryArray)
.Refresh
End With
End If
Next
End Function
Public Function TableExists(strName As String) As Boolean
On Error GoTo HandleErr
Dim db As DAO.Database, tDef As DAO.TableDef
Set db = CurrentDb
TableExists = False
For Each tDef In db.TableDefs
If tDef.Name = strName Then
TableExists = True
Exit For
End If
Next tDef
For Each qDef In db.QueryDefs
If qDef.Name = strName Then
TableExists = True
Exit For
End If
Next qDef
ExitFunction:
db.Close
Set db = Nothing
Exit Function
HandleErr:
TableExists = False
Resume ExitFunction
End Function
Type Mismatch

You're almost there, you just need to refer to the specific item in the array you're looping through:
Change your current code to:
If TableExists(CStr(tableArray(amx))) Then
With db.TableDefs
.Delete CStr(tableArray(amx))
.Refresh
End With
End If
and
If TableExists(CStr(queryArray(qdi))) Then
With db.QueryDefs
.Delete CStr(queryArray(qdi))
.Refresh
End With
End If
You could also use a For Each loop in the future, they tend to be easier to understand.

Related

When the error reappears error handling does not work

Logics:
User. Presses the button;
Code . Creates a "Recordset" "rstStud" (students) and "rstGroupStud" (Student Groups);
Code . Cycle. Enumerates "rstStud";
Code . Add entry to "rstGroupStud";
Code . If the record exists, go to the next record in the loop;
Code . If the entry is new, add an entry in "rstGroupStud";
Essence: one click of a button - add one unique entry.
Problem.
When the cycle passes! StudentName = "Student Name 2" in the line ".Update" I get an error.
Error:
"Failed to make changes due to duplicate values ​​in
index, primary key or relationships. Change the data in one
or multiple fields containing duplicate values
delete the index or override it by allowing
duplicate values ​​and try again. "
In other words, the first time the error handler works normally, and when I re-fire, I get an error.
Question.
How to make the code work according to the described logic?
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset '
Dim rstGroupStud As DAO.Recordset '
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot) '
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset) '
' *** rstStud
With rstStud
Do Until .EOF = True
nameStud = !nameStud
On Error GoTo errend
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Sub
errend:
.MoveNext
Loop
End With
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
End Sub
Update_1
File - link
You need to de-tangle the execution paths; normal and error execution states are intertwined, that's why any error beyond the first one can't be handled.
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset '
Dim rstGroupStud As DAO.Recordset '
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot) '
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset) '
' *** rstStud
With rstStud
Do Until .EOF = True
On Error GoTo ErrHandler
nameStud = !nameStud
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Do
TryNext:
On Error Resume Next
.MoveNext
If Err.Number <> 0 Then Exit Do
On Error GoTo 0
Loop
End With
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
On Error GoTo 0
Exit Sub
ErrHandler:
Resume TryNext
End Sub
That way ErrHandler only ever runs in an error state; TryNext runs in the "happy path", and Exit Do breaks out of the loop (but not out of the procedure) so that the cleanup code can run whatever the outcome is.
Don't do it in VBA. Do it in a query instead.
For example, you could do this way:
Create a query named qryAssignStudentsToGroup:
PARAMETERS id_GroupFrm INT;
INSERT INTO tbl_03_GruopsStudents (idGroup, nameStud)
SELECT id_GroupFrm, nameStud
FROM tbl_02_Students AS s
WHERE NOT EXISTS (
SELECT NULL
FROM tbl_03_GruopsStudents AS g
WHERE s.nameStud = g.nameStud
AND g.idGroup = id_GroupFrm
);
Then your code behind becomes:
Private Sub btnAddRecord_Click()
With CurrentDb.QueryDefs("qryAssignStudentsToGroup")
.Parameters("id_GroupFrm") = Me.id_GroupFrm
.Execute
End With
Me.frm_03_GruopsStudents_tbl.Requery
End Sub
This gives you much less code for same thing, and you eliminate the complexity around error handling simply by filtering out the rows. More importantly, you perform a bulk update once, rather than for each row you touch. This leverages the power of database engine. It's convenient to do things in a loop with a DAO.Recordset but that's row-by-agonizing-row programming. You really want to get in mindset of set-based programming.
You must call Err.Clear to reset the error state
errend:
Err.Clear
.MoveNext
I would call Me.frm_03_GruopsStudents_tbl.Requery after the loop. There is not point in requerying the form all the time.
But jumping to another regular code part instead of going to an error handler is not the usual way of handling errors. To address the possibility to handle errors on MoveNext, change the code like this:
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset
Dim rstGroupStud As DAO.Recordset
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot)
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset)
' *** rstStud
With rstStud
Do Until .EOF = True
nameStud = !nameStud
On Error GoTo UpdateError
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Sub
continue_loop:
On Error GoTo MoveNextError
.MoveNext
Loop
End With
CleanUp:
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
Exit Sub
UpdateError:
Resume continue_loop
MoveNextError:
MsgBox Err.Description
Resume CleanUp
End Sub
This pattern is extensible. You can add as many error handlers as required.

Save Excel Workbook From Access VBA

I am exporting a recordset from an access query to an Excel Workbook. The export goes fine, and my syntax prompts the user for a filename/location just as I need. However, the file is not actually saved. Am I missing a step in the process or what code changes need to take place in order to have this function?
Sub ETE()
Dim ExcelApp As Object, wbOutput As Object, wsOutput As Object, bExcelOpened As Boolean
Dim db As DAO.Database, rs As DAO.Recordset, targetRow As Long
Dim targetPath As String, fd As FileDialog, Title As String, saveInfo As Variant
DoCmd.Hourglass True
Set ExcelApp = GetObject(, "Excel.Application")
If Err.Number <> 0 Then
Err.Clear
On Error GoTo Error_Handler
Set ExcelApp = CreateObject("Excel.Application")
bExcelOpened = False
Else
bExcelOpened = True
End If
On Error GoTo Error_Handler
ExcelApp.ScreenUpdating = False
ExcelApp.Visible = False
Set wbOutput = ExcelApp.Workbooks.Add()
Set wsOutput = wbOutput.Sheets(1)
Set db = CurrentDb
Set rs = db.OpenRecordset("qryTakeDataToExcel", dbOpenSnapshot)
With rs
If .RecordCount <> 0 Then
'Write the data to Excel
End If
End With
Set fd = Application.FileDialog(msoFileDialogSaveAs)
With fd
.AllowMultiSelect = False
.Title = "Select Save Location And File Name"
.InitialFileName = "File_" & Format(Now(), "mmddyyyy") & ".xlsx"
If .Show = True Then
wbOutput.SaveAs FileName:=fd.InitialFileName, FileFormat:=50
wbOutput.Close
End If
End With
End Sub
Your filedialog code is not working as expected, and because of that, you're not getting a valid file name and location.
If you want to return the file name picked, you should use .SelectedItems(1), not .InitialFileName. .InitialFileName sets an initial value and doesn't return the full path.
If .Show = True Then
wbOutput.SaveAs FileName:=.SelectedItems(1), FileFormat:=50
wbOutput.Close
End If
This would've probably been easier to catch if you'd have used a valid error handler. Use On Error GoTo 0 to use the default error handler.

Access VBA function While...Wend or Do ...Loop

I am working on a function for my access database that fills in a form field in my task form automatically based on the data entered in products forms.
Function IsProductReceived(varID As Variant) As String
Dim rst As New ADODB.Recordset
Dim strSQL As String
Dim lngTOID As Long
Dim strReceiveDate As Date
Dim bAcceptable As Boolean
On Error GoTo ErrorHandler
If IsNull(varID) Then
IsProductReceived = "TBD"
Else
lngTOID = varID
strSQL = "SELECT tblProduct.TaskID, tblProduct.Received, tblProduct.Acceptable FROM tblProduct WHERE tblProduct.TaskID = " & lngTOID
rst.Open strSQL, CurrentProject.Connection, adOpenDynamic, adLockOptimistic
If rst.BOF And rst.EOF Then
IsProductReceived = "TBD"
Exit Function
Else
While rst.EOF = False
If rst![Received] <> "" Then
strReceiveDate = rst![Received]
bAcceptable = rst![Acceptable]
If IsDate(strReceiveDate) Then
If bAcceptable = False Then
IsProductReceived = "YES/NOT ACCEPTED"
Else
IsProductReceived = "YES/ACCEPTED"
End If
Else
IsProductReceived = "NO"
End If
Else
IsProductReceived = "NO"
End If
rst.MoveNext
Wend
End If
rst.Close
Set rst = Nothing
End If
Exit Function
ErrorHandler:
MsgBox Err.Description
Err.Clear
If rst.State = adStateOpen Then
rst.Close
Set rst = Nothing
End If
End Function
There is often more that one product forms related to the task form and products are received at different times. I want the "IsProductReceived = "no" to remain on the task form until ALL products related to the task are received.
This code seems to be working as long as the first product has not been received. I can seem to figure out how to make it remain "no" until all products are received.
I currently am using a while/wend, I have attempted a Do/loop but am still not having satisfactory results. Any help would be much appreciated
How about:
Function IsProductReceived(TaskID) As String
Dim product As New ADODB.Recordset
Dim sql As String
Dim countAll As Integer
Dim countReceived As Integer
Dim countAccepted As Integer
IsProductReceived = "TBD"
If Not IsNumeric(TaskID) Then Exit Function
sql = "SELECT Received, Acceptable FROM tblProduct WHERE TaskID = " & TaskID
product.Open sql, CurrentProject.Connection, adOpenDynamic, adLockOptimistic
While Not product.EOF
countAll = countAll + 1
If IsDate(product!Received) Then countReceived = countReceived + 1
If product!Acceptable Then countAccepted = countAccepted + 1
product.MoveNext
Wend
product.Close
If countAll = 0 Then
IsProductReceived = "No"
ElseIf countAll = countAccepted Then
IsProductReceived = "YES/ACCEPTED"
ElseIf countAll = countReceived Then
IsProductReceived = "YES/NOT ACCEPTED"
Else
IsProductReceived = "No"
End If
End Function
A few notes:
Indent your code better.
Drop the faux Hungarian notation, use descriptive variable names.
Avoid deep nesting, especially when it comes to determining the return value.
Check parameters and exit early if the check fails. This removes nesting depth from the function.
Avoid Variant parameter types unless the function must deal with different data types. Here an Integer or Long type would probably be a better fit. (Using a typed function parameter removes the need for a type check entirely.)
While x = False is an antipattern. Use While Not x.
No need to save recordset fields in local variables first. Just use them directly.
Avoid building SQL from string concatenation. After an IsNumeric() check the above is probably okay, but you really should use parameterized queries.
The issue I'm seeing with your code is that you're getting a record set from a table, looping through the set and testing "Recieved" and then assigning a return value for your function after each test. Effectively, you're just returning the value of the very last record in the recordset. Perhaps instead of setting the value of isProductRecieved inside the While loop, set a bool value to false whenever you encounter a product that hasn't been recieved and then set the return value of the function after the loop:
Dim receive As Boolean
Dim accept As Boolean
receive = True
accept = False
If rst![Received] <> "" Then
strReceiveDate = rst![Received]
bAcceptable = rst![Acceptable]
If IsDate(strReceiveDate) Then
If bAcceptable = False Then
accept = False
Else
accept = True
End If
Else
receive = False
End If
Else
receive = False
End If
So now, if "receive" makes it all the way to the end of your while loop, you know that each product is received but if any product was not received it would be set to false. You could also build a short circuit in there to make it a tiny bit faster.

Passing Functions through Sub Procedure

I am trying to call a function when running a sub proecudere, however, I keep getting an error message saying "Argument not optional", can someone help?
Code as follows:
Public Sub ret()
Dim FSO As New Scripting.FileSystemObject
Const cstrFolderF = "\\tblSCFLAGCHECKER.txt"
If FSO.FileExists(cstrFolderF) Then
DoCmd.RunSQL "DELETE * FROM [tblSCFLAG_CHECKER]"
DoCmd.TransferText acImportDelim, "tblSCFLAG_CHECKER", "tblSCFLAG_CHECKER", cstrFolderF, True
changefieldnames
Else
'SCAnswer = MsgBox("SC Flags does not exist, do you wish to continue?", vbYesNo Or vbQuestion Or vbDefaultButton2)
'If SCAnswer = vbNo Then Exit Sub
End If
End Sub
Private Sub changefieldnames()
Dim db As Database
Dim tdf As TableDef
Dim n As Object
Set db = CurrentDb
Set tdf = db.TableDefs("tblSCFLAG_CHECKER")
For Each n In tdf.Fields
If n.Name = "?Person ID" Then n.Name = "Person ID"
Next n
Set tdf = Nothing
Set db = Nothing
End Sub
Your changefieldnames function requires two arguments but you give none in the call after
DoCmd.TransferText acImportDelim, "tblSCFLAG_CHECKER", "tblSCFLAG_CHECKER", cstrFolderF, True
changefieldnames
As a remark: you should try to debug your code instead of just posting an error without even stating where exactly the error occurs.

How to write VBA with Do While Loop?

I have a table with 3 fields: DONOR_CONTACT_ID, RECIPIENT_CONTACT_ID, ORDER_NUMBER. I want to sort DONOR_CONTACT_ID in ascending order which I did with my query Q_RECIPIENT_SORT. Then I want to use temporary variables to check to see if the records have the same DONOR_CONTACT_ID and then display a message if they do (Most of the records have the same DONOR_CONTACT_ID). My program does everything it is supposed to, but at the end it always gets an error that says "No Current Record". Here is my code:
Option Compare Database
Option Explicit
Function UsingTemps()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim strTemp1 As Long
Dim strTemp2 As Long
DoCmd.SetWarnings False
DoCmd.OpenQuery ("Q_RECIPIENT_SORT")
DoCmd.OpenTable ("T_RECIPIENT_SORT")
DoCmd.SetWarnings True
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("T_RECIPIENT_SORT", dbOpenTable)
rst.MoveFirst
strTemp1 = rst!DONOR_CONTACT_ID
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
Do While Not (rst!DONOR_CONTACT_ID = rst.EOF)
If strTemp1 = strTemp2 Then
MsgBox ("Equal")
Else
MsgBox ("Not equal")
End If
strTemp1 = strTemp2
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
Loop
Set dbs = Nothing
End Function
I think the problem is with the following lines:
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
I think it is trying to move to the next record when there are no more records left. Probably something wrong with my logic. But I've been staring at it for a while and my changes haven't worked. I need another set of eyes to take a look at it.
Any help is appreciated!
Consider what happens when your recordset loop is on the last row, and you then do this ...
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
MoveNext positions the recordset at EOF --- no record is "current". So, in the next line, the code attempts to store the value from the current row's DONOR_CONTACT_ID to strTemp2. However, since you're at EOF, no record is "current", so Access complains "No Current Record".
I think this version will avoid that error. Test the logic to make sure it also does what you need.
rst.MoveFirst
strTemp1 = rst!DONOR_CONTACT_ID
rst.MoveNext
'strTemp2 = rst!DONOR_CONTACT_ID
'Do While Not rst!DONOR_CONTACT_ID = rst.EOF
Do While Not rst.EOF
strTemp2 = rst!DONOR_CONTACT_ID
If strTemp1 = strTemp2 Then
MsgBox "Equal"
Else
MsgBox "Not equal"
End If
strTemp1 = strTemp2
rst.MoveNext
'strTemp2 = rst!DONOR_CONTACT_ID
Loop
The general idea is as this:
Set rst = dbs.OpenRecordset("T_RECIPIENT_SORT", dbOpenDynaset)
Do Until rst.EOF
'do or check what you want
'....
rst.MoveNext
Loop
rst.Close