How to unlink a query from a structured table AND deleting that query in VBA - vba

I know that since Excel 2016 I can simply delete a query like this:
ActiveWorkbook.Queries("aaa").delete
However this will leave an "orphaned" query connection. So if I refresh the remaining structured table, I will see the error
Query xxx was not found
I know that I can avoid this by first unlinking the query like this:
ActiveSheet.ListObjects("aaa").Unlink
How can I combine these 2 codes into a macro that:
Takes the query name as input (name in the Power Query Editor list)
Remove the link to the structured table
Delete that query
I prefer a method that works in the Queries collection of ActiveWorkbook. Because ListObjects is Sheet-dependent, and sheet name may change. Something like this:
ActiveWorkbook.Queries("aaa").[something].unlink
ActiveWorkbook.Queries("aaa").delete

Regarding ListObject.Unlink method, documentation (https://learn.microsoft.com/en-us/office/vba/api/excel.listobject.unlink) suggests:
Removes the link to a Microsoft SharePoint Foundation site from a list. Returns Nothing.
Doesn't seem relevant, as you don't mention SharePoint. Perhaps the code below is what you want.
Option Explicit
Private Sub DeleteQueryAndConnection()
Dim nameOfQueryToDelete As String
nameOfQueryToDelete = "someQuery"
With ThisWorkbook
.Queries(nameOfQueryToDelete).Delete
.Connections("Query - " & nameOfQueryToDelete).Delete
End With
End Sub
I didn't get the "Query xxx was not found" error after running this code.

Related

MS access check column value and change another

I have an ms access database with some yes/no columns I want to check and set the value of a third. The statement should be something like the following
if !col1 && !col2:
col3 = no
else:
col3= yes
I keep searching but don't really understand vba and can't find what I need .. Mostly a segment of an answer to something else that I cant make work. Currently trying to create it in the "module" section is that even right? Would be best if this could be done automatically as those columns are changed or maybe run once and do all rows. Please help me get on the right track, any help greatly appreciated.
Here is what I would do:
1- Create a form and add at least one command button on it. Name it cmdMyButton or cmdAnythingThatYouWant (cmd is the prefix used in examples from Microsoft for command buttons)
2- in the design view, double click the command button so to pop the code window
3- In the onClick() function, write the code that opens up a recordset for your table, loop through records and for each row, verify the value of those 2 columns and update if needed. (look at the documentation for DAO.recordset)
Let's say you have an Access table named Table1, with some Access fields:
The following Access SQL statement will update the value of col3 based on the values of col1 and col2, for every row in Table1:
UPDATE Table1
SET col3 = NOT col1 AND NOT col2
There are a number of ways to leverage this SQL statement:
You can paste it into the Query Designer in SQL view, and execute it via the Access UI
You can run it as part of an Access macro
You can execute it in VBA, using ADO or DAO
You can execute it in VBA, using the DoCmd.RunSQL method
Instead of VBA, you can use another Automation-supporting programming language
I would just create this on a calculated column in your DB table. See screen shot below:
Notice in the properties I set a formula to acquire the desired results based on the first 2 columns. The "Result type" is set to yes/no to mimic the yes/no field. Only difference is, the third column will display a -1 (True) or 0 (False). but when displaying this information on a form, you can have it display the information in a checkbox fashion. The calculated field is also ideal cause it will only update the third column when the target record is updated, not updating the whole table, very useful if the table size starts holding over 100k records.
If you want a VBA code, then you will need a trigger. I'm assuming its a button on the form, if not, you can always change the below code to match the event you want it triggered on. The below code is also a module that can be called/used for any trigger. The code is also assuming you have a primary key.
Public Sub UpdateThirdColumn(ByVal RecordPK As String) 'RecordPK is a passed variable
'that is the primary key identifier to find the
'record in the table
'Set variavble name for SQL statement, gives you one place to update instead of hunting through code
Dim strSQL As String
'Creates the SQL string in the variable assigned
strSQL = "UPDATE {YourTableNameHere} " & _
"SET {YourThirdFieldNAmeHere} = True " & _
"WHERE ((({YourFirstFieldNAmeHere}) = True AND ({YourSecondFieldNAmeHere}) = True AND ({YourPrimaryKeyFieldHere}) ='" & RecordPK & "'));"
'Executes the SQL statement, dbFailOnError will fail if problem exists in SQL statement
CurrentDb.Execute strSQL, dbFailOnError
End Sub
'Use this code if you have a button to trigger event
Private Sub YourButton_Click()
Call UpdateThirdColumn(Me.YourPrimaryKeyControlName)
End Sub
'Use the bottom 2 codes if you want the update for each check box to be checked and update
Private Sub FirstFieldName_AfterUpdate()
Call UpdateThirdColumn(Me.YourPrimaryKeyControlName)
End Sub
Private Sub SecondFieldName_AfterUpdate()
Call UpdateThirdColumn(Me.YourPrimaryKeyControlName)
End Sub
Let me know if you need more assistance or explanation and I will be glad to help.

MS Access - SQL append query behavior is erratic

I've been working on an Access database for the last couple weeks, and it's my first project with the tool. Dealing with append queries seems to have become an utter nightmare, and is incredibly frustrating. Even more so because it seems to have simply stopped working in any consistent manner overnight.
The SQL query that I have written goes thus:
PARAMETERS noteDetails LongText, noteTime DateTime, srcUserID Long;
INSERT INTO tblNotes (NOTE_DETAILS, NOTE_TIME_CREATED, NOTE_SOURCE_USER)
VALUES (noteDetails, noteTime, srcUserID)
In tblNotes:
NOTE_ID is an AutoNumber
NOTE_DETAILS is a Long Text
NOTE_TIME_CREATED is a Date/Time
NOTE_SOURCE_USER is a Number
The way that I'm running this query is through VBA:
Set qdf = CurrentDb.QueryDefs("qerApndNote")
qdf.Parameters(0).Value = txtDetails.Value
qdf.Parameters(1).Value = Now()
qdf.Parameters(2).Value = getCurrentUserID()
qdf.Execute dbFailOnError
qdf.Close
Set qdf = Nothing
' Where CurrUserID is a global long
' txtDetails.Value is a textbox's contents
' Now() is the VBA built-in function to return a date/time combo
I have attempted to run this query manually from the navigation bar, and it works fine when done in that manner.
However, running it from VBA has resulted in such things as there being no time / date inserted, sometimes a user ID is not inserted, sometimes both, sometimes even the details text is missing.
What is it that I'm missing? Is there any general advice for users of MS Access to follow that I am not? I'm aware that NOTE is a restricted word in Access, but I really don't think that should apply here, right?
Thanks in advance!
EDIT: The form that I'm passing data from is called frmNewNote, and there is a control in it named txtDetails. It's just a regular textbox. Don't really know what else to share about that.
The getCurrentUserID function is in a module, modGlobal:
Public CurrUserID As Long
Public Function getCurrentUserID() As Long
getCurrentUserID = CurrUserID
End Function
Public Function setCurrentUserID(CurrID As Long)
CurrUserID = CurrID
End Function
It's about as barebones as you can get, really. And there is never a circumstance that you'll get to the form before SetCurrentUserID has been called during your... session? There's a login form involved.
#Andre's code for logging:
0 noteDetailsText This is a note test
1 noteTimeCreated 9/6/2017 10:28:45 AM
2 srcUserID 1
As for my architecture, um, it's just the single database file right now, on the desktop. The entire function/sub is run when you click a button, btnEnter. It does some other stuff before it gets to the SQL statement bit - checks for null values and prompts user for entries if that's the case.
I just remembered something:
MS Access 2013 calling insert queries from VBA with strange errors
You have a LongText parameter. These don't really work. See also https://stackoverflow.com/a/37052403/3820271
If the entered notes will always be <= 255 characters, change the parameter to ShortText.
If the text can be longer, you'll have to use either SunKnight0's approach with a concatenated INSERT statement.
Or use a Recordset and its .AddNew method, which will be a similar amount of code to your current solution, but also be completely safe from injection or formatting issues.
You are doing way more work than you have to. All you need is:
DoCmd.RunSQL("INSERT INTO tblNotes (NOTE_DETAILS, NOTE_TIME_CREATED, NOTE_SOURCE_USER) VALUES ('" & Me.txtDetails & "',Now()," & CurrUserID & ")")
Note the change from txtDetails.Value to Me.txtDetails which is what may have been messing you up. This of course assumes the code runs in the form's context, otherwise you have to get he value of the text field using a reference to the form.
The only other thing to consider is making sure Me.txtDetails does not have any single quotes, so probably use Replace(Me.txtDetails,"'","''") instead.
That way you can also replace DoCmd.RunSQL with MsgBox to troubleshoot the exact query.

How to determine what caused an update to the database

I have a question regrading how to determine where a record update came from...
I have an Access 2010 database with multiple tables and multiple forms. All forms have VBA code behind them. Many of the VBA subs and functions insert, update or delete records. I use the afterupdate, afterinsert and afterdelete events to write details of changes made to an audit table. The audit table stores details such as when the change was made, who by and what was changed (action type, table, record id etc.).
As I mentioned, many different subs / functions make changes to the database. What I want to know is, how can I find out which sub / function module initiated the database change request?
At present I have added a global string variable to each sub / function module that updates the database and called a function within the setfield of a crearecord within the afterupdate / afterdelete / afterinsert event to read the variable and so put it on the audit table record. This is a solution but not a great one. Anybody got any better ideas?
In web development, I could read the HTTP server variables to see how the user got to the current page (referrer and referrer_URL). Is there something hidden away in one of the objects within Access that would give me something similar?
I'm assuming (hoping? praying??) that this all gets done in one single module. In other words, you're passing all this info as a variable to a public Function. If not, you should. And while you're passing the variables, just pass the function name to it along with all the other data.
For instance, you would have a public Function in a Module like this:
Public Function ChangeLog(chgDate As Date, strByWhom As String, etc..., strModName as String)
On Error GoTo Err_Handler
'Put some code here to write all the variables into fields in your ChangeLog table
Exit_Handler:
Exit Function
Err_Handler:
MsgBox "Error " & Err.Number & " - " & Err.Description, vbExclamation, "CalendarFor()"
Resume Exit_Handler
End Function
Then you just pass all the variables into it like (assume a form named MyForm and this is placed in the After Update event):
Call ChangeLog(Now(), CurrUser, etc..., "MyForm - AfterUpdate")
Obviously this is "air code", but I'm sure you get the idea.

Access Call Macro from Query (Opening a Form) Run-time error '2486': You can't carry out this action at the present time

I have an Access Query that requiers a value to be set in a combo-box within a form in order to work
Criteria: Forms![_SelectCustomer]![CmbSelectCustomer]
So far so good, however, I would like the query to open, read and close this form programatically when it is run using a macro.
I have been following #David-W-Fenton's answer in THIS similar stack overflow question and have come up with the following code:
Public Function rtnSelectCustomer() As Variant
DoCmd.OpenForm "_SelectCustomer", , , , , acDialog
With Forms![_SelectCustomer]
If .Tag <> "Cancel" Then
rtnSelectCustomer = Nz(!CmbSelectCustomer, "*")
Else
rtnSelectCustomer = "*"
End If
End With
Close acForm, "_SelectCustomer"
End Function
I call this function from within the criteria field of the property I want to filter by in the Query:
Like rtnSelectCustomer()
At this point I run into several problems:
The first being, I'm not sure where to place the actual code: I can't seem to create a specific class or module for my query within the "Microsoft Access Class Objects" folder so I have resorted to creating my own module within the Modules folder. (Is this the correct approach?)
The second issue is that when I run the query with the code in the current module I have created I get the following error:
Run-time error '2486':
You can't carry out this action at the present time.
Any advice would be much appreciated
Edit:
I should clarify that after further testing the line that seems to cause the Run-time error is the following:
DoCmd.OpenForm "_SelectCustomer", , , , , acDialog
The function is actually called as replacing the internal code with the following does actually work (although is admittedly useless)
Public Function rtnSelectCustomer() As Variant
rtnSelectCustomer
End Function
Generally, I hate things that are "pre-programmed" by Microsoft, I'd rather do them myself. It seems this is your case as well...
I would do this in 2 steps.
Step1: Show things to the user as if the query was running (without actually running it) and store the values the user picks.
Step2: Use the values to parameterize the query
If your function works well, then simply remember what the user picks and then do:
set qdf = new QueryDef
' set the qdf and add all parameters to it
DoCmd.Execute qdf
for further reference on how QueryDef works I would use this msdn site

SQL / Excel Query Parameter with a JOIN

I am learning how to use parameters in an excel-driven SQL query (in fact I am still learning SQL in general). Thanks to the nice people that helped me build my query to modify the results as I need, I want to take this a step further and supply a parameter in Excel to filter the results.
Here is my query:
SELECT
fun.FUNCTION_ID
,COALESCE(fun.parent_function, fun2.function_id) as PARENT_FUNCTION
,fun.MODULE_ID
,fun.DESCRIPTION
,fun.FUNCTION_PURPOSE
,fun.PB_OBJECT
,sec.GROUP_ID
,sec.ACCESS_LEVEL
from
MODULE_FUNCTION fun
LEFT JOIN MODULE_FUNCTION fun2
ON fun.function_id = fun2.function_id
AND fun2.function_id IN (SELECT parent_function FROM MODULE_FUNCTION)
LEFT OUTER JOIN FUNCTION_SECURITY sec
ON fun.FUNCTION_ID = sec.FUNCTION_ID
AND sec.GROUP_ID = 'GROUP_NAME'
What I need to do is allow people from a team to run this query in the excel sheet and supply their group name for the "GROUP_NAME" in the second JOIN. Unfortunately I cannot use the syntax WHERE (sec.GROUP_ID = ?) (found here) as I need to pull all results from the MODULE_FUNCTION table and only insert results on the right from the FUNCTION_SECURITY table when there is a match on the supplied group (leaving null when there is no match).
When I try to use AND (sec.GROUP_ID = ?) at the end I get a "Invalid Parameter Number" in Excel. From what I have gathered, the "?" can only be use with WHERE (and works find for me in test queries).
I have tried many things, including declaring a #parameter, but no avail.
I'm tempted to try this technique but I'd like to avoid VB if possible.
I know you said you want to avoid VB, but it isn't too complicated for what you want to do.
You can have the sheet have a cell for the group name, then a button that calls a macro where you would change the sql query to adjust for the group_id.
Something like:
Dim sql As String
sql = "select ... from ... and sec.GROUP_ID = '?'"
sql = Replace(sql, "?", Worksheets("Analysis").Range("A1").Value)
With ActiveWorkbook.Connections("connection name").OLEDBConnection
.CommandText = sql
.Refresh
End With
Where:
Worksheets("Analysis").Range("A1").Value
is the Group_ID. You can set this to a specific cell in any sheet in your workbook. I would create a button right next to it called "Refresh table" or something like that.
If you already made a table that links to a database, then there is a connection object in Excel. Go to the Data tab, then click "Connections". A new window will pop up. Find the connection that matches to the SQL query. Click on that connection and click "Properties" then change the connection name to something easy (it's usually some long name based on the server/table you connect to). Use that for the
ActiveWorkbook.Connections("connection name")
section.
Link to create button on worksheet and link to macro:
http://office.microsoft.com/en-us/excel-help/add-a-button-and-assign-a-macro-to-it-in-a-worksheet-HP010236676.aspx