Access Query Match lowest unused number - sql

I have got a query result in MSAccess.
QueryMatch :
InvoiceNumber RegionNumber Group
9448180 73657 A
9448180 74170 A
9448180 74171 A
9448180 78761 A
9448196 73657 A
9448196 74170 A
9448196 74171 A
9448196 78761 A
9448201 73657 A
9448201 74170 A
9448201 74171 A
9448201 78761 A
1234567 12345 B
so on..
Table 2:
RegionNumber InvoiceNumber
73657
74170
74171
78761
The query has a long list , separated by groups.
There can be x + n RegionNumber for x InvoiceNumbers.
n = 0 to 25.
One RegionNumber must be matched with One InvoiceNumber only for each group.
How do we update Table2?
Let us do for smallest RegionNumber to match smallest InvoiceNumber within the Matchresult.
Leaving the last RegionNumber NULL.
Please provide a VBA or can this be done with queries alone ?
Selecting MIN (InvoiceNumber) for each RegionNumber will result in the same InvoiceNumber.
Thanks

Let's consider the following [QueryMatch] sample data
InvoiceNumber RegionNumber Group
123 678 A
234 678 A
345 678 A
123 789 A
We could try to just iterate through the RegionNumber values (ascending) and pick the lowest InvoiceNumber, but that approach will ultimately fail. We would assign InvoiceNumber 123 to RegionNumber 678 and then when it comes time to process RegionNumber 789 the only possible choice would be InvoiceNumber 123 and it has already been taken.
So, we'd better start by getting a list of the RegionNumber values and the number of distinct InvoiceNumbers that each one has. That will let us process the most constrained RegionNumber values first.
SELECT qm.RegionNumber, Count(qm.InvoiceNumber) AS NumDistinctInvoiceNumbers
FROM
(
SELECT DISTINCT RegionNumber, InvoiceNumber FROM QueryMatch
) qm
GROUP BY qm.RegionNumber
ORDER BY 2 ASC
...which returns...
RegionNumber NumDistinctInvoiceNumbers
789 1
678 3
...lets us know that we need to process RegionNumber 789 first, then assign one of the "leftovers" to RegionNumber 678.
Now, to find the lowest unused InvoiceNumber for a given RegionNumber we need to exclude any ones that we have already written to [Table 2]. So, assuming that we have already "given" InvoiceNumber 123 to RegionNumber 789, one way to find a suitable candidate for RegionNumber 678 would be...
DMin("InvoiceNumber", "QueryMatch", "RegionNumber=678 AND InvoiceNumber NOT IN (Select InvoiceNumber FROM [Table 2])")
...which will return the smallest unused InvoiceNumber, or Null if not match is found.
Wrap that up in some VBA code and we get
Public Sub AssignInvoicesToRegions()
Dim cdb As DAO.Database, rstRegion As DAO.Recordset, rst2 As DAO.Recordset
Dim vInvNo As Variant
Set cdb = CurrentDb
Set rst2 = cdb.OpenRecordset("Table 2", dbOpenDynaset)
Set rstRegion = cdb.OpenRecordset( _
"SELECT qm.RegionNumber, Count(qm.InvoiceNumber) AS NumDistinctInvoiceNumbers " & _
"FROM " & _
"( " & _
"SELECT DISTINCT RegionNumber, InvoiceNumber FROM QueryMatch " & _
") qm " & _
"GROUP BY qm.RegionNumber " & _
"ORDER BY 2 ASC", _
dbOpenSnapshot)
Do While Not rstRegion.EOF
Debug.Print rstRegion!RegionNumber
vInvNo = DMin("InvoiceNumber", "QueryMatch", "RegionNumber=" & rstRegion!RegionNumber & " " & _
"AND InvoiceNumber NOT IN (Select Nz(InvoiceNumber, 0) AS InvNo FROM [Table 2])")
If IsNull(vInvNo) Then
MsgBox "No available InvoiceNumber for RegionNumber=" & rstRegion!RegionNumber, _
vbCritical, "Lookup Failed"
Else
rst2.FindFirst "RegionNumber=" & rstRegion!RegionNumber
rst2.Edit
rst2!InvoiceNumber = vInvNo
rst2.Update
End If
rstRegion.MoveNext
Loop
Debug.Print "Done."
rstRegion.Close
Set rstRegion = Nothing
rst2.Close
Set rst2 = Nothing
Set cdb = Nothing
End Sub
Note that in its current form this algorithm is not guaranteed to find a match for every RegionNumber. Depending on the order in which the RegionNumber values are processed some regions may find that all of their candidates have been taken (hence the IsNull() check in the code). In that case you may have to tweak the algorithm to give those regions "first shot" at an InvoiceNumber, possibly by manually assigning a higher priority to those "difficult" regions.

Related

Access Query/VBA to Group from two fields and Concatenate values

I have a challenging problem I'm attempting to solve and could use your expertise in this matter.
I am attempting to replicate some reports in Access 2013 using queries that I otherwise get from the front-end application to a FootPrints Service Core 11.6 Database. I've completed queries and calculations to replicate most fields from the front end reports, except for the assignee information.
(Note: Assignee is the individual or [generally] teams that a ticket is assigned to for work, can be multiple [teams and individuals])
These assignees are listed out separately within an assignees table of FootPrints Database (See attached images). When the front end application generates reports it somehow groups together the individual and team assignee information in a particular way I'm unable to emulate (See Image). This is where I need your help!
I need to combine all the assignees (individual and team assignees) within a single field, grouped by the ticket number (mrID) they associate with.
So, where there is the following in the database
MrID | Assignee | Team
12345 | Bob Smith | Help Desk Tier 1
12345 | Jane Smith | Help Desk Tier 1
12345 | (Null) | Telecom
23456 | (Null) | Help Desk Tier 2
34567 | Chuck Norris | (Null)
45678 | (Null) | Help Desk Tier 1
45678 | (Null) | Help Desk Tier 2
45678 | (Null) | Networking
45678 | (Null) | Access Control
It should appear as 1 field, like this:
MrID | Assignees
12345 | Help Desk Tier 1: Bob Smith, Jane Smith. Telecom:
23456 | Help Desk Tier 2:
34567 | Chuck Norris
45678 | Help Desk Tier 1: . Help Desk Tier 2: . Networking: . Access Control:
As you can see in the above example, each team assignee is followed by a :, multiple team members (individuals) are seperated by ,'s, and multiple teams are separated by .'s
Following this convention Is there a way to mimic this process through the use of a query (or VBA if necessary) in Access?
Sincerely,
Kris
You have not provided enough data to make more tests. Yes, you included screenshots, but data to copy and paste just 3 records, so I worked with that
I replied your table like this (name of my table is Table1):
Then, I have a Query like this:
The SQL code for this query is:
SELECT DISTINCT Table1.MrID, FINAL_ASSIGNEES([mrid]) AS ASSIGNEES FROM Table1;
As you can see, this SQL code invokes an UDF coded in VBA named FINAL_ASSIGNEES. The code of this UDF is:
Public Function FINAL_ASSIGNEES(ByVal vThisMrID As Long) As String
Dim RST As Recordset
Dim SqlStr As String
SqlStr = "SELECT DISTINCT Table1.MrID, CONCATENATE_ASSIGNEE([MrID],[Team]) AS ASSIGNEES FROM Table1 " & _
"WHERE Table1.MrID=" & vThisMrID & ";"
Set RST = Application.CurrentDb.OpenRecordset(SqlStr, 2, 4)
With RST
If .EOF <> True And .BOF <> True Then
.MoveLast
.MoveFirst
Do Until .EOF = True
FINAL_ASSIGNEES = FINAL_ASSIGNEES & .Fields(1).Value & ". "
.MoveNext
Loop
FINAL_ASSIGNEES = Left(FINAL_ASSIGNEES, Len(FINAL_ASSIGNEES) - 2) 'minus 2 to get rid of extra ". "
End If
Set RST = Nothing
End With
End Function
And yes, this VBA code calls a second UDF named CONCATENATE_ASSIGNEE. The code of this second UDF is:
Public Function CONCATENATE_ASSIGNEE(ByVal vMrID As Long, ByVal vTeam As String) As String
Dim MyRST As Recordset
Dim MySQL As String
MySQL = "SELECT Table1.Assignee FROM Table1 " & _
"WHERE (((Table1.MrID)=" & vMrID & ") AND ((Table1.Team)='" & vTeam & "'));"
Set MyRST = Application.CurrentDb.OpenRecordset(MySQL, 2, 4)
DoEvents
With MyRST
If .EOF <> True And .BOF <> True Then
.MoveLast
.MoveFirst
Do Until .EOF = True
If IsNull(.Fields(0)) = True Then
CONCATENATE_ASSIGNEE = CONCATENATE_ASSIGNEE & "Unassigned" & ", "
Else
CONCATENATE_ASSIGNEE = CONCATENATE_ASSIGNEE & .Fields(0).Value & ", "
End If
.MoveNext
DoEvents
Loop
CONCATENATE_ASSIGNEE = vTeam & ": " & Left(CONCATENATE_ASSIGNEE, Len(CONCATENATE_ASSIGNEE) - 2) 'minus 2 to get rid of the extra ", "
End If
Set MyRST = Nothing
End With
End Function
But this gets kind of what you are after. If you are working with big recordsets, probably it will take some time to make calculations. But at least you can adapt this to your needs.

Update a MS Access table using a formula stored in another table

I have one table called PayerFormulas that includes a different formula for each payer. Another table is PayerData that has the payer and the data we receive. I want to populate PayerData.CheckNum using the appropriate formula stored in PayerFormulas. The CheckNum data in the table below is what I would like the result to be.
In pseudocode it feels like this is what I'm looking for.
Update PayerData PD
inner join PayerFormulas PF on PD.Payer = PF.Payer
set PD.Check = PF.Formula
I've tried the above in a regular Access query and it doesn't work. Trying the following code only puts the formula text in the table, not the result. I've looked at using Eval() in some way, but since my result will often include text, it doesn't look like the route to go down..
Sub testFormula()
Dim SQLString As String
Dim ActiveQuery As QueryDef
SQLString = "Update PayerData PD inner join PayerFormulas PF on PD.Payer = PF.Payer set PD.CheckNum = PF.Formula"
Set ActiveQuery = CurrentDb.CreateQueryDef("", SQLString)
ActiveQuery.Execute dbFailOnError
End Sub
PayerFormulas table
Payer | Formula
-----------|--------
Visa | mid([PayerData].[Data],3,2)
Mastercard | left([PayerData].[Data],2)
Amex | right([PayerData].[Data],2)
PayerData table
Payer | Data | CheckNum
-----------|--------|---------
Visa | 123456 | 34
Visa | ABCDEF | CD
Visa | qwerty | er
Mastercard | 123456 | 12
Mastercard | ABCDEF | AB
Mastercard | qwerty | qw
Amex | 123456 | 56
Amex | ABCDEF | EF
Amex | qwerty | ty
Thank you for any help!
Okay, to continue with the original example of 3 formulas. Your idea to use Eval() is valid but have to concatenate the variable provided by [Data] field. Modify the formulas table to break up the function parts into separate fields.
Payer | Func | Arg1 | Arg2
-----------|---------------------
Visa | mid | 3 | 2
Mastercard | left | 2 |
Amex | right | 2 |
Then in the query that joins tables:
CheckNum: Eval([Func] & "('" & [Data] & "', " & [Arg1] & ", " & [Arg2] & ")")
Note the apostrophe delimiters around [Data].
Or the args can be in one field and entered as comma separated value: 3, 2. Then:
CheckNum: Eval([Func] & "('" & [Data] & "', " & [Args] & ")")
Or, if you really want the formula in one field, enter it as: Mid(PayData,3,2), Left(PayData,2), Right(PayData,2). Then in query calculation, Replace PayData with value of Data field:
CheckNum: Eval(Replace([Formula], "PayData", "'" & [Data] & "'"))
BTW, don't really need to save the result to table, calculate when needed.
Only 3 formulas? Use conditional code in the VBA. Send a card identifier to the procedure as an argument:
Sub testFormulat(strC As String)
Dim result As String
Select Case strC
Case "Visa"
result = Mid(Me.Data, 3, 2)
Case "Mastercard"
result = Left(Me.Data, 2)
Case "AMEX"
result = Right(Me.Data, 2)
End Select
CurrentDb.Execute "Update PayerData set CheckNum = '" & result & "' WHERE ID = " & Me.ID
End Sub
Where will the Data value come from? Is code behind a form? Is form bound to the table result needs to be saved in? If so, the SQL is not needed, simply:
Me!PayerData = result

Quickest way to index an existing database

I just got a table in excel from our existing accounting program including all the products in our company.
I immediately noticed that the fields "HOOFDGROEP" (translates to "Category") and "SUBGROEP" (translates into "Subcategory") do not include primary keys, but string values.
I imported this table into access, but now I want to make 2 seperate tables, "Categories" and "Subcategories". This way I can index the database. I used an SQL query to extract the groups like this :
INSERT INTO Hoofdgroepen
SELECT DISTINCT HOOFDGROEP
FROM Artikelen
But now how do I quickly replace all the record's 'HOOFDGROEP' fields to its index value without doing it manually or with find/replace?
As an example I want to as quickly as possible create a table called hoofdgroepen like this (achieved by above query):
ID HOOFDGROEP
3 Cat V
4 Contante betalingskorting
5 Diversen
out of this :
ID §kop_artikel A_ inv_ product HOOFDGROEP
238 80010077 Chappe Cat V
239 80010517 Beton per m3 Cat V
240 9799044704539 Betalingskorting Contante betalingskorting
241 9799044704537 Viszak klein Diversen
242 9799044704538 Viszak middel Diversen
and then make that table into this:
ID §kop_artikel A_ inv_ product HOOFDGROEP
238 80010077 Chappe 3
239 80010517 Beton per m3 3
240 9799044704539 Betalingskorting 4
241 9799044704537 Viszak klein 5
242 9799044704538 Viszak middel 5
Something like this:
Dim sSQL As String
Dim db As Database
Set db = CurrentDb
sSQL = "SELECT t.HOOFDGROEP INTO HOOFDGROUP " & _
"FROM aTable t " & _
"GROUP BY t.HOOFDGROEP;"
db.Execute sSQL, dbFailOnError
sSQL = "ALTER TABLE HOOFDGROUP ADD COLUMN ID AUTOINCREMENT"
db.Execute sSQL, dbFailOnError
sSQL = "SELECT t.ID, t.[§kop_artikel], t.[A_ inv_ product], HOOFDGROUP.ID INTO " & _
"aNewTable " & _
"FROM aTable t INNER JOIN HOOFDGROUP ON t.HOOFDGROEP = HOOFDGROUP.HOOFDGROEP;"
db.Execute sSQL, dbFailOnError
Depending on what you want, you may also need:
ALTER TABLE HOOFDGROUP ADD PRIMARY KEY(ID)

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