How to send a query string from function - vba

I'm having a hard time coming up with a good title, but i hope i can explain the situation better. I currently have a query with a criteria which is ("11:00 - 21:00" Or "11:01 - 21:00"), this works perfectly fine when executed, however i will need this criteria in multiple queries therefore i decided to come up with function like below
Function timeIntervals()
timeIntervals = "11:00 - 21:00" & " Or " & "11:01 - 21:00"
End Function
and call it in each query, therefore each time i require to modify this string i can do it through that one instance, however when running this string above it does not function, im assuming its caused by the quotes on the Or, ive tried triple quotes """ and chr(34), however it doesn't work, can someone suggest a work around thank you!

As Remou indicated, you won't be able to get this to work. If you really want to do the check via a VBA function, you could write something like this:
Function timeIntervals(Value) As Boolean
If Value = "11:00 - 21:00" Or Value = "11:01 - 21:00" Then
timeIntervals = True
End If
End Function
Pass the value you want to check, and if the resulting function is true you then display the row. Something like: where timeIntervals(myvalue) = true.
Probably the best solution though is to make a table for the timeIntervals. Then in your query simply write something like:
Where MyValue IN(Select timeValue from timeIntervals)
Using this latter method you can update the table which will update the results for all users, and doesn't require a re-release of your front-end.

No matter what you do with quotes, that is not going to work, because Or will be returned as a string, not the boolean Or that you want. You could use a hidden form with two textboxes, then you can say:
WHERE timeIntervals = Forms!MyForm!Text1 Or Forms!MyForm!Text2
as long as the form remains open, your queries and any forms or reports based on them will work, furthermore, it will be very easy to change the intervals without modifying code.

Related

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 find out where a function is being called from in MS Access?

I am working on an MS Access database application that was created by someone else. There is one particular line of code (a Function) that will randomly get called and I have no idea why it is being called or what it does. I have searched (ctrl+F) the entire project for something that calls this function but I can't find it. How can I find out why this Function is being called? (See below). Thank you!
Public Function Concat(strIOSC As String, strFeature As String) As String
Static strLastIOSC As String
Static strFeatures As String
If strIOSC = strLastIOSC Then
strFeatures = strFeatures & ", " & strFeature
Else
strLastIOSC = strIOSC
strFeatures = strFeature
End If
Concat = strFeatures
End Function
If you have only searched the scripts and modules, then your scope is too narrow.
A public function like this can also be used in expressions, so you need to check queries, reports, form controls, macros, and possibly even tables if you use calculated fields. Depending on the size of the database, and how often the function is called, you can either search manually in a targeted way or possibly use a public sub to output something searchable. This sub can get you started. I think it outputs every possible location for expressions. Unfortunately, each object will have its own text file which will need to be searched separately unless you build a sub to do that too.
As for what your function does, it looks like it logs each input using the Static strLastIOSC variable, compares to the arguments passed on the second function call, and if they match it concatenates the two strFeature inputs together and outputs the result.
So basically the first argument tells the function whether this is the beginning of a new concatenation instance, or the continuation of an existing instance. The second argument is the item to be concatenated.
The Static keyword means that the value is stored even after the function runs so it can compare the last call with the current call to determine whether to add the second argument to the one saved from before, or clear the memory and prepare for a new concatenation.
Given its design, it's probably being used in a query/report/form, where strIOSC is likely a primary key field or a field in a GROUP BY.

How can I improve my Classic VBA coding using SQL? I'm making a mess with loops

Well... I am a n00b, I know that. And this is pretty straight foward.
Basically this is the code I've been writing.
rstSwift.MoveFirst
Do While Not rstSwift.EOF
If Left(rstSwift!Date, 2) & Mid(rstSwift!Date, 4, 2) & Right(rstSwift!Date, 4) = fromdate Then
INSERTFUNCTIONHERE
Else
End If
rstSwift.MoveNext
Loop
I recently made a question about SQL and it seems that it does have a lot of solutions to my problems... you see, my current database has around +15000 entries - and everytime I need to do a query about all the entries related to a single day it takes a while - after all, I'm asking it to do a LOOP across the entire database.
Now, It has come to my attention that I might be a little dumb to be doing so - well, that was the only way I could figure out to do it - my main question is:
Can I substitute that 'do while not' for a SQL query? Like
NEWTABLEVARIABLE = SELECT * from rstSwift WHERE rstSwift.Date = '20/11/2011';
NEWTABLEVARIABLE.MoveFirst
Do While Not NEWTABLEVARIABLE.EOF
INSERTFUNCTIONHERE
NEWTABLEVARIABLE.MoveNext
Loop
And on the results of this query, run the code? Would it be faster than my current code? Even if it's not faster - is it slower? Cause it seems much easier to write that single select argument than having to deal with the While-Loop situation...
The aim should be to remove the looping entirely.
Say you INSERTFUNCTIONHERE increments the column data_col by 1: instead of:
rstSwift.MoveFirst
Do While Not rstSwift.EOF
If Left(rstSwift!swift_date, 2) & _
Mid(rstSwift!swift_date, 4, 2) & _
Right(rstSwift!swift_date, 4) = fromdate Then
rstSwift!data_col = rstSwift!data_col + 1
rstSwift.Update
End If
rstSwift.MoveNext
Loop
Instead, re-write the entire operation in SQL e.g.
CREATE PROCEDURE IncrementSwifts
(
:fromdate DATETIME
)
AS
UPDATE Swifts
SET data_col = data_col + 1
WHERE swift_date = :fromdate;
Then in VBA, use a Command object to execute the proc, using a Parameter object to pass in the fromdate value.
Yes, you can get the filtering done on the database side. You would pass the query you specified in when you open the recordset. Something like this:
set rstSwift = connMyDbConnection.Execute("SELECT * from rstSwift WHERE rstSwift.Date = #20/11/2011#")
Do While Not rstSwift.EOF
INSERTFUNCTIONHERE
rstSwift.MoveNext
Loop
EDIT:
When using the Database.OpenRecordset() method use this syntax instead of the above:
set rstSwift = mdb.OpenRecordset("SELECT * from rstSwift WHERE rstSwift.Date = #20/11/2011#")
Do While Not rstSwift.EOF
INSERTFUNCTIONHERE
rstSwift.MoveNext
Loop
It would potentially be faster than what you are currently using, depending on how many records match the filter.
A WORD OF CAUTION:
The example I provide can be prone to SQL Injection attacks if you modify the query to be created dynamically based on some kind of input. ALWAYS make sure you're cleansing your SQL before running it.

VB.NET - I'm curious, why does the return from "Right" not work as a string?

If I have a string like input = "AA["
If Right(input, 1) = "[" Then Do stuff
The If statement returns false, even if I try converting things around to chars, etc etc. But if I do this is returns true:
Dim temp As String = Right(input, 1)
If temp = "[" Then Do Stuff
I like knowing little semantics like this, any idea why it comes out this way?
Or don't use Right at all since this is .Net
Dim s As String = "AAAAAAA]"
If s.Substring(s.Length - 1, 1) = "]" Then
Stop
End If
'or
If s(s.Length - 1) = "]" Then
Stop
End If
I've seen weird behavior like that when debugging.
In fact, today I had something simlar
Dim records As Integer
records = If(o.dr Is Nothing, o.ADO.rs.RecordCount, o.ADO.DS.Tables("tbl").Rows.Count)
That should work, using the rs.RecordCount when dr is nothing, otherwise using the Rows.Count. It didn't, records was ending up as zero. Rewrote it as a full if then/else block and it works.
It's never my first thought that the compiler/debugger/ide is messing things up, but you should keep it in the back of your mind for consideration: the programmers that wrote those programs are just as human and fallible as you or me.
It shouldn't. Are you sure you didn't have a typo? The result of RIGHT is a string, and if the input was truly "AA[" the IF will have passed.
I've never had VB act wonky on something like this.
If the code appears in a form, then the .Right property of the form overrides the string manipulation function. You need to specify the parent namespace - e.g. VisualBasic.Right - to ensure that you get the correct method.
I think you may have some kind of strange overload confusion occuring here.
You are specifiing "Right" (which could be calling a local "Right" function).
The function your implying is "Microsoft.VisualBasic.Strings.Right" in most of my code ends up being "Strings.Right" due to the global import.
I would try changing you code to the below and see if it still happens, in order to rule out some overload/scope confusion. (and/or the reduced "Strings.Right")
If Micosoft.VisualBasic.Strings.Right(input, 1) = "[" Then Do stuff

MS-Access: Replace "bracket"

In one of the ms-access table I work with we have a text field with a set size.
At the end of this field there is some extra code that varies depending on the situation.
I'm looking for a way to remove one of these code but even when the last part is truncated by the field maximum size.
Let's call the field "field" and the code I'm looking to remove "abc-longcode".
If I use the replace SQL function with the string abc-longcode the query will only work when the code is complete.
If I also want my update query (that does nothing but remove this specific code at the end of my field) to work on incomplete codes how would that translate into ms-SQL?
It would have to remove (or replace with "" to be precise) all of the following (example of course, not the real codes):
abc-longcode
abc-longcod
abc-longco
abc-longc
abc-long
abc-lon
abc-lo
abc-l
Obviously I could do that with several queries. Each one replacing one of the expected truncated codes... but it doesn't sound optimal.
Also, when the field is big enough to get all of the code, there can sometime be extra details at the end that I'll also want to keep so I cannot either just look for "abc-l" and delete everything that follows :\
This query (or queries if I can't find a better way) will be held directly into the .mdb database.
So while I can think of several ways to do this outside of a ms-sql query, it doesn't help me.
Any help?
Thanks.
You can write a custom VBA replace method that will replace any of the given cases {"abc-longcode", ... "abc-l"}. This is essentially the same tack as your "several queries" idea, except it would only be one query. My VBA is rusty, but something like:
public function ReplaceCodes(str as string) as string
dim returnString as string
returnString = str
returnString = replace(returnString,"abc-longcode","")
// ... etc...
ReplaceCodes = returnString
end function
I may have gotten the parameter order wrong on replace :)
I would use my own custom function to do this using the split function to get the first part of the string. You can then use that value in the update query.
Public Function FirstPart(thetext As String) As String
Dim ret As String
Dim arrSplitText As Variant
arrSplitText = Split(thetext, "-")
ret = arrSplitText(0)
FirstPart = ret
End Function
Can you use:
Left(FieldX,InStr(FieldX,"abc-")-1)
EDIT re Comment
If there is a space or other standard delimiter:
IIf(InStr(InStr(FieldX, "abc-"), FieldX, " ") = 0, Left(FieldX, InStr(FieldX, "abc-") - 1), Replace(FieldX, Mid(FieldX, InStr(FieldX, "abc-"), InStr(InStr(FieldX, "abc-"), FieldX, " ") - InStr(FieldX, "abc-")), ""))