Variable scope using VB in MS Access - function

I have not used VB much but as far as I can tell scope works the same as in C#. The problem is, I'm using VB in MS Access so I am unsure if the rules are a bit different (though I assume they are not). The following code shows values being assigned to variables which have only been declared within the function parameters. Specifically looking at PurchaseOrderID, I am unsure as to how it is retaining its assigned value for use in the function proceeding it.
Function Create(SupplierID As Long, EmployeeID As Long, OrderID As Long, PurchaseOrderID As Long) As Boolean
Dim rsw As New RecordsetWrapper
If rsw.OpenRecordset("Purchase Orders") Then
With rsw.Recordset
.AddNew
![Supplier ID] = SupplierID
If EmployeeID > 0 Then
![Created By] = EmployeeID
![Creation Date] = Now()
![Submitted By] = EmployeeID
![Submitted Date] = Now()
![Status ID] = Submitted_PurchaseOrder
End If
If OrderID > 0 Then
![Notes] = InsertString(PurchaseGeneratedBasedOnOrder, CStr(OrderID))
End If
If rsw.Update Then
.Bookmark = .LastModified
PurchaseOrderID = ![Purchase Order ID]
Create = True
End If
End With
End If
End Function
Function CreateLineItem(PurchaseOrderID As Long, ProductID As Long, UnitCost As Long, Quantity As Long) As Boolean
Dim rsw As New RecordsetWrapper
If rsw.OpenRecordset("Purchase Order Details") Then
With rsw.Recordset
.AddNew
![Purchase Order ID] = PurchaseOrderID
![Product ID] = ProductID
![Quantity] = Quantity
![Unit Cost] = UnitCost
CreateLineItem = rsw.Update
End With
End If
End Function
Can someone give me some insight on this?

In VBA, how you call a procedure can be important.
"Even if a called procedure has declared its parameters as ByRef, you
can force those to be ByVal by enclosing each argument within
parentheses."
-- http://www.cpearson.com/excel/byrefbyval.aspx
This is true of VBA in all Office applications. For example, let us say you have two procedures:
Sub SubByVal(ByVal Total As Integer)
Total = 50
End Sub
Sub SubByRef(ByRef Total As Integer)
Total = 50
End Sub
And you run a few tests:
Dim Total As Integer
Total = 100
These three versions work as expected and Total is equal to 100
Call SubByVal(Total)
SubByVal (Total)
SubByVal Total
These two work as expected and Total is equal to 50
Call SubByRef(Total)
SubByRef Total
However, in this version, in spite of calling ByRef, Total is equal to 100, because it is forced to ByVal by the parentheses.
SubByRef (Total)

Perhaps this is best handled by types. Something like this:
Type PurchaseSet
PurchaseOrderID As Long
OrderCreated as boolean
End Type
Function Create(SupplierID As Long, EmployeeID As Long, OrderID As Long ) As PurchaseSet
Dim rsw As New RecordsetWrapper
Dim ps as PurchaseSet
ps.OrderCreated = false
If rsw.OpenRecordset("Purchase Orders") Then
With rsw.Recordset
.AddNew
![Supplier ID] = SupplierID
If EmployeeID > 0 Then
![Created By] = EmployeeID
![Creation Date] = Now()
![Submitted By] = EmployeeID
![Submitted Date] = Now()
![Status ID] = Submitted_PurchaseOrder
End If
If OrderID > 0 Then
![Notes] = InsertString(PurchaseGeneratedBasedOnOrder, CStr(OrderID))
End If
If rsw.Update Then
.Bookmark = .LastModified
ps.PurchaseOrderID = ![Purchase Order ID]
ps.OrderCreated = True
End If
End With
End If
Create = ps
End Function

You can pass arguments to a VBA procedure by value or by reference. If you don't specify either ByVal or ByRef in the procedure's declaration, it defaults to ByRef. So the following two declarations are equivalent ...
Function DoSomething(PurchaseOrderID As Long) AS Boolean
Function DoSomething(ByRef PurchaseOrderID As Long) AS Boolean
The key here is that ByRef allows changes to the argument within the procedure to be transmitted back to the caller.

Related

Access ComboBox binding and Selected Value

I am trying to create a simple form for entering data. I have two tables, jobs and reports. The report table refers to a job with a one to many (one job, many reports). When browsing through the reports I want the combo box that lists all of the jobs to show the corresponding job as the selected value. This is easy in a .NET environment, but I'm not understanding how to set this up in the property sheet for the combobox. My ComboBox record source is from a query:
SELECT Jobs.UID, Jobs.Projectcode, Jobs.Projectname, Jobs.Owner, Jobs.Contractor
FROM Jobs
ORDER BY Jobs.[Projectcode];
And the form is based on a query that joins the tables:
SELECT Report.ID, Report.ReportNumber, Report.ReportDate, Report.Temperature, Report.Weather, Report.Progress, Report.PeopleatOAC, Report.Trades, Jobs.UID, Jobs.Projectname, Jobs.Owner, Jobs.Contractor
FROM Report
INNER JOIN Jobs ON Jobs.UID = Report.JobID
UNION ALL SELECT Report.ID, Report.ReportNumber, Report.ReportDate,
Report.Temperature, Report.Weather, Report.Progress, Report.PeopleatOAC,
Report.Trades, Jobs.UID, Jobs.Projectname, Jobs.Owner, Jobs.Contractor
FROM Report
LEFT JOIN Jobs ON Jobs.UID = Report.JobID WHERE (((Report.JobID) Is Null))
ORDER BY Report.ID;
The way I have this set up, a report can have a null job field. So I want to be able to select a job from the combo box to update the report table AND I want the combo box to reflect the correct job if the current record has a jobID associated with it. Is this possible?
What I have implemented is a tie to the Current event for the form that sets the combobox value (or clears it) depending on the JobID. As well as an tied to the changed event for the combobox to update the database with a selection. This works well enough but VBA feels so limiting compared to C#, WPF, and MVVM.
For anyone stumbling accross this with a similar question here are the 2 VBA functions:
Private Sub Form_Current()
Dim JobID As Integer
Dim i As Integer
Dim TempVal As Variant
Dim TestVal As Integer
TempVal = Me.JobID.Value
If Me.JobID.Value <> Empty Then
JobID = Me.JobID.Value
Else: JobID = -2
End If
With Me.JobCodeCombo
If (JobID >= 0) Then
For i = 0 To .ListCount - 1
TestVal = .Column(0, i)
If .Column(0, i) = JobID Then
.Value = .ItemData(i)
Exit For
End If
Next
Else
Me.JobCodeCombo = Null
End If
End With
End Sub
Private Sub JobCodeCombo_Change()
Dim ReportID As Long
Dim JobID As Long
Dim dbs As DAO.Database
Dim qdfUpdateJobID As DAO.QueryDef
Dim CurrentRecord As Long
CurrentRecord = Me.CurrentRecord
Set dbs = CurrentDb
Set qdfUpdateJobID = dbs.QueryDefs("UpdateReportWithJobID")
ReportID = Me.ReportID.Value
JobID = Me.JobCodeCombo.Column(0)
qdfUpdateJobID.Parameters(0).Value = JobID
qdfUpdateJobID.Parameters(1).Value = ReportID
qdfUpdateJobID.Execute
qdfUpdateJobID.Close
DoCmd.Save acForm, "Form1"
Me.Requery
DoCmd.GoToRecord acDataForm, "Form1", acGoTo, CurrentRecord
End Sub
The query called from the second function is a simple update query in my access file that has two parameters:
PARAMETERS [P1] Long, [P2] Long;
UPDATE Report SET JobID = P1
WHERE [ID] = P2;

MS Access 2007 DAO open recordset using a specified index

This is the first time I've asked a question although I have found the solutiion to many of my broblems here over the years.
I have a frustrating problem to which I cannot find an answer. I want to do the initial read prior to a read sequentially through a linked table opened as a dynaset DAO recordset using a specific index name as defined on the table.
My code returns error 3251 'operation is not supported...' on the .index line. No doubt there is an obvious solution (Mind you, I'm trying to avoid opening a SQL query which would be the obvious answer).
Public Function IOrdCustomerStock(CustomerID As Long, ProductID As Long, KeepRsOpen As Boolean, Optional UseIndexName As String) As Boolean
Set zcls_CS.CS_rs = CurrentDb.OpenRecordset(Name:=CS_TableName, Type:=RecordsetTypeEnum.dbOpenDynaset)
With zcls_CS.CS_rs
If Not IsMissing(UseIndexName) Then
.Index = UseIndexName
End If
.FindFirst "CS_CustomerID = " & CustomerID & " and CS_ProductID = " & ProductID
If .NoMatch Then
zcls_CS.CS_EOF = True
Else
zcls_CS.CS_EOF = False
zcls_CS.CS_ID = .Fields("[ID]")
zcls_CS.CS_CustomerID = .Fields("[CS_CustomerID]")
zcls_CS.CS_PhysSalesStock = .Fields("[CS_PhysSalesStock]")
zcls_CS.CS_ProductID = .Fields("[CS_ProductID]")
zcls_CS.CS_PurQuantityRecvd = .Fields("[CS_PurQuantityRecvd]")
zcls_CS.CS_PurUnitDesc = .Fields("[CS_PurUnitDesc]")
zcls_CS.CS_PurUnitFactor = .Fields("[CS_PurUnitFactor]")
zcls_CS.CS_SaleQuantityAlloc = .Fields("[CS_SaleQuantityAlloc]")
zcls_CS.CS_SaleQuantityOrdered = .Fields("[CS_SaleQuantityOrdered]")
zcls_CS.CS_SaleUnitDesc = .Fields("[CS_SaleUnitDesc]")
zcls_CS.CS_SaleUnitFactor = .Fields("[CS_SaleUnitFactor]")
End If
End With
If Not KeepRsOpen Then
Call IOclCustomerStock
End If
IOrdCustomerStock = Not zcls_CS.CS_EOF
End Function
Once I'd restricted the problem to linked tables, I found the following post:
https://social.msdn.microsoft.com/Forums/office/en-US/d402a8d2-0771-458c-b57e-09e2d6f0c536/trying-to-open-a-linked-table-whats-going-on?forum=accessdev
I don't pretend to understand the OpenDatabase parameters but it works. I just need to add the usual error handling to my little proof of concept:
Public Function IOksInitRsIX1CustomerStock(UseIndexName As String, CustomerID As Long, ProductID As Long) As Boolean
Set zcls_CS.CS_rs = OpenDatabase(Mid(DBEngine(0)(0).TableDefs(CS_TableName).Connect, 11)).OpenRecordset(CS_TableName)
With zcls_CS.CS_rs
zcls_CS.CS_rs.Index = UseIndexName
If (CustomerID > 0 And ProductID > 0) Then
.Seek "=", CustomerID, ProductID
Else
If CustomerID > 0 Then
.Seek "=", CustomerID
End If
End If
If .NoMatch Then
zcls_CS.CS_EOF = True
Else
zcls_CS.CS_EOF = False
zcls_CS.CS_ID = .Fields("[ID]")
zcls_CS.CS_CustomerID = .Fields("[CS_CustomerID]")
zcls_CS.CS_PhysSalesStock = .Fields("[CS_PhysSalesStock]")
zcls_CS.CS_ProductID = .Fields("[CS_ProductID]")
zcls_CS.CS_PurQuantityRecvd = .Fields("[CS_PurQuantityRecvd]")
zcls_CS.CS_PurUnitDesc = .Fields("[CS_PurUnitDesc]")
zcls_CS.CS_PurUnitFactor = .Fields("[CS_PurUnitFactor]")
zcls_CS.CS_SaleQuantityAlloc = .Fields("[CS_SaleQuantityAlloc]")
zcls_CS.CS_SaleQuantityOrdered = .Fields("[CS_SaleQuantityOrdered]")
zcls_CS.CS_SaleUnitDesc = .Fields("[CS_SaleUnitDesc]")
zcls_CS.CS_SaleUnitFactor = .Fields("[CS_SaleUnitFactor]")
End If
End With
IOksInitRsIX1CustomerStock = Not zcls_CS.CS_EOF
End Function

RecordsetClone.RecordCount won't return zero

The following function correctly returns numbers if greater than zero. If zero, it does nothing - no zero, no number, not even an error code. The result is not 'null' either. The text box is just blank.
Public Function NumRecs() As Integer
Me.RecordsetClone.MoveLast
NumRecs = Me.RecordsetClone.RecordCount
End Function
It is on a form whose data comes from:
SELECT tblClient.ClientID, tblDisclosure.ID, tblDisclosure.ProbChased
FROM tblDisclosure INNER JOIN tblClient ON tblDisclosure.Client = tblClient.ClientID
WHERE (((tblDisclosure.ProbChased) Is Not Null)) ORDER BY tblClient.ClientID ;
All you need should be:
Public Function NumRecs() As Integer
NumRecs = Me.RecordsetClone.RecordCount
End Function
It cannot return "nothing", so if that is what you see, the function isn't called.
If you absolutely need a zero returned you can check .BOF and .EOF
Public Function NumRecs() as Integer
NumRecs=0
with Me.RecordsetClone
If Not (.BOF and .EOF) then
.MoveLast
NumRecs=.RecordCountd
End if
End with
End Function

SSRS distinct lookupset function

I'm using Join(Lookupset) to find unique group values which returns a sequence number. This is my function:
Join(LookupSet(Fields!itemId.Value & Fields!UseByDate.Value & Fields!rackId.Value
, Fields!itemId.Value & Fields!UseByDate.Value & Fields!rackId.Value
, Fields!CustomerSeqNo.Value
, "PickingList"), ",")
The problem is on some items there are multiple transactions. I want to remove the duplicates.
I found a blog http://blogs.msdn.com/b/bobmeyers/archive/2012/06/18/creating-short-lists-using-the-lookupset-function.aspx but could not get SSRS Report Builder to reference Linq assembly. My issue is
How can I just show the unique values?
You don't need Linq, but you do still need custom code (in BIDS go to Report -> Report Properties -> Code)
You can put a RemoveDuplicates function in here, something like this:
Public Shared Function RemoveDuplicates(m_Array As Object()) As String()
System.Array.Sort(m_Array)
Dim k As Integer = 0
For i As Integer = 0 To m_Array.Length - 1
If i > 0 AndAlso m_Array(i).Equals(m_Array(i - 1)) Then
Continue For
End If
m_Array(k) = m_Array(i)
k += 1
Next
Dim unique As [String]() = New [String](k - 1) {}
System.Array.Copy(m_Array, 0, unique, 0, k)
Return unique
End Function
To use it in your Join:
Join(Code.RemoveDuplicates(LookupSet(...)),",")
I agree with #user3697615 that Report Code is best. However, I prefer to build it straight into a string:
public shared function JoinDistinct(
dups as object(),
delimiter as string
) as string
dim result as string = ""
system.array.sort(dups)
for i as integer = 0 to dups.length - 1
if i <> 0 then result += delimiter
if i = 0 orElse dups(i) <> dups(i-1) then result += dups(i)
next i
return result
end function
This way, we eliminate one nested function on the call:
=Code.JoinDistinct(LookupSet(...), ",")
If you're like me, you also want the elements in order based on frequency (descending order).
I created the following VisualBasic code to do so
Public Shared Function RemoveDuplicates(dataset As Object()) As String()
Dim unique As New System.Collections.Generic.List(Of String)
Dim frequency As New System.Collections.Generic.List(Of Integer)
For i As Integer = 0 To dataset.Length - 1
Dim index As Integer = -1
For j As Integer = 0 To unique.Count - 1
If dataset(i).Equals(unique(j)) Then
index = j
Exit For
End If
Next
If index < 0 Then
unique.Add(dataset(i))
frequency.Add(1)
Else
frequency(index) += 1
End If
Next
Dim uniqueArray As [String]() = unique.ToArray()
Array.Sort(frequency.ToArray(), uniqueArray)
Array.Reverse(uniqueArray)
return uniqueArray
End Function
This is based off others' answers where the SSRS expression is the following
Join(Code.RemoveDuplicates(LookupSet(...)),",")
Note: I learned VisualBasic in about an hour to solve this problem, so my algorithm probably isn't the most efficient.
I liked pwilcox's idea, so I wrote this one which filters out null and blank values.
Public Function JoinDistinct(arr As Object(), delimiter As String) As String
System.Array.Sort(arr)
Dim result As String = String.Empty
Dim lastvalue As String = String.Empty
For i As Integer = 0 To arr.Length - 1
If Not arr(i) Is Nothing And arr(i) <> lastvalue And arr(i) <> String.Empty Then
If result = String.Empty Then
result = arr(i)
Else
result = result + delimiter + arr(i)
End If
End If
lastvalue = arr(i)
Next
Return result
End Function
Usage:
=Code.JoinDistinct(LookupSet(...), ",")

How to avoid duplicate entries in access from vb6?

I am using Vb6 and Access 2007. I am adding records to the access table name "subjectcode" from vb6. The details of subjectcode table are below.
Subjectcode table : Heading(Degree,Branch,Year1,Year2,Semester,Subjectcode,Subjectname,Theory_Practical, Major_Allied_Elective) values (Bsc,computerscience,2001,2004,1,RACS1,Vb6 programming,Theory,Major)
Note :The primary key in the above table is Degree,Branch,Year1,Year2,Semester,Subjectcode
And the code i used to add entry to the access table from vb6 are given below :
If degree = "" Or branch1 = "" Or year1 = "" Or year2 = "" Or semester = "" Or subcode.Text = "" Or subname.Text = "" Or theory.Text = "" Or major.Text = "" Then
MsgBox "Fields can't be empty ! All are mandatory!"
Else
rs.Open "select * from subjectcode", con, 1, 3
rs.AddNew
rs!degree = degree
rs!branch = branch1
rs!year1 = year1
rs!year2 = year2
rs!semester = semester
rs!Subjectcode = subcode.Text
rs!Subjectname = subname.Text
rs!Theory_Practical = theory.Text
rs!Major_Allied_Elective = major.Text
rs.Update
MsgBox "Successfully Saved !", vbOKOnly + vbInformation, "info"
rs.Close
End If
And the screenshot of that Add form of vb6 is here: http://tinypic.com/r/w7c7if/6
The record is added when the same entry is not exist. And if the record is already exist it should say "Record Already exists" and i don't know how to do that. Could you guys give me idea please.
Write a save method to save your record, and a method to query the database and check for an existing record before you save the data.
Public Sub SaveSubjectCode(ByVal vDegree As String, ByVal vBranch As String, ByVal vYear1 As Integer, ByVal vYear2 As Integer, ByVal vSemester As Integer, ByVal Subjectcode...)
If (DoesRecordExist(vDegree, vBranch, vYear1, vYear2, vSemester, vSubjectcode) = True Then
' Warn the user
MessageBox("I'm sorry Dave I can't do that. The record already exists.")
Else
' Save the record
End If
End Sub
Private Function DoesRecordExist(ByVal vDegree as String, ByVal vBranch As String, ByVal vYear1 As Integer, ByVal vYear2 As Long, ByVal vSemester As Integer, ByVal vSubjectcode As String) As Boolean
RecordSet = query 'Query the database for the existing record
If RecordSet.BOF And RecordSet.EOF Then
DoesRecordExist = False
Else
DoesRecordExist = True
End If
End Function
Also, you want to avoid the the Select * query that selects every record unless you really need it because it is likely to be slow, and get slower as the number of records grow. If you want to get a recordset just to use to add a new record you can include a Where clause that does not return any records "select * from subjectcode WHERE 1 = 2, con, adOpenKeyset, adLockOptimistic
When you create a field in a table you specify whether duplicates are allowed or not. Then an offending add or update raises an exception.
The SQL DML looks like:
ALTER TABLE tblCustomers
ADD CONSTRAINT CustomerNames UNIQUE
([Last Name], [First Name])
And there are other powerful features such as check contraints as well:
ALTER TABLE tblInvoices
ADD CONSTRAINT CheckAmount
CHECK (Amount > 0)
One advantage of constraints is that your database's integrity is not held hostage by rogue applications that may fail to do chatty pre-qualification queries (or fail to implement them properly). Another is the performance improvement over chatty techniques and the fact that when there are multiple updaters the database can change between pre-qual query and update.