Merging Cells row by row without losing any data - vba

I'm getting data that is in 1, 2, or 3 columns (possibly more). I need each row to combine the data in the respective row without losing any of the data from any columns.
I managed to get some code together that will combine the cells properly, but I'm struggling to use this code to look through each row and combine the cells in that row, for all rows that contain data.
Here is what I have so far:
Sub JoinAndMerge()
'joins all the content in selected cells
'and puts the resulting text in top most cell
'then merges all cells
Dim outputText As String
Const delim = " "
On Error Resume Next
For Each cell In Selection
outputText = outputText & cell.value & delim
Next cell
With Selection
.Clear
.Cells(1).value = outputText
.Merge
.HorizontalAlignment = xlGeneral
.VerticalAlignment = xlCenter
.WrapText = True
End With
End Sub
And here's what I've got as far as trying to get it to look through each row.
Sub JoinAndMerge2()
'joins all the content in selected cells
'and puts the resulting text in top most cell
'then merges all cells
Dim outputText As String
Const delim = " "
On Error Resume Next
Dim cell_value As Variant
Dim counter As Integer
Dim xlastRow As Long
Dim xlastColumn As Long
xlastRow = Worksheets("Sheet48").UsedRange.Rows.Count
xlastColumn = Worksheets("Sheet48").UsedRange.Columns.Count
'Looping through A column define max value
For i = 1 To xlastRow
'Row counter
counter = 1
'Take cell one at the time
cell_value = ThisWorkbook.ActiveSheet.Cells(1, i).value
For Each cell In Selection
outputText = outputText & cell.value & delim
Next cell
With Selection
.Clear
.Cells(1).value = outputText
.Merge
.HorizontalAlignment = xlGeneral
.VerticalAlignment = xlCenter
.WrapText = True
End With
counter = counter + 1
Next i
End Sub
How do I get this to loop properly through each row?
If it helps, before on left, after on right:

I never recommend merging cells, but if you must...
This is dynamic by row (determined by Column A) and column. Each merge size is dependent on each rows furthest right non-blank column. Therefore, some merged cells will span 2 columns and some will span 3. If you don’t want that to be the case, you will need to find the max used column and merge by that column index
I.E. replacing MyCell.Resize(1, i -1).Merge with MyCell.Resize(1, MaxCol).Merge where MaxCol is your max used column.
Option Explicit
Sub Merger()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim MyCell As Range, Merged As String, i, iArr
Application.ScreenUpdating = False
Application.DisplayAlerts = False
For Each MyCell In ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
For i = 1 To ws.Cells(MyCell.Row, ws.Columns.Count).End(xlToLeft).Column
Merged = Merged & Chr(32) & MyCell.Offset(, i - 1) 'Build String
Next i
MyCell.Resize(1, i - 1).Merge 'Merge
MyCell = Right(Merged, Len(Merged) - 1) 'Insert String
Merged = "" 'Reset string for next loop
MyCell.HorizontalAlignment = xlGeneral
MyCell.VerticalAlignment = xlCenter
MyCell.WrapText = True
Next MyCell
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub

It can't be stressed too much. Merged cells should be avoided.
They play havoc when dragging an area to populate cells. They interrupt the double click autofill and make copying and pasting an exercise in frustration. They delay development and add complexity to formulas and VBA code all the while creating more opportunities for an error to occur or a bug to go unnoticed.
So i urge you to reconsider using merged cells.
Almost to prove the point, you'll find "*****" on a few lines in the two solutions below. Each one of those lines needs to be handled uniquely. Care to guess why? The merged cell you currently have in row 1. That merged cell can cause those lines to either halt with an error or continue with the possibility of unwanted consequences depending on which cell addresses actually hold row 1 data.
Merged cells are absolutely horrid and considered among the greatest of Excel sins.
Here are two ways forward without merged cells...
In VBA (psuedo code)
For (Columns, 2, LastColumn, Step 2)
For(Rows, 3, LastRow)
With Worksheet
If .Cells(Row,Column) <> vbNullString then
.cells(Row,Column-1)=.cells(Row,Column-1).Value2 _
& StringDeliminator & .cells(Row,Column).Value2
End If
End with
Next Rows
Columns (Column).EntireColumn.Delete*****
Next Columns
Using formulas in a worksheet
Add a new column C
In cell C3 use the formula
=If(A3<>"",C3=A3 & " " & B3,"")
Drag the formula down(copy to other columns if needed)
Ctrl Shift Up to select all the formulas
Copy *****
Paste Special Values *****
Delete columns A and B *****
There is one situation where merged cells are ok...
if you're in a situation where you're against the wall, there is nothing you can do because your manager doesn't care if your work is incompatible with his analyst's automation tools and refuses to accept center across selection as a viable alternative because "i know what center does and it does not help, you have to merge cells to get the text centered over those columns ".... if this is your situation then merged cells are ok, just use this method:: first, start looking for another job (or a promotion above your manager, your company should already be looking) and second, submit the broken merged cell version to the snowflake and quitely slip the functional version to your analyst as a preliminary estimate
That's the only time I authorize you to use merged cells.

Related

copy cell content based if adjacent cell meets criteria

I have a series of matrices consisting of 7 columns with a varied number of rows. I want the company names that are in column 2 of the matrix if the corresponding data in column 4 is "CM" aggregated into one cell per matrix (lets say B3:B98 for all the different matrices) with a space in between the different names. Please see the below picture for an example of what the matrices look like
The end result is that all the company names in Column E will be aggregated in B3 if the cell on the same row in column G is "CM", the next matrix beginning in Column M in B4 and so on.
I am having zero success in getting my if statement to recognize "CM" in the cell content, or aggregating the results using the Join statement. Any help is much appreciated.
Edits:
The objective is to have all the underwriters on a particular security aggregated in one cell, so that the cell can be easily searched in another part of the sheet for the presence of a particular underwriter.
The code below, as you can likely tell, does not work. I hit a wall as I could not get it to distinguish between cells that contained "CM" and those that did not. (I know that the code below would not aggregate the result in any cell, only copying the result into column B, as I said, it is a work in progress that has stalled.)
Dim Ws5 As Worksheet: Set Ws5 = Worksheets(5)
'turn off some Excel functionality so code runs faster
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
'Compiles the managers in the matrices into a column on the MgrMatrix sheet to be used
'for the entry sheet column of underwriters.
Dim CoL As Range: Set CoL = Ws5.Range("D3:K104")
Dim CeL As Range
For Each CeL In CoL.Columns(4)
If CeL.Text = "CM" Then
CeL.Offset(0, -5) = "CM"
Else
CeL.Offset(0, -5) = CeL.Offset(0, -2).Value
End If
Next
Edit: Using urdearboy's code, i modified it to work for multiple matrices on the same sheet in the below way. This version doesn't have the same finesse as his did, as this version relies on all matrices containing the same number of columns and not exceeding 100 rows.
For i = 7 To 857 Step 9
For y = 3 To 100
If Cells(y, i) = "CM" Then
s = s & Cells(y, i).Offset(0, -1).Value & " "
End If
Next y
If s = "" Then
s = "Sole Lead"
End If
Cells(Rows.Count, 2).End(xlUp).Offset(1, 0) = Trim(s)
s = ""
Next i
Paste code in VBE within Sheet 5 (Or whatever sheet you want to run this on).
The string, s, will build itself as it loops through your column checking for "CM" matches.
As is, the code will add commas between each new value added like, so, and, so, and then remove the last coma at the end before displaying the final string like, so, and, so
Option Explicit
Sub TextCM()
Dim i As Long, s As String
For i = 3 To Range("G" & Rows.Count).End(xlUp).Row
If Range("G" & i) = "CM" Then
s = s & Range("E" & i).Value & ", " 'Remove & ", " if you do not want the comma + space
End If
Next i
Range("B2") = Left(s, Len(s) - 2) 'Change to Range("B2") = s to not delete last character in string
End Sub
You should be able to figure out how to extend this to multiple tables (matrices?) no problem.

Merge adjacent cell only to merged cells in a range

I am trying to merge cell in the adjacent row into cells that are already merged. I want to merge an adjacent cell in column C, if the adjacent cell in column D is merged. Not all cells are merged in column D. I have code below that provides me with the correct row numbers when I use the variable in a MsgBox, however, when I add the variable to a Range to be merged, every row gets merged. I am thinking it must be some simple, but I just can determine what is causing every row in the range to get merged. I normally don't mess with merging anything, but I need to leave the spreadsheet with these merged cells. Your help to crack this one will be greatly appreciated.
Sub FindMerge()
Dim cell As Range
Dim Lrow As Long
Application.ScreenUpdating = False 'Turn off screen updating. Code runs faster without screen flicker
Application.DisplayAlerts = False 'stops Windows Alerts from poping up
'loops through range to find merged cells
'For Each cell In ActiveSheet.UsedRange 'commented out to try static range below.
For Each cell In Range("D1:D81")
If cell.MergeCells Then
If cell.Address = cell.MergeArea.Cells(1, 1).Address Then
' Msgbox "Row: " & cell.row 'displays correct row number where merged cell is located
Lrow = cell.row
Range("C2:O" & Lrow).Merge True 'Unintentionally merges every row
End If
End If
Next cell
Application.ScreenUpdating = True 'Turns screen updating back on
Application.DisplayAlerts = True 'Turns Windows Alerts back on
End Sub
You're merging all rows starting from row 2 by doing this:
Range("C2:O" & Lrow).Merge
What you most likely want is this:
Range("C1:O1").Offset(Lrow - 1).Merge

How to remove a certain value from a table that will vary in size in Excel

I'm new to the community and I apologize if there is a thread elsewhere, but I could not find it!
I'm currently diving into VBA coding for the first time. I have a file that I dump into a worksheet that currently I'm manually organizing and pushing out. When put into the worksheet, it delimits itself across the cells. This dump file will have varying row and column lengths every time I get it in a given day and dump into a work sheet. For example, one day it may be twenty rows and one day it may be thirty.
A certain roadblock in my VBA code creation process has presented itself. I'm trying to create a code that will parse through the worksheet to remove any time a certain value appears (See below image - I'm referring to the (EXT)). After doing so I'm trying to concatenate the cells in the row up until there is a space (which with the rows that have (EXT), there usually isn't a space after until the (EXT) is removed).
The code I made works for now but I recognize it's not very efficient and not reliable if the names extend longer than two cells. I was hoping someone on here could provide me with guidance. So, I'm looking for two things:
For the code to scan the whole active used range of the table and remove (EXT). As it may appear in various columns.
A way to concatenate the cells in every row in the active range from A to the cell before a blank cell
Keep in mind I have no coding background, I'm learning and I'm not familiar with VBA terms and whatnot all that much just yet - so if you could please explain in laymen's terms I'd appreciate it. I hope all of this makes sense... Thanks in advance!
This is just an example of part of what the dump code looks like, so my code probably doesn't match with the example below - I just wanted to provide a visual:
http://i.imgur.com/IwDDoYd.jpg
The code I currently have:
Sub DN_ERROR_ORGANIZER()
' Removes any (EXT) in Column 3 in actual dump data file
For i = 200 To 1 Step -1
If (Cells(i, 3).value = "(EXT)") Then
Cells(i, 3).Delete Shift:=xlToLeft
End If
Next i
' Removes any (EXT) in Column 4 in actual dump data file
For j = 200 To 1 Step -1
If (Cells(j, 4).value = "(EXT)") Then
Cells(j, 4).Delete Shift:=xlToLeft
End If
Next j
' Removes any (EXT) in Column 5 in actual dump data file
For k = 200 To 1 Step -1
If (Cells(k, 5).value = "(EXT)") Then
Cells(k, 5).Delete Shift:=xlToLeft
End If
Next k
' Places a new column before A and performs a concatenate on cells B1 and C1 to
' form a name, then copies all through column A1 to repeat on each row
Columns("A:A").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Range("A1").Select
ActiveCell.FormulaR1C1 = "=PROPER(CONCATENATE(RC[1],"", "", RC[2]))"
Range("A1").Select
Selection.AutoFill Destination:=Range("A1:A51")
Range("A1:A51").Select
End Sub
edited: to keep the comma after the first "name" only
this should do:
Sub main()
Dim cell As Range
With Worksheets("names")
With Intersect(.UsedRange, .Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).EntireRow)
For Each cell In .Rows
cell.Cells(1, 2).Value = Replace(Replace(Replace(Join(Application.Transpose(Application.Transpose(cell.Value)), " "), " ", " "), " (EXT)", ""), " ", ", ", , 1)
Next cell
.Columns(1).FormulaR1C1 = "=PROPER(RC[1])"
.Columns(1).Value = .Columns(1).Value
.Offset(, 1).Resize(, .Columns.Count - 1).ClearContents
End With
End With
End Sub
just remember to change "names" to you actual worksheet name
edited 2:
code for stopping cells to be processed at every line at the last one before the first blank one
Sub main()
Dim cell As Range, dataRng As Range
With Worksheets("names") '<--| change "names" to you actual worksheet name
Set dataRng = Intersect(.UsedRange, .Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).EntireRow)
For Each cell In dataRng.Columns(1).Cells
cell.Offset(, 1).Value = Replace(Replace(Replace(Join(Application.Transpose(Application.Transpose(.Range(cell, cell.End(xlToRight)).Value)), " "), " ", " "), " (EXT)", ""), " ", ", ", , 1)
Next cell
With dataRng
.Columns(1).FormulaR1C1 = "=PROPER(RC[1])"
.Columns(1).Value = .Columns(1).Value
.Offset(, 1).Resize(, .Columns.Count - 1).ClearContents
End With
End With
End Sub
I believe you are quite close to achieve what you are asking for and, based on your request, I will not give you a solution but some guidance to complete it by yourself.
First 3 loops: You could simplify by having a single set of nested loops: An outer loop running from 3 to 5, an inner loop running from 200 to 1; the outer loop will run over index, say "p", the inner over index, say "q", and your reference to cells would become Cells(q,p). If you need to run this over more than 3 rows, just start the outer loop from, say, 3 and till, say 10000 (being 10000 the maximal number of rows your data may display) and add a condition that if the first cell of the row is empty, you exit the outer loop.
The second part (this is what I understood) is to take the 2-3 first cells and concatenate them into a new cell (i.e. the column you add at the left). Once again, you can just loop over all your rows (much the same as in the outer loop mentioned above), except that now you will be looking at the cells in columns 2-4 (because you added a column at the left). The same exit condition as above can be used.
I'm not sure if this is what you were looking for, but this is what I understood you were looking for.
After reading user3598756's answer, I realized that I missed the boat with my original answer.
Sub DN_ERROR_ORGANIZER()
Dim Target As Range
Set Target = Worksheets("Sheet1").UsedRange
Target.Replace "(EXT)", ""
With Target.Offset(0, Target.Columns.Count).Resize(, 1)
.FormulaR1C1 = "=PROPER(C1&"", ""&TEXTJOIN("" "",TRUE,RC[-" & (Target.Columns.Count - 1) & "]:RC[-1]))"
.Value = .Value
End With
Target.Delete
End Sub
UPDATE
If you are running an older version of Excel that doesn't support TEXTJOIN then use this:
Sub DN_ERROR_ORGANIZER()
Dim Data
Dim x As Long, y As Long
Dim Target As Range
Dim Text As String
Set Target = Worksheets("Sheet1").UsedRange
Target.Replace "(EXT)", ""
Data = Target.Value
For x = 1 To Target.Rows.Count
Data(x, 1) = Data(x, 1)
For y = 2 To Target.Columns.Count
If Data(x, y) <> vbNullString Then Text = Text & " " & Data(x, y)
Next
If Len(Text) Then Data(x, 1) = Data(x, 1) & "," & Text
Text = vbNullString
Next
Target.ClearContents
Target.Columns(1).Value = Data
End Sub

Merging Rows of column B with the count of already merged rows A

I want to merge cells in one row (belongs to Column B) with the count of already merged different cell(belongs to Column A) .How can i start coding ?
this is the screenshot that i want
Merging cells in a spreadsheet means taking two or more cells and
constructing a single cell out of them. When you merge two or more
adjacent horizontal or vertical cells, the cells become one larger
cell that is displayed across multiple columns or rows. When you
merge multiple cells, the contents of only one cell (the upper-left
cell for left-to-right languages, or the upper-right cell for
right-to-left languages) appear in the merged cell. The contents of
the other cells that you merge are deleted. For more details please
go through this MSDN article Merge and unmerge
cells
Simple VBA code for Merging Cell
Sub merg_exp_1()
ActiveSheet.Range("A1:C10").Merge
End Sub
Sample data before and after running the program is shown.
Now let us see, If we merge a row what happens. Sample code for this
exercise though general is being tested for one situation only and
it as follow :
Sub Merge_Rows()
Dim rng As Range
Dim rrow As Range
Dim rCL As Range
Dim out As String
Dim dlmt As String
dlmt = ","
Set rng = ActiveSheet.Range("A1:C5")
For Each rrow In rng.Rows
out = ""
For Each rCL In rrow.Cells
If rCL.Value <> "" Then
out = out & rCL.Value & dlmt
End If
Next rCL
Application.DisplayAlerts = False
rrow.Merge
Application.DisplayAlerts = True
If Len(rrow.Cells(1).Value) > 0 Then
rrow.Cells(1).Value = Left(out, Len(out) - 1)
End If
Next rrow
End Sub
Sample data before and after running the program is shown. You can see this won't meet your objective.
Next we can try merging by column approach. Here also we are trying
for one column i.e. Column B to see the effect. Sample code as
follows.
Sub Merge_col_exp()
Dim cnum As Integer
Dim rng As Range
Dim str As String
For i = ActiveSheet.UsedRange.Rows.Count To 1 Step -1
cnum = Cells(i, 1).MergeArea.Count
Set rng = Range(Cells(i, 2), Cells(i - cnum + 1, 2)) ' only to demonstrate working in 2nd column
For Each cl In rng
If Not IsEmpty(cl) Then str = str + "," + cl
Next
If str <> "" Then str = Right(str, Len(str) - 1)
Application.DisplayAlerts = False
rng.Merge
rng = str
Application.DisplayAlerts = True
str = ""
i = i - cnum + 1
Next i
End Sub
Sample data before and after running the program is shown. You can see this is closer to your requirement. You can extend functionality of this program by finding Last Column in the Actively used range. Extend program functionality to cover upto last column.

Looping through all available autofilter criteria one at a time in vba

I was wondering if there was a way to get all the different autofilter criteria in a list in order to iterate through each criteria, to in the end copy and paste each different table that would appear to a separate sheet as it iterates through.
Ideally this would be run n times:
ActiveSheet.Range(AllRows).AutoFilter Field:=10, Criteria1:=CritVariable
Where n is the number of different CritVariables there are.
I'd like to stress that I know how to copy and paste in the macro itself, but I was curious how to iterate through all the different criteria because the criteria could be different depending on the day. If a list of it isn't available how would I best go about iterating through the criteria?
You can study and adapt the following. Here is an outline of what is going on.
I have a staff-table starting at cell A5, with a list of Offices in
column G;
I'm copying from G5 downwards (assuming there are no blanks in this column's data) to W1;
From range W1 downwards I am removing duplicates;
Then I'm looping through this data, using Advanced Filter to copy the data for each office to an area starting at cell Z1;
This filtered data is then moved (Cut) to a new worksheet, which is named from the current Office name (the criteria);
After each Advanced Filter the cell W2 is deleted, making the value in W3 move up, so that it can be used for the next filter operation.
This does mean that when you press Ctrl-End to go to the last-used cell it goes further than it needs to. You can find a way to resolve this if necessary ;).
Sub SheetsFromFilter()
Dim wsCurrent As Worksheet
Dim wsNew As Worksheet
Dim iLeft As Integer
Set wsCurrent = ActiveSheet
Application.ScreenUpdating = False
Range("G5", Range("G5").End(xlDown)).Copy Range("W1")
Range("W1").CurrentRegion.RemoveDuplicates Columns:=1, Header:=xlYes
iLeft = Range("W1").CurrentRegion.Rows.Count - 1
Do While iLeft > 0
wsCurrent.Range("A5").CurrentRegion.AdvancedFilter xlFilterCopy, _
wsCurrent.Range("W1:W2"), wsCurrent.Range("Z1")
Set wsNew = Worksheets.Add
wsCurrent.Range("Z1").CurrentRegion.Cut wsNew.Range("A1")
wsNew.Name = wsCurrent.Range("W2").Value
wsCurrent.Range("W2").Delete xlShiftUp
iLeft = iLeft - 1
Loop
wsCurrent.Range("W1").Clear
Application.ScreenUpdating = True
End Sub
BTW I don't intend to modify this for your specific file; this is something that you should do (or pay someone to do ;) ).
BTW It could be done using the normal (rather than Advanced) Filter. You would still copy the column and remove duplicates. This would have the benefit of not increasing the apparent size of the worksheet too much. But I decided to do it this way ;).
Added: Well, I felt inspired to achieve this with AutoFilter as well:
Sub SheetsFromAutoFilter()
Dim wsCurrent As Worksheet
Dim wsNew As Worksheet
Dim iLeft As Integer
Set wsCurrent = ActiveSheet
Application.ScreenUpdating = False
Range("G5", Range("G5").End(xlDown)).Copy Range("W1")
Range("W1").CurrentRegion.RemoveDuplicates Columns:=1, Header:=xlYes
iLeft = Range("W1").CurrentRegion.Rows.Count - 1
Do While iLeft > 0
Set wsNew = Worksheets.Add
With wsCurrent.Range("A5").CurrentRegion
.AutoFilter field:=7, _
Criteria1:=wsCurrent.Range("W1").Offset(iLeft).Value
.Copy wsNew.Range("A1")
.AutoFilter
End With
wsNew.Name = wsCurrent.Range("W1").Offset(iLeft).Value
iLeft = iLeft - 1
Loop
wsCurrent.Range("W1").CurrentRegion.Clear
Application.ScreenUpdating = True
End Sub
[Both procedures could be improved using Defined Names and some error handling/checking.]
if you want you can build a new collection which will have an array of only unique values and then loop over them. you will know that each
I know it's late and you've already selected an answer, but I'm working on a similar project involving a pivot table and decided to do it this way:
'Here I'm Filtering a column of Week numbers to get rid of non-numbers
'From a pivot table
'I select sheet where my underlying pivot data is located and establish the range
'My data is in column 2 and it ends after "DSLastRow" Rows starting at Row 2
Sheets("DataSheet").Select
DSLastRow = Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
'I create and redim an array that is large enough to hold all of the data in the range
Dim FilterCriteria(): RedimFilterCriteria(1 To DSLastRow)
For r = 2 To DSLastRow 'r for row / my data has a header in row 1
If Cells(r, 2).Value <> "" Then 'again, starting in column B (2)
'Check if it's already in the FilterCriteria Array
For CheckFCA = 1 To r
'Jumps to next row if it finds a match
If FilterCriteria(CheckFCA) = Cells(r, 2).Value Then GoTo Nextr
'Saves the value and jumps to next row if it reaches an empty value in the array
If IsEmpty(FilterCriteria(CheckFCA)) Then
FilterCriteria(CheckFCA) = Cells(r, 2)
GoTo Nextr
End If
Next CheckFCA
End if
Nextr:
Next r
'At this point FilterCriteria() is filled with all of the unique values
'I'm filtering a pivot table which is why I created the unique array from
'the source data, but you should be able to just loop through the table
Sheets("Pivot").Select
ActiveSheet.PivotTables("ReportPivot").PivotFields("Week").ClearAllFilters
With ActiveSheet.PivotTables("ReportPivot").PivotFields("Week")
For FilterPivot = 1 To DSLastRow
'I'm filtering out all non-numeric items
If IsEmpty(FilterCriteria(FilterPivot)) Then Exit For
If Not IsNumeric(FilterCriteria(FilterPivot)) Then
.PivotItems(FilterCriteria(FilterPivot)).Visible = False
End If
Next FilterPivot
End With