The do while loop structure - vba

In the do while loop structure usually there's a part where you declare a variable equal to a number (in this case i) and then in a second part you make a increment (i+1). I've made this example in vba, but the structure could be repeated in several different programming languages like the for in php when you're getting data from a database. Now, what I would like to understand better is the relation between the previous mentioned declarations, that is i = some number and i = i + 1 . Wouldn't this generate a problem of interpretation since you're declaring a variable to something and then assigning a different value right after it? Is the second declaration of the variable value, i = i + 1, a new variable calling the previous one or both i's are the same? This is the general orientation I intend with this question. I think explaining the scoop of both variables would help understanding. Thanks!
Sub DoWhile()
Dim x, i, sum
x = 10
i = 1
sum = 0
Do While i < x
sum = sum + i
i = i + 1
Loop
MsgBox “Sum = ” & sum
End Sub

A variable is really just a location in memory. That location can have any value. By setting i=i+1, you're really saying "take the value at position i, add 1 to it, and store it at position i". No new variable is created. There's no problem with the computer interpreting this- what it cares about is the location of i, which isn't changing. It still knows where to find i, regardless of how many times you change the value there.

Since you have created the variable i as a global variable, any reference or modification to i in the sub will be on the same variable. That being said:
Dim i as int
i = 1
Do while i < 11
MsgBox("The value of i is: " & i)
i = i + 1
Loop
would display 10 messageboxes showing the value of i being between 1 and 10.
When the program encounters i = i + 1, the computer 'sees' this as take the value of i, add one to it, and store the result in the variable i.
Hope that helps.

Related

Generating Custom Patient IDs in Access Database using VBA UDF

First, let me say this. I am new to using access and VBA functions.
My overall goal is to add functionality to my database as described below:
This database consists of patients enrolled in a Clinical Trial, these patients have a unique identifier in the format GKID-XXXXX where the XXXXX is an alphanumeric base 35 counting system.
Eg. the numbering goes like this GKID-00000, GKID-00001, GKID-00002, GKID-00003,... , GKID-0000Z. Base 35 because it exclude the letter O.
Previously, we would generate these IDs and type them in manually. However, in the future, we would like these to be automatically created when a new patient is added to the database. However,
we want to retain the ability to add IDs in manually without changing any existing IDs, delete records without changing the assigned IDs, and the IDs created cannot be already used.
I have tried many things and the naive strategy I have made progress with is as follows.
Take the existing "Working Table" that contains all of the existing IDs in a field. This field would be left blank for newly added patients who we want to automatically generate an ID for.
Using this working table, create a new table with a query. This would be the table with the IDs. It would exactly match the existing table except the ID column from the first table would be replaced with one that generates IDs with a custom VBA function. The function takes the Working Table ID field in as a variable and returns the generated ID. If the field is occupied, it simply returns the ID, if not, it generates a new one. Below is the progress I have made in accomplishing this.
Option Compare Database
Function GavFun2(EIID As String) As String
strAlphabet = "0123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"
These are the characters in the base 35 counting system.
N = Mid(EIID, 6, 5)
This simply extracts the 5 alphanumeric digits of the Working Table ID
Dec = (InStr(strAlphabet, Mid(N, 5, 1)) - 1) * 35 ^ 0 + (InStr(strAlphabet, Mid(N, 4, 1)) - 1) * 35 ^ 1 + (InStr(strAlphabet, Mid(N, 3, 1)) - 1) * 35 ^ 2 + (InStr(strAlphabet, Mid(N, 2, 1)) - 1) * 35 ^ 3 + (InStr(strAlphabet, Mid(N, 1, 1)) - 1) * 35 ^ 4
This Decodes this back into a base 10 system
GavFun2 = GavFun(CInt(Dec))
This converts the number back into the base 35 system and returns the ID in its full string form (function included below).
If EIID = Empty Then
End If
End Function
This if statement is where I am running into a wall. I want to fund the maximum value of Cint(Dec), then simply return GavFun2 = GavFun(Max(Cint(Dec))+1). I feel like this would be a good start, but there would be a number of problems if I was able to even get this to work.
A. If there ware multiple blank records, they would all have the ID (maybe replace with a for loop that runs through each blank consecutively and start the counter at Max(Cint(Dec))+1, but I don’t know how to do this.)
B. If I were to add a new patient with a custom ID (or delete one), this could potentially change all of the generated IDs.
Any thoughts on my general approach or advice on how to proceed would be very much appreciated. Thank you so much for your help.
Option Compare Database
Function GavFun(IDD As Integer) As String
strAlphabet = "0123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"
If IDD = 0 Then
GavFun = "0"
Exit Function
End I
GavFun = vbNullString
Do While IDD <> 0
GavFun = Mid(strAlphabet, IDD Mod 35 + 1, 1) & GavFun
IDD = IDD \ 35
Loop
ZZ = Array("0", "00", "000", "0000", "00000")
L = Len(CStr(GavFun))
MM = ZZ(4 - L)
GavFun = "GKID-" & MM & GavFun
End Function
Ok, the way to approach this?
Well we often need all kinds of speical numbering systems. For invoice, PO numbers, maybe a badge number, employee numbers etc. Now in these cases? Such numbers have VERY little to do with relational databases. I mean becuase you might not yet have some silly invoice number, your whole software package comes crashing down?
So, such external numbers? They don't have anything to do with how you build your relationships between tables. For that you use the PK (ID - autonumber), and then the FK (forighen key) in the child table. things like City, invoice number, name etc? That's just data you store - ZERO to do with relationships.
Ok, so now that we got above out of the way.
First up:
You don't want to hit the whole database to find some max number or some such. While this might work for single user, it not really a good idea.
So, for badge numbers, incrementing product numbers, invoice numbers, and hey, maybe even a clincial trial number?
You create a table with a SINGLE row in that table. That way you can then even say go and change/set what the starting number/point is to be. So yes, you ARE on the right tack - you want to build your own custom numbering system, and then use that for a column in that table (as I stated, this is just a number, or value - not different then city, first name, or say some invoice number - NOTHING to do with the PK, and NOTHING to do with relatonal database stuff.
Ok, so, now we need some things here.
First, we need that table that keeps track of this number for us.
Next, we need a routine to get this "value" and then increment the value for the next time.
And then we need to setup a nice way to get and then put/shove/have that number auto matic be setup and entered into that form for us, and of course make sure if that record already has a number, we don't over write it.
Thus we can break this problem down into separate parts.
First, create our increment number handy dandy maintains table like this:
So that's our table. Nice part is we can edit the starting number, and even the prefix - so if you do another run/study, we can easy start a new number set. we DON'T care about the existing data - build a number system - it just runs and works like a good water pump.
Ok, next we need our routine to go please get me the next number.
Our code can work like this:
Public Function GetNextPID() As String
Const Alpha = "0123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"
Const MaxDigts = 4
Const Base = 35
Dim OnePart As Long
Dim OneDigit As Long
Dim rstNext As DAO.Recordset
Dim OurNum As Long
Dim i As Integer
Dim strResult As String
Dim strPrefix As String
Set rstNext = CurrentDb.OpenRecordset("NextPatientNum")
OurNum = rstNext!NextNumber
strPrefix = rstNext!Prefix
rstNext.Edit
rstNext!NextNumber = OurNum + 1
rstNext.Update
rstNext.Close
' convert our number to base
Dim s As String
For i = MaxDigts - 1 To 0 Step -1
OnePart = Int(OurNum / (Base ^ i))
s = Mid(Alpha, OnePart + 1, 1)
strResult = strResult & s
OurNum = OurNum - Int(OnePart * (Base ^ i))
Next i
GetNextPID = strPrefix & "-" & strResult
End Function
So, place the above code function in a standard code module (not in a form).
Now, hit ctrl-g, and we can test it like this from the debug (immediate window)
? GetNextPID
Output: GKID-0007
So, now you can just edit that "one row" maintains table to start at whatever number you like or feel like. once you set that number, then each time you call that routine, it will go get you the next number based on our new number system.
Now, all we have to do is in that form? Well, we could just write code to check if the text box/field is not yet set, but there is ALSO a event on the form that fires ONLY when you add/insert that record in the form. It not 100% clear when/how your form works, but we can do something like this:
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.PatientNum = GetNextPID
End Sub
(of course you don't type in the event stub part).
Note close: if the record is blank - not dirty, then nothing happens. You can even close the form. However, the instant you start typing anything into that form with a new record, then like magic that code will run, and shove in the new number into that text box on the form, the new patient number will appear.
As noted, we can even go edit that maintains table, give it a new prefix and new number for a whole new study.
Edit: feel free to use your existing logic - either way, don't try and hit/use the database table with existing data to get/make/generate the new ID - use your function idea, but it will simple automatic return + generate the next new number you need upon calling that function.

Absolute reference with R1C1 using a variable to dictat row number (VBA)

I have a code that creates a number of separate lists under each other. For each of these lists I'm running a for loop to assign an equation to each row at the end column. This formula should multiply the neighbour cell (a relative reference) with cell at the top of that particular list (an absolute referance). The problem is that the lists are of arbitrary length and generated earlier in the code, so I can't assign the absolute referance beforhand.
I was thinking on saving the row number (row 1 = 1, row 2 = 2 etc) in a variable and then use the variable in the R1C1 notation (= "R(variable)C5*RC[-1]), but I cant seem to get this to work... The variable will be the same throughout the for loop given in the example below, but will change next time the same for loop is entered.
Is this even possible?
(I know the parantheses in the R1C1 are not the proper notation, but this is to show where I want my variable)
...
variable = 3
For i = 1 to count
last = ActiveSheet.Cells(Rows.Count, "C").End(xlUp).Row + 1
Cells(siste, "E").FormulaR1C1 = "=R(variable)C5*RC[-1]"
Next
Just one small change:
Cells(siste, "E").FormulaR1C1 = "=R" & variable & "C5*RC[-1]"

Excel VBA For loop in range contains gaps of 0's in sequence

I have isolated the problem and simplified the code to the root cause. More frustrating is that this loop works elsewhere without this error.
If you see the comments in the sequence here, there are 6 cells in the middle of the loop that turn into 0, and then after this 6 cell gap of 0's the loop works. I did use a msgbox to confirm the values were there. For whatever reason rows 130-135 always read as 0 though.
For x = 1 To 140
Cells(3 + x, "AW").Value = 70
'MsgBox (Cells(3 + x, "AW").Value)
'MsgBox confirms the correct value
'rows 130-135 are always empty with 0
Next
Any help greatly appreciated - very stumped at such a simple thing!
it was a simple logical error that was nested inside:
For a = 1 To Number_of_CalcRows
Cells(137 + a, "AW").Value = TenkanCurrent
Next
The problem was that I has not declared the TenkanCurrent with a value.

For loop to print control characters

Alright, so I am editing a Macro for Reflection Workspace Terminal Emulator. I have a macro (it is long and not necessarily relevant, I can post the full code if anyone wants, it is about a page)
At the very end of the macro, a "Good Morning" Message is printed, and then also the value of a string variable named myName. This works fine.
What I want to do is then use a For loop to print a number of control characters (tab) equal to the amount printed in the Good Morning User. Here is the code I have so far:
Dim X As Integer
For i = 1 To i = (13 + Len(myName)) 'Good Morning + a space character will always = 13
ibmCurrentScreen.SendKeys (ControlKeyCode_Tab)
Next i
End Sub
I would use the Chr() function to print the control characters, but it seems that this particular program uses ControlKeyCode_X to print them.
The For...Next loop in VBA is a simple single variable counter based loop and cannot have complex criteria or multiple counters like in some other languages (e.g. in C# for(int i = 0, int j = 10; i < 10; i++, j--)). In VBA, therefore, the i = after To is redundant because it cannot be anything other than i.
The 'For' loop in VBA therefore only specifies the counter once. It should be:
For i = startVal To endVal [step incrementVal]
...
Next [i]
i = variable to vary
startVal = start value
endVal = end value (inclusive, i.e. To 10 will include 10 as the final value)
incrementVal = optional value to increment/decrement by each loop
(default = 1)
The counter is always incremented/decremented at the end of each loop then evaluated against endVal (i.e. for a vanilla increment by one loop, after exiting the loop it will be endValue + 1).
Note (unrelated to your situation) that you can change the value of i in the loop, e.g. to increment twice because of some special circumstance.
Specifying the variable with the Next statement is optional and has no effect on behaviour (any nested loop must always be closed before an outer loop) but is good practice for reference when you have many For...Next loops so it is clear which loop the Next is closing (although you should be religiously indenting or otherwise delineating your code)
So in your case For i = 1 To (13 + Len(myName)) instead.

Nested loop with same variable

Is that possible for me to do nested loop in VB with same counter
The code is somehow like this
For a As Integer = 1 to Console.ReadLine
For a = 1 to a
Console.WriteLine("*")
Next
Console.WriteLine()
Next
The program is designed for drawing a triangle of * with just a single variable at all
VB just disallow me to use a in nested loop again
Error: ...Variable 'a' is alreay used by a independent loop.
I have my own usage, can only use 1 variable.
What changing the second FOR loop to a WHILE loop?
For a As Integer = 1 to Console.ReadLine
Do While a <=5
Console.WriteLine("Line: " & a)
Exit Do
Loop
Next
Here's a different idea. You may consider splitting your integer variable into 2 parts 16-bit parts, keep user's input in the upper 16-bits, and current iteration value in the lower 16-bits (you'll need to use WHILE instead of FOR).
In fact, what you need is to start your inner counter by the value of a, if I've understand. And what you are doing is create another loop inside starting by 1.
For a As Integer = 1 to Console.ReadLine
For b As Integer = a to 5
Console.WriteLine("Line: " & a)
Next
Next
You cannot declare another variable with the same name in the same scope. But the inner loop is in the same scope as the outer loop. That's why you get that compiler error.
You can use a different name:
Dim i As Int32
If Int32.TryParse(Console.ReadLine, i) AndAlso i > 0 Then
For a As Integer = 1 To i
For aa = 1 To i
Console.WriteLine("Line: {0} {1}", a, aa)
Next
Next
End If
To draw a triangle, as you describe, you need two variables, like this:
For a As Integer = 1 to Console.ReadLine
For b As Integer = 1 to a
Console.Write("*")
Next
Console.WriteLine()
Next
If you used the same variable in the inner loop, the inner loop would change the value of the variable, which in most cases would not be what you want, and in all cases would be incredibly confusing. For that reason, VB forces you to use a different iterator in each nested For loop.