Adding records based on value from other records - vba

I'm going to try this again because I'm so lost..: I have an Access database and the backend contains the tables:
tblStat
groupID
Userid
complex
open
new account
Shipped
not-shipped
code
A
123
Yes
No
Yes
869
B
147
No
Yes
no
936
And tblCode
uniqueid
codetype
code
A,yes,No,Yes
shipped
869
B,No,Yes,No
not-Shipped
936
When I upload a report, it populates the tblStats and using the groupid, complex, open and new account records to make a uniqueid and later adds the code to tblStats. Based on the uniqueID, it can be shipped or not-shipped. The old reports we used contained shipped or not-shipped number values, but with the new reports, I will need to use codetype and code record to reference the tblStat for each order and determine whether the order is shipped or not, and add 1 to tblStats.
I want to do something like
if tblCode.codetype = shipped and the code are the same then tblStat.shipped = 1 else if tblCode.Codetype = not-shipped and the codes are the same tblStat.not-shipped = 1
and then:
with recordset .shipped = shipped .not-shipped = notshipped end with
I just can't seem to figure it out..
I really hope I've provided enough info and cleraity this time around. If you need any code, let me know. thanks.

I cant get my head around for a solution purely in SQL, but in VBA, the method below does the following:
Gather records due to be updated from tblStat.
Loop and try to find a match in tblCode.
Update record accordingly.
There's a helper function to try to find the match in tblCode and an enum the function returns to make the code a bit cleaner and easier to read.
I'm pretty sure something better exists.
Private Enum ShipEnum
None = 0
Shipped
NotShipped
End Enum
Sub T()
On Error GoTo catch
'get records due to be updated
Dim r As DAO.Recordset
Set r = CurrentDb().OpenRecordset("SELECT groupID, complex, [open], [new account], Shipped, [not-shipped], code FROM tblStat WHERE Shipped Is Null AND [not-shipped] Is Null;", dbOpenDynaset)
If r.EOF Then
MsgBox "No records."
GoTo Leave
End If
r.MoveLast
r.MoveFirst
'loop and try to match each record with a record in tblCode
Dim idx As Long, uniqueId As String, shippedStatus As ShipEnum
For idx = 1 To r.RecordCount
'build the unique id for the record
uniqueId = r![groupID] & "," & r![Complex] & "," & r![Open] & "," & r![new account]
'get codetype
shippedStatus = CodeTypeByCriteria(uniqueId, r![Code])
'update if shipped or not-shipped
If shippedStatus <> ShipEnum.None Then
r.Edit
r![Shipped] = IIf(shippedStatus = ShipEnum.Shipped, 1, 0)
r![not-shipped] = IIf(shippedStatus = ShipEnum.NotShipped, 1, 0)
r.Update
End If
r.MoveNext
Next idx
'all good
MsgBox "Complete"
Leave:
If Not r Is Nothing Then r.Close
Exit Sub
catch:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub
Private Function CodeTypeByCriteria(ByVal stringUniqueId As String, ByVal stringCode As String) As ShipEnum
With CurrentDb().OpenRecordset("SELECT codetype FROM tblCode WHERE StrConv(uniqueId, 2)='" & StrConv(stringUniqueId, vbLowerCase) & "' And code='" & stringCode & "'", dbOpenSnapshot)
If Not .EOF Then
Select Case ![codetype]
Case "shipped":
CodeTypeByCriteria = ShipEnum.Shipped
Case "not-shipped":
CodeTypeByCriteria = ShipEnum.NotShipped
Case Else:
Exit Function
End Select
End If
End With
End Function

If code column in both the tables have unique values and you haven't made mistake while writing code field's values for both tables in your question then these queries should work:
UPDATE tblStat JOIN tblCode ON tblStat.code = tblCode.code
SET shipped = 1
WHERE CONCAT(groupID, ',', complex, ',', open, ',', new_account) = uniqueid
AND codetype = 'shipped';
UPDATE tblStat JOIN tblCode ON tblStat.code = tblCode.code
SET not_shipped = 1
WHERE CONCAT(groupID, ',', complex, ',', open, ',', new_account) = uniqueid
AND codetype = 'not-shipped';
Edit:
These queries are according to MySQL. In VBA, there should be some other function or way - like joining string with + operator?

Related

VBA - Struggling with worksheet_change. Not working with no error given

I have a sheet in which our wholesale team are to enter L09 Part Codes and quickly see how much we have in stock of that item. The problem is that new starters may struggle to learn these part numbers as they don't follow a simple rule. What I did was create an easier code to remember which is simply: "Cable Type" & "Core Size" & "Cut Length", they also have the option to add "Colour" and "Brand" separated by spaces.
Their entered string may look like 6242y 2.5 100, or maybe 6242y 2.5 100 Grey, etc. and so where to look in my mapped table for what they've written depends on how many terms they put in. As you can see from the attached picture I need to select the correct column to look in for their code, and then offset back a few columns to suggest the correct L09 Part Number.
I hope the context makes a bit of sense and helps with the below code. The idea was for a new starter to enter something simple and it be replaced before their very eyes...
If anyone could help me to correct the following it would be greatly appreciated:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim P, Products, S, Search As Range
Dim Column As String
Dim Counter As Integer
Dim Spaces As Long
'On Error Resume Next
Counter = 0
'For top table only
If Target.Column = 1 And Target.Row < 100 Then
'Count spaces
Spaces = UBound(Split(Target, " "), 1)
Select Case Spaces
Case Is = 2
Column = "M"
Case Is = 3
Column = "O"
Case Is = 4
Column = "Q"
End Select
'When string has spaces
If Spaces <> 0 Then
'Set simple code range
Set Search = Sheets("Cherries").Range(Column & 1 & ":" & Column & 10000)
For Each S In Search
If S = Target Then
Target = S.Offset(0, 3 - 2 * Spaces)
End If
Next S
End If
Set Products = Sheets("Order Entry").Range("A3:A99")
For Each P In Products
If P.Value <> "" Then
Counter = Counter + 1
End If
Next P
Sheets("Order Entry").Rows("3:" & Counter + 11).Hidden = False
Sheets("Order Entry").Rows(Counter + 11 & ":99").Hidden = True
End If
End Sub
Unfortunately I'm not sure which line is erroring as no error message is given.
Thank you for your time.

Show records based on same id in three subforms in Access within a form

I know I'm looking for a quick fix and that the main issue is in the database design but for the moment I cannot do anything about it.
So here's my wish:
I have three tables TableA, TableB, TableC all sharing ID as key with referential integrity turned on (de facto it would be one large table that has more than 255 columns which is the limit I have to find a workaround for). What I would like to achieve is to show all records simultaneously as datatable next to each other and have the following behaviours:
if I filter in table A, Table B und C should show the same rows
Sorting should be also equal and should be done by certain columns in Table A
I already managed to have the cursor in the same row on all tables
I thought of making a select * from tableB where id in filteredrecordset of tableA or some sort of join on that recordset but did not manage to achieve that.
As a sidenote: there are about 100k records in that database and the performance has to be fast as this view is mainly used for data entry/updates on multiple columns and rows which requires such a flat data structure.
Thanks in advance for your help!
You can use the forms On Filter event to sync up the filters. However, I assume you've bound the subforms directly to the table.
Because you've bound the subforms directly to table, you can't listen to events. However, I've recently encountered that issue, and have a hacky workaround, but you don't need to use that if your field names and table names are constant. You just need to wrap TableA in a datasheet form.
Open up TableA, and go to the Create tab, then More Forms -> Datasheet. You now have a datasheet form that captures all fields from TableA. Then, add a module to that datasheet form. You don't need any code in that module.
Then, instead of binding the first subform to TableA, we bind it to this datasheet form instead.
Now, on the parent form, we're going to set up an event handler for the filter.
On the parent form (I'm assuming the name of your subform control for tableA is SubA, for tableB SubB, for TableC SubC):
Private WithEvents tblAForm As Form 'Declare tblAForm to handle events
Private Sub Form_Load()
'Initialize event handler, sync initially
Set tblAForm = Me.Controls("subA").Form
tblAForm.OnApplyFilter = "[Event Procedure]"
SyncFilters 'Not needed if you're not persisting filters, which you likely aren't
End Sub
Private Sub tblAForm_ApplyFilter(Cancel As Integer, ApplyType As Integer)
'Sync filters
SyncFilters(ApplyType)
End Sub
Private Sub SyncFilters(ApplyType As Integer)
Dim srcB As String
Dim srcC As String
Dim strFilter As String
Dim strOrder As String
'If filter or sort are on on TableA, we need to join in TableA for sorting and filtering
If tblAForm.FilterOn Or tblAForm.OrderByOn Then
srcB = "SELECT TableB.* FROM TableB INNER JOIN TableA On TableA.ID = TableB.ID"
srcC = "SELECT TableC.* FROM TableC INNER JOIN TableA On TableA.ID = TableC.ID"
'Filter to SQL
strFilter = " WHERE " & tblAForm.Filter
'Sort to SQL
strOrder = " ORDER BY " & tblAForm.OrderBy
If tblAForm.FilterOn And tblAForm.Filter & "" <> "" And ApplyType <> 0 Then
'If the filter is on, add it
srcB = srcB & strFilter
srcC = srcC & strFilter
End If
If tblAForm.OrderByOn And tblAForm.OrderBy & "" <> "" Then
'If order by is on, add it
strB = srcB & strOrder
srcC = srcC & strOrder
End If
Else
srcB = "SELECT TableB.* FROM TableB"
srcC = "SELECT TableC.* FROM TableC"
End If
If srcB <> Me.SubB.Form.RecordSource Then Me.SubB.Form.RecordSource = srcB
If srcC <> Me.SubC.Form.RecordSource Then Me.SubC.Form.RecordSource = srcC
End Sub
Note that you do need some spare fields to allow for filtering and ordering. Any field that's used for that does count towards the maximum of 255 fields. If you might hit that, you could consider splitting the dataset into 4 tables instead of 3
Consider using RecordSourceClone property and a temp table of IDs that runs the following routine:
On Exit of any subform after filtering, IDs are iteratively appended to a temp table.
The other two subforms have their RecordSources filtered to temp table IDs.
Reset button removes all filters in order to run different criteria.
VBA
Option Compare Database
Option Explicit
' RESET ALL SUBFORMS
Private Sub RESET_Click()
Me.Controls("frm_TableA").Form.RecordSource = "TableA"
Me.Controls("frm_TableB").Form.RecordSource = "TableB"
Me.Controls("frm_TableC").Form.RecordSource = "TableC"
End Sub
Private Sub frm_TableA_Exit(Cancel As Integer)
Call RunFilters("frm_TableA", "frm_TableB", "frm_TableC")
End Sub
Private Sub frm_TableB_Exit(Cancel As Integer)
Call RunFilters("frm_TableB", "frm_TableA", "frm_TableC")
End Sub
Private Sub frm_TableC_Exit(Cancel As Integer)
Call RunFilters("frm_TableC", "frm_TableA", "frm_TableB")
End Sub
Function RunFilters(curr_frm As String, frm1 As String, frm2 As String)
On Error GoTo ErrHandler
Dim rst As Recordset, tmp As Recordset
' DELETE PREVIOUS TEMP
CurrentDb.Execute "DELETE FROM IDTempTable", dbFailOnError
Set tmp = CurrentDb.OpenRecordset("IDTempTable")
' RETRIEVE FILTERED FORM RECORDSOURCE
Set rst = Me.Controls(curr_frm).Form.RecordsetClone
' ITERATIVELY ADD IDs
Do While Not rst.EOF
With tmp
.AddNew
!ID = rst![ID]
.Update
rst.MoveNext
End With
Loop
tmp.Close: rst.Close
Set tmp = Nothing: Set rst = Nothing
' FILTER OTHER FORMS
Me.Controls(frm1).Form.RecordSource = "SELECT * FROM " & Replace(frm1, "frm_", "") & _
& " WHERE [ID] IN (SELECT ID FROM IDTempTable)"
Me.Controls(frm2).Form.RecordSource = "SELECT * FROM " & Replace(frm2, "frm_", "") & _
& " WHERE [ID] IN (SELECT ID FROM IDTempTable)"
ExitHandler:
Exit Function
ErrHandler:
MsgBox Err.Number & " - " & Err.Description, vbCritical, "RUN-TIME ERROR"
Resume ExitHandler
End Function

SQL for MSACCESS how to implement character delimited data hierarchy?

Say, there's a flattened sequential hierarchy table with a special character "+" denoting hierarchy level
hr_table
ID FIELD1 My irrelevant comments
----------------
1 ASSETS No pluses - it means level0 hierarchy
2 +ASSETS_01 Level1 hierarchy
3 ++345667654 Level2 hierarchy
4 ++563255512 Level2 hierarchy
5 ...
Is there a way to create field structure in MSACCESS using SQL? I am trying to structure final data as follows:
final_data_table
ID LEVEL0 LEVEL1 LEVEL2 ...
-------------------------------------------
1 ASSETS ASSETS_01 345667654
2 ASSETS ASSETS_01 563255512
Any/all help greatly appreciated!
Curiosity got the better of me so I explored query approach. I resorted to using domain aggregate functions. Be aware domain aggregate functions can perform slowly with large datasets. However, consider:
Query1:
SELECT hr_table.ID, IIf([Field1] Like "+*",Left([Field1],InStrRev([Field1],"+")),0) AS Prefix, IIf([Field1] Like "+*",Null,[Field1]) AS Asset1, IIf(InStrRev([Field1],"+")=1,Mid([Field1],2),Null) AS Asset2, IIf([Field1] Like "++*",Mid([Field1],InStrRev([Field1],"+")+1),Null) AS Data
FROM hr_table;
Query2:
SELECT Query1.ID, Query1.Prefix, DMax("Asset1","Query1","ID<=" & [ID]) AS A1, DMax("Asset2","Query1","ID<=" & [ID]) AS A2, Query1.Data
FROM Query1
WHERE ((Not (Query1.Data) Is Null));
Query3:
SELECT Query2.Prefix, Query2.A1, Query2.A2, Query2.Data, DCount("*","Query2","A1='" & [A1] & "' AND A2='" & [A2] & "' AND Prefix = '" & [Prefix] & "' AND ID<=" & [ID]) AS GrpSeq
FROM Query2;
Query4:
TRANSFORM Max(Query3.Data) AS MaxOfData
SELECT Query3.A1, Query3.A2, Query3.GrpSeq
FROM Query3
GROUP BY Query3.A1, Query3.A2, Query3.GrpSeq
PIVOT Query3.Prefix;
I am definitely not sure about treatment of Level3 and beyond. Could be VBA will be only way to resolve.
The following code has been tested and works with the data structure you have mentioned. It is currently set to handle up to 10 levels, but that can be easily changed. The tricky part was to NOT write out the record until you have all levels for that ONE row (New row starts with different level1 value, or when more than one level-n value provided). The final row is written after eof of input.
Option Compare Database
Option Explicit
Function Parse_Fields()
Dim dbs As DAO.Database
Dim rsIN As DAO.recordSet
Dim rsOUT As DAO.recordSet
Dim i As Integer
Dim iPlus As Integer
Dim aLevels(10)
Dim bAdding As Boolean
Set dbs = CurrentDb
Set rsIN = dbs.OpenRecordset("hr_table")
Set rsOUT = dbs.OpenRecordset("final_data_table")
bAdding = False
Do While Not rsIN.EOF
'Debug.Print "Input: " & rsIN!field1
If left(rsIN!field1, 1) <> "+" Then
' Check if not first time thru... if not, write prior levels..
If bAdding = True Then
rsOUT.Update
End If
iPlus = 0
rsOUT.AddNew
rsOUT!Level0 = rsIN!field1
bAdding = True
' Don't issue the .Update yet! Wait until another Level0 or EOF.
Else
For iPlus = 1 To 10 ' Change code if more than ten levels
If Mid(rsIN!field1, iPlus, 1) <> "+" Then Exit For
Next iPlus
' Check if same level as previous! If so, do NOT overlay!
If Not IsNull(rsOUT.Fields(iPlus)) Then
For i = 1 To iPlus - 1 ' Save the proper levels for the new record.
aLevels(i) = rsOUT.Fields(i)
Next i
rsOUT.Update ' Need to write out record.
rsOUT.AddNew
For i = 1 To iPlus ' Populate the new record with prior levels
rsOUT.Fields(i) = aLevels(i)
Next i
rsOUT.Fields(iPlus) = Mid(rsIN!field1, iPlus) ' Add the new level
Else
rsOUT.Fields(iPlus) = Mid(rsIN!field1, iPlus)
End If
End If
rsIN.MoveNext ' Get next input
Loop
' Need to write out the final record we have beenbuilding!
rsOUT.Update
rsIN.Close
rsOUT.Close
Set rsIN = Nothing
Set rsOUT = Nothing
Set dbs = Nothing
Debug.Print "FINISHED!!"
End Function

Excel VBA : Auto numbering

I'm creating a database on Excel, and encountered some problems as I tried to assign auto number to each row.
Requirements are:
generate auto number to each row(on the column A) when column B is not blank.
the number should be unique and must always be connected to the contents of the same row even when the column is sorted or when new rows are inserted, etc.
when a new row is inserted (anywhere on the same column), a new number should be assigned (the newest number should be the biggest number)
if
possible, the auto number should have a prefix, and number should be displayed in four digits (e.g. 0001, 0011)
I have tried some VBA codes I found from other people's questions (e.g. Excel VBA : Auto Generating Unique Number for each row).
So far, the code below has worked the best, but the requirement (3) and (4) couldn't be solved by that code.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim maxNumber
If Not Intersect(Target, Range("B:B")) Is Nothing Then
' don't run when more than one row is changed
If Target.Rows.Count > 1 Then Exit Sub
' if column A in the current row has a value, don't run
If Cells(Target.Row, 1) > 0 Then Exit Sub
' get the highest number in column A, then add 1 and write to the
' current row, column A
maxNumber = Application.WorksheetFunction.Max(Range("A:A"))
Target.Offset(0, -1) = maxNumber + 1
End If
End Sub
I'm short of the knowledge of VBA and I hope someone could help me this.
Many thanks.
Alternative via CustomDocumentProperties
Instead of using a hidden sheet as proposed by #TimWilliams, one can assign incremented values to a user defined custom document property (CDP), naming it e.g. "InvNo" holding the newest invoice number. The cdp remain stored in the saved workbook.
The function below gets the current number saved to this workbook related property and returns the next number by adding 1 to the current value. It uses a help procedure RefreshCDP to assign the new value (could be used of course independantly to reset values programmaticaly to any other value). - If the cdp name isn't passed as (optional) argument, the function assumes "InvNo" by default.
Note that code requires some error handling to check if the cdp exists.
Example call
Dim InvoiceNumber as Long
InvoiceNumber = NextNumber("InvNo") ' or simply: NextNumber
Public Function NextNumber(Optional CDPName As String = "InvNo") As Long
'a) get current cdp value
Dim curVal As Long
On Error Resume Next
curVal = ThisWorkbook.CustomDocumentProperties(CDPName)
If Err.Number <> 0 Then Err.Clear ' not yet existing, results in curVal of 0
'b) increment current cdp value by one to simulate new value
Dim newVal As Long
newVal = curVal + 1
'Debug.Print "Next " & CDPName & " will be: " & newVal
'c) assign new value to custom document property
RefreshCDP CDPName, newVal, msoPropertyTypeNumber
'Debug.Print "New " & CDPName & " now is: " & ThisWorkbook.CustomDocumentProperties(CDPName)
NextNumber = newVal
End Function
Help procedure RefreshCDP
Sub RefreshCDP(CDPName As String, _
newVal As Variant, docType As Office.MsoDocProperties)
On Error Resume Next
ThisWorkbook.CustomDocumentProperties(CDPName).Value = newVal
'If cdp doesn't exist yet, create it (plus adding the new value)
If Err.Number > 0 Then
ThisWorkbook.CustomDocumentProperties.Add _
Name:=CDPName, _
LinkToContent:=False, _
Type:=docType, _
Value:=newVal
End If
End Sub
Related links
MS help: Excel.Workbook.CustomDocumentProperties
Check if BuiltInDocumentProperty is set without error trapping
Chip Pearson: Document Properties
How to add a DocumentProperty to CustomDocumentProperties in Excel?
Do not use Max() to find the next number - use instead a hidden sheet or name to store the current number, and increment it each time a new Id is required.
For example:
Public Function NextNumber(SequenceName As String)
Dim n As Name, v
On Error Resume Next
Set n = ThisWorkbook.Names(SequenceName)
On Error GoTo 0
If n Is Nothing Then
'create the name if it doesn't exist
ThisWorkbook.Names.Add SequenceName, RefersTo:=2
v = 1
Else
'increment the current value
v = Replace(n.RefersTo, "=", "")
n.RefersTo = v + 1
End If
NextNumber = v
End Function
This allows you to use multiple different sequences as long as you give each one a distinct name.
Dim seq
seq = NextNumber("seqOne")
'etc

Insert number of record based on checkboxlist selected value in vb.net

I have this skill checkboxlist which contains skills that can be selected by the user. If the user select two skills,two records will be inserted to the table.
I tried this one:
Dim varSkillID As Integer()
varSkillID = split(skills, ",")
If varSkillID(0).value > 0 Then
Dim sql As String = Nothing
For I = 0 To varSkillID()
sql = "INSERT INTO tblConstituentSkills (skillID) VALUES ( " & varSkillID.Value & ")"
Next I
end if
but its not working. Thanks!
I also tried this code.
Dim varSkillID() As String = Split(skillID, ",")
For i As Integer = 0 To varSkillID.Length - 1
If varSkillID(i) <> "" Then
Using sql As New SqlProcedure("spInsertSkill")
sql.AddParameter("#ConstituentIdNo", constituentIdNo)
sql.AddParameter("#skillID", varSkillID(i))
sql.ExecuteNonQuery()
End Using
End If
Next i
It works when I only select single skill. But if I select two or more skills this error appears "Nullable object must have a value."
please use the editor help to design your request. It is very hard to read.
What does the errormessage say?
There is an End If missing
Where does constituentIdNo.Value is coming from?
To call the Sub:
Call r101("123456", "1,2,3,4")
the Sub:
Public Sub r101(ByVal constituentIdNo As String, ByVal skills As String)
Dim varSkillID() As String = Split(skills, ",")
Dim sql As String = Nothing
For i As Integer = 0 To varSkillID.Length - 1
If varSkillID(i) <> "" Then sql = "INSERT INTO tblConstituentSkills (ConstituentIdNo, skillID) VALUES (" & constituentIdNo & ", " & varSkillID(i) & ")"
Next i
End Sub
This is not the cleanest code, but the best I could create from your given feedback.
I don't see why to convert skills to integer.
I just read this and saved me.separate comma separated values and store in table in sql server
Thanks stackoverflow!you're a savior!