Lookup value in Access table without a unique identifier using VBA - vba

I have a table like this:
+-----------+------+
| TableName | Flag |
+-----------+------+
| TableG | 0 |
| TableE | 0 |
| TableR | 1 |
| TableL | 1 |
| TableN | 0 |
What I'm trying to do is have a function that looks up every TableName where Flag=1 so that I can assign each table where Flag=1 to a button in a form (e.g. assign the first instance to Button1, etc.). The tricky part is that this table cannot be edited, so no unique ID/count can be assigned to easily grab items.
Here's what I've tried so far:
DLookup does not handle having multiple lookup matches (e.g. DLookup("TableName", "MyTable", "Flag=1") would not allow me to access all the values where Flag=1), so I don't think I can use that to store them in an array
I've tried using a RecordSet (see below), but I could not figure out how to have the SQL string automatically add a count so that each table name could be referenced (e.g. TableNameRS(1) to pull the name of the first table with Flag=1)
Public Function TableNameRS(TableID As Integer)
Dim RS As DAO.Recordset
Dim SQL As String
Dim Name As Variant
SQL = "select TableName, COUNT(TableName) from MyTable where Flag=1"
Set RS = CurrentDb.OpenRecordset(SQL)
Name = RS("TableName")
TableNameRS = Name
RS.Close
End Function
I am out of ideas - what are my options here?

If the number of records and buttons are fixed, say 10, and buttons have names like btn1, btn2, etc., consider a procedure like:
Public Sub TableNameRS(TableID As Integer)
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT TableName FROM MyTable WHERE Flag=1 ORDER BY TableName")
For x = 1 to 10
Me("btn" & x).Caption = RS("TableName")
RS.MoveNext
Next
RS.Close
End Function
Procedure can be called in form Open event. Then code in button Click event can pass its caption as an argument to whatever other procedure needs to take action with TableName value.
Calculating a sequence identifier in Access query is a common topic MS Access Restart Number Sequence.
Assuming TableName values are unique:
SELECT TableName, DCount("*", "table", "TableName<'" & [TableName] & "' AND Flag=1")+1 AS Seq,
FROM table
WHERE Flag=1
ORDER BY TableName;
Can use that query as RecordSource for form or report or execute DLookup() on it.
Be aware any change in filter will impact sequence calculation.

Related

Access Query or VBA - Split data into line level

How do I get the following
Project | Code 1 | Code 2 | Code 3
1 | a | b | c
2 | a | d
translate into getting code on line level by project?
Project | Code
1 | a
1 | b
1 | c
2 | a
2 | d
I am currently doing this manually but the problem is I have 30 codes. My current process is do a select query and then do a union for all 30 codes. Can someone help me out with this? I know a vba script will do the job but weren't sure how to do this. Thanks!
You can support any number of project codes by creating a new table, let's call it ProjectCode.
So you'd have one table for Projects, that doesn't have anything to do with codes.
If codes have extra data attached, you can create a Code table and add that information.
ProjectCode helps the database to see how they are related by using a JOIN. the structure would be
ProjectID | CodeID
What I've done for cases like this, is to use the main form for projects, and have a continuous form in datasheet view as a subform, linking to the ProjectCode-like table, and link the ID from Projects to the ProjectID of the subform. (Link Master Fields & Link Child Fields properties of the subform object), note you'll want to hide ProjectID on the subform.
To get a list of codes, you can just query ProjectCode with the project ID. if you need additional information from projects, use a join:
SELECT * FROM Projects INNER JOIN ProjectCode ON ProjectCode . ProjectID = Projects.ID
WHERE ID = 'some id'
Note that the information from Projects will be repeated for each code.
With this function you can read data from a table with N codes fields named (code1, code2, ..., codeN) and write them to a table with only two fields:
Function Verticalize(numberofcodes As Integer)
Dim sql As String
Dim rst As New ADBDb.Recordset
sql = "SELECT * FROM tname"
rst.Open sql, CurrentProject.AccessConnection
While Not rst.EOF
For i = 1 To numberofcodes
DoCmd.RunSQL "insert into tdest values (" & rst(0) & "," & rst("code") & Str(i) & ")"
Next i
rst.MoveNext
Wend
End Function

VB Loop for MS Access that adds numbers

I'm new to coding with Access. I'm wanting to create a Do While Until loop that compares the amounts of two tables. One table has a record with customer numbers with the total amount for each customer number and then another table that has all detail line items with amounts for the items for each customer.
Example:
Summary
CustID | Total |
1234 | $20.00 |
2345 | $40.00 |
Detail
CustID | DocNo | Amount | Included |
1234 | 0000 | $15.00 | |
1234 | 1111 | $5.00 | |
1234 | 2222 | $3.00 | |
I wanted to be able to execute the loop and then update the Included column to "Yes" to all applicable line items whose accumulated sum added to the total in the Summary table.
So the example above would update the first and second rows of the Detail table to "Yes" and then move on to the next CustID, because the first two row Amounts totaled to $20.00 - the amount in the Summary table. The last record in the detail table will not be updated to anything, because it does not apply. The good thing about the order of these records is that the records that will be labeled "Yes" will be the first cumulative set of records; not every other record, or random records. There are no fixed number of records for each total, it varies - therefore, I need the loop.
Also, does anyone have a recommendation for how to best learn VB/VBA for Access and Excel? I only understand SQL and I'm trying to enhance my coding skills, so I'm not having to manually subtotal these amounts and then delete the 30,000 non applicable records in an Excel spreadsheet.
Thanks in advance!
I've worked out a first approach, using DAO, your tables are supposed to be Summary and Detail:
Sub sof20295931SummaryDetail()
Dim lCustID0 As Long
Dim dblDetailTotal As Double, dblSummaryTotal As Double
Dim strSQL As String
Dim rst0 As DAO.Recordset, rst As DAO.Recordset
'
' get summary recordset:
'
strSQL = "SELECT CustID, Total" _
& " FROM Summary" _
& " ORDER BY CustID;"
Set rst0 = CurrentDb.OpenRecordset(strSQL)
'
' get detail recordset:
'
strSQL = "SELECT CustID, DocNo, Amount, Included" _
& " FROM Detail" _
& " ORDER BY CustID,DocNo;"
Set rst = CurrentDb.OpenRecordset(strSQL)
lCustID0 = rst0!CustID
dblSummaryTotal = rst0!Total
dblDetailTotal = 0
'
' check the recordset of the Detail:
'
Do While (Not rst.EOF)
'
' change CustID:
'
If (rst!CustID > lCustID0) Then
rst0.MoveNext
lCustID0 = rst0!CustID
dblSummaryTotal = rst0!Total
dblDetailTotal = 0
End If
'
' sum up:
'
dblDetailTotal = dblDetailTotal + rst!amount
'
' modify record:
'
rst.Edit
'
If (dblDetailTotal <= dblSummaryTotal) Then
rst!Included = "Yes"
Else
rst!Included = Null
End If
'
rst.Update
'
' go to the next record:
'
rst.MoveNext
Loop
'
' destruct objects:
'
rst0.Close
Set rst0 = Nothing
'
rst.Close
Set rst = Nothing
End Sub
My Windows Locale used Euro Money format, here is the Summary table:
And Detail table in which I added some extra records:
Detail after modification:
Since no reply on whether a more SQL-centered approach is okay, here it is.
The field Included does not logically belong in Detail, but in Summary. If you want to keep it in Detail, using SQL is a challenge: you can't use the aggregate function Sum() and also update values. But if you move Included to Summary, SQL is the natural solution. I would recommend this approach.
qrySummary (to be nested in second query below)
SELECT CustID, Sum([Total]) AS CustomerTotal
FROM tblDetail GROUP BY CustID;
qryValidate
UPDATE tblSummary LEFT JOIN qrySummary ON tblSummary.CustID = qrySummary.CustID
SET tblSummary.Included =
IIf([qrySummary].[CustomerTotal]=[tblSummary].[Amount],True,False);
To follow through on the spreadsheet, you will need to view Included at the detail level; this will require a query that joins Detail and Summary.

Create Access report that lists information from one table, which is matched on ID from another table

I'm updating this as the previous answer turned out to not be what I really needed.
I have two tables in my Access database. One is called Billings, one is called Bookings. Each table has a column called Booking Number, which is how they are related. The Billings table also has a field called Container. What I want to do is:
Create a one page report for each Booking Number (which can be expanded to multiple pages depending on the number of containers)
In the body of the report I would like to list each Container (along with other information about each container) on separate rows
Here are my tables:
Bookings
----------------
ID | Booking Number
1 | '1234'
2 | '1235'
Billings
----------------
ID | Booking Number | Container
1 | '1234' | '12'
2 | '1234' | '16'
3 | '1235' | '18'
Based on these two tables, there should be a report for Booking #1234, and Booking #1235. Booking #1234's report should list Container's 12 and 16, while Booking #1235's report would only list 18. I'm a PHP/MySQL developer, so I understand SQL queries. However, the way I would write the query for MySQL obviously does not work for Access. At the moment I am using a Module similar to the answer below, but it does not do what I need it to do. This is my current query:
SELECT
b.*,
ListContainers(b.[Booking Number]) AS Containers
FROM
Bookings AS b
WHERE
((b.[Booking Number]) Is Not Null);
This will create a comma separated list of the containers associated with each Booking Number, but I want to create a separate row for each Container, which will also include other information from the Billings table.
Has anyone had any experience with a similar situation, or know any steps I could take to accomplish what I'm looking to do?
For your new requirements you'll want to use a "subreport" to list the containers (and related information). For details, see my recent answer to a similar question here.
[This is my answer to the original question, before the "spec" changed.]
Create a new Module in Access and paste in the following code
Public Function ListContainers(Booking_Number As String) As String
Dim cdb As DAO.Database, rst As DAO.Recordset, rtn As String
Const Separator = ", "
Set cdb = CurrentDb
rtn = ""
Set rst = cdb.OpenRecordset("SELECT Container FROM Billings WHERE [Booking Number]=""" & Booking_Number & """ ORDER BY Container", dbOpenSnapshot)
Do While Not rst.EOF
rtn = rtn & rst!Container & 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
ListContainers = rtn
End Function
You can then use that Function in a query, like this
SELECT [Booking Number], ListContainers([Booking Number]) AS Containers
FROM Billings
That will return
Booking Number Containers
-------------- ----------
1234 12, 16
1235 18

How do I limit multi-select options for one field based on data entered into another field when they both reference the IDs of another table?

I have two tables, we'll call them T1 and T2. T1 currently has nearly 600 records in it, one of which contains the ID number and another of which contains a title, so ID and TITLE:
T1
ID | TITLE
-----|----------
1 | Title ABC
... | ...
201 | Title XYZ
... | ...
411 | Title 123
T2 has an ID field, a Titles field, an Accepted Titles field, and a Rejected Titles field, so ID, TITLES, ACCEPTED TITLES, and REJECTED TITLES. The access form uses a multiple select ListBox to select one or more TITLES from T1, however many are required, but usually no more than ten. Once entries are made into the TITLES field of T2, which is Numeric for the record IDs corresponding to the titles selected from T1, I want a combo box for each of the ACCEPTED TITLES and REJECTED TITLES in T2 to be limited to showing only those titles that correspond to IDs entered into the TITLES field. So, if I have in the TITLES field of T2,
T2
ID | TITLES | ACCEPTED TITLES | REJECTED TITLES
---|---------------|------------------|----------------
1 | 1, 201, 411 | |
I want the dropdown for the ComboBox to show only the titles corresponding to those IDs entered into the TITLES field. So, taking the ACCEPTED TITLES field, it might look like this:
T2
ID | TITLES | ACCEPTED TITLES | REJECTED TITLES
---|---------------|--------------------|---------------
1 | 1, 201, 411 | | [ ] Title ABC \/|
| [ ] Title XYZ |
| [ ] Title 123 |
I'm thinking I should be able to build a SELECT WHERE IN (...) statement that I can use in the "Row Source" properties of ACCEPTED TITLES and REJECTED TITLES. Then the list would be as short as the items selected for TITLES rather than 600+ records long. This also completely eliminates the potential for erroneous input under ACCEPTED TITLES or REJECTED TITLES since those titles can only be selected from those entered under TITLES. But, I don't yet know how to build such a SELECT statement.
Any assistance will be appreciated. Thanks for your time.
I propose a slightly different design for T2 (TitleStatus).
Create Table [TitleStatus] ([TitleID] Number, [StatusID] Number);
Create Index TitleIDindex ON [TitleStatus] (TitleID) WITH PRIMARY;
Then another table to house the statuses. Something like
Create Table [Statuses] ([StatusID] Number, [StatusText] String);
Create Index StatusIDindex ON [Statuses] (StatusID) WITH PRIMARY;
(this table could be organized with one column, but either way you want to prevent statuses like 'Accepted' and 'Accepted Title' from creeping in. Then you'd have two records which are the same thing but you don't naturally know to look for both)
Then T2 (TitleStatus) will look like
TitleStatus
TITLEID | STATUSID
----------|----------
1 | 1
... | ...
201 | 1
... | ...
411 | 1
500 | 2
Where Statuses Looks like
StatusID | StatusText
1 | Accepted
2 | Rejected
You're inserts should be fairly straight forward form there.
You can get all the accepted titles like this
Select T1.Title, Status.StatusText
From T1
Inner join TitleStatus TS on TS.TitleID = T1.ID
Inner Join Statuses S on S.StatusID = TS.StatusID
Where S.StatusText = 'Accepted'
I found the solution. It's actually much simpler than I realized:
Public Function GetTitleIDs() As String
Dim IDData As ADODB.Recordset
Dim SubLookup As Variant
Dim SelectSubmissions As String
' Should pull the same records and in the same order as '
' those found in originating Listbox '
Set IDData = CurrentProject.Connection.Execute("SELECT [Poems].[ID], [Poems].[Title] FROM Poems ORDER BY [Title];")
SubLookup = IDData.GetRows
' Submissions is the name of my originating Listbox. The rest remains unaltered. '
Dim listrow As Integer
For listrow = 0 To Me.Submissions.ListCount - 1
If Me.Submissions.Selected(listrow) = True Then
SelectSubmissions = ", " & SubLookup(0, listrow) & SelectSubmissions
End If
Next
If Len(SelectSubmissions) > 0 Then
SelectSubmissions = Right(SelectSubmissions, Len(SelectSubmissions) - 1)
End If
GetTitleIDs = SelectSubmissions
End Function
Private Sub Form_Current()
' Needed to update existing Listbox entry or entries when record is loaded '
' in form. If record is new, Listbox(es) will simply contain no records. This '
' can source all records for the fields you want in order to show existing '
' Listbox entries without fail. '
Me.Declined.RowSource = "SELECT [Poems].[ID], [Poems].[Title], [Poems].[Year Completed], [Poems].[Blog Location] FROM Poems ORDER BY [Title];"
Me.Accepted.RowSource = "SELECT [Poems].[ID], [Poems].[Title], [Poems].[Year Completed], [Poems].[Blog Location] FROM Poems ORDER BY [Title];"
End Sub
Private Sub Declined_GotFocus()
' Presumably your subset selection list gets focus after '
' checking list entries in originating Listbox. '
Me.Declined.RowSource = "SELECT [Poems].[ID], [Poems].[Title], [Poems].[Year Completed], [Poems].[Blog Location] FROM Poems WHERE [ID] IN (" & GetTitleIDs & ") ORDER BY [Title];"
End Sub
Private Sub Accepted_GotFocus()
Me.Accepted.RowSource = "SELECT [Poems].[ID], [Poems].[Title], [Poems].[Year Completed], [Poems].[Blog Location] FROM Poems WHERE [ID] IN (" & GetTitleIDs & ") ORDER BY [Title];"
End Sub
This will show only options in Declined and Accepted that are checked under Submissions, limiting selection options to only those values of interest for these fields. It's perfect.
So long as the table and ordering is the same for both the records assigned to IDData and the ListBox you're checking against, the IDs will always line up correctly.

Returning SQL rows with data concated per row in access

In access I have a table that is like
ID| ROOMS | NOTES|
78| 234| |
3 | 231| key |
78| 195| |
3 | 164| |
I want to a sql query that will take the ID and combine them into one row each so it is like
78 -> 234,195
3->231, 164 -> key
i just want to combine the rows only in the query no into a new table
Unfortunately you'll have to build a function to do this, but thankfully access supports the use of VBA functions inside of your SQL query.
For example function to concatenate the rim's together based on a given ID would be as follows:
Public Function MyRooms(lngID As Variant) As Variant
Dim rstRooms As DAO.Recordset
If IsNull(lngID) Then Exit Function
Set rstRooms = "select Rooms from tblBookings where id = " & lngID
Do While rstRooms.EOF
If MyRooms <> "" Then MyRooms = MyRooms & "->"
MyRooms = MyRooms & rstRooms!Rooms
rstRooms.MoveNext
Loop
rstRooms.Close
End Function
Now your query can look like this:
Select id, MyRooms([id]) as Rooms, Notes from some table.
You can also make an identical function much the same for the notes column, and again simply place that in the above query.