Dynamically sort data only after specific cell data - excel

I am looking to sort data after a dynamic row is created with specific text in one cell in the A column. I am able to set up the condition where the data is only sorted once the cell is located, but I am struggling with then specifying to only apply the sort to the rows beyond that location. Here is what I have tried, attempting to sort only the data one row below where Cell 8 does not equal Cell 9 and beyond for column C:
Dim intl As Range
Dim rSortRangez As Range
Dim iRowz As Integer, iColz As Integer
Dim cus As Range
Set intl = shtDest.Range("C8")
iRowz = intl.Row
iColz = intl.Column
Set rSortRangez = sheets("Sheet1").Range("A8", "P99")
Set cus = intl.Offset(1, 0)
Do
For Each intl In rSortRangez
If intl <> intl.Offset(1, 0) Then
rSortRangez.Sort _
Key1:=sheets("Sheet1").Range("cus"), Order1:=xlDescending, _
Header:=xlNo, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal, _
DataOption2:=xlSortNormal, DataOption3:=xlSortNormal

You can use the vba Range.Sort method.
It can be used as follows:
Dim customSortRangeString As String
customSortRangeString = "C1:E5" 'enter in whatever you want
'key1 is the column that you will be sorting based on the first time.
'order1 is the order that it will sort the first time.
'header tells excel that the first row contains headers.
'Note: there are also key2 and key3 (as well as an order for each) allowing you to have multiple
'search criteria.
ActiveWorkbook.Worksheet("YOUR WORKSHEET HERE").Range(customSortRangeString).Sort key1:=Range("C2"), _
order1:=xlAccending, header:=xlYes
More information on the sort method can be found here
UPDATE - 5/9/19: Added information for a dynamic sort range based on criteria provided in this answer's comments.
Private Sub test()
Dim wb As Workbook
Dim ws As Worksheet
Dim totalRow As Integer
Dim startSortRow As Integer
Dim sortRange As String
Dim sortKey As String
'Setting both the workbook and sheet to variables to make them easier to use.
Set wb = ActiveWorkbook
Set ws = wb.Worksheets("YOUR WORK SHEET")
'getting the total number of rows we may have to loop through to find the appropreate cell.
totalRow = ws.Range("C" & Rows.Count).End(xlUp).Row
'loops through the rows
For a = 1 To totalRow
'checks if the previous cell does not equal the current cell
If ws.Range("C" & a).Value <> ws.Range("C" & a - 1) Then
'if above statement is true, set the startSortRow to the value of a and exit the loop
startSortRow = a
Exit For
End If
Next
'Create the sort range string
sortRange = "A" & startSortRow & ":P100"
'Create the sort key
sortKey = "C" & startSortRow
'Sort based on the values found above
ws.Range(sortRange).Sort key1:=Range(sortKey), order1:=xlAccending, Header:=xlNo
End Sub
This should work as expected with the dynamic sort range. (Have not tested)

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")

Splitting data in one sheet and sorting it into preexisting sheets based on part number

I have a workbook already made and it is set up specifically to create histograms on data read in from a separate program. When I pull the data into the workbook, it all goes into one sheet in my workbook. From here I need to split the data apart and sort it into specific tabs based on part number. I have 9 part numbers total and around 25,000 rows of data a day that needs to be sorted. Column A is the date, B is the serial number, C is the part number, D is a machine code, E is the static flow data, and F is a detail. I need to sort by Column C 9 potential part numbers which look like this "'111". "'123" etc with an apostrophe before each number. They are already in that format. The only data that needs to go to the corresponding worksheet is numbers from Column E. This is what I have so far but it doesn't work.
'For loop to filter through all the available part times and put the data in the correct tab
For i = 1 To 11
'PartType array is all 9 part types possible
Worksheets("Paste Data Here").AutoFilter Field:=3, Criteria1:=PartType(i) 'This is where it fails
Debug.Print ("Filtered")
Worksheets("Paste Data Here").SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Debug.Print ("Selected")
'InputRanges is where in each worksheet the data needs to go, this is established
'in another sub
'TabList is an array of each worksheet in the same order at the PartType array
ThisWorkbook.Sheets(TabList(i)).InputRanges(daterange).Select
ThisWorkbook.Sheets(TabList(i)).InputRanges(daterange).Paste
Debug.Print ("Pasted")
Application.CutCopyMode = False
Debug.Print ("i: " & i)
Debug.Print ("PartType(i): " & PartType(i))
Next i
Neither AutoFilter nor SpecialCells works like that for a worksheet.
You need to specify some kind of range to apply these methods to.
Dim ws As Worksheet
Set ws = Worksheets("Paste Data Here")
ws.UsedRange.AutoFilter Field:=3, Criteria1:=PartType(i)
ws.UsedRange.SpecialCells(xlCellTypeVisible).Copy ThisWorkbook.Sheets(TabList(i)).InputRanges(DateRange)
For i = 1 To 11
Debug.Print ("Searching Part: " & PartType(i))
Dim ws As Worksheet
Set ws = Worksheets("Paste Data Here")
ws.AutoFilterMode = False
Dim rng1 As Range
Set rng1 = Range("C:C").Find(PartType(i), , xlValues, xlWhole)
If Not rng1 Is Nothing Then
Dim lastrow1 As Long
lastrow1 = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Dim copyRange As Range
Set copyRange = ws.Range("E2:E" & lastrow1)
ws.UsedRange.AutoFilter Field:=3, Criteria1:=PartType(i)
copyRange.SpecialCells(xlCellTypeVisible).Copy ThisWorkbook.Sheets(TabList(i)).Range(InputRanges(daterange))
End If
Next i

Move rows to different excelfiles matching multiple cellvalues per file

I would like to be able to move the rows of a large-ish excel file into different excel files. Multiple matching cell values from a single column in the source file will need to be moved to each destination file. I would like to do this in an easily extensible way since there will be an increasing number of cell value and destination file pairs in the future.
In my current implementation I first move the rows to separate sheets, one for each destination file and later make separate files from each sheets, that part of the code works well and for brevity I have not included it in this question.
So far I have tried using the following for moving the rows to their respctive new worksheets, and it works but has made for lots of redundancies in the code. I have included a code sample using just one pair, "PRE Name1*" which goes in the sheet "Destfile1", in my current project I have the big code block repeated for every pair, which makes the project not very easy to work with. The first part of the pair, that matches the cell value is using wildcard matching since most of the values have common prefixes in their names.
Sub SplitOnCellvalues()
Dim xRg As Range
Dim I As Long
Dim J As Long
Dim K As Long
I = Worksheets("Source").UsedRange.Rows.Count
J = Worksheets("Destfile1").UsedRange.Rows.Count
If J = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Destfile1").UsedRange) = 0 Then J = 0
End If
Set xRg = Worksheets("Source").Range("O1:O" & I)
On Error Resume Next
Application.ScreenUpdating = False
For K = 1 To xRg.Count
If CStr(xRg(K).Value) Like "PRE Name1*" Then
xRg(K).EntireRow.Copy Destination:=Worksheets("Destfile1").Range("A" & J + 1)
xRg(K).EntireRow.Delete
K = K - 1
J = J + 1
End If
Next
Application.ScreenUpdating = True
End Sub
I currently also have a separate sub that pre-creates the sheets so it is safe to assume they exist.
I have also tried to build a multidimensional array that I could iterate over instead, but I can't seem to find a good way to build the array that feels like it will scale well when the list of pairs grow larger.
To illustrate the list of matching cell values and destination sheets they need to be moved to looks similar to this
"PRE Name1*" "Destfile1111"
"PRE Name2*" "DestfileAAAA"
"PRE AAAAA*" "Destfile1111"
"PRE DDDDD*" "Destfile2222"
"PRE Name4*" "DestfileAAAA"
"PRE Name4*" "DestfileAAAA"
I am mostly looking for a way to do this with fairly clean and extensible code as this project will need to be amended with new pairs of cell values and destination files on at least a monthly basis for the foreseeable future.
This code should do what you want. It doesn't delete the source range though.
I haven't added the * wildcard to the end of each value. It gets added during the filter.
Option Explicit
Public Sub SplitOnCellValues()
Dim MatchesArray() As Variant
MatchesArray = Array("PRE Name1", "Destfile1111", _
"PRE Name2", "DestfileAAAA", _
"PRE AAAAABV", "Destfile1111", _
"ABC", "Destfile1234")
Dim SourceSheet As Worksheet
Set SourceSheet = ThisWorkbook.Worksheets("Source")
'Make sure the source sheet isn't filtered and set a range
'to the data including and excluding headers.
With SourceSheet
If .AutoFilterMode Then .AutoFilterMode = False
Dim SourceLastCell As Range
Set SourceLastCell = LastCell(SourceSheet)
Dim SourceRange As Range
Set SourceRange = .Range(.Cells(1, 1), SourceLastCell)
Dim SourceData As Range
Set SourceData = .Range(.Cells(2, 1), SourceLastCell)
End With
'Loop through the array and filter the source data.
'If data is found then copy the visible rows to the bottom of the required sheet.
Dim TargetLastCell As Range
Dim TargetSheet As Worksheet
Dim x As Long
For x = LBound(MatchesArray) To UBound(MatchesArray) Step 2
SourceRange.AutoFilter Field:=15, Criteria1:=MatchesArray(x) & "*"
If SourceRange.Columns(1).SpecialCells(xlCellTypeVisible).Count > 1 Then
Set TargetSheet = ThisWorkbook.Worksheets(MatchesArray(x + 1))
Set TargetLastCell = LastCell(TargetSheet)
SourceData.SpecialCells(xlCellTypeVisible).Copy _
Destination:=TargetSheet.Cells(TargetLastCell.Row + 1, 1)
End If
Next x
End Sub
'Find the last cell on a sheet. Returns A1 if the sheet is empty.
Public Function LastCell(wrkSht As Worksheet) As Range
Dim LastCol As Long, LastRow As Long
On Error Resume Next
With wrkSht
LastCol = .Cells.Find("*", , , , xlByColumns, xlPrevious).Column
LastRow = .Cells.Find("*", , , , xlByRows, xlPrevious).Row
End With
If LastCol = 0 Then LastCol = 1
If LastRow = 0 Then LastRow = 1
Set LastCell = wrkSht.Cells(LastRow, LastCol)
On Error GoTo 0
End Function

Copy/Paste dynamic range

Starting from Sheet "DATA" range B4:Hx, where x is my last row taking by a row count. I need to copy this range and paste it as values on sheet "bat" starting at A1.
Going forward I need to offset columns in 6. So my second copy will be I4:Ox and so one copying appending into bat sheet.
I know where I must stop and I'm informing it using the Funds value.
The first error I'm having is when I try set Column2 = Range("H" & bottomD) value that is giving me "overflow".
And sure I don't know yet if my For loop would work.
Sub Copy_bat()
Dim bottomD As Integer
Dim Column1 As Integer
Dim Column2 As Integer
Dim i As Integer
Dim Funds As Integer
Funds = Sheets("bat").Range("u3").Value
Sheets("DATA").Activate
bottomD = Range("A" & Rows.Count).End(xlUp).Row
Column1 = Range("B4")
Column2 = Range("H" & bottomD)
For i = 1 To Funds
Range(Column1 & ":" & Column2).Copy
Sheets("Data").Cells(Rows.Count, "A").End(xlUp)(2).PasteSpecial Paste:=xlPasteValues, SkipBlanks:=True, Transpose:=False
Column1 = Colum1.Range.Offset(ColumnOffset:=6)
Column2 = Colum2.Range.Offset(ColumnOffset:=6)
Next i
End Sub
Always use Option Explicit at the beginning of every module to prevent from typos. Always! You had typos at the bottom - Colum1 and Colum2.
Avoid Activate and Select (you had Sheets("DATA").Activate) - better performance, smaller error chance. Instead, you should always explicitly tell VBA which sheet you are referring to.
While pasting values you can simply do something like Range2.value = Range1.value. No need to .Copy and then .Paste.
I did my best to understand what you need. From my understanding you did not use Range data type, while you needed that. This caused you errors.
Option Explicit
Sub Copy_bat()
Dim bottomD As Integer
Dim i As Integer
Dim Funds As Integer
Dim rngArea As Range
Funds = Sheets("bat").Range("u3").Value
With Sheets("Data")
bottomD = .Range("A" & .Rows.Count).End(xlUp).Row
Set rngArea = Range(.Range("B4"), .Range("H" & bottomD))
End With
For i = 1 To Funds
Sheets("bat").Cells(Rows.Count, "A").End(xlUp)(2).Resize(rngArea.Rows.Count, rngArea.Columns.Count).Value = _
rngArea.Value
Set rngArea = rngArea.Offset(, 7)
Next
End Sub
I made one rngArea variable of type Range instead of 2 variables (Column1 and Column2). This code takes info from "Data" sheet and puts that to "bat" sheet. Then offsets to right by 7(!) columns in "Data" sheet and puts data in "bat" sheet below the data that was put previously.

Split one column into multiple columns

I was wondering if anybody can kindly advise how to split a string with comma-separated values into multiple columns. I have been trying to figure this out but have been having a hard time finding a good solution. (also checked online, seems several that comes close but not necessarily fit what I exactly need)
Let's say I have a worksheet, call it "example", for instance,
and in the worksheet has the following strings under multiple
rows but all in column "A".
20120112,aaa,bbb,ccc,3432
20120113,aaa,bbb,ccc
20120113,ddd,bb,ccc,ddd,eee,fff,ggg,hhhh
20120132,aaa,bbb,ccc
20120112,aaa,bbb,ccc
20120112,xxx,bbb,ggg,ggg,333
20120112,aaa,bbb,ccc
20120112,abbd,bbb,ccc
How can I create a macro that will split the above into multiple columns.
Just several points
(1) I should be able to specify the worksheet name
ex: something like
worksheets("example").range(A,A) '
(2) The number of columns and rows are not fixed, and so I do not
know how many comma-separated values and how many rows there
would be before I run the vba script.
You could use InputBox() function and get the name of the sheet with data which shlould be splitted.
Then copy the data into variant array, split them and create new array of splitted values.
Finally assign the array of splitted values back to excel range. HTH
(Notice that the source data are modified directly so finally it is separated into columns and original un-splitted state is lost. But it is possible to modify the code so the original data won't be overwritten.)
Option Explicit
Private Const sourceColumnName As String = "A"
Private Const delimiter As String = ","
Public Sub Splitter()
' splits one column into multiple columns
Dim sourceSheetName As String
Dim sourceSheet As Worksheet
Dim lastRow As Long
Dim uboundMax As Integer
Dim result
On Error GoTo SplitterErr
sourceSheetName = VBA.InputBox("Enter name of the worksheet:")
If sourceSheetName = "" Then _
Exit Sub
Set sourceSheet = Worksheets(sourceSheetName)
With sourceSheet
lastRow = .Range(sourceColumnName & .rows.Count).End(xlUp).row
result = SplittedValues(data:=.Range(.Cells(1, sourceColumnName), _
.Cells(lastRow, sourceColumnName)), _
partsMaxLenght:=uboundMax)
If Not IsEmpty(result) Then
.Range(.Cells(1, sourceColumnName), _
.Cells(lastRow, uboundMax)).value = result
End If
End With
SplitterErr:
If Err.Number <> 0 Then _
MsgBox Err.Description, vbCritical
End Sub
Private Function SplittedValues( _
data As Range, _
ByRef partsMaxLenght As Integer) As Variant
Dim r As Integer
Dim parts As Variant
Dim values As Variant
Dim value As Variant
Dim splitted As Variant
If Not IsArray(data) Then
' data consists of one cell only
ReDim values(1 To 1, 1 To 1)
values(1, 1) = data.value
Else
values = data.value
End If
ReDim splitted(LBound(values) To UBound(values))
For r = LBound(values) To UBound(values)
value = values(r, 1)
If IsEmpty(value) Then
GoTo continue
End If
' Split always returns zero based array so parts is zero based array
parts = VBA.Split(value, delimiter)
splitted(r) = parts
If UBound(parts) + 1 > partsMaxLenght Then
partsMaxLenght = UBound(parts) + 1
End If
continue:
Next r
If partsMaxLenght = 0 Then
Exit Function
End If
Dim matrix As Variant
Dim c As Integer
ReDim matrix(LBound(splitted) To UBound(splitted), _
LBound(splitted) To partsMaxLenght)
For r = LBound(splitted) To UBound(splitted)
parts = splitted(r)
For c = 0 To UBound(parts)
matrix(r, c + 1) = parts(c)
Next c
Next r
SplittedValues = matrix
End Function
If you don't need to work on this task later again, here is a manual way as workaround:
Use a text editor (Notepad++) to replace "," to "tab".
Copy the content and paste into an empty Excel sheet.
Or you can try Excel import the data from file ("," as separator).
In case you need an automatic script, try this:
1) Press Ctrl+F11 to open VBA editor, insert a Module.
2) click the Module, add code inside as below.
Option Explicit
Public Function LastRowWithData(ByRef sht As Excel.Worksheet, Optional colName As String = "A") As Long
LastRowWithData = sht.Range(colName & sht.Rows.Count).End(xlUp).Row
End Function
Sub SplitToColumns(ByRef sColNames As String, ByRef strSeparator As String, ByRef rngDest As Excel.Range)
Dim arrColNames As Variant, i As Long
arrColNames = Split(sColNames, strSeparator)
For i = LBound(arrColNames) To UBound(arrColNames)
rngDest.Offset(0, i).Value = arrColNames(i)
Next i
End Sub
Sub PerformTheSplit()
Dim totalRows As Long, i As Long, sColNames As String
totalRows = LastRowWithData(Sheet1, "A")
For i = 1 To totalRows
sColNames = Sheet1.Range("A" & i).Value
Call SplitToColumns(sColNames, ",", Sheet2.Range("A" & i))
Next i
End Sub
3) Suppose you have the column name in Sheet1:
Press "Alt+F8" to run macro "PerformTheSplit", you will see result in Sheet2:
I would just use the Text-to-Columns wizard, with VBA routines to allow you to select the sheet and range to process, as you request above.
The Input boxes are used to obtain the sheet and range to process, and will default to the Active Sheet and Selection. This could certainly be modified in a variety of ways.
The built-in text to columns feature is then called, and, although you did not so specify, ti seems your first column represents a date in YMD format, so I added that as an option -- it should be obvious how to remove or change it if required.
Let me know how it works for you:
Option Explicit
Sub TTC_SelectWS_SelectR()
Dim WS As Worksheet, R As Range
Dim sMB As String
Dim v
On Error Resume Next
Set WS = Worksheets(Application.InputBox(Prompt:="Enter Worksheet Name: ", _
Title:="Select Worksheet", _
Default:=ActiveSheet.Name, _
Type:=2))
If Err.Number <> 0 Then
sMB = MsgBox("Invalid Worksheet Name", vbRetryCancel)
If sMB = vbRetry Then TTC_SelectWS_SelectR
Exit Sub
End If
On Error GoTo 0
Set R = (Application.InputBox(Prompt:="Select Range to Process: ", _
Title:="Select Range", _
Default:=Selection.Address, _
Type:=8))
Set R = WS.Range(R.Address)
R.TextToColumns DataType:=xlDelimited, textqualifier:=xlTextQualifierDoubleQuote, _
consecutivedelimiter:=False, Tab:=False, semicolon:=False, comma:=True, Space:=False, _
other:=False, fieldinfo:=Array(Array(1, xlYMDFormat))
End Sub

Resources