Concatenate two columns and skip blank cells - excel

My current spreadsheet has two columns of data I would like to concatenate. In my provided code, I create a column to the right of the columns I would like to combine and then use a FOR loop to combine each value with a ", " between the values. I would like to adjust the code to skip cells/rows without values because now I end up with a ", " in my combined column if the two initial columns had no values.
Public Sub MergeLatLong()
Dim LastRow As Long
Worksheets("Raw_Data").Activate
Columns("AT:AT").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
LastRow = Range("AR" & Rows.Count).End(xlUp).Row
For i = 2 To LastRow
Cells(i, 46) = Cells(i, 44) & ", " & Cells(i, 45)
Next i
End Sub

Do you need to use VBA? I would recommend using a TEXTJOIN formula (if you have Excel 2016). Assuming your cells in columns AR and AS and the formula in AT.
The parameters for the formula are =TEXTJOIN(delimiter,ingnore_blanks,range)
So the below formula in AT1 would return a concatenation of the two columns for each row with a comma as the delimiter if there is contents in both columns.
=TEXTJOIN(“,”,TRUE,AR1:AS1)
If you are using a version less than 2016. You could just use the following
=AR1&IF(ISBLANK(AS1),””,”, AS1”)
Either of these can be dragged down and you wouldn’t have any extra commas in any rows with a blank in column AS.

The code below should do what you intend. It will enter a blank if both values are missing, the first only (without comma) if the second is missing, and the second only (with leading comma) if the first is missing. You might adjust that part to better suit your needs.
Public Sub MergeLatLong()
Dim Ws As Worksheet
Dim LastRow As Long
Dim Combo As String, Tmp As String
Dim R As Long
' No need to Activate or Select anything!
Set Ws = Worksheets("Raw_Data")
With Ws
.Columns(46).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
LastRow = .Cells(Rows.Count, "AR").End(xlUp).Row
For R = 2 To LastRow
' if you mean the 'Value' it's better to specify the 'Value' property
Combo = Trim(.Cells(R, 44).Value) ' treat Space as blank
Tmp = Trim(.Cells(R, 45).Value) ' treat Space as blank
If Len(Tmp) Then Tmp = ", " & Tmp
If Len(Combo) And Len(Tmp) > 0 Then Combo = Combo & Tmp
Cells(R, 46).Value = Combo
Next R
End With
End Sub
As did #Dude Scott, I also felt that a worksheet function might be more suitable. VBA might have some advantage if it's a very frequently recurring task only.
If the number of entries is large, add Application.ScreenUpdating = False before the For .. Next loop and reset ScreenUpdating to True at the end of the procedure. That will make for significantly better speed.

you could loop through column AR not blank cells only and check for column AS ones content to properly add comma
moreover, avoid Activate/Select pattern and use direct and explicit reference to ranges:
Public Sub MergeLatLong()
Dim cell As Range
With Worksheets("Raw_Data") ' reference wanted worksheet
.Columns("AT:AT").Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
For Each cell In .Range("AR2", .Cells(.Rows.Count, "AR").End(xlUp)).SpecialCells(xlCellTypeConstants) ' loop through referenced sheet column AR cells with some "constant" values
If IsEmpty(cell.Offset(, 1)) Then
cell.Offset(, 2) = cell.Value
Else
cell.Offset(, 2) = cell.Value & ", " & cell.Offset(, 1)
End If
Next
End With
End Sub

2 Columns 2 One
Fast Array Version
Sub MergeLatLong() ' Array Version
Dim vnt1 As Variant ' 1st Array
Dim vnt2 As Variant ' 2nd Array
Dim vntR As Variant ' Result Array
Dim NoR As Long ' Number of Rows
Dim i As Long ' Row Counter
Dim str1 As String ' 1st String
Dim str2 As String ' 2nd String
Dim strR As String ' Result String
' Speed up.
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' Handle possible error.
On Error GoTo ErrorHandler
With ThisWorkbook.Worksheets("Raw_Data")
' Insert column ("AT") to the right of column ("AS").
.Columns("AT").Insert xlToRight, xlFormatFromLeftOrAbove
' Calculate Number of Rows (Last Used Row - First Row + 1).
NoR = .Cells(.Rows.Count, "AR").End(xlUp).Row - 2 + 1
' Copy values of column "AR" to 1st Array.
vnt1 = .Columns("AR").Cells(2).Resize(NoR)
' Copy values of column "AS" to 2nd Array.
vnt2 = .Columns("AS").Cells(2).Resize(NoR)
End With
' Resize Result Array to size of 1st Array (or 2nd Array).
ReDim vntR(1 To UBound(vnt1), 1 To 1) As String
' Remarks: All arrays are of the same size.
' Loop through rows of arrays.
For i = 1 To NoR
' Write current value in 1st array to 1st String.
str1 = vnt1(i, 1)
' Write current value in 2nd array to 2nd String.
str2 = vnt2(i, 1)
' Check if 1st String is not empty ("").
If str1 <> "" Then ' 1st String is not empty.
' Check if 2nd String is not empty ("").
If str2 <> "" Then ' 2nd String is not empty.
' Concatenate.
strR = str1 & ", " & str2
Else ' 2nd String is empty.
strR = str1
End If
Else ' 1st String is empty.
If str2 <> "" Then ' 2nd String is not empty.
strR = str2
Else ' 2nd String is empty.
strR = ""
End If
End If
' Write Result String to current row of Result Array.
vntR(i, 1) = strR
Next
With ThisWorkbook.Worksheets("Raw_Data").Columns("AT")
' Copy Result Array to Result Range.
.Cells(2).Resize(NoR) = vntR
' Adjust the width of Result Column.
.AutoFit
' ' Apply some additional formatting to Result Range.
' With .Cells(2).Resize(NoR)
' ' e.g.
' .Font.Bold = True
' End With
End With
ProcedureExit:
' Speed down.
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
Exit Sub
ErrorHandler:
MsgBox "An unexpected error has occurred. Error '" & Err.Number & "': " _
& Err.Description, vbInformation, "Error"
GoTo ProcedureExit
End Sub
Slow Range Version
Sub MergeLatLongRange() ' Range Version
Dim LastRow As Long ' Last Row Number
Dim i As Long ' Row Counter
Dim str1 As String ' 1st String
Dim str2 As String ' 2nd String
Dim strR As String ' Result String
' Speed up.
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' Handle possible error.
On Error GoTo ErrorHandler
With ThisWorkbook.Worksheets("Raw_Data")
' Insert column ("AT") to the right of column ("AS").
.Columns("AT").Insert xlToRight, xlFormatFromLeftOrAbove
' Calculate Last Used Row using 1st column "AR".
LastRow = .Cells(.Rows.Count, "AR").End(xlUp).Row
' Loop through rows in columns.
For i = 2 To LastRow
' Write value of cell at current row in column "AR" to 1st String.
str1 = .Cells(i, "AR")
' Write value of cell at current row in column "AS" to 2nd String.
str2 = .Cells(i, "AS")
' Check if 1st String is not empty ("").
If str1 <> "" Then ' 1st String is not empty.
' Check if 2nd String is not empty ("").
If str2 <> "" Then ' 2nd String is not empty.
' Concatenate.
strR = str1 & ", " & str2
Else ' 2nd String is empty.
strR = str1
End If
Else ' 1st String is empty.
If str2 <> "" Then ' 2nd String is not empty.
strR = str2
Else ' 2nd String is empty.
strR = ""
End If
End If
' Write Result String to cell at current row in column "AT".
Cells(i, "AT") = strR
Next
' Adjust the width of column "AT".
.Columns("AT").AutoFit
End With
ProcedureExit:
' Speed down.
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
Exit Sub
ErrorHandler:
MsgBox "An unexpected error has occurred. Error '" & Err.Number & "': " _
& Err.Description, vbInformation, "Error"
GoTo ProcedureExit
End Sub

Here is the code I ended up using, a blend of the responses above. I create some additional code to find the columns with latitude and longitude, that way if the columns were to somehow be rearranged, the program would still be looking at the correct columns for values.
Sub concatenateLatLong()
Dim WS As Worksheet
Dim lastRow As Long
Dim longName As String
Dim longColumn As Long
Dim latName As String
Dim latColumn As Long
Dim latValue As String
Dim longValue As String
Dim i As Long
Set WS = Worksheets("Data")
With WS
lastRow = .Cells.Find(What:="*", After:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, MatchCase:=False).Row
'MsgBox "The last row with entered data is " & lastRow
'Find Longitude column
longName = "LONGITUDE"
longColumn = .Rows(1).Find(What:=longName, LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False).Column
'MsgBox "The " & longName & " header is found in column " & longColumn
'Insert a row to the right of the longitude column
.Columns(longColumn + 1).Insert Shift:=xlToLeft, CopyOrigin:=xlFormatFromLeft
'Give new column header "LAT, LONG"
.Cells(1, longColumn + 1).Value = "LAT, LONG"
'Find Latitude column
latName = "LATITUDE"
latColumn = .Rows(1).Find(What:=latName, LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False).Column
'MsgBox "The " & latName & " header is found in column " & latColumn
'Combine latitude and longitude
For i = 2 To lastRow
latValue = Trim(.Cells(i, latColumn).Value)
longValue = Trim(.Cells(i, longColumn).Value)
If Len(longValue) Then longValue = ", " & longValue
If Len(latValue) And Len(longValue) > 0 Then latValue = latValue & longValue
.Cells(i, longColumn + 1).Value = latValue
Next i
End With
End Sub

Related

Excel VBA: How do I add text to a blank cell in a specific column then loop to the next blank cell and add text?

I need a macro to add text to blank cells in Column A. The macro needs to skip cells that have text. The macro needs to stop looping at the end of the data set.
I am trying to use an If Else statement, but I think I'm on the wrong track. My current, non-working code is below. Thank you so much - I'm still new to VBA
Sub ElseIfi()
For i = 2 To 100
If Worksheets("RawPayrollDump").Cells(2, 1).Value = "" Then
Worksheets("RawPayrollDump").Cells(2, 1).Value = "Administration"
Else if(not(worksheets("RawPayrollDump").cells(2,1).value="")) then 'go to next cell
End If
Next
End Sub
To find the last row of data, use the End(xlUp) function.
Try this code. It replaces all empty cells in column A with Administration.
Sub ElseIfi()
Set ws = Worksheets("RawPayrollDump")
lastrow = ws.Cells(Rows.Count, 1).End(xlUp).Row ' last data row
For i = 2 To lastrow ' all rows until last data row
If ws.Cells(i, 1).Value = "" Then ' column A, check if blank
ws.Cells(i, 1).Value = "Administration" ' set text
End If
Next
End Sub
There is no need to loop. Please try this code.
Sub FillBlanks()
Dim Rng As Range
With Worksheets("RawPayrollDump")
Set Rng = Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp))
End With
On Error Resume Next
Set Rng = Rng.SpecialCells(xlCellTypeBlanks)
If Err Then
MsgBox "There are no blank cells" & vbCr & _
"in the specified range.", _
vbInformation, "Range " & Rng.Address(0, 0)
Else
Rng.Value = "Administration"
End If
End Sub
Replace Blanks feat. CurrentRegion
Range.CurrentRegion
Since OP asked for "... stop looping at the end of the data set. ",
I've written this CurrentRegion version.
As I understand it, the end of the data set doesn't mean that there
cannot be blank cells below the last cell containing data in column
A.
Use the 1st Sub to test the 2nd, the main Sub (replaceBlanks).
Adjust the constants including the workbook (in the 1st Sub) to fit your needs.
Criteria is declared as Variant to allow other data types not just strings.
The Code
Option Explicit
Sub testReplaceBlanks()
Const wsName As String = "RawPayrollDump"
Const FirstCellAddress As String = "A2"
Const Criteria As Variant = "Administration"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
replaceBlanks ws, FirstCellAddress, Criteria
End Sub
Sub replaceBlanks(Sheet As Worksheet, _
FirstCellAddress As String, _
Criteria As Variant)
' Define column range.
Dim ColumnRange As Range
Set ColumnRange = Intersect(Sheet.Range(FirstCellAddress).CurrentRegion, _
Sheet.Columns(Sheet.Range(FirstCellAddress) _
.Column))
' To remove the possibly included cells above the first cell:
Set ColumnRange = Sheet.Range(Range(FirstCellAddress), _
ColumnRange.Cells(ColumnRange.Cells.Count))
' Note that you can also use the addresses instead of the cell range
' objects in the previous line...
'Set ColumnRange = sheet.Range(FirstCellAddress, _
ColumnRange.Cells(ColumnRange.Cells.Count) _
.Address)
' or a mixture of them.
' Write values from column range to array.
Dim Data As Variant
If ColumnRange.Cells.Count > 1 Then
Data = ColumnRange.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = ColumnRange.Value
End If
' Modify array.
Dim i As Long, k As Long
For i = 1 To UBound(Data)
If IsEmpty(Data(i, 1)) Then Data(i, 1) = Criteria: k = k + 1
Next i
' Write modified array to column range.
' The following line is used when only the first cell is known...
'Sheet.Range(FirstCellAddress).Resize(UBound(Data)).Value = Data
' ...but since the range is known and is the same size as the array,
' the following will do:
ColumnRange.Value = Data
' Inform user.
If k > 0 Then GoSub Success Else GoSub Fail
Exit Sub
' Subroutines
Success:
MsgBox "Wrote '" & Criteria & "' to " & k & " previously " _
& "empty cell(s) in range '" & ColumnRange.Address & "'.", _
vbInformation, "Success"
Return
Fail:
MsgBox "No empty cells in range '" & ColumnRange.Address & "'.", _
vbExclamation, "Nothing Written"
Return
End Sub

Find Cell containing text in column and does NOT contain certain word in first 6 characters of string

I am searching a column for cell that contains text and does not contain the word "cat" in the first 6 characters (needs to be case insensitive). This will then cut that entire row to another sheet. Cannot get the code to run without compile errors. the below code is before i try to change it. I do not know how to code it to look at the first 6 characters.
tried instr & iserror but i think my existing code just needs a small alteration which escapes me.
Sub CATDEFECTS()
UsdRws = Range("C" & Rows.Count).End(xlUp).Row
For i = UsdRws To 2 Step -1
If Range("C" & i).Value Like "<>""" And Range("c" & i).Value Like "CAT" Then
Rows(i).Cut Sheets("AWP DEFECTS").Range("A" & rows.Count).End(xlUp).Offset(1)
Rows(i).Delete
End If
Next i
End Sub
Regardless of how you decide to implement the macro, your test to see if a cell is blank is entirely redundant. You can just test if the cell meets your CAT criteria. If it does, it is definitely not blank so no need to test it.
Method 1
You can look at the first 6 characters with LEFT(Range, 6)
If Left(Range("C" & i), 6) Like "*CAT*" Then
This needs Option Compare to work (Thanks #Comintern)
Method 2
I would prefer this method. Its explicit and does not delete or shift anything inside the loop so your action statements are greatly minimized.
Sub Cat()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1") '<--UPDATE
Dim ps As Worksheet: Set ps = ThisWorkbook.Sheets("AWP DEFECTS")
Dim LR As Long, DeleteMe As Range, i As Long
LR = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
For i = 2 To LR
If InStr(Left(ws.Range("C" & i), 6), "CAT") Then
If Not DeleteMe Is Nothing Then
Set DeleteMe = Union(DeleteMe, ws.Range("C" & i))
Else
Set DeleteMe = ws.Range("C" & i)
End If
End If
Next i
Application.ScreenUpdating = False
If Not DeleteMe Is Nothing Then
LR = ps.Range("A" & ps.Rows.Count).End(xlUp).Row
DeleteMe.EntireRow.Copy ps.Range("A" & LR)
DeleteMe.EntireRow.Delete
End If
Application.ScreenUpdating = True
End Sub
If cat is within the first 6 characters then InStr will report its position being less than 5.
Sub CATDEFECTS()
dim UsdRws as long, pos as long
UsdRws = Range("C" & Rows.Count).End(xlUp).Row
For i = UsdRws To 2 Step -1
pos =instr(1, cells(i, "C").value2, "cat", vbtextcompare)
If pos > 0 and pos < 5 Then
Rows(i).Cut Sheets("AWP DEFECTS").Range("A" & rows.Count).End(xlUp).Offset(1)
Rows(i).Delete
End If
Next i
End Sub
Criteria Backup (Hide/Delete)
To enable the deletion of the rows in the Source Worksheet you have to set cDEL to True in the constants section. Adjust the other constants to fit you needs.
The Code
Option Explicit
'Option Compare Text
Sub CATDEFECTS()
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
On Error GoTo ProcedureExit
' Source Constants
Const cSource As Variant = "Sheet1" ' Worksheet Name/Index
Const cCol As Variant = "C" ' Search Column Letter/Number
Const cFirstR As Long = 2 ' First Row Number
Const cChars As Long = 6 ' Number of Chars
Const cSearch As String = "CAT" ' Search String
' Target Constants
Const cTarget As Variant = "AWP DEFECTS" ' Worksheet Name/Index
Const cColTgt As Variant = "A" ' Column Letter/Number
Const cFirstRTgt As Long = 2 ' First Row Number
Const cDEL As Boolean = False ' Enable Delete (True)
' Variables
Dim rngH As Range ' Help Range
Dim rngU As Range ' Union Range
Dim vntS As Variant ' Source Array
Dim i As Long ' Source Range Row Counter
' The Criteria
' When the first "cChars" characters do not contain the case-INsensitive
' string "cSearch", the criteria is met.
' Source Worksheet
With ThisWorkbook.Worksheets(cSource)
' Calculate Last Cell in Search Column using the Find method and
' assign it to Help (Cell) Range.
Set rngH = .Columns(cCol).Find("*", , xlFormulas, _
xlWhole, xlByColumns, xlPrevious)
' Calculate Source Column Range from Help (Cell) Range.
If Not rngH Is Nothing Then ' Last Cell was found.
' Calculate Source Column Range and assign it to
' Help (Column) Range using the Resize method.
Set rngH = .Cells(cFirstR, cCol).Resize(rngH.Row - cFirstR + 1)
' Copy Help (Column) Range into 2D 1-based 1-column Source Array.
vntS = rngH
' Show hidden rows to prevent the resulting rows (the rows to be
' hidden or deleted) to appear hidden in Target Worksheet.
rngH.EntireRow.Hidden = False
Else ' Last Cell was NOT found (unlikely).
MsgBox "Empty Column '" & cCol & "'."
GoTo ProcedureExit
End If
' Loop through rows of Source Array.
For i = 1 To UBound(vntS)
' Check if current Source Array value doesn't meet Criteria.
If InStr(1, Left(vntS(i, 1), cChars), cSearch, vbTextCompare) = 0 _
Then ' "vbUseCompareOption" if "Option Compare Text"
' Note: To use the Like operator instead of the InStr function
' you have to use (uncomment) "Option Compare Text" at the beginning
' of the module for a case-INsensitive search and then outcomment
' the previous and uncomment the following line.
' If Not Left(vntS(i, 1), cChars) Like "*" & cSearch & "*" Then
Set rngH = .Cells(i + cFirstR - 1, cCol)
If Not rngU Is Nothing Then
' Union Range contains at least one range.
Set rngU = Union(rngU, rngH)
Else
' Union Range does NOT contain a range (only first time).
Set rngU = rngH
End If
End If
Next
End With
' Target Worksheet
If Not rngU Is Nothing Then ' Union Range contains at least one range.
With ThisWorkbook.Worksheets(cTarget)
' Calculate Last Cell in Search Column using the Find method and
' assign it to Help Range.
Set rngH = .Columns(cColTgt).Find("*", , xlFormulas, _
xlWhole, xlByColumns, xlPrevious)
' Calculate Last Cell from Help Range, but in column 1 ("A").
If Not rngH Is Nothing Then ' Last Cell was found.
Set rngH = .Cells(rngH.Row + 1, 1)
Else ' Last Cell was NOT found.
Set rngH = .Cells(cFirstRTgt - 1, 1)
End If
' Copy the entire Union Range to Target Worksheet starting from
' Help Range Row + 1 i.e. the first empty row (in one go).
' Note that you cannot Cut/Paste on multiple selections.
rngU.EntireRow.Copy rngH
End With
' Hide or delete the transferred rows (in one go).
If cDEL Then ' Set the constant cDEL to True to enable Delete.
rngU.EntireRow.Delete
Else ' While testing the code it is better to use Hidden.
rngU.EntireRow.Hidden = True
End If
End If
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Remarks
The use of the array did not speed up considerably.
The InStr function was a few milliseconds faster than the Like operator in my data set.
Calculating the Real Used Range and copying it into a Source Array
and then writing the data that meets the criteria from Source Array
to a Target Array and copying the Target Array to the Target
Worksheet, might be faster, and/but would additionally copy the data without formulas or formatting.

Excel formula or SQL script that would put bulk text into individual cell

I have a SQL database of illnesses (Records of 9,000) but the illnesses are copy-pasted into a single field and arranged in numbers. From here we always extract to Excel to manipulate the data. My problem now is to put these illnesses into separate cell compartment in excel so that we can filter it properly.
I have only tried the Text-to-Column in Excel but it does not do the job.
Example:
And this is what I'm trying to achieve:
I hope it makes sense.
Thank you.
Comorbidity 2.0
Intro
Wikipedia: In medicine, comorbidity is the presence of one or more additional diseases or disorders co-occurring with (that is, concomitant or concurrent with) a primary disease or disorder; in the countable sense of the term, a comorbidity (plural comorbidities) is each additional disorder or disease.
Since you couldn't get the first script to work I could only conclude that you might have a different line delimiter than the Line Feed at the end of each line in the multi-line cells. So I wrote an improved version of the whole thing and added a function to determine the delimiter for each cell. Now you only have to select a column e.g. A, B, or T (in your sample picture) etc. in the customize section of the code:
'-- CUSTOMIZE BEGIN --------------------
Const cStrColumn As String = "T" '<-- ***COLUMN IN HERE***
Const cStrColumnResult As String = "A" 'Resulting Data Column
Const cLoRow As Long = 0 '0 to use the first row of the initial data range.
'-- CUSTOMIZE END ----------------------
... and the code does the rest itself.
The Code
Option Explicit
'-------------------------------------------------------------------------------
Sub MultilineCellExtractor()
'-------------------------------------------------------------------------------
'Description
'Copies the contents of each cell of a specified COLUMN in a worksheet,
'skipping blank cells and converting multiple lines in cells each to a new
'cell, and returns the result in a COLUMN of a newly created worksheet.
'Arguments as constants
'cStrColumn
'The Column of the Initial Data in ThisWorkbook's ActiveSheet
'cStrColumnResult
'The Column of the Resulting Data in a Newly to be Created Worksheet
'cLoRow
'The First Row of the Resulting Data in the Newly Created Worksheet
'Returns
'A new worksheet with a column of the processed data.
'Usage
'Open the workbook to be processed. Go to VBE and insert a new module. Copy
'this script ('MultilineCellExtractor') and the function 'FirstNonPrintable'
'and paste them into the module. Edit the 'customize section' to fit your
'needs. Exit VBE and start the Run Macro Dialog (Play Button). DoubleClick or
'select 'MultilineCellExtractor' and click Run to execute.
'Remarks
'If there is no data in the column to be processed a message pops up (the only
'error handling done so far). If there are no multiline cells, the data is
'just copied while skipping the blanks.
'There can be no damage done using this script in the previously described way
'because the worksheet is only to be READ from, and the result is always
'pasted into a NEW worksheet.
'-------------------------------------------------------------------------------
'-- CUSTOMIZE BEGIN --------------------
Const cStrColumn As String = "T" 'Initial Data Column
Const cStrColumnResult As String = "A" 'Resulting Data Column
Const cLoRow As Long = 0 '0 to use the first row of the initial data range.
'-- CUSTOMIZE END ----------------------
'-------------------------------------------------------------------------------
Dim oRng As Range 'Initial Colum, Initial Range, Resulting Range
Dim arrRng As Variant 'Array Containing the Initial Data Range (Column)
Dim arrSplit As Variant 'Array Containing the Cell Lines
Dim arrData() As Variant 'Array Containing the Resulting Data Range (Column)
Dim loRow1 As Long 'First Row of the Initial Data Range (Column)
Dim loRow2 As Long 'Last Row of the Initial Data Range (Column)
Dim loRowResult As Long 'First Row of the Resulting Data Range (Column)
Dim loRng As Long 'Initial Array Rows Counter
Dim iSplit As Integer 'Multiline Cell Lines Counter
Dim loData As Long 'Resulting Array(Range) Rows Calculator and Counter
Dim strRng As String 'Initial Data Reader: Shortcut for arrRng(loRng, 1).
Dim str1 As String 'Debug String Writer
Dim lo1 As Long 'Debug String Array Data Counter
'-------------------------------------------------------------------------------
'Column of Initial Data
'Needed to calculate first and last rows of data.
Set oRng = ThisWorkbook.ActiveSheet.Range(cStrColumn & ":" & cStrColumn)
'First Row Containing Data
On Error Resume Next
loRow1 = oRng.Find(What:="*", After:=Cells(Rows.Count, cStrColumn), _
LookIn:=xlValues, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlNext).Row
If Err Then
MsgBox "You have probably selected a column with no data."
GoTo ProcedureExit
End If
'Last Row Containing Data
loRow2 = oRng.Find(What:="*", After:=Cells(1, cStrColumn), _
LookIn:=xlValues, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
'Calculate Initial Range
Set oRng = ThisWorkbook.ActiveSheet.Range(Cells(loRow1, cStrColumn), _
Cells(loRow2, cStrColumn))
' str1 = "Calculate Initial Range (Results):"
' str1 = str1 & vbCrLf & Space(2) & "loRow1 = " & loRow1
' str1 = str1 & vbCrLf & Space(2) & "loRow2 = " & loRow2
' str1 = str1 & vbCrLf & Space(2) & "oRng.Address: " & oRng.Address
' Debug.Print str1 & vbCrLf
'Paste range into array
arrRng = oRng
Set oRng = Nothing 'Release the variable, initial data is in arrRng.
' str1 = "arrRng Contents:"
' For lo1 = LBound(arrRng) To UBound(arrRng)
' str1 = str1 & vbCrLf & Space(2) & lo1 & ". " & arrRng(lo1, 1)
' Next
' Debug.Print str1 & vbCrLf
'-------------------------------------------------------------------------------
'Now arrays are taking over
'Count data in arrRng to calculate size of arrData.
For loRng = LBound(arrRng) To UBound(arrRng)
strRng = arrRng(loRng, 1)
If strRng <> "" Then 'Not empty cell, continue.
If FirstNonPrintable(strRng) > 0 Then 'Non printable character found.
'Splitting arrSplit by 'FirstNonPrintable'
arrSplit = Split(strRng, Chr(FirstNonPrintable(strRng)))
loData = loData + UBound(arrSplit) + 1 '+ 1 i.e. arrSplit is 0-based.
Else 'Nonprintable character not found.
loData = loData + 1
End If
' Else 'Empty cell, do nothing.
End If
Next
'Redeclare arrData using the result of the counting (loData).
ReDim Preserve arrData(1 To loData, 1 To 1)
'Reset counter for counting.
loData = 0
'Read data from arrRng and write to array.
For loRng = LBound(arrRng) To UBound(arrRng)
strRng = arrRng(loRng, 1)
If strRng <> "" Then 'Not empty cell, continue.
If FirstNonPrintable(strRng) > 0 Then 'Non printable character found.
'Splitting arrSplit by 'FirstNonPrintable'
arrSplit = Split(strRng, Chr(FirstNonPrintable(strRng)))
'
' str1 = "arrSplit Contents:"
' For lo1 = LBound(arrSplit) To UBound(arrSplit)
' str1 = str1 & vbCrLf & Space(2) & lo1 + 1 & ". " & arrSplit(lo1)
' Next
' Debug.Print str1 & vbCrLf
'
'Writing arrSplit data to arrData.
For iSplit = LBound(arrSplit) To UBound(arrSplit)
loData = loData + 1
arrData(loData, 1) = arrSplit(iSplit)
Next
Erase arrSplit 'Is repeatedly newly created to write data to arrData.
Else 'Nonprintable character not found.
loData = loData + 1
arrData(loData, 1) = strRng
End If
' Else 'Empty cell, do nothing.
End If
Next
Erase arrRng 'No longer needed, resulting data is in arrData.
'
' str1 = "arrData Contents:"
' For lo1 = LBound(arrData) To UBound(arrData)
' str1 = str1 & vbCrLf & Space(2) & lo1 & ". " & arrData(lo1, 1)
' Next
' Debug.Print str1
'
'-------------------------------------------------------------------------------
'Return data in new worksheet
'Calculate the first row of data in the resulting worksheet.
If cLoRow > 0 Then
loRowResult = cLoRow 'Row as the constant in the 'customize section'.
Else
loRowResult = loRow1 'Same row as in the initial worksheet.
End If
'Add a new (resulting) worksheet positioned after the initial worksheet.
ThisWorkbook.Worksheets.Add _
After:=ActiveSheet 'The resulting worksheet is active now.
'Calculate the resulting range in the new worksheet.
Set oRng = ActiveSheet.Range(Cells(loRowResult, cStrColumnResult), _
Cells(loRowResult + loData - 1, cStrColumnResult))
'Paste data into the resulting range.
oRng = arrData
Erase arrData 'No longer needed, all data is in oRng.
'-------------------------------------------------------------------------------
ProcedureExit:
Set oRng = Nothing 'Release the variable, all data is in the worksheet.
End Sub
'-------------------------------------------------------------------------------
'-------------------------------------------------------------------------------
Function FirstNonPrintable(StringToClean As String, _
Optional Code0Position1String2 As Integer = 0) As Variant
'-------------------------------------------------------------------------------
'Description
'Finds the first character in a string that is different from the character
'at the same position in the cleaned version of the same string and returns
'its code, position or string.
'Arguments
'StringToClean (String)
'The string to clean.
'Code0Position1String2 (Integer)
'Returns for
'0, the character code (Asc) of the found character to be used with
'the Chr function.
'1, the position of the found character.
'2, the found character.
Dim strCleaned As String
Dim loLen As Long
strCleaned = WorksheetFunction.Clean(StringToClean)
If StringToClean = strCleaned Then Exit Function
For loLen = 1 To Len(StringToClean)
If Mid(StringToClean, loLen, 1) <> Mid(strCleaned, loLen, 1) Then
Select Case Code0Position1String2
Case 0
FirstNonPrintable = Asc(Mid(StringToClean, loLen, 1))
Case 1
FirstNonPrintable = loLen
Case 2
FirstNonPrintable = Mid(StringToClean, loLen, 1)
End Select
Exit Function
End If
Next
End Function
'-------------------------------------------------------------------------------
Some additional info
To put more lines into a cell you have to hold the left ALT key and press enter after each line.
For character codes look here.
This is possible in Get&Transform if you're on a recent version of Excel.
Get Data from range
Right Click the Column>Split Column>By Delimiter
In advanced options you can split by row and get the "new line" character i.e. cr/lf
Comorbidity
Intro
Wikipedia: In medicine, comorbidity is the presence of one or more additional diseases or disorders co-occurring with (that is, concomitant or concurrent with) a primary disease or disorder; in the countable sense of the term, a comorbidity (plural comorbidities) is each additional disorder or disease.
The Code
Beware: There is no error handling, so if something goes wrong just don't save anything. Close without saving and try again.
The code was tested and works fine with Excel 2003 and should work fine with all newer versions, too.
The code blocks starting with ' str1 = are just for debugging, a kind of 'subtotals' and can be deleted if you wish.
Sub Comorbidities()
'Description
'Writes the contents of cell values with several rows (per cell) to a new
'worksheet each row in a seperate row. For this to work the worksheet with
'the 'several row cells values' has to be active (selected).
'-- CUSTOMIZE BEGIN --------------------
Const cStrHeader As String = "Comorbidities" 'Header
Const cLoRow As Long = 2 'Starting row of initial data
Const cStrColumn As String = "T" 'Column of initial data
Const cLoRowResult As Long = 2 'Starting row of resulting data
Const cStrColumnResult As String = "A" 'Column of resulting data
'-- CUSTOMIZE END ----------------------
Dim oRng As Range 'Initial Range
Dim oRngResult As Range 'Resulting Range
Dim arrRng As Variant 'Array containing the initial data pasted from the range
Dim arrSplit As Variant 'Array containing the rows inside a cell
Dim arrData() As Variant 'Array containing the resulting data
Dim loData As Long 'Count of all rows
Dim loRng As Long 'Counter
Dim loArr As Long 'Counter
Dim iSplit As Integer 'Counter
Dim str1 As String 'Debug String
Dim lo1 As Long 'Debug Counter
'Determine the range to be processed
Set oRng = Range(Cells(cLoRow, cStrColumn), _
Cells(Cells(Rows.Count, cStrColumn).End(xlUp).Row, cStrColumn))
'Paste range into array
arrRng = oRng
Set oRng = Nothing 'Release the variable, initial data is in arrRng.
'Now arrays are taking over
' str1 = "arrRng"
' For lo1 = LBound(arrRng) To UBound(arrRng)
' str1 = str1 & vbCrLf & lo1 & ". " & arrRng(lo1, 1)
' Next
' Debug.Print str1
'Counting data - split each cells value and add to sum (loData)
'Reading data from arrRng.
For loRng = LBound(arrRng) To UBound(arrRng)
'Splitting arrSplit by Chr(10)
arrSplit = Split(arrRng(loRng, 1), Chr(10))
loData = loData + UBound(arrSplit) + 1 '+ 1 i.e. arrSplit is 0-based.
Next
'Redeclare arrData using the result of the counting (loData).
ReDim Preserve arrData(1 To loData, 1 To 1)
'Reading data from arrRng.
For loRng = LBound(arrRng) To UBound(arrRng)
'Splitting arrSplit by Chr(10).
arrSplit = Split(arrRng(loRng, 1), Chr(10))
' str1 = "arrSplit"
' For lo1 = LBound(arrSplit) To UBound(arrSplit)
' str1 = str1 & vbCrLf & lo1 + 1 & ". " & arrSplit(lo1)
' Next
' Debug.Print str1
'Writing arrSplit data to arrData.
For iSplit = LBound(arrSplit) To UBound(arrSplit)
loArr = loArr + 1
arrData(loArr, 1) = arrSplit(iSplit)
Next
Erase arrSplit 'Is repeatedly newly created to write data to arrData.
' str1 = "arrData"
' For lo1 = LBound(arrData) To UBound(arrData)
' str1 = str1 & vbCrLf & lo1 & ". " & arrData(lo1, 1)
' Next
' Debug.Print str1
Next
Erase arrRng 'No longer needed, resulting data is in arrData.
' str1 = "arrData"
' For lo1 = LBound(arrData) To UBound(arrData)
' str1 = str1 & vbCrLf & arrData(lo1, 1)
' Next
' Debug.Print str1
'Output to new worksheet
'Add a new worksheet positioned after the initial worksheet.
Worksheets.Add After:=ActiveSheet
'Determine the resulting range in the new worksheet.
Set oRngResult = Range(Cells(1, 1), Cells(loData, 1))
'Paste data into range
oRngResult = arrData
Erase arrData 'No longer needed, all data is in oRngResult.
Set oRngResult = Nothing 'Release the variable, all data is in the worksheet.
'Write Header
Cells(cLoRowResult - 1, cStrColumnResult).Value = cStrHeader
End Sub
Some additional info
How to put more lines into one cell
You have to hold the left ALT key and press enter after each line.
How I got the delimiter
When you go into a cell (click in the formula bar) e.g. cell 'A1' with more lines (bulk data), you select the end of a line expanding over the 'invisible' kind of 'space looking' character right after the 'visible' part and copy it. Then paste it into another cell e.g. A2. Now in e.g. cell A3 write the formula =CODE(A2)and the result will be 10. So in VBA this means you choose this character by using the Chr Function: Chr(10) (in Excel this is =CHAR(10).
BTW the character is called Line Feed (LF) or New Line (NL). For other character codes look here.

EXCEL VBA Code to search cell for match to a list and delete if no match

(pic link below for this example): The data starts on row "A11", one block of data is A11 to A14, I need to search that range to see if it contains a member name from a list on sheet 2, for example Erik Christensen, if the list on sheet 2 doesnt have that name I need to delete rows A11 thru A14 and continue to the next block. The list on sheet 2 will have a varying amount of members to check so that needs to be taken into consideration. Once all the rows have been processed, I need to sorth them back to start at row A11.Please see pic and I will be extremely thankful for any help.
Sheet 1
For the below answer, I have made a few assumptions:
Your data will always start on row 11 of the first sheet in the
workbook.
The search term will always be found in the second row, below
Object:...
The data will always present in rows of 4, as shown in the picture,
with End: in the 4th row.
The list of valid names is in column A (beginning on A1) of the
second sheet in the workbook.
By "sorted back to start on row A11", I assume you mean that the
remaining blocks of data should start on row A11 and continue to the
end of the data, not that any actual sorting (i.e. by name) is
required.
This code will loop through all blocks of data (beginning with the last one, since we are deleting rows). If any of the names in column A of the second sheet appear in the block of data, that block is skipped. Otherwise, if no names appear, that block is deleted.
Sub SearchAndDeleteList()
Dim i As Long
Dim j As Long
Dim LRow As Long
Dim LListRow As Long
Dim BMatch As Boolean
'Find last instance of "End:" in
LRow = Sheets(1).Range("A:A").Find(what:="End*", searchdirection:=xlPrevious).Row
'Find last non-blank row in column A of second sheet
LListRow = Sheets(2).Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Application.EnableEvents = False
If LRow >= 11 Then
'Make sure there are at least 11 rows of data
i = LRow
'MsgBox "First checkpoint: Last row of data is " & LRow 'Comment out this line
Do
BMatch = False
For j = 1 To LListRow
'Test this block to see if the value from j appears in the second row of data
If InStr(1, Sheets(1).Range("A" & i - 2).Value2, Sheets(2).Range("A" & j).Value2) > 0 Then
BMatch = True
Exit For
End If
Next j
'Application.StatusBar = "Match status for row " & i & ": " & BMatch
If Not BMatch Then
'Loop backwards to find the starting row (no lower than 11)
For j = i To 11 Step -1
If Sheets(1).Range("A" & j).Value2 Like "Object:*" Then Exit For
Next j
Sheets(1).Rows(j & ":" & i).Delete
i = j - 1
Else
'Find next block
If i > 11 Then
For j = i - 1 To 11 Step -1
If Sheets(1).Range("A" & j).Value2 Like "End:*" Then Exit For
Next j
i = j
Else
i = 10 'Force the loop to exit
End If
End If
'Application.StatusBar = "Moving to row " & i
Loop Until i < 11
'Loop back through and delete any blank rows
LRow = Sheets(1).Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row
'MsgBox "Second checkpoint: new last row of data is " & LRow
For i = LRow To 11 Step -1
If Sheets(1).Range("A" & i).Value2 = vbNullString Then Sheets(1).Rows(i).Delete
Next i
End If
'Application.StatusBar = False
Application.EnableEvents = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
thanks to Nick's cracking actual OP's needs, I hereby propose a solution that should be more maintainable and/or changeable as per Op's future needs
Option Explicit
Sub SearchAndDeleteList2()
Dim dataSht As Worksheet
Dim dataRng As Range, namesRng As Range, cell As Range, rangeToDelete As Range
Dim firstAddress As String
'------------------------------
' setting stuff - begin
Set dataSht = ThisWorkbook.Sheets("Sheet1Data") '<== change 'data' sheet as per your needs
With dataSht
Set dataRng = .Range("A11:A" & .Cells(.Rows.Count, 1).End(xlUp).row)
End With
If dataRng.Rows(1).row < 11 Then Exit Sub
With ThisWorkbook.Sheets("Sheet2Names") '<== change 'names' sheet as per your needs
Set namesRng = .Range("A1:A" & .Cells(.Rows.Count, 1).End(xlUp).row)
End With
Call ApplicationSet(False, False, xlCalculationManual, False)
' setting stuff - end
'------------------------------
'------------------------------
' core code - begin
Set cell = dataRng.Find("End:", LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext)
If Not cell Is Nothing Then
firstAddress = cell.Address
Do
If Not MyMatch(GetName(cell.Offset(-2)), namesRng) Then Call UpdateRngToDelete(rangeToDelete, dataSht.Rows(cell.row).Offset(-3).Resize(4))
Set cell = dataRng.FindNext(cell)
Loop While cell.Address <> firstAddress
rangeToDelete.Delete
End If
' core code - end
'------------------------------
Call ApplicationSet(True, True, xlCalculationAutomatic, True)
End Sub
Function GetName(cell As Range) As String
Dim iIni As Integer
Dim iEnd As Integer
iIni = InStr(cell.value, """") '<== the 'name' is always preceeded by '"' character
iEnd = InStr(cell.value, "\") '<== the 'name' is always follwed by '/' character
GetName = Mid(cell.value, iIni + 1, iEnd - iIni - 1)
End Function
Sub UpdateRngToDelete(baseRng As Range, toBeAddedRng As Range)
If baseRng Is Nothing Then
Set baseRng = toBeAddedRng
Else
Set baseRng = Union(baseRng, toBeAddedRng)
End If
End Sub
Function MyMatch(value As String, rng As Range) As Boolean
MyMatch = Not IsError(Application.Match(value, rng, 0))
End Function
using separate functions or subs makes it easier (and faster!) to keep control and debug future code changes

Range of cells into single cell with carriage return

I am working through my first VBA book and would appreciate if someone would point me in the right direction. How would I transfer a range of rows into a single cell with carriage returns? I would then like to repeat this action for all ranges in the column.
I think I need to:
find the first cell with a value in the column
verify that the next row is not empty
find the last cell in the range
perform "the operation" on the range
Following up on my comments. here is a very simple way to achieve what you want.
Option Explicit
'~~> You can use any delimiter that you want
Const Delim = vbNewLine
Sub Sample()
Dim rngInput As Range, rngOutput As Range
Application.ScreenUpdating = False
Set rngInput = Range("A1:A5") '<~~ Input Range
Set rngOutput = Range("B1") '<~~ Output Range
Concatenate rngInput, rngOutput
Application.ScreenUpdating = True
End Sub
Sub Concatenate(rng1 As Range, rng2 As Range)
Dim cl As Range
Dim strOutPut As String
For Each cl In rng1
If strOutPut = "" Then
strOutPut = cl.Value
Else
strOutPut = strOutPut & Delim & cl.Value
End If
Next
rng2.Value = strOutPut
End Sub
Within the context of a worksheet-level code, the following will work. Column 2 is hard-coded, so you might want to pass in a value or otherwise modify it to fit your needs.
Dim rng As Range
Set rng = Me.Columns(2)
Dim row As Integer
row = 1
' Find first row with non-empty cell; bail out if first 100 rows empty
If IsEmpty(Me.Cells(1, 2)) Then
Do
row = row + 1
Loop Until IsEmpty(Me.Cells(row, 2)) = False Or row = 101
End If
If row = 101 Then Exit Sub
' We'll need to know the top row of the range later, so hold the value
Dim firstRow As Integer
firstRow = row
' Combine the text from each subsequent row until an empty cell is encountered
Dim result As String
Do
If result <> "" Then result = result & vbNewLine
result = result & Me.Cells(row, 2).Text
row = row + 1
Loop Until IsEmpty(Me.Cells(row, 2))
' Clear the content of the range
Set rng = Me.Range(Me.Cells(firstRow, 2), Me.Cells(row, 2))
rng.Clear
' Set the text in the first cell
Me.Cells(firstRow, 2).Value2 = result

Resources