Extracting Rows Based On Search Criteria - excel

My issue is that I am trying to extract some information from a very large data sheet. The information that is being extracted is based on some search criteria that is entered on a form. The search form counts how many occurrences of this criteria exist, but then I need to extract the individual rows into a second sheet.
The bit I'm having difficulty with is understanding how to actually structure the extraction code. I'm in need of being pointed in the right direction. If the code can count how many occurrences there are, surely I can get the row numbers for those occurrences and extract the information, I'm just not getting anywhere trying to figure it out.
Here's my SEARCH code (this code works to get the number of occurrences based on the criteria asked)
Public Sub Run_Count_Click()
'// Set Ranges
Dim Cr_1, CR1_range, _
Cr_2, CR2_range, _
Cr_3, CR3_range, _
Cr_4, CR4_range, _
Cr_5, CR5_range _
As Range
'// Set Integers
Dim CR1, V1, CR1_Result, _
CR2, V2, CR2_Result, _
CR3, V3, CR3_Result, _
CR4, V4, CR4_Result, _
CR5, V5, CR5_Result, _
total_result, _
total_result2, _
total_result3, _
total_result4, _
total_result5 _
As Integer
'Set Strings
Dim V_1, V_2, V_3, V_4, V_5 As String
Dim ws As Worksheet
Set ws = Worksheets("database")
Sheets("Settings").Range("Start_Date").Value = Format(Me.R_Start.Value, "mm/dd/yyyy")
Sheets("Settings").Range("End_Date").Value = Format(Me.R_End.Value, "mm/dd/yyyy")
'Collect Start & End Dates
Dim dStartDate As Long
Dim dEndDate As Long
dStartDate = Sheets("Settings").Range("Start_Date").Value
dEndDate = Sheets("Settings").Range("End_Date").Value
ws.Activate
On Error GoTo error_Sdate:
Dim RowNum As Variant
RowNum = Application.WorksheetFunction.Match(dStartDate, Range("B1:B60000"), 0)
'MsgBox "Found " & Format(dStartDate, "dd/mm/yyyy") & " at row : " & RowNum
On Error GoTo error_Edate:
Dim RowNumEnd As Variant
RowNumEnd = Application.WorksheetFunction.Match(dEndDate, Range("B1:B60000"), 1)
' MsgBox "Found " & Format(dEndDate, "dd/mm/yyyy") & " at row : " & RowNumEnd
GoTo J1
error_Sdate:
Dim msg As String
msg = "You entered " & Format(dStartDate, "dd/mm/yyyy") & " as your Start Date, but no referrals were made on that date"
msg = msg & vbCrLf & "Please enter a different date in the Start Date box"
MsgBox msg, , "Start Date Not Found"
Err.Clear
Exit Sub
error_Edate:
msg = "You entered " & Format(dEndDate, "dd/mm/yyyy") & " as your End Date, but no referrals were made on that date"
msg = msg & vbCrLf & "Please enter a different date in the End Date box"
MsgBox msg, , "End Date Not Found"
Err.Clear
Exit Sub
J1:
'// Get Criteria From Form And Search Database Headers
Set Cr_1 = ws.Cells.Find(What:=Me.Count_Criteria_1.Value, After:=ws.Cells(1, 1), MatchCase:=False)
If Not Cr_1 Is Nothing Then
CR1 = Cr_1.Column '//Set CR1 as the Column in which the Criteria Header was found
Else
MsgBox "Criteria 1 Has Not Been Found In The Database. Report Has Failed To Generate"
Exit Sub
End If
'// Get Variable Value From Form And Set Shortcode
V_1 = Me.Criteria_1_Variable.Value
Set CR1_range = ws.Range(ws.Cells(RowNum, CR1), ws.Cells(RowNumEnd, CR1))
CR1_Result = Application.CountIf(CR1_range, V_1)
Me.Count_Result.visible = True
Me.Count_Result.Value = "Based On Your Search Criteria Of:" & vbNewLine & vbNewLine & _
"- " & Me.Count_Criteria_1.Value & ": " & Me.Criteria_1_Variable.Value & vbNewLine & vbNewLine & _
"The Results Are: " & CR1_Result & " entries found between the dates " & Format(dStartDate, "dd/mm/yyyy") & _
" and " & Format(dEndDate, "dd/mm/yyyy")
Exit Sub
Is there an easy way of doing this with a loop? I know loops are not the best way of handling things, but Im looking for anything that works and I can tweak to suit my needs.
Thanks if you can help in advance, it's a monster of a spreadsheet!
----------------------------
*Update With Accepted Answer:*
----------------------------
Public Sub Count_Extract_Click()
'Collect Information To Be Extracted
Set ws = Worksheets("database")
Set ps = Worksheets("Extracted Rows")
ps.Range("A3:AM60000").Clear
For i = RowNum To RowNumEnd
If ws.Cells(i, CR1).Value = V_1 Then
ws.Range("A" & i & ":AM" & i).Copy
ps.Activate
'find first empty row in database
emR = ps.Cells.Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row + 1
ps.Range("A" & emR & ":AM" & emR).PasteSpecial
End If
Next i
End If
End Sub

You should be able to set a For loop to check each value in the range you've found and copy it to (another cell, an array, whatever you like.)
For i = rowNum To rowNumEnd
If Cells(i,CR1).Value = V_1 Then
MsgBox "Found match on row " & i
End If
Next i
I haven't tested this, but it should work. Let me know if you get any errors.

I can't really try this out, but maybe you can. Keep the line V_1 = Me.Criteria_1_Variable.Value but replace the next 2 by :
CR1_Result = 0 'Initiates counter at 0
Dim CR1_Lines(1000) As Long 'Declares an array of 1001 (indexes 0-1000) Longs (big integers)
For x = RowNum To RowNumEnd 'Loops through all the rows of CR1
If ws.Cells(x, CR1) = V_1 Then 'Match!
'Double array size if capacity is reached
If CR1_Result = UBound(CR1_Lines) Then
ReDim Presrve CR1_Lines(UBound(CR1_Lines) * 2)
End If
'Store that line number in the array
CR1_Lines(CR1_Result) = x
'Increment count of matches
CR1_Result = CR1_Result + 1
End If
Next x 'Next row!
You can then loop through that array with this code :
For i = 0 to UBound(CR1_Lines)
'Do something! (Why not just an annoying pop-up box with the content!)
MsgBox CR1_Lines(i)
Next i
EDIT : I just read that the spreadsheet is monstruous, and re-dimensioning every time a new match is found might be neat, but it's a hell of a performance drop. I made some changes directly in the above code to make it somewhat more effective.
EDIT #2 : I've simplified code so you don't have anything to do but a copy paste (please forgive me not assuming RowNum and RowNumEnd had valid data). It should work exactly as accepted answer, but was posted a bit before and actually shows how to extract the line number. I understand if all you needed is a pop-up box with the line number, and will be satisfied with the upvote already received.

Related

Combine Multiple MsgBox to one

I am trying to combine multiple msgbox but i couldnot figure out.
Here is my Code:
If InStr(ThisWorkbook.Worksheets("DailyReport").Range("F8").Value, "DCT") > 0 Then
If IsEmpty(Sheet2.Range("G34").Value) Then
MsgBox ("The Following Test is not Performed " & Sheet2.Range("E34").Value)
End If
If IsEmpty(Sheet2.Range("G35").Value) Then
MsgBox ("The Following Test is not Performed " & Sheet2.Range("E35").Value)
End If
If IsEmpty(Sheet2.Range("G36").Value) Then
MsgBox ("The Following Test is not Performed " & Sheet2.Range("E36").Value)
End If
End If
I want to search for word DCT in Cell F8 of Worksheets DailyReport and if it exist then I want to look at multiple cell like G34,G35,G36.... and if these cell are empty then display msgbox saying "The following Test is Not performed: E34,E35,E36...."
Let's Say if G34 and G35 is Empty then the msg box should display
The following Test is not Performed:
Cell value in E34
Cell Value in E35
Msgbox Should have continue and Cancel button
If User hit Continue Then Continue the sub
If user Hit Cancel then Exit the sub
Return Combined Messages in a Message Box
Sub CombineMessages()
Dim CheckCells() As Variant: CheckCells = Array("G34", "G35", "G36")
Dim ValueCells() As Variant: ValueCells = Array("E34", "E35", "E36")
Dim CheckString As String
CheckString = CStr(ThisWorkbook.Worksheets("DailyReport").Range("F8").Value)
Dim UntestedCount As Long, MsgString As String
If InStr(CheckString, "DCT") > 0 Then
Dim n As Long
For n = LBound(CheckCells) To UBound(CheckCells)
If IsEmpty(Sheet2.Range(CheckCells(n))) Then
MsgString = MsgString & vbLf & " " _
& CStr(Sheet2.Range(ValueCells(n)).Value)
UntestedCount = UntestedCount + 1
End If
Next n
End If
If UntestedCount > 0 Then
MsgString = "The following test" _
& IIf(UntestedCount = 1, " is", "s are") & " not performed:" _
& vbLf & MsgString & vbLf & vbLf & "Do you want to continue?"
Dim Msg As Long: Msg = MsgBox(MsgString, vbQuestion + vbYesNo)
If Msg = vbNo Then Exit Sub
End If
MsgBox "Continuing...", vbInformation
End Sub
I want to look at multiple cell like G34,G35,G36....
if these cell are empty then display msgbox saying "The following Test is Not performed: E34,E35,E36...."
G34,G35,G36.... Looks like this range is dynamic? Or will it always be these 3? And if it is dynamic then how are you deciding the range. For example why G34 and not G1? Or till where do you want to check? Till last cell in G? All this will decide how you write a concise vba code. I am going to assume that you want to check till last cell in column G. In case it is say from G34 to say G60(just an example), then change the For Next Loop from For i = 34 To lRow to For i = 34 To 60
Is this what you are trying? (UNTESTED)
Option Explicit
Sub Sample()
Dim i As Long
Dim lRow As Long
Dim CellAddress As String
If InStr(ThisWorkbook.Worksheets("DailyReport").Range("F8").Value, "DCT") > 0 Then
With Sheet2
'~~> Find last row in Col G
lRow = .Range("G" & .Rows.Count).End(xlUp).Row
'~~> Check the range for blank cells
For i = 34 To lRow
If Len(Trim(.Range("G" & i).Value2)) = 0 Then
CellAddress = CellAddress & "," & "E" & i
End If
Next i
End With
'~~> Check if any addresses were found
If CellAddress <> "" Then
CellAddress = Mid(CellAddress, 2)
Dim ret As Integer
'~~> Ask user. There is no CONTINUE button. Use YES/NO
ret = MsgBox("The following Test is Not performed:" & _
vbNewLine & CellAddress & vbNewLine & _
"Would you like to continue?", vbYesNo)
If ret = vbYes Then
'~~> Do what you want
Else
'~~> You may not need the else/exit sub part
'~~> Depending on what you want to do
Exit Sub
End If
'
'
'~~> Rest of the code
'
'
End If
End If
End Sub

How to extend the rows of table automatically and split string if overflows the cell to next line when writing to excel from accces using vba

Background:
I am trying to use access to automatically make quotation, but if use the built-in to convert to pdf. It's not what I want. So I am writing some vba in access to write to excel. In my vba, I open a template excel called "clean.xlsx" to write data from access to it.
The screenshot is the table as part of the quotation template inside the excel
Q1:However, sometimes my data may be too much for a single cell and part of it will be hidden. And I want it to go to next line to be fully displayed instead of being hidden.
The screenshot is the hidden data
Q2:Another thing is if I have too many items. The rows of the table is not automatically extended even though I double checked the setting of AutoCorrect Options in the excel. It just doesn't work. What shall I do?
The following is my code:
'''
Option Compare Database
Private Sub Command31_Click()
On Error GoTo SubError
'******************************************************
' Updated Commits
'******************************************************
'1.Quotation no. 10 Error fixed
'2.positions added
'3.Excel is kept open now
'4.Order of items is now same as the natural input order
'5.Weights now have two digits of decimals
'6."kg" is added after weights
'7.Subtotal added
'declare vars
Dim appExcel As Excel.Application
Dim myWorkbook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
Dim SQL As String
Dim rsl As DAO.Recordset
Dim i, position As Integer
Dim Message, Title, Default, MyValue
'user input for the quotation no.
Message = "Plz Enter Quotation No.:" ' Set prompt.
Title = "InputBox Demo" ' Set title.
Default = "1" ' Set default.
' Display message, title, and default value.
MyValue = InputBox(Message, Title, Default)
'Show user work is being performed
DoCmd.Hourglass (True)
'******************************************************
' RETRIEVE DATA
'******************************************************
'SQL statement to retrieve Article_No from Quotation_Detail table
'SQL = "SELECT Article_No AS [Article No]" & _
'"FROM Quotation_Detail " & _
'"ORDER BY Article_No "
'SQL statement to retrieve Company(0), Person(1), Telefone(2), Email(3), Address(4), City(5), Postcode(6),
'Province(7), USCI Num(8), Short Name(9) from Customers Table and Buyer(10), Quotation No(11),Quotation
'Date(12), Revision(13), Article_No(14), Quantity(15), Matchcode(16), RMB_price(17),reference(18) from Quotation Query 1,
'Description(19) & Weight(20) from Spare_Parts Table.
SQL = "SELECT Customers.Company, Customers.Person, Customers.Telefone, Customers.[E-Mail], " & _
"Customers.Address, Customers.City, Customers.Postcode, Customers.Province, Customers.[USCI Num], " & _
"Customers.[Short Name], [Quotation Query1].Buyer, [Quotation Query1].[Quotation No], " & _
"[Quotation Query1].[Quotation Date], [Quotation Query1].Revision, " & _
"[Quotation Query1].Article_No, [Quotation Query1].Quantity, [Quotation Query1].Matchcode, " & _
"[Quotation Query1].RMB_price,[Quotation Query1].[Our Reference], Spare_Parts.Description, Spare_Parts.[Weight (kg)] " & _
"FROM Spare_Parts INNER JOIN (Customers INNER JOIN [Quotation Query1] ON Customers.[Short Name] = [Quotation Query1].[Buyer]) ON Spare_Parts.[Article_No] = [Quotation Query1].[Article_No] " & _
"WHERE CStr([Quotation Query1].[Quotation No]) = '" & MyValue & "' "
'To select only one quotation with the quotation no. to make the quotation
'Quotation No. is an auto no.,the index shall be used instead of what seems like
' '"& is fixed format for text
'Execute query and populate recordset
Set rsl = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
'If no data, don't bother opening Excel, just quit
If rsl.RecordCount = 0 Then
MsgBox "No data selected for export", vbInformation + vbOKOnly, "No data exported"
GoTo SubExit
End If
'Loop each row to print data in rsl(0) to test what's in the rsl
'rsl(0) is the short name
'rsl(1) is the company name
' Contrl G to see what have been printed
' After this, the cursoe will at the end and filuence accessing values of fields in the next step
' Thus, this should be commented out
'Do While Not rsl.EOF
' Debug.Print rsl(5)
' rsl.MoveNext
'Loop
Set appExcel = CreateObject("Excel.Application")
Set myWorkbook = appExcel.Workbooks.Open("C:\Users\Cindy\Desktop\clean.xlsx")
appExcel.Visible = True
Set xlSheet = myWorkbook.Worksheets(1)
With xlSheet
.Name = "Quotation"
.Cells.Font.Name = "Calibri"
.Cells.Font.Size = 11
'build quotation info
'Quotation No. in C16
If rsl.Fields(11).Value < 10 Then
.Range("C16").Value = "SHA-002-000" + CStr(rsl.Fields(11).Value)
ElseIf rsl.Fields(11).Value < 100 Then
.Range("C16").Value = "SHA-002-00" + CStr(rsl.Fields(11).Value)
ElseIf rsl.Fields(11).Value < 1000 Then
.Range("C16").Value = "SHA-002-0" + CStr(rsl.Fields(11).Value)
ElseIf rsl.Fields(11).Value < 10000 Then
.Range("C16").Value = "SHA-002-" + CStr(rsl.Fields(11).Value)
Else
GoTo SubExit
End If
'Company name in A6
.Range("A6").Value = Nz(rsl.Fields(0).Value, "")
'Contact Person name in A7
.Range("A7").Value = Nz(rsl.Fields(1).Value, "")
'Fowarding address in A8
.Range("A8").Value = Nz(rsl.Fields(4).Value, "")
'Postcode in A9
.Range("A9").Value = Nz(rsl.Fields(6).Value, "")
' City in C9
.Range("C9").Value = Nz(rsl.Fields(5).Value, "")
' If there's province
If rsl.Fields(5).Value <> rsl.Fields(7).Value Then
' Province in F9
.Range("F9").Value = Nz(rsl.Fields(7).Value, "")
.Range("H9").Value = "Province"
End If
'Telephone in B12
.Range("B12").Value = Nz(rsl.Fields(2).Value, "")
' Email in C13
.Range("C13").Value = Nz(rsl.Fields(3).Value, "")
' USCI in B14
.Range("B14").Value = Nz(rsl.Fields(8).Value, "")
'Revision in F16
.Range("F16").Value = Nz(rsl.Fields(13).Value, "")
' Date in B17
.Range("B17").Value = Nz(rsl.Fields(12).Value, "")
' Reference in C18
If rsl.Fields(18).Value = 1 Then
.Range("C18").Value = "DD"
ElseIf rsl.Fields(18).Value = 2 Then
.Range("C18").Value = "WY"
End If
'Put the name of the feilds in rsl to Cell(1,cols + 1)
'For cols = 0 To rsl.Fields.Count - 1
' .Cells(1, cols + 1).Value = rsl.Fields(cols).Name
'Next
'Copy data from recordset to sheet
'.Range("A2").CopyFromRecordset rsl
'provide initial value to row counter
i = 25
'provide initial value to row posiiton counter
position = 1
'Loop through recordset and copy data from recordset to sheet
Do While Not rsl.EOF
'Item No. are written staring from B25 to the end
.Range("B" & i).Value = Nz(rsl.Fields(14).Value, "")
'Quantitities are written staring from C25 to the end
.Range("C" & i).Value = Nz(rsl.Fields(15).Value, "")
'Unit prices are written starting from I25 to the end
.Range("I" & i).Value = Nz(rsl.Fields(17).Value, "")
'Matcth codes are written starting from D25 to the end
.Range("D" & i).Value = Nz(rsl.Fields(16).Value, "")
'Despcriptions are written starting from E25 to the end
.Range("E" & i).Value = Nz(rsl.Fields(19).Value, "")
'Weights are written starting from H25 to the end
' .Range("H" & i).Value = Format(Nz(rsl.Fields(20).Value, ""), "#,##0.00")
'To add kg after weight
'Have issue: error number:94= invalid use of null even empty checked
If Not IsNull(rsl.Fields(20).Value) Then
.Range("H" & i).Value = CStr(Format(rsl.Fields(20).Value, "#,##0.00")) + "kg"
End If
'Positions are written starting from A25 to the end
.Range("A" & i).Value = Nz(position, "")
'Subtotals are written starting from J25 to the end
.Range("J" & i).Value = rsl.Fields(15).Value * rsl.Fields(17).Value
i = i + 1
position = position + 1
rsl.MoveNext
Loop
End With
'Save as excel file using the input MyValue and clean appExcel
myWorkbook.SaveAs FileName:="C:\Users\Cindy\Desktop\" & MyValue & ".xlsx"
Set appExcel = Nothing
SubExit:
On Error Resume Next
DoCmd.Hourglass False
appExcel.Visible = True
rsl.Close
Set rsl = Nothing
Exit Sub
SubError:
MsgBox "Error Number:" & Err.Number & "= " & Err.Description, vbCritical + vbOKOnly, "An Error occured"
GoTo SubExit
End Sub
'''

Unable to pass a String Parameter to a Sub when using Checkbox .OnAction

As the title says, I am unable to pass a String parameter through an OnAction that occurs when a checkbox is checked. I have successfully passed two integer values to the sub when the checkbox is checked, but now I need to also pass a String parameter (the String is actually the name of the current Worksheet).
This is currently what it looks like:
'Start of for loop which will run from the lower bound of esq to the upper bound.
For i = LBound(esq) To UBound(esq)
'Inserts a row at the specified location, the current row + 1 + the value of i (0 to 12 depending on which run of the loop it is currently on).
workSource.Rows(rowPos + 1 + i).Insert
'Sets cb as equal to the specified cell in the newly inserted row.
Set cb = workSource.CheckBoxes.Add(Cells(rowPos + 1 + i, colPos + 1).Left, Cells(rowPos + 1 + i, colPos + 1).Top, _
Cells(rowPos + 1 + i, colPos + 1).Width, Cells(rowPos + 1 + i, colPos + 1).Height)
'Start of With which sets the attributes of cb.
With cb
'Sets the caption as the current element of esq.
.Caption = esq(i)
'Links the checkbox with the cell directly beneath it.
.LinkedCell = Cells(rowPos + 1 + i, colPos + 1).Address
'Adds a macro which will be activated when it is clicked. The cell's row and column position will be passed as parameters to the macro.
.OnAction = "'ProcessCheckBox " & rowPos + 1 & "," & colPos + 1 + i & "," & currentName & "'"
'.OnAction = "'" & ThisWorkSheet.Name & "'!ProcessCheckBox"
'.OnAction = "'ProcessCheckBox " & rowPos + 1 & "," & colPos + 1 + i & "," & """SES""" & "'"
'.OnAction = "'ProcessCheckBox " & currentName & "'"
'End of With.
End With
'Starts next run of loop and increments i.
Next i
There are three commented out lines of OnAction that I attempted to experiment with in order to get just the string to be passed. Unfortunately, none of them worked. Here is the start of the code for the ProcessCheckBox sub:
'Sub to process when a checkbox has been changed.
Sub ProcessCheckBox(ByVal rowPos As Integer, ByVal colPos As Integer, ByVal currentSheet As String)
'Sub ProcessCheckBox(ByVal currentSheet As String)
MsgBox currentSheet
'Declares a worksheet object named currentSheet.
Dim activeSheet As Worksheet
'Sets currentSheet equal to the active worksheet.
Set activeSheet = ThisWorkbook.Worksheets(currentSheet)
'Set currentSheet = ActiveSheet
After clicking the checkbox, a msgbox appears that is completely blank, and then I run into an error where it says the subscript is out of range.
I gather from this that the sub is being called, the String value is just not being passed along. The string value in the first sub (currentName) does have a value, as I can print it out and use it for calculations just fine.
I think the problem is in the OnAction line itself. It took me a while to figure out how to pass the integer values due to not knowing the correct number of single and double quotes to use. I think it has to do with this, however, all of the other examples I saw passed String values like this. I even experimented by adding or removing quotes just to see if it would work out and nothing.
Other errors I thought it might be: a sub has a limit to how many/large parameters can be passed to it, only parameters of a single type can be passed (either String or Integer). Neither of these make sense because I have encountered many examples that pass much more data across many different types to a sub.
Thank you to the people who answered, but neither of the solutions offered worked. I've been testing the macro and it appears no matter what I do, it will not pass a string as a parameter, either alone or with other parameters. I don't know why.
Here are the two lines in question I have narrowed it down to :
.OnAction = "'ProcessCheckBox " & colPos + 1 & "," & rowPos + 1 + i & ",""" & nameSheet & """'"
And the first line of the sub :
Sub ProcessCheckBox(ByVal colPos As Integer, ByVal rowPos As Integer, ByVal sheetName As String)
Ticking the checkbox gives me an error saying "Argument Not Optional." However, it doesn't allow me to go into debug mode, and it doesn't highlight the specific line either, although I have tested it and believe these two lines to be the problem.
I've given up on figuring VBA's single and double quotes and acknowledge that I never needed anything to be passed as argument that wasn't available in the workbook in which the check box resides. Therefore I can easily get all the information I might want to pass directly from the worksheet.
Where that may not be enough, I also get access to the CheckBox object itself. All my needs for arguments can be satisfied completely without any quotation marks.
Private Sub CheckBox1_Click()
Dim ChkBox As Shape
Set ChkBox = ActiveSheet.Shapes(Application.Caller)
MsgBox ChkBox.Parent.Name & vbcr & _
ChkBox.OLEFormat.Object.Name
End Sub
Here are 3 possible solutions for your problem, all involving the Application.Caller.
Please run this code on your project.
Sub CheckBox1_Click()
Dim ChkBox As Shape
Dim WsName As String, Rpos As Long, Cpos As Long
Dim Cell As Range
' Solution 1
WsName = ActiveSheet.Shapes(Application.Caller).OLEFormat.Object.Parent.Name
MsgBox "You already know how to pass the cell coordinates" & vbCr & _
"Just get the Sheet name from here:" & vbCr & _
"Sheet name is """ & WsName & """"
Set ChkBox = ActiveSheet.Shapes(Application.Caller)
' Solution 2
Set Cell = ChkBox.OLEFormat.Object.TopLeftCell
Rpos = Cell.Row
Cpos = Cell.Column
WsName = Cell.Worksheet.Name
MsgBox "Solution 2" & vbCr & _
"The checkbox Top Left is aligned with the" & vbCr & _
"Linked Cell's Top Left:-" & vbCr & _
"Row number = " & Rpos & vbCr & _
"Column number = " & Cpos & vbCr & _
"Worksheet name = " & WsName & vbCr & _
"If Alignment of underlying cell and TopLeft" & vbCr & _
"isn't perfect, modify the placement in your other code." & vbCr & _
"Here is the TopLeft address:-" & vbCr & _
"TopLeftCell address = " & Cell.Address
' Solution 3
Set Cell = Range(ChkBox.OLEFormat.Object.LinkedCell)
Rpos = Cell.Row
Cpos = Cell.Column
WsName = Cell.Worksheet.Name
MsgBox "Solution 3" & vbCr & _
"Get the information directly from the Linked Cell." & vbCr & _
"(This is probably the one you are interested in):-" & vbCr & _
"Row number = " & Rpos & vbCr & _
"Column number = " & Cpos & vbCr & _
"Worksheet name = " & WsName & vbCr & _
"This method might fail if the linked cell" & vbCr & _
"is altered, perhaps manually."
End Sub

Excel filter table data to meet the sampling requirement

I need to obtain 2 random cases for each complaint handler in a data table that can be used for sampling.
Assuming I would have to group the data using the Handler ID (unique reference for each complaint handler) and then some how select two random pieces of information from the groups.
I have grouped this information using a Pivot. All case handlers in this has 2 or less cases so no further action needs to be taken on these. However, there is an exception with Chris Smith (h238) as he has three cases and the max sampling is 2 per case handler.
I need a script that will select two random cases for Chris and remove any additional cases (so we have a random sample of 2 cases).
I can do this manually by going filtering table by Chris' cases and then removing cases until there is only two remaining. However, the actual data set would be much larger so would be very time consuming and this process needs to be ran several times a day with the data in the table continually changing.
That's an interesting one!
Here is my solution. I've tried several possible versions.
Try 1:
As per originally posted data - Chris Smith (h238) is overloaded with 1 task and there are enough people to reassign tasks:
Try 2:
Chris Smith (h238) is still overloaded, but this time with 3 tasks and there are enough people to reassign tasks:
Try 3:
Poor Chris Smith (h238) is totally overloaded, but this time there are not enough people to reassign tasks:
Try 4:
This time Jane Doe (h324) is in line with Chris Smith (h238) - they are overloaded, but there are not enough people to reassign tasks:
Cases where there are no overloaded or no free people are breaking with appropriate messages, didn't do screenshot.
The code:
Sub ReassignCases()
' Variables
' people related:
Dim handlerIdRange As Range, handlerId As Range
Dim maxCases As Long
Dim cases As Long
Dim name As String, id As String
Dim nameTo As String, idTo As String
Dim caseRef As Range
' arrays:
Dim overloaded() As String
Dim free() As String
' counters:
Dim o As Long, f As Long, i As Long, c As Long, j As Long
' unique values container
Dim handlersList As New Collection
' output
Dim msg As String
Dim workSht As Worksheet
'----------------------------------------------------
' reassign the sheet name as you have in your workbook
Set workSht = ThisWorkbook.Sheets("Sheet1")
' parameter that can be changed if needed
maxCases = 2
With workSht
Set handlerIdRange = Range(.Cells(2, 2), .Cells(Rows.Count, 2).End(xlUp))
End With
' get the list of handlers
On Error Resume Next
For Each handlerId In handlerIdRange
handlersList.Add handlerId & ";" & handlerId.Offset(0, -1), handlerId & ";" & handlerId.Offset(0, -1)
Next
Err.Clear
On Error GoTo 0
For i = 1 To handlersList.Count
' look for overloaded
If Application.WorksheetFunction.CountIf(handlerIdRange, Split(handlersList(i), ";")(0)) > maxCases Then
ReDim Preserve overloaded(o)
' adding to array: id;name;qty of cases
overloaded(o) = handlersList.Item(i) & ";" & Application.WorksheetFunction.CountIf(handlerIdRange, Split(handlersList(i), ";")(0))
o = o + 1
' look for those who has less the 2 cases. If one has 2 cases - he is not free
ElseIf Application.WorksheetFunction.CountIf(handlerIdRange, Split(handlersList(i), ";")(0)) < maxCases Then
ReDim Preserve free(f)
free(f) = handlersList.Item(i)
f = f + 1
End If
Next
' check whether there are overloaded handlers
If Not Not overloaded Then
' if yes - proceed further
Else
' if not - inform and quit
MsgBox "There are no overloaded handlers.", vbInformation, "Info"
Exit Sub
End If
' check whether there are free handlers
If Not Not free Then
' if yes - proceed further
Else
' if not - inform and quit
o = UBound(overloaded) + 1
MsgBox "There " & IIf(o = 1, "is ", "are ") & o & " overloaded " & IIf(o = 1, "handler", "handlers") & ", but 0 free.", vbInformation, "Info"
Exit Sub
End If
msg = ""
' go through array of overloaded
For i = LBound(overloaded) To UBound(overloaded)
' Id of overloaded
id = Split(overloaded(i), ";")(0)
' Name of overloaded
name = Split(overloaded(i), ";")(1)
' number of over cases = total assigned - 2 (max cases)
cases = Split(overloaded(i), ";")(2) - maxCases
'
' check that there some free people left
If Not c > UBound(free) Then
' go through each handler in the array of free people
' free people are those, who have only 1 task and can take another 1
' if c was not used yet it is 0, otherwise, it will continue looping through free people
For c = c To UBound(free)
idTo = Split(free(c), ";")(0)
nameTo = Split(free(c), ";")(1)
' find the first match of the id in Id range
Set caseRef = handlerIdRange.Find(what:=id, LookIn:=xlValues)
' give an outcome of what was reassigned
msg = msg & "Task: " & caseRef.Offset(0, 1).Text & " was reassigned from " & name & " (" & id & ") "
With caseRef
.Value = idTo
.Offset(0, -1).Value = nameTo
End With
msg = msg & "to " & nameTo & " (" & idTo & ")" & Chr(10)
cases = cases - 1
' when all needed cases are passed to other stop looking through free people
If cases = 0 Then Exit For
Next
' if the loop through free people is finished,
' but there left some more - go to warning creation
If Not cases = 0 Then GoTo leftCases
Else
leftCases:
msg = msg & Chr(10) & Chr(10) & "There are no more free handlers." & Chr(10)
For j = i To UBound(overloaded)
msg = msg & Split(overloaded(j), ";")(1) & " is still overloaded with " & cases & " cases." & Chr(10)
Next
msg = msg & Chr(10) & "Operation completed with warnings." & Chr(10)
msg = msg & Chr(10) & "Would you like to save results?"
If MsgBox(msg, vbExclamation + vbYesNo, "Done") = vbYes Then SaveResults (msg)
Exit Sub
End If
Next
msg = msg & Chr(10) & "Operation completed." & Chr(10)
msg = msg & Chr(10) & "Would you like to save results?"
If MsgBox(msg, vbInformation + vbYesNo, "Done") = vbYes Then SaveResults (msg)
End Sub
Sub SaveResults(Text As String)
Dim lines() As String, temp() As String
Dim i As Long, j As Long
Dim FileName As String
lines = Split(Text, Chr(10))
For i = LBound(lines) To UBound(lines)
If lines(i) Like "Task:*" Then
ReDim Preserve temp(j)
temp(j) = lines(i)
j = j + 1
End If
Next
Dim fi As Long
FileName = "Task reassignment log"
FileName = Application.GetSaveAsFilename(InitialFileName:=FileName, FileFilter:="Text Files (*.txt), *.txt", Title:="Saving as text...")
If UCase(FileName) = "FALSE" Then Exit Sub
If CheckFileExists(FileName) Then
If MsgBox("The file " & Dir(FileName) & " already exists. Overwrite?", vbQuestion + vbYesNo) = vbYes Then
WriteToFile FileName, temp
Else
i = 0
Do Until Not CheckFileExists(FileName)
For j = Len(FileName) To 1 Step -1
If Mid(FileName, j, 1) = Application.PathSeparator Then Exit For
Next
FileName = Left(FileName, j)
If i = 0 Then
FileName = FileName & "Task reassignment log.txt"
Else
FileName = FileName & "Task reassignment log (" & i & ")" & ".txt"
End If
i = i + 1
Loop
WriteToFile FileName, temp
MsgBox "The file was saved with " & Chr(34) & Dir(FileName) & Chr(34) & " name", vbInformation
End If
Else
WriteToFile FileName, temp
End If
End Sub
Sub WriteToFile(FileName As String, Text() As String)
Dim i As Long
Open FileName For Output As #1
For i = LBound(Text) To UBound(Text)
Write #1, Text(i)
Next
Close #1
End Sub
Function CheckFileExists(FileName As String) As Boolean
CheckFileExists = False
If Not Dir(FileName) = "" Then CheckFileExists = True
End Function
Note
1. I didn't randomize a list of free people, so they are taken one by one. If you do need this - you can easily find a macro to randomize an array and insert it as side function.
2. I'm not sure that it works perfectly - comments are appreciated!
Update
I've slept on this question and decided to complete my answer with such an essential thing as saving the reassignment log to text file, so the code is updated.

VBA: Return more than 2 filter criterias

I am having a standard filter on a bunch of columns and i want to read the filter criterias. This wasn't really a problem until the case where more than 2 criteria are selected. I have a row with different strings and i want do be able to get the criteria the user has chosen. Currently I am working with this piece of code:
Set ws = Worksheets(actSheet)
For Each flt In ws.AutoFilter.Filters
If flt.On = True Then
criterias = criterias & flt.Criteria1 & ", "
criterias = criterias & flt.Criteria2 & ", "
End If
Next flt
This only gives me the opportunity to get 2 Criteria max.
I have found this line of code in different forums, but it was used for other reasons and i do not really know how to use this code for me:
ActiveSheet.AutoFilter Field:=1, Criteria1:=Array(param1, param2, param3,...) _
Operator:=xlFilterValues
This way you can set criteria i think, but i want to get it.
Any ideas how i can use this code? Or another suggestion for my problem?
Thanks in advance!
Edit:
Well i've worked a lot of hours on this and still no Solution. It is not really possible to get the Array in Criteria1 in an Array. Always the same Error "Can not assign to array". Although i assigned the same array to the Filter Criteria1 10 lines of code before...
So this works:
Dim arr(3) As String
arr(2) = "test1"
arr(1) = "test2"
arr(3) = "test3"
ActiveSheet.Range("A1:C1").AutoFilter Field:=1, Criteria1:=arr, Operator:=xlFilterValues
But this doesn't:
arr = ws.AutoFilter.Filters.Criteria1
Edit2: This gets the filters and it's headers
Sub GetFilteredItems()
Dim fl As Filter
Dim ws As Worksheet: Set ws = ActiveSheet
Dim i As Long: i = 0
Dim myfilters As String
For Each fl In ws.AutoFilter.Filters
If fl.On Then
If Len(myfilters) = 0 Then
myfilters = ws.AutoFilter.Range.Offset(0, i).Resize(1, 1).Value
Else
myfilters = myfilters & "; " & _
ws.AutoFilter.Range.Offset(0, i).Resize(1, 1).Value
End If
If fl.Count > 2 Then
myfilters = myfilters & ": " & Replace(Join(fl.Criteria1), "=", "")
Else
myfilters = myfilters & ": " & Replace(fl.Criteria1, "=", "")
On Error Resume Next
myfilters = myfilters & " " & Replace(fl.Criteria2, "=", "")
On Error GoTo 0
End If
End If
i = i + 1
Next
Debug.Print myfilters
End Sub
I remove other codes to avoid confusing the reader.
This is much like the OP's approach but a little bit direct. HTH.
Finally i found an answer!
The issue was not too big, just really hard to get an answer on this because you nearly don't find anything on the internet.
Dim criterias As String
Dim arr As Variant
For Each flt In ws.AutoFilter.Filters
If flt.On = True Then
'write the column head in the string
criterias = criterias & "=" & _
ws.AutoFilter.Range.Offset(0, i - 1).Resize(1, 1).Value & ": "
If flt.Count > 2 Then
arr = flt.Criteria1 '<----- my problem
For i = LBound(arr) To UBound(arr) '<-----
criterias = criterias & arr(i) '<-----
Next '<-----
Else
criterias = criterias & flt.Criteria1
On Error Resume Next
criterias = criterias & flt.Criteria2 & ", "
End If
End If
i = i + 1
Next flt
ws is just my active Worksheet
I didn't realise that i should make the array Variant, and not initialise it as a simple String array.
After that the criterias String looks like "Criteria1: =test1=test2=test3", so just replace the "=" with ", " or something like that and you're done!
Axel Richter from a german office board was a big help in this issue ;) (for the german readers: http://www.office-loesung.de/p/viewtopic.php?f=166&t=666472&p=2773974#p2773974)

Resources