Excel VBA - Find matching column headings and delete the column - vba

Apologies if this has been answered before, I'm unable to find anything that matches my specific case.
I have a workbook with 18 sheets, and a variable number of columns per sheet starting at B2. Occasionally the program that generates the sheet will create duplicate columns, due to this, I need a macro triggered by button to search each sheet for matching column headers and then delete one of these columns (the whole column, not just the header).
So far I'm pretty stuck, I've been able to delete all matches from any cell in the sheet, which pretty much wipes the entire sheet out. I just need to match headers and then delete the entire column based on that.
Let me know if you need any more information, and thank you for the help!
What I have so far, the code is doing some other stuff too so this needs to continue working.
Sub RemoveExtras()
Dim MyRange As Range
Dim ws As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
BadCharacters = Array(Chr(10), Chr(13))
wsNumber = Sheets.Count
For Each ws In Worksheets
With ws
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
For Each i In BadCharacters
MyRange = Replace(MyRange, i, vbNullString)
Next i
End If
For t = 1 To wsNumber
Columns(t).RemoveDuplicates Columns:=Array(1), Header:=xlYes
Next t
Next MyRange
End With
Next ws
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Dictionaries are perfect for handling unique values:
Sub RemoveExtras()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim c As Integer, i As Integer, ws As Worksheet
Dim dict As Object
For Each ws In Worksheets
Set dict = CreateObject("Scripting.Dictionary")
'Find Last column
c = ws.UsedRange.Columns.Count
'Loop backwards
For i = c To 2 Step -1
'If column does not exist in dictionary, then add it
If Not dict.Exists(ws.Cells(2, i).Value) Then
dict.Add ws.Cells(2, i).Value, 1
Else
'Otherwise delete column
ws.Columns(i).Delete Shift:=xlToLeft
End If
Next i
Set dict = Nothing
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Here you do not compare every pair of column headers in the sheet. Also this compares headers across all the worksheets, not just duplicates inside one individual sheet.

See if this helps you
Sub test()
Dim book As Workbook, sheet As Worksheet, text As String
For Each sheet In Worksheets
Set MR = Range("B2:Z2") 'change Z2 as per your requirement
For Each cell In MR
Set BR = Range("B2:Z2") 'change Z2 as per your requirement
For Each cell2 In BR
If cell.Value = cell2.Value Then cell.EntireColumn.Delete
Next
Next
Next sheet
End Sub

Related

Deleting rows based on criteria

I have a little code so I can move specific rows to a specific sheet which is structured as follows:
sheet 1 (contains all data)
sheet 2 (the destination sheet of rows to move)
So basically the code looks for a keyword on a specific column, and copies all rows that meet that criteria on the specified column from sheet 1 to sheet 2, it does that like a charm. The problem I have is because of data organization, I need to delete the rows once they have been copied, I tried using the .cut target instead of .copy target, and it works too, but it takes extremely long (about 1+ min), and it looks like that whole time is frozen as it doesn't let you select anything.
Any suggestions to accomplish this more efficiently? I am learning VBA, so please bear with me.
Sub Copydatatoothersheet()
Dim c As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
' Change worksheet designations as needed
Set Source = ActiveWorkbook.Worksheets("Sheet1")
Set Target = ActiveWorkbook.Worksheets("Sheet2")
j = 3 ' Start copying to row 3 in target sheet
Application.ScreenUpdating = False
For Each c In Source.Range("BB:BB")
If c = "UNPAID" Then
'THIS IS THE LINE WHERE I REPLACE COPY WITH CUT
Source.Rows(c.Row).Copy Target.Rows(j)
j = j + 1
End If
Next c
Application.ScreenUpdating = True
End Sub
Try store the desired ranges in a variable then delete the entire rows of that stored range
Sub Copydatatoothersheet()
Dim c As Range
Dim j As Integer
Dim source As Worksheet
Dim target As Worksheet
Dim oRange As Range
' Change worksheet designations as needed
Set source = ActiveWorkbook.Worksheets("Sheet1")
Set target = ActiveWorkbook.Worksheets("Sheet2")
j = 3 ' Start copying to row 3 in target sheet
Application.ScreenUpdating = False
For Each c In source.Range("BB:BB")
If c = "UNPAID" Then
'THIS IS THE LINE WHERE I REPLACE COPY WITH CUT
source.Rows(c.Row).Copy target.Rows(j)
If oRange Is Nothing Then Set oRange = c Else Set oRange =
Union(oRange, c)
j = j + 1
End If
Next c
If Not oRange Is Nothing Then oRange.EntireRow.Delete
Application.ScreenUpdating = True
End Sub
Use AutoFilter
Sub foo()
Dim Source As Worksheet
Dim Target As Worksheet
' Change worksheet designations as needed
Set Source = ActiveWorkbook.Worksheets("Sheet1")
Set Target = ActiveWorkbook.Worksheets("Sheet2")
With Source
With .Range("BB:BB" & .Cells(.Rows.Count, "BB").End(xlUp).Row) 'reference its column BB cells from row 1 (header) down to last not empty one
.AutoFilter field:=1, Criteria1:= "UNPAID"' filter referenced cells on 1st column with "UNPAID" content
If Application.WorksheetFunction.Subtotal(103, .Columns(1)) > 1 Then
With .Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible)
Intersect(.EntireRow, .Parent.UsedRange), .Parent.UsedRange).Copy Destination:=Target.Range("A1") ' if any filtered cell other than the header then copy their entire rows and paste to 'Target' sheet starting from its cell A1
.EntireRow.Delete ‘finally, delete these rows
End With
End If
End With
.AutoFilterMode = False
End With
End Sub
You may also add the ScreenUpdating toggling

(Excel) How Can I Add Worksheet Name as Prefix for Each Column Header?

I have a header that starts in Column E and might go on for 100+ columns.
I am trying to change each column header to add a prefix (the name of the "tab" aka. worksheet) (ie. if Worksheet is called 'Beverage', I'd like each column header to be prefixed with "Beverage -")
I will be running script across multiple sheets, so am trying to find a way to reference the current sheet name.
Before: (For Worksheet "Beverage")
After: (For Worksheet "Beverage". Note: Columns don't need to be resized, just did it to demonstrate)
I've tried adapting code from this thread, however I can't get it to work.
Here is the code I have so far (non-working):
Sub Worksheet_Name_Prefix()
Dim columnNumber As Long, x As Integer
Dim myTab As ListObject
Set myTab = ActiveSheet.ListObjects(rows.Count, 1)
For x = 5 To rows.Count ' For Columns E through last header cell with value
columnNumber = x
myTab.HeaderRowRange(1, columnNumber) = ActiveSheet.Name
Next x
End Sub
Any suggestions on what's wrong with my code? Any help would be greatly appreciated.
I hope this help you...
Sub Worksheet_Name_Prefix_v2()
Dim h 'to store the last columns/header
Dim rngHeaders As Range 'the whole range with the headers from E1 to x1
Dim i 'just and index
Dim sht As Worksheet 'the sheet where you want the job done
h = Range("E1").End(xlToRight).Column 'find the last column with the data/header
Set rngHeaders = Range(Cells(1, 5), Cells(1, h)) 'the range with the headers E = column 5
'Cells 1 5 = E1
'Cells 1 h = x1 where x is the last column with data
Set sht = ActiveSheet 'the sheet with the data, _
'and we take the name of that sheet to do the job
For Each i In rngHeaders 'for each cell in the headers (every cells in row 1)
i.Value = sht.Name & " - " & i.Value
'set the value "sheet_name - cell_value" in every cell
Next i
End Sub
If you need any emprovement please tell me... I'm not sure if I get the real idea of what you need.
Edit #1
Use this in a regular module:
Option Explicit
Sub goForEverySheet()
Dim noSht01 As Worksheet 'store the first sheet
Dim noSht02 As Worksheet 'store the second sheet
Dim sht 'just a tmp var
Set noSht01 = Sheets("AA") 'the first sheet
Set noSht02 = Sheets("Word Frequency") 'the second sheet
appTGGL bTGGL:=False
For Each sht In Worksheets ' for each sheet inside the worksheets of the workbook
If sht.Name <> noSht01.Name And sht.Name <> noSht02.Name Then
'IF sht.name is different to AA AND sht.name is diffent to WordFrecuency THEN
'TIP:
'If Not sht.Name = noSht01.Name And Not sht.Name = noSht02.name Then 'This equal
'IF (NOT => negate the sentence) sht.name is NOT equal to noSht01 AND
' sht.name is NOT equal to noSht02 THEN
sht.Activate 'go to that Sheet!
Worksheet_Name_Prefix_v3 'run the code
End If '
Next sht 'next one please!
appTGGL
End Sub
Sub Worksheet_Name_Prefix_v3()
Dim h 'to store the last columns/header
Dim rngHeaders As Range 'the whole range with the headers from E1 to x1
Dim i 'just and index
Dim sht As Worksheet 'the sheet where you want the job done
h = Range("E1").End(xlToRight).Column 'find the last column with the data/header
Set rngHeaders = Range(Cells(1, 5), Cells(1, h)) 'the range with the headers E = column 5
'Cells 1 5 = E1
'Cells 1 h = x1 where x is the last column with data
Set sht = ActiveSheet 'the sheet with the data, _
'and we take the name of that sheet to do the job
For Each i In rngHeaders 'for each cell in the headers (every cells in row 1)
i.Value = sht.Name & " - " & i.Value
'set the value "sheet_name - cell_value" in every cell
Next i
End Sub
Public Sub appTGGL(Optional bTGGL As Boolean = True)
Debug.Print Timer
Application.ScreenUpdating = bTGGL
Application.EnableEvents = bTGGL
Application.DisplayAlerts = bTGGL
Application.Calculation = IIf(bTGGL, xlCalculationAutomatic, xlCalculationManual)
End Sub
Your code was not running because, you do not use this line sht.Activate you say, for every sheet in the workbook do this, but you not say to go to every sheet, and the the code run n times in the same sheet (as many sheets there in the workbook less two). But if you say, for every sheet do this, AND got to each of one of that sheets and do this (less that two sheets) you will get whay you want

Check merged cell and compare adjacent to set unique value from compared cells values

I'm writing a macro in Excel 2010 for a problem that is as follows:
I have two columns, one with a Key string value and one with a uuid. The idea is that every key should have only one uuid but as the table is now, key cell could be merged cells or single cells.
The macro needs to recognize which cells are merged and which are not, so, I have two options:
If cell is merged, check all its adjacent cells, pick first uuid value and copy/paste it to other adjacent cells, that is to say, cell below(Could be with an Offset())
If cell is not merged , but key value is repeated in multiple cells, copy/paste uuid value to adjacent cells.
So basically is to check merged cells MergeArea but I don't know if I need to iterate through its addresses or check cells in the range with an offset of Offset(0,1) or what.
With my code I can know if the cells are merged but now, how con I iterate through it's adjacent cells values?
Code as is now:
Sub CopyUUID()
Dim lRow As Long
Dim rng As Range
Dim ws As Worksheet
Dim rMerged As Range
Dim value As Variant
Set ws = Sheets(ActiveSheet.Name)
On Error GoTo ExitProgram 'If an error happens within the execution, skips it and continue in next step
Application.DisplayAlerts = False 'We can cancel the procedure without errors
With ws
lRow = .Range("F" & .Rows.count).End(xlUp).row
Set rng = .Range(.Cells(3, 6), .Cells(lRow, 6))
rng.Select
For Each cell In rng
If cell.MergeCells Then
'Code for merged cells
Else
'Code to use for single cells
End If
Next cell
End With
ExitProgram:
Exit Sub
End Sub
Option Explicit
Sub CopyUUID()
Const UUID As Long = 31 'col AE
Dim lRow As Long, cel As Range, isM As Boolean, copyID As Boolean, kCol As Long
With ActiveSheet
kCol = -25 'col F
lRow = .Cells(.Rows.Count, UUID + kCol).End(xlUp).Row
For Each cel In .Range(.Cells(3, UUID), .Cells(lRow, UUID))
isM = cel.Offset(0, kCol).MergeCells
copyID = isM And Len(cel.Offset(0, kCol)) = 0
copyID = copyID Or (Not isM And cel.Offset(0, kCol) = cel.Offset(-1, kCol))
If copyID Then cel = cel.Offset(-1)
Next
End With
End Sub
Try the following code. Note that this is going to overwrite the current contents of UUID, so make a backup copy before testing. If you don't want the UUID column modified, you can modify this to suit your needs.
Sub CopyUUID()
Dim lRow As Long
Dim rng As Range
Dim c As Range
Dim ws As Worksheet
Dim rMerged As Range
Dim value As Variant
Set ws = Sheets(ActiveSheet.Name)
On Error GoTo ExitProgram 'If an error happens within the execution, skips it and continue in next step
' Application.DisplayAlerts = False 'We can cancel the procedure without errors
With ws
lRow = .Range("F" & .Rows.Count).End(xlUp).Row
Set rng = .Range(.Cells(3, 6), .Cells(lRow, 6))
' rng.Select
For Each c In rng
If c.MergeCells Then
'Code for merged cells
c.Offset(0, 1).Formula = c.MergeArea.Cells(1, 1).Offset(0, 1).Formula
Else
'Code to use for single cells
If c.Formula = c.Offset(-1, 0).Formula Then
c.Offset(0, 1).Formula = c.Offset(-1, 1).Formula
End If
End If
Next c
End With
ExitProgram:
Exit Sub
End Sub
When in a MergedCell, it makes the UUID the same as the UUID of the first cell in the merged area. When not in a MergedCell, it copies UUID from the row above if Key is the same as the row above.
I changed your variable cell to c (I don't like to use variable names that can be confused with built-ins) and commented out a couple of lines.
Hope this helps
I adopt a simple approach to this problem as illustrated through steps taken by me.
sample sheet showing data with merged cells and unmerged cells.
Run the program code to unmerge the cells. Output of the program is appended below.
If this structure of data matches your case then addition of 2 lines of code for column B will leave the data as per following image.
Program code is as follows:
'Without column deletion:
Sub UnMergeRanges()
Dim cl As Range
Dim rMerged As Range
Dim v As Variant
For Each cl In ActiveSheet.UsedRange
If cl.MergeCells Then
Set rMerged = cl.MergeArea
v = rMerged.Cells(1, 1)
rMerged.MergeCells = False
rMerged = v
End If
Next
End Sub
'With coumn deletion
Sub UnMergeRangesB()
Dim cl As Range
Dim rMerged As Range
Dim v As Variant
For Each cl In ActiveSheet.UsedRange
If cl.MergeCells Then
Set rMerged = cl.MergeArea
v = rMerged.Cells(1, 1)
rMerged.MergeCells = False
rMerged = v
End If
Next
Columns("B:B").Select
Selection.Delete Shift:=xlToLeft
End Sub

Handling Merged Cells when Deleting Rows

I'm working on writing a module to remove unwanted text from a number of worksheets within a single workbook. I've pieced together enough to remove rows that have a specific font type, and rows that are empty; however, I've hit a snag.
The worksheets have a number of merged cells. I want to delete specific rows based on key phrases. Example, if "Comments" is found anywhere in Column A delete the row. However, if comments is merged between A2:A4, text in B3:B4 remains, leaving junk in the sheets I don't want.
Is there a way to delete the merged cell, and all rows to the right of that cell, if in the value in Column A is any number of keywords I'm looking for?
Here's what I have so far...
Sub Delete_Rows_Courier()
Dim ws As Excel.Worksheet
Dim LastRow As Long
Dim i As Integer
For Each ws In Application.ThisWorkbook.Worksheets
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
i = 1
Do While i <= LastRow
If ws.Range("A" & i).Font.Name = "Courier New" Then
ws.Rows(i).Delete
i = i - 1
LastRow = LastRow - 1
End If
i = i + 1
Loop
Next
End Sub
Sub Delete_Empty_Rows()
Dim ws As Worksheet
Dim wb As Workbook
Dim i As Long
For Each ws In Application.ThisWorkbook.Worksheets
'Deletes the entire row within the selection if the ENTIRE row contains no data.
'We use Long in case they have over 32,767 rows selected.
'We turn off calculation and screenupdating to speed up the macro.
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
'We work backwards because we are deleting rows.
For i = ws.UsedRange.Rows.Count To 1 Step -1
If WorksheetFunction.CountA(ws.Rows(i)) = 0 Then
ws.Rows(i).EntireRow.Delete
End If
Next i
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
Next ws
End Sub
Sub RunMacros()
Delete_Empty_Rows
Delete_Rows_Courier
End Sub
With .Range("A" & i).Mergearea
x = .Rows.Count 'if you need to know how many rows were deleted
.EntireRow.Delete 'delete merged rows
End With

Store AutoFilter Row Numbers using VBA

How do I store and retrieve the row numbers returned from an AutoFilter action using VBA? For example, I used #brettdj code from this question (see code below) to delete all rows with "X" under column B. Now I need to store the row numbers with X (B4,B6,B9 - see screen shots below) because I need to delete the same rows on other sheets in the same workbook.
Sub QuickCull()
Dim ws As Worksheet
Dim rng1 As Range
Set ws = Sheets("Sheet1")
Set rng1 = ws.Range(ws.[b2], ws.Cells(Rows.Count, "B").End(xlUp))
Application.ScreenUpdating = False
With ActiveSheet
.AutoFilterMode = False
rng1.AutoFilter Field:=1, Criteria1:="X"
rng1.Offset(1, 0).EntireRow.Delete
.AutoFilterMode = False
End With
Application.ScreenUpdating = True
End Sub
Using the code from Is it possible to fill an array with row numbers which match a certain criteria without looping? you could return these rows quickly without the AutoFilter
For example, this code will return a range of rows where X is found within B2:B50000
Sub GetEm()
Dim StrRng As String
StrRng = Join(Filter(Application.Transpose(Application.Evaluate("=IF(B2:B50000=""X"",""B""&ROW(B2:B50000),""X"")")), "X", False), ",")
If Len(StrRng) > 0 Then MsgBox Range(StrRng).EntireRow.Address & " could be deleted elsewhere"
End Sub