Finding the occurrence across multiple sheets but with a twist - excel

I wanted to find the number of sick leave of the workers per month. I know that can be achieved easily with =Ifs (), but as you can see from this screenshot:
the sheets may contain the data of other months (late December and early February in this case). There are 12 sheets in total, each contains one month of the year.
I want to write a function that can count the numbers of holidays given by "休息" for each month for each person.
The difficulty is that:
The roaster changes every month and cell position of each person changes, the other problem is:
As mentioned above, each sheet may contain data of other months, which implies the function has to refer to other sheets to count these leaves as well.
Can this be achieved with excel intrinsic functions or should this be done with VBA?

It is real difficult and lot of assumption, to try and understand the problem in unknown language and scanty details. However the solution worked out based of a number of assumption and hope to offer only broad outline to be modified as real time solution according to actual layout of the working file
Assumptions:
Names are in column B
Names start at row 11
For trial only rows up to 35 are used
Days span from column D to AF (Approx)
Column AL is free and used for Countif formula
For solution with excel formulas
Populate column AL of all the monthly sheets with formula
=COUNTIF(D11:AE11,"休息")
In a blank Sheet named “Main” populate column B with list of all the names (may be starting from row 11)
Now Column C representing Month1 may be added with formula
=IF(ISNA(MATCH($B11,Sheet1!$B$11:$B$35,0)), "",INDEX(Sheet1!$AL$11:$AL$35,MATCH($B11,Sheet1!$B$11:$B$35,0),1))
Similarly Column D representing Month1 may be added with formula
=IF(ISNA(MATCH($B12,Sheet2!$B$11:$B$35,0)), "",INDEX(Sheet2!$AL$11:$AL$35,MATCH($B12,Sheet2!$B$11:$B$35,0),1))
And similarly add formula to all the 12 columns pointing towards 12 sheets in the workbook and the create a summation in next blank column
Result Sheet Layout - Main and Month sheets Layout
VBA solution
Add one blank sheet Named “MainVBA” containing list of all the names in the column B starting from row 11 as done in sheet named “Main”. Alternatively this employee list could be created by using dictionary object and collecting all unique names (case sensitive) from all the monthly sheets. For using this code please add reference to “Microsoft Scripting runtime”. (VBA window – Tools – Reference)
Code:
Sub MakeList()
Dim MnWs As Worksheet, Ws As Worksheet
Dim Srw As Long, S1rw As Long
Dim Erw As Long, E1rw As Long
Dim Xname As String
Dim TRw As Long
Dim Cnt As Long
Dim Dict As Dictionary
Set MnWs = ThisWorkbook.Sheets("MainVBA")
Srw = 11 'Starting Row in Result sheet Named "MainVBA"
Erw = MnWs.Cells(Rows.Count, "B").End(xlUp).Row 'Last Row in Result sheet Named "MainVBA"
MnWs.Range("B" & Srw & ":B" & Erw).ClearContents ' Delete last row
Set Dict = New Dictionary
'Get Names from monthly sheets
For Each Ws In ThisWorkbook.Worksheets
If Ws.Name <> "MainVBA" And Ws.Name <> "Main" Then ' Consider all sheets Other than specified two having sick leave data
S1rw = 11
E1rw = Ws.Cells(Rows.Count, "B").End(xlUp).Row 'Last Row in Month sheet
For TRw = S1rw To E1rw
Xname = Ws.Cells(TRw, "B").Value
If Dict.Exists(Xname) = False Then
'Debug.Print Ws.Name, TRw, Xname
Dict.Add Xname, 1
End If
Next
End If
Next Ws
'create list in Sheet "MainVBA" Column B from dictionary
For Cnt = 0 To Dict.Count - 1
MnWs.Cells(Cnt + 11, "B").Value = Dict.Keys(Cnt)
Next
Set Dict = Nothing
End Sub
Code for calculating sick leave count month wise. I used Range B1 to hold the string "休息" (as I refrained to tweak the regional settings etc) , it may be used directly in your VBA module.
Option Explicit
Sub test()
Dim MnWs As Worksheet, Ws As Worksheet
Dim Srw As Long, S1rw As Long, Scol As Long
Dim Erw As Long, E1rw As Long, Ecol As Long
Dim Rw As Long, Col As Long, Xname As String, ChineseStr As String
Dim TRw As Long, XRw As Long, Have As Boolean
Dim Rng As Range, Cnt As Long
Set MnWs = ThisWorkbook.Sheets("MainVBA")
ChineseStr = MnWs.Range("B1").Value 'May use string directly with quotes
Srw = 11 'Starting Row in Result sheet Named "MainVBA"
Erw = MnWs.Cells(Rows.Count, "B").End(xlUp).Row 'Last Row in Result sheet Named "MainVBA"
Scol = 3 'Starting Col Month1 in Result sheet Named "MainVBA"
Ecol = 3 + 11 'End Col Month12 in Result sheet Named "MainVBA"
For Rw = Srw To Erw
Xname = MnWs.Cells(Rw, "B").Value
Col = Scol
For Each Ws In ThisWorkbook.Worksheets
If Ws.Name <> "MainVBA" And Ws.Name <> "Main" Then ' Consider all sheets Other thah specified two having Sick leave data
S1rw = 11
E1rw = Ws.Cells(Rows.Count, "B").End(xlUp).Row 'Last Row in Month sheet
XRw = 0
For TRw = S1rw To E1rw
If Ws.Cells(TRw, "B").Value = Xname Then
XRw = TRw
Exit For
End If
Next
Cnt = 0
If XRw > 0 Then
'Debug.Print Xname, Ws.Name, TRw
Set Rng = Ws.Range("D" & XRw & ":AF" & XRw) ' Col D to AF condsidered having sick leave data
Cnt = Application.WorksheetFunction.CountIf(Rng, ChineseStr) 'sum of month sick leave
End If
MnWs.Cells(Rw, Col).Value = Cnt
Col = Col + 1
End If
Next Ws
Next Rw
End Sub
To make this code working, it may be required to modify all the sheets details like sheets name, row, column details etc. Feel free to feedback if code is some where near to your objective and actual scenario.

Related

VBA to auto populate a table from data entered in another table

I'm an electrical contractor and I made a worksheet to help me bid projects.
Say I'm bidding on wiring a new house. I have broken down each task "outlet"/"Switch" to materials and labor needed for each task. Those materials are then multiplied by the quantity needed and populate 3 different tables automatically.
Here is the process: (24 outlets are needed for this job)
"Bid Cut Sheet" Sheet where quantities of specific tasks are entered.
"Job List" Tasks are broken down into materials needed for that task, multiplied by the quantity entered in "Bid Cut Sheet"
"Material Sheet" Total of all material needed for the job in 3 different tables/stages of the project
What I am trying to do is populate rows in EACH table where materials are needed. Essentially consolidate the data in EACH table by eliminating with quantities of 0 and ADDING rows with quantities >0 and fill down rows with material needed: updating every time data is entered in the "Bid Cut Sheet"
This code eliminates values of 0 after I run the code, but does not update data entered in the "bid cut sheet" after I run the code. Also, I would like this to be imbedded in the workbook so I dont have to run the code each time I use the workbook.
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim i As Long, LastRow As Long, Row As Variant
Dim listObj As ListObject
Dim tblNames As Variant, tblName As Variant
Dim colNames As Variant, colName As Variant
'Names of tables
tblNames = Array("Rough_Material", "Trim_Material", "Service_Material")
colNames = Array("Rough", "Trim", "Service")
'Loop Through Tables
For i = LBound(tblNames) To UBound(tblNames)
tblName = tblNames(i)
colName = colNames(i)
Set listObj = ThisWorkbook.Worksheets("MaterialSheet").ListObjects(tblName)
'Define First and Last Rows
LastRow = listObj.ListRows.Count
'Loop Through Rows (Bottom to Top)
For Row = LastRow To 1 Step -1
With listObj.ListRows(Row)
If Intersect(.Range, _
listObj.ListColumns(colName).Range).Value = 0 Then
.Delete
End If
End With
Next Row
Next i
End Sub
This is what it looks like after running the code, it works one time but does not update.
If I understand your question correctly, what you are looking for is something like this:
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim LastRow As Long, FirstRow As Long
Dim Row As Long
Dim columns As Variant, column As Variant
columns = Array("A", "D", "G")
With ThisWorkbook.Worksheets("Sheet1") '<- type the name of the Worksheet here
'Define First and Last Rows
FirstRow = 1
LastRow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
'Loop Through Columns
For Each column In columns
'Loop Through Rows (Bottom to Top)
For Row = LastRow To FirstRow Step -1
If .Range(column & Row).Value = 0 Then
.Range(column & Row).Resize(1, 2).Delete xlShiftUp
End If
Next Row
Next column
End With
End Sub
Test it out and see if this does what you want.
Alternatively, it might be wiser to be more explicit and make the code more flexible. If your tables are actually formatted as tables, you can also loop over these so-called ListObjects. That way, if you insert columns/rows in the future, the code won't break.
To do this, you could use code like this:
Sub DeleteRowsBasedonCellValue()
'Declare Variables
Dim i As Long, LastRow As Long, Row As Variant
Dim listObj As ListObject
Dim tblNames As Variant, tblName As Variant
Dim colNames As Variant, colName As Variant
'The names of your tables
tblNames = Array("Rough_Materials", "Trim_Materials", "Service_Materials")
colNames = Array("quantity_rough", "quantity_trim", "quantity_service")
'The name of the column the criterion is applied to inside each table
'Loop Through Tables
For i = LBound(tblNames) To UBound(tblNames)
tblName = tblNames(i)
colName = colNames(i)
Set listObj = ThisWorkbook.Worksheets("Sheet1").ListObjects(tblName)
'Define First and Last Rows '^- the name of the Worksheet
LastRow = listObj.ListRows.Count
'Loop Through Rows (Bottom to Top)
For Row = LastRow To 1 Step -1
With listObj.ListRows(Row)
If Intersect(.Range, _
listObj.ListColumns(colName).Range).Value = 0 Then
.Delete
End If
End With
Next Row
Next i
End Sub
Edit in response to your comment:
Make sure your table is actually formatted as a table and has been given the right name! You can also change the table names in your code to your liking in the line tblNames = Array("Rough_Materials", "Trim_Materials", "Service_Materials"). Also, the column names have to be correct/you should adapt them in the code: colNames = Array("quantity_rough", "quantity_trim", "quantity_service")

How to do Excel looping using VBA

I am working on a tool to build a stored procedure based on the available prices getting directly from vendor. As per the attached snap, I paste all the rics (identifier) under column I and put the number of days in cell B2 (let's say I am looking for last 300 days of historical prices for STIM.OQ, VOD.L (vodafone) and AAPL.OQ (Apple Inc).
Currently this tool only fetches the prices for a single ric based on the cell value A2, I wanted to put this in a loop so that whatever rics I paste in column I go through that loop and the code should copy the stored procedure from column F and append it to a new single sheet for each of the rics.
Note: the stored procedure takes the value from cell B8, D8 and E8
Use ws.Cells(Rows.Count, "I").End(xlUp).Row to find the last row and then loop through starting at row 2.
Option Explicit
Sub CreateProcs()
Dim wb As Workbook, ws As Worksheet, wsRic As Worksheet
Dim iLastRow As Long, r As Long, n As Long, i As Integer
Dim ric As String
Set wb = ThisWorkbook
Set ws = wb.Sheets(1) ' as appropriate
n = ws.Range("B2").Value ' days
' loop through rics in col I
iLastRow = ws.Cells(Rows.Count, "I").End(xlUp).Row
For r = 2 To iLastRow
ric = ws.Cells(r, "I")
ws.Range("A2").Value2 = ric
' create sheet
Set wsRic = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
wsRic.Name = ric
' copy data
wsRic.Range("A1:A" & n).Value2 = ws.Range("F8").Resize(n).Value2
i = i + 1
Next
MsgBox i & " sheets created", vbInformation
End Sub

VBA Add rows based on how many times a criteria is met

I am quite new to VBA so my question may sound silly to some of you.
I have a problem with my code. I am trying to check between 2 sheets(1st: test and 2nd: test data. In the code I am using the code name of the sheets) how may times a criteria/my product is met and based on that number to add that many rows beneath the SKU/product.
What I want to do is, if for my first product/SKU I have stock in 5 different locations, I want to add 5 rows in the first sheet and to see the qty in column D and the location in column E.
TestData tab
The reason why I need to list the quantity in different cells is that the stock is in different locations, so I can not just add in to one cell.
I am also adding screen shots of how my two sheets look like.
I have not add the loop to find the location yet, but I want to understand how to add the rows first.
Sub test()
Dim myrange As Range, testrange As Range, cell As Range, result As Range
Dim i As Long, testlastrow As Long, lastrow As Long
Dim helprng As Range
lastrow = TESTTAB.Range("a" & Rows.Count).End(xlUp).row
testlastrow = TDATA.Range("a" & Rows.Count).End(xlUp).row
Set testrange = TDATA.Range("a2:c" & testlastrow)
Set myrange = TESTTAB.Range("b2:b" & lastrow)
Set result = TESTTAB.Range("d2:e" & testlastrow)
Set helprng = TESTTAB.Range("f2:f" & lastrow)
For Each cell In myrange
For i = 1 To lastrow
If cell.Cells(i, 1) = testrange.Cells(i, 1) Then
result.Cells(i, 1) = testrange.Cells(i, 2)
End If
Next i
Next cell
End Sub
Here is the raw structure you were asking for.
Sub test()
' 011
Dim Rng As Range
Dim Tmp As Variant
Dim Radd As Long ' number of rows to add
Dim R As Long ' row counter
With Tdata
' Range, Cells and Rows Count, all in the same sheet
' exclude caption row(s)
Set Rng = .Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp))
End With
Application.ScreenUpdating = False ' speed up the process
With TestTab
' end the loop in row 2 because row 1 might contain captions
' loop bottom to top because row numbers will change
' below the current row as you insert rwos
' column 1 = "A"
For R = .Cells(.Rows.Count, 1).End(xlUp).Row To 2 Step -1
Tmp = .Cells(R, 1).Value
Radd = Application.CountIf(Rng, Tmp)
If Radd > 1 Then
.Range(.Rows(R + 1), .Rows(R + Radd)).Insert
.Cells(R + 1, 1).Value = Radd ' for testing purposes
End If
Next R
End With
Application.ScreenUpdating = True ' now you look
End Sub
As our Evil Blue Monkey has suggested, inserting blank rows and then populating them may not be the most efficient way. You can copy a row and then click Insert to insert the copied data into a new row. Get the syntax from the Macro recorder if it's of interest. It works with the Range object the same way.

Copy a set of data multiple times based on criteria on another sheet

Excel 2010. I am trying to write a macro that could copy a set of data multiple times based on criteria on another sheet, but I've been stuck for a long time. I very much appreciate any help that could be offered to help me solve this problem.
Step 1: In the "Criteria" worksheet, there are three columns in which each row contains a specific combination of data. The first set of combination is "USD, Car".
Criteria worksheet
Step 2: Then the macro will move to the Output worksheet (please refer to the below link for screenshots), and then filter column A and B with the first set of criteria "USD" and "Car" in the "Criteria" worksheet.
Step 3: Afterwards, the macro will copy the filtered data into the last blank row. But the tricky part here is that, the filtered data has to be copied two times (as the "Number of set" column in the "Criteria" tab is 3 in this combination, and it doesn't have to copy the data three times since the filtered data would be treated as the first set of data)
Step4: After the filtered data have been copied, the "Set" column D will need to fill in the corresponding number of set that the rows are in. Therefore, in this 1st example, cell D2 and D8 will have "1" value, cell D14-15 will have "2" value, and cell D16-17 will have "3" value.
Step5: The macro will then move back to the "Criteria" worksheet and continue to based on the 2nd set of combination "USD, Plane" to filter the data in the "Output" worksheet. Again, it will copy the filtered data based on the "Number of set" in the "Criteria" worksheet. This process will continue until all the different combinations in the "Criteria" worksheet have been processed.
Output worksheet
Ok sorry for delay, here is a working version
you just have to add a sheet called "BF" because the autofilter count wasn't working properly so I had to use another sheet
Sub testfct()
Dim ShC As Worksheet
Set ShC = ThisWorkbook.Sheets("Criteria")
Dim EndRow As Integer
EndRow = ShC.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To EndRow
Get_Filtered ShC.Cells(i, 1), ShC.Cells(i, 2), ShC.Cells(i, 3)
Next i
End Sub
Sub Get_Filtered(ByVal FilterF1 As String, ByVal FilterF2 As String, ByVal NumberSetsDisered As Integer)
Dim NbSet As Integer
NbSet = 0
Dim ShF As Worksheet
Set ShF = ThisWorkbook.Sheets("Output")
Dim ColCr1 As Integer
Dim ColCr2 As Integer
Dim ColRef As Integer
ColCr1 = 1
ColCr2 = 2
ColRef = 4
If ShF.AutoFilterMode = True Then ShF.AutoFilterMode = False
Dim RgTotal As String
RgTotal = "$A$1:$" & ColLet(ShF.Cells(1, Columns.Count).End(xlToLeft).Column) & "$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row
ShF.Range(RgTotal).AutoFilter field:=ColCr1, Criteria1:=FilterF1
ShF.Range(RgTotal).AutoFilter field:=ColCr2, Criteria1:=FilterF2
'Erase Header value, fix? or correct at the end?
ShF.AutoFilter.Range.Columns(ColRef).Value = 1
Sheets("BF").Cells.ClearContents
ShF.AutoFilter.Range.Copy Destination:=Sheets("BF").Cells(1, 1)
Dim RgFilt As String
RgFilt = "$A$2:$B" & Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row '+ 1
Dim VR As Integer
'Here was the main issue, the value I got with autofilter was not correct and I couldn't figure out why....
'ShF.AutoFilter.Range.SpecialCells(xlCellTypeVisible).Rows.Count
'Changed it to a buffer sheet to have correct value
VR = Sheets("BF").Cells(Rows.Count, 1).End(xlUp).Row - 1
Dim RgDest As String
ShF.AutoFilterMode = False
'Now we need to define Set's number and paste N times
For k = 1 To NumberSetsDisered - 1
'define number set
For j = 1 To VR
ShF.Cells(Rows.Count, 1).End(xlUp).Offset(j, 3) = k + 1
Next j
RgDest = "$A$" & ShF.Cells(Rows.Count, 1).End(xlUp).Row + 1 & ":$B$" & (ShF.Cells(Rows.Count, 1).End(xlUp).Row + VR)
Sheets("BF").Range(RgFilt).Copy Destination:=ShF.Range(RgDest)
Next k
ShF.Cells(1, 4) = "Set"
Sheets("BF").Cells.ClearContents
'ShF.AutoFilterMode = False
End Sub
And the function to get column letter using an integer input :
Function ColLet(x As Integer) As String
With ActiveSheet.Columns(x)
ColLet = Left(.Address(False, False), InStr(.Address(False, False), ":") - 1)
End With
End Function

How to Search Specific Column In Each Worksheet of Excel

I have an excel document with over 50 worksheets all with a similar naming convention.
As this will be so unfriendly for users to navigate to, I wrote a VBA macro which creates a worksheet called summary with the list of all the worksheets hyperlinked in a tabular form with Sheet A B and C as the Column and Sheet 1 and 2 as rows.
Now I am trying to go through each row of a specific column in Sheet 1 and Sheet 2 and look for any reference to SheetB, SheetC and SheetD and for each reference found and I want to mark that creating a matrix.
I am not sure how to achieve this. Any assistance will be much appreciated.
I have managed to search Sheet 1 and 2 for any reference to SheetB as shown below but I am not sure how to update the corresponding cell in my summary sheet.
Function findWord(word As String, wSheet As String) As Boolean
Dim LastRow As Long
Dim i As Long
LastRow = Worksheets(wSheet).Cells(Rows.Count, "D").End(xlUp).Row
For i = LastRow To 1 Step -1
If Worksheets(wSheet).Range("D" & i).Value = word Then
findWord = True
Exit Function
End If
Next i
End Function
For Each wsSheet In wbBook.Worksheets
If (wsSheet.Name <> wsActive.Name) And (Left(wsSheet.Name, 4) <> "fact") Then
For i = 2 To lastColumn
MsgBox wsSheet.Name
If findWord(columnNames(counter2), wsSheet.Name) Then
'Update summary sheet
End If
counter = counter2 + 1
Next i
End If
Next wsSheet
If the result in "Summary sheet" you are looking for is similar to this :
Then you can use something like this (read the comments inside the code for explanations)
Sub MarkReferencesToSheets()
Dim wsSummary As Worksheet 'sheet with summary table matrix
Dim wsSheetRow As Worksheet 'sheets in which we will search references to other sheets
Dim strSheetColumnName As String 'name of the reference we are looking for
Dim intSheetRow As Integer 'for loop purposes
Dim intSheetColumn As Integer 'for loop purposes
Set wsSummary = Sheets("Summary")
For intSheetRow = 2 To 3 'change to suit; headers for rows in summary sheet
Set wsSheetRow = Worksheets(wsSummary.Cells(intSheetRow, 1).Value)
For intSheetColumn = 2 To 4 'change to suit; headers for columns in summary sheet
strSheetColumnName = wsSummary.Cells(1, intSheetColumn) 'name of sheet we are looking for
If Not wsSheetRow.Columns(4).Find(strSheetColumnName) Is Nothing Then 'look only in column "D", or 4
wsSummary.Cells(intSheetRow, intSheetColumn) = "X" ' if we found it, mark it
Else
'if you want something else in the cell when reference is not found, put it here
End If
Next intSheetColumn
Next intSheetRow
End Sub

Resources