Access: Grouping in Reports - ms-access

I have a report in my Access database containing multiple fields, e.g., it looks like as follows:
Name John
Number 18
Place somewhere
Name John
Number 19
Place somewhere
Name John
Number 20
Place somewhere
Name Bogo
Number 30
Place somewhere
John can have multiple Numbers. As you can see, "John" is listed multiple times, each with a different Number. I would like to get just one detail row with the name "John", something like the following:
Name John
Number 18; 19; 20
Place somewhere
Name Bogo
Number 30
Place somewhere
How can I achieve this?

The solution is to use VBA code in an Access Module to "string together" the repeated values ("Number" in your case). The general form of the code is:
Option Compare Database
Option Explicit
Public Function ListChildNames(ParentID As Long) As String
Dim cdb As DAO.Database, rst As DAO.Recordset, rtn As String
Const separator = "; "
Set cdb = CurrentDb
Set rst = cdb.OpenRecordset( _
"SELECT [ChildName] FROM [ChildTable] " & _
"WHERE ID=" & ParentID, _
dbOpenSnapshot)
rtn = ""
Do While Not rst.EOF
rtn = rtn & rst![ChildName] & separator
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
Set cdb = Nothing
If Len(rtn) > 0 Then
rtn = Left(rtn, Len(rtn) - Len(separator)) '' trim trailing separator
End If
ListChildNames = rtn
End Function
(You will have to tweak the table and column names to match your table structures.)
Then, in the Record Source for your report, instead of using...
SELECT [Name], [Number] ... FROM ...
...you would use something like...
SELECT [Name], ListChildNames([Name]) AS Numbers ... FROM ...
...to retrieve the [Name] and the (concatenated list of) [Number] values in a single row.

Related

Select randoms records from table but not more than 2 records of same name

I want to fetch TOP N random records from the table but not more than 2 records for same name.
SELECT TOP 7 Table1.ID, Table1.Name, Table1.Salary, Rnd(Abs([Table1]![id])) AS Expr1
FROM Table1
GROUP BY Table1.ID, Table1.Name, Table1.Salary, Rnd(Abs([Table1]![id]))
ORDER BY Rnd(Abs([Table1]![id]));
It is giving more than two records for same name. Would someone please provide some assistance.
Use this query:
SELECT
ID,
[Name]
FROM
[Table1]
ORDER BY
Rnd(-Timer()*[ID]);
Then open it as a Recordset and traverse it from the start and pick IDs (could be saved in an array) while recording the the Name used (a Collection could be used for this).
If a Name has been used twice, skip the record and move to the next.
When you have picked seven IDs, stop. The array of IDs will identify your seven records.
Save the query as RandomAll. Then use it in this function:
Public Function RandomTwo() As long()
Dim rs As DAO.Recordset
Dim Names As New Collection
Dim Used As Integer
Dim Index As Integer
Dim Ids() As Long
Set rs = CurrentDb.OpenRecordset("RandomAll")
ReDim Ids(0)
Do While Not rs.EOF
Used = 0
' Read used count. Will fail if not used.
On Error Resume Next
Used = Val(Names.Item(rs.Fields(1).Value))
On Error GoTo 0
Debug.Print Used, ;
If Used = 1 Then
' Remove key to be added later with updated use count.
Names.Remove rs.Fields(1).Value
End If
If Used < 2 Then
' Record the use count (as text) of the key.
Names.Add CStr(Used + 1), rs.Fields(1).Value
Debug.Print rs!ID.Value, rs.Fields(1).Value
' Add ID to array.
Ids(UBound(Ids)) = rs!ID.Value
If UBound(Ids) = 6 Then
' Seven IDs found.
Exit Do
Else
' Prepare for next ID.
ReDim Preserve Ids(UBound(Ids) + 1)
End If
End If
rs.MoveNext
Loop
rs.Close
' List the found IDs.
For Index = LBound(Ids) To UBound(Ids)
Debug.Print Index, Ids(Index)
Next
' Return the IDs.
RandomTwo = Ids
End Function
The function will return the array holding the seven IDs.
Taking inspiration from Gustav's answer I have designed a bit of VBA code that will generate a SQL string which when used will give you N amount of random records with a limit of 2 per name.
Const PicksLimit As Long = 7 'How many records do you want to select
Dim rs As DAO.Recordset
'Select randomised table
Set rs = CurrentDb.OpenRecordset("SELECT ID, Name From Table1 ORDER BY Rnd(Abs(ID))")
'Define variables for keeping track of picked IDs
Dim Picks As Long, PickNames As String, PicksSQL As String
Picks = 0
PickNames = ""
PicksSQL = ""
With rs
If Not (.BOF And .EOF) Then 'If table is not empty...
.MoveFirst
'Loop until limit reached or table fully looked through
Do Until Picks = PicksLimit Or .EOF
'If name has been picked less than twice before
If Len(PickNames) - Len(Replace(PickNames, "[" & !Name & "]", "")) < ((Len(!Name) + 2) * 2) Then
Picks = Picks + 1 'Increment counter
PickNames = PickNames & "[" & !Name & "]" 'Add name for later checks
PicksSQL = PicksSQL & "ID = " & !Id & " OR " 'Append SQL string
End If
.MoveNext
Loop
'Add front sql section and remove last OR
PicksSQL = "SELECT * FROM Table1 WHERE " & Left(PicksSQL, Len(PicksSQL) - 4)
Else
'If the table is empty no need for ID checks
PicksSQL = "SELECT * FROM Table1"
End If
End With
rs.Close
Set rs = Nothing
'Print SQL String (This can be changed to set a RecordSource or similar
Debug.Print (PicksSQL)
At the moment the SQL string is just printed to the Immediate window but this can be changed to go wherever you need, like a subform's RecordSource for instance.
The code will need to be run every time you want a new random list but it shouldn't take a huge amount of time so I don't see that being too big an issue.

Run time error '3164': 'Field cannot be updated' - Access VBA issue

I'm having an issue with a piece of VBA I've written for Access. I have a table with a concatenation field 'Concat' (string field) and field called 'Age' (integer field with numerical values).
There are another 61 fields (named '0','1','2'...'60' respectively) where the code needs to work though: I want the code to loop through and, per record entry - for the VBA to Dlookup using the Concat + age fields to another table (called: tbl_Final_Probabilities) and pull back a probability and populate each of these 61 fields with the correct probability. These fields are set up at a numerical field, data type as Single.
The code pulls the correct probability but when I try to update the record for that field at the code line: "rs.Fields(a) = b" (also highlighted in code), I get the error message: "Run time error '3164': 'Field cannot be updated'".
All help welcome on how I need to correct this please, the code used is below.
Punch and pie.
Code:
Dim rs As DAO.Recordset
Dim a As Integer
Dim b As Single
Dim lookup As String
Set rs = CurrentDb.OpenRecordset("SELECT * FROM tbl_Circuit_plus_prob")
For a = 0 To 60
If Not (rs.EOF And rs.BOF) Then
rs.MoveFirst
Do Until rs.EOF = True
rs.Edit
lookup = rs!Concat & (rs!age + a)
b = DLookup("Prob_Date", "tbl_Final_Probabilities", "Concat2 = '" & lookup & "'")
rs.Fields(a) = b '- CODE BREAKS DOWN HERE
rs.Update
rs.MoveNext
Loop
End If
Next a
rs.Close
Set rs = Nothing
Thanks in advance for any and all help.
You loop is turned inside out:
Dim rs As DAO.Recordset
Dim a As Integer
Dim b As Single
Dim lookup As String
Set rs = CurrentDb.OpenRecordset("SELECT * FROM tbl_Circuit_plus_prob")
If Not (rs.EOF And rs.BOF) Then
rs.MoveFirst
Do Until rs.EOF = True
rs.Edit
For a = 0 To 60
lookup = rs!Concat & (rs!age + a)
b = DLookup("Prob_Date", "tbl_Final_Probabilities", "Concat2 = '" & lookup & "'")
rs.Fields(a).Value = b
Next
rs.Update
rs.MoveNext
Loop
End If
rs.Close
Set rs = Nothing
Your code: rs.Fields(a) = b addresses the field with the index 'a' (which is 0 in the first loop), in your table, this is probably an auto increment field and therefore cannot be updated. If you want to write in the fields with the names '0','1',... use this syntax: rs.Fields(x + a) = b, where x is the number of fields+1 (because your loop starts with 0) in the table existing before your field '0'.

Is there a way to conditionally list field names on an access form?

I have a large database that monitors employees attendance at training events. Each record is an employee, and there are (along with basic information) a hundred or so fields that represent training events over the last 10 years. These fields are all yes/no checkboxes and so are checked if the employee attended that workshop.
I have a form that allows viewing information for individual employees easily, but I want a dynamic list on this form that shows what events the currently selected employee attended.
So I want a list to see which fields are designated as true for the specified record.
Is there any way to do this? Been searching all day and can't find a solution. Thanks.
Maybe somthing like this, assuming that all boolean fields are relevant and field name is workshop name:
Public Function getWorkshops(ByVal strEmployee As String) as String
' Declare vars
Dim db as DAO.Database
Dim rs as DAO.Recordset
Dim lngFieldsCount as Long
Dim n as Long
Dim strWorkshops as string
Set db = CurrentDb() '
lngFieldsCount = db.TableDefs("myTable").Fields.Count ' Count number of fields to iterate through
Set rs = db.OpenRecordset("SELECT * FROM myTable WHERE employeeName LIKE '" & strEmployee & "';",DbOpenSnapshot)
Do While not rs.Eof
For n = 0 to lngFieldsCount -1 ' iterate through all fields
If TypeOf rs.Fields(n) is dbBoolean Then ' check if field is boolean
If rs.Fields(n) Then ' check if boolean is true
strWorkshops = strWorkshops & rs.Fields(n).Name & vbCrLf ' add field names to string, separated by linebreaks
End If
End If
Next n
rs.MoveNext
Loop
getWorkshops = strWorkshops 'Set result of function
'Clean up
rs.Close
Set rs = Nothing
Set db = Nothing
End Function
This returns the name of all true fields in a string, separated with linebreaks (not tested).

RecordSet and Ms Access 2007

I have a query that has CustID with mutiple Business affiliated with the CustID. I can't use Dlookup because it only returns one variable. I want to show on a form that for this custID, here are all the businesses it's affiliated it. I want the Businesses to show up into a field (business) in another table on the form.
I started out by this
Public Sub OpenRecordset()
Dim db As Database
Dim rs As Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("Q:businesses")
Do While Not rs.EOF
T:Custinfo!business = NAME (I am lost in between on how to identify the custid and place the businesses into the table field as a Dlookup)
rs.movenext
Loop
rs.Close
Set rs = Nothing
db.Close
End Sub
I keep looking at other examples but can't seem to tie together where the dlookup replacement will take place and how will you have to put this on a form as a datasheet?
You don't need a DLookup. You could do one of two things:
1) Use a listbox and set the recordsource equal to your query (assuming Q:businesses has been appropriately defined to give the businesses as a result)
2) Still need your query to be appropriate, but you could create a string with all of the businesses in it:
Public Sub OpenRecordset()
Dim db As Database
Dim rs As Recordset
Dim StrBusinesses As String
Set db = CurrentDb
Set rs = db.OpenRecordset("qryBusinesses")
If rs.EOF and rs.BOF Then
MsgBox("No businesses exist for this Customer")
Exit Sub 'Or do whatever else you want if there are no matches
Else
rs.MoveFirst
End If
StrBusinesses = ""
Do While Not rs.EOF
StrBusinesses = StrBusinesses & rs!Business & ", "
rs.movenext
Loop
rs.Close
StrBusinesses = Left(StrBusinesses, Len(StrBusinesses) - 2)
Forms!MyForm.MyField = StrBusinesses 'Set the field equal to the string here
Set rs = Nothing
db.Close
End Sub
Of course this assumes that the query "Q:Business" is defined to get the appropriate info such as:
SELECT custID, business FROM tblBusinesses WHERE custID = X
where "X" is the custID you are looking for.
If you need to set the query dynamically, you will need to set a querydef.
EDIT to include querydef code***********************
Also changed the name of the query to "qryBusinesses" in the code above and below as I'm not sure whether you can make a query with a colon in it.
To set the querydef, put this at the beginning of the code:
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("qryBusinesses")
qdf.SQL = "SELECT custID, business FROM tblBusinesses" _
& " WHERE custID = " & Forms!MyForm.CustID 'replace with actual form and field
This assumes that i) qryBusinesses exists already,
ii) custID is a number field
EDIT**************
If you define the query to look at the form itself, you would not need to set the sql, so if the query were defined (either in VBA or through the query wizard) as:
qdf.sql = "SELECT custID, business FROM tblBusinesses" _
& " WHERE custID = Forms!MyForm.CustID"
then you would not need to redefine the sql. However, it is a bit more dynamic to put the custID into the qdf itself as it is easier to debug any issues as you can see the exact sql that is being run in the original method.

How to evaluate text of a Field Name in Access VBA

I'm looking to iterate through a set of fields as part of a table that follow a specific format. The table contains N pairs of these fields (Var_1, Value_1, Var_2, Value_2, etc).
I need to iterate through these fields and extract the data to write it out to a text file. I'd like to use a loop, I'd rather not hardcode each field into my VBA as the fields will likely grow in time and not every field will be populated for every record. I guess if there was a way to "evaluate" a field when its name is dynamically created in a string, that would solve my problem.
So, for example...
For i = 1 to FieldCount
' Write data to each field
Initially I thought of something like this
For i=1 to FieldCount
myStr = "!Var_"+CStr(i) + " = " + "!Value_" + CStr(i)
WriteLine(myStr)
but course, it prints the text literally... any way to get Access to evaluate that text first?
Perhaps there is a better way to approach this entirely?
You can use your record set's Fields collection, and refer to the value of any individual field by referencing the field's name.
rs.Fields("field_name").Value
You can generally drop the explicit .Value since that's the field's default property.
Public Sub printFieldPairs()
Dim strSql As String
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim N As Long
strSql = "SELECT * FROM tblJoeS;"
Set db = CurrentDb
Set rs = db.OpenRecordset(strSql)
With rs
Do While Not .EOF
For N = 1 To 3
Debug.Print .Fields("Var_" & N) & " = " & _
.Fields("Value_" & N)
Next N
.MoveNext
Loop
End With
rs.Close
Set rs = Nothing
Set db = Nothing
End Sub
That's what I think you're asking for. However, if the number of field pairs grows, you will have to revise the table structure. So my inclination would be to use a different table structure.
row_identifier fKey fValue
1 Var_1 foo
1 Var_2 bar
1 Var_3 baz
2 Var_1 abcd
As suggested improvement to HansUp answer:
....
Dim fld as DAO.Field
For Each fld in rs.fields
debug.? fld.value
Next fld