VBA Script which compares two sheets and returns unique values in a new sheet - automation

I created a VBA Script which compares a sheet named "New Data" and a sheet named "Old Data" to return only rows with a column differing. I created this in order to find back dating which occurs from time to time, so the two sheets "New Data" and "Old Data" should be identical with 1-5 differences, sometimes they add a new row, sometimes they take one of the old data and change the value attached to it. This is why I have it checking as unique rows, its fine if column a-y is identical but once column z differs then its a unique row.
Please see code below:
Sub CompareSheets()
'Declare variables for the two sheets
Dim sheet1 As Worksheet
Dim sheet2 As Worksheet
'Set the variables to the appropriate sheets
Set sheet1 = ThisWorkbook.Sheets("New Data")
Set sheet2 = ThisWorkbook.Sheets("Old Data")
'Declare a variable for the last row in each sheet
Dim lastRow1 As Long
Dim lastRow2 As Long
'Set the last row variables to the last used row in each sheet
lastRow1 = sheet1.Cells(sheet1.Rows.Count, "A").End(xlUp).Row
lastRow2 = sheet2.Cells(sheet2.Rows.Count, "A").End(xlUp).Row
'Declare a variable for the first empty row in a new sheet
Dim nextRow As Long
'Create a new sheet to hold the unique rows
ThisWorkbook.Sheets.Add After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)
ActiveSheet.Name = "Unique Rows"
'Set the first empty row variable to the first row of the new sheet
nextRow = 1
' Create a dictionary object
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
' Add the data from sheet2 to the dictionary
Dim key As String
For row2 = 2 To lastRow2
key = ""
For col = 1 To sheet2.Cells(1, sheet2.Columns.Count).End(xlToLeft).Column
key = key & sheet2.Cells(row2, col)
Next col
dict.Add key, row2
Next row2
' Turn off screen updating and calculation during execution
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'Loop through each row in sheet1
For row1 = 2 To lastRow1
key = ""
'Create a key for the current row
For col = 1 To sheet1.Cells(1, sheet1.Columns.Count).End(xlToLeft).Column
key = key & sheet1.Cells(row1, col)
Next col
'Check if the key exists in the dictionary
If Not dict.Exists(key) Then
For col = 1 To sheet1.Cells(1, sheet1.Columns.Count).End(xlToLeft).Column
ThisWorkbook.Sheets("Unique Rows").Cells(nextRow, col).Value = sheet1.Cells(row1, col).Value
Next col
nextRow = nextRow + 1
End If
Next row1
'Turn on screen updating and calculation
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
I've had this script work and give me unique variables from time to time but in some cases it gives me an error: Run-Time Error '457': That key is already associated with an element of this collection. I've received one other error Run-Time Error '1004' but that's only when Unique Rows sheet already exists, if I delete it would fix this issue. Can you please assist on having this code run smoothly without error?
Thank you, I appreciate your time and effort!

Related

List table name AND cell in last row and column

I am looking to list all table names in a sheet, together with the table's corresponding cell in the last row and column. The below code finds the table names in sheet "A1.6Laster" (Except table "Lastkategori") and then lists them in sheet "A1.6.5Lastkombinationer".
Since I can add/delete tables i sheet "A1.6Laster", the list is first deleted/cleared.
In other words; the below code work fine listing the names of the tables, but in the column next to the name list I want each table's corresponding cell in the last row and column to be listed as well. Do I need to add some code in the For Each loop?
Any input is welcome, and please ask if you need further information!
Sub Laster()
Dim tbl As ListObject
Dim wsSummary As Worksheet
Dim ws As Worksheet
Dim lRow As Long
Dim SearchText As String
Dim GCell As Range
SearchText = "Laster"
Set GCell = Worksheets("A1.6.5Lastkombinationer").Cells.Find(SearchText).Offset(0)
Set wsSummary = Worksheets("A1.6.5Lastkombinationer")
Set ws = Worksheets("A1.6Laster")
With Worksheets("A1.6.5Lastkombinationer").ListObjects("Laster").DataBodyRange
If .Rows.Count > 1 Then
.Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).Rows.Delete
End If
End With
Worksheets("A1.6.5Lastkombinationer").ListObjects("Laster").DataBodyRange.Rows(1).ClearContents
lRow = GCell.Row
For Each tbl In Worksheets("A1.6Laster").ListObjects
If tbl.Name <> "Lastkategori" Then
lRow = lRow + 1
With wsSummary
.Cells(lRow, "A") = tbl.Name
End With
End If
Next tbl
ws.ListObjects("Lastkategori").ListColumns(1).DataBodyRange.Copy
wsSummary.ListObjects("Laster").DataBodyRange(1, 1).End(xlDown).Offset(1).PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End Sub
I am assuming when you say last row and column means the bottom right hand corner cell of each table.
Add the following snippet within With wsWsummary ... End With. What it does is it takes the range of cells for each table's data range and gets the last row's last column and spits out the data in that cell into the column next to the table's name.
Dim r As Range
Dim last As Range
Set r = tbl.DataBodyRange
Set last = r.Cells(r.Rows.Count, r.Columns.Count)
ws.Cells(lRow, "B").Value = last

Macro: Removing group of rows associated with cell in first column based on criteria, then deleting blank rows

I'm having a difficult time merging code to accomplish my goal. I am working between two sheets within one workbook. Column "A" references an item that may have multiple rows in column "C". "C" could have thousands of label codes, but there are 52 label codes that are listed in sheet "SheetCode". What my goal is to look at an item and see if it has one of the 52 label codes and if so then remove the item and all rows below it until the next Item in Column "A" label number. I want my macro to:
Search Column C for any value listed in sheet "SheetCode" (A2:A53)
If found, reference the associated populated cell in Column A and delete all rows below until it runs into the next populated cell in Column A, but continues to search the rest of column "C" for more (A2:A53) values.
Loop
I posted 2 images. The SheetCode worksheet has the list of values. I adde conditional formatting so that any cell value in main spreadsheet is colored. Ultimately the code should then delete all rows below the Column A value. This example would show rows 14-21 and 29-44 deleted.
Here is what I have so far. My problem is I want to avoid
Sub Remove_TBI_AB()
Const TEST_COLUMN As String = "C"
Dim Lastrow As Long
Dim EndRow As Long
Dim i As Long
Application.ScreenUpdating = False
With ActiveSheet
Lastrow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
EndRow = Lastrow
For i = Lastrow To 1 Step -1
If .Cells(i, TEST_COLUMN).Value2 Like "161000" Then
'Here I could at continuous "_or" and then in next line add the next code to find, but I have the list and would rather reference the list of values
.Rows(i & ":" & EndRow).Delete
EndRow = i - 1
' Here I need code to delete all cells below the associated value in Column A until the next populated cell.
EndRow = i - 1
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
SheetCode; values to target
Main Worksheet
You're on the right track, with some use of arrays and worksheet functions it could be completed; the key is that we will iterate backward "by item zone" instead of by individual rows. For each item zone, if at least one code is matched in the SheetCode list, the whole zone is deleted.
Sub Remove_TBI_AB()
Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
On Error GoTo Cleanup
Dim codes As Range: Set codes = Worksheets("Sheetcode").Range("A2:A53")
Dim lastrow As Long, startRow As Long
'[startRow, lastRow] mark the start/end of current item
With Worksheets("Main")
lastrow = .Cells(.Rows.count, 3).End(xlUp).row
Do While lastrow > 1
startRow = lastrow
Do Until Len(Trim(.Cells(startRow, 1).Value2)) > 0
startRow = startRow - 1
Loop ' find the beginning of current item
With .Range("C" & startRow & ":C" & lastrow) ' range of current item codes
If Application.SumProduct(Application.CountIf(codes, .Value2)) > 0 Then
.EntireRow.Delete ' at least one code was matched
End If
End With
lastrow = startRow - 1
Loop ' restart with next item above
End With
Cleanup:
Application.ScreenUpdating = False: Application.Calculation = xlCalculationAutomatic
End Sub

loop through columns on worksheet, copy data to new worksheet in new workbook - im stuck

I have a workbook that consists of several worksheets all with the same column headers. The rows in each worksheet identify an employee task and other task information. Columns starting at AB - BE containing an employee’s title as the column name along with email address in the row if they assisted in that task. Some of the rows are in a particular column if that employee roll has not touched that task.
I am looking to do the following.
Create a new workbook for new worksheets to be added
Loop through AB:BE and create a new worksheet in the new workbook with the column header name as the worksheet name
Filter this column (example: AB) to only include data that is in this list and not blanks
Copy this column data (AB as an example) into this new worksheet
Also copy Rows B, F, H from original worksheet to this new worksheet
Clear the filters on the main worksheet
Loop to next column (example AC) , repeat with creation of new worksheet in the workbook
I have done this in the past with rows just fine – I am having issues conceptually thinking about how this should work.
Does anyone have any examples? I have searched google for a few days and can get close in some areas however it does not scale well / loop on the data well.
Note: This could also be done with an Advanced Filter. That allows a filtered range to be copied to a new sheet.
I'm not sure I'm entirely understanding the sheet layout, but here's some basic code to create a new sheet for each column AB:BE, then for each row in column AB that is not empty, copy that cell value, along with the value in columns B, F, and H to a row in that new worksheet. Repeating then for columns AC:BE.
Sub CopyRoles()
Dim nSheet As Integer
Dim nTasks As Integer
Dim nSourceRow As Long
Dim nDestRow As Long
Dim wkb As Workbook
Dim wksSource As Worksheet
Dim wksDest As Worksheet
Set wksSource = ActiveSheet
Set wkb = Workbooks.Add
For nTasks = wksSource.Range("AB1").Column To wksSource.Range("BE1").Column
nSheet = nTasks - wksSource.Range("AB1").Column + 1
With wkb.Sheets
If .Count < nSheet Then ' Checks if sheet count on wkb exceeded
Set wksDest = .Add(after:=.Item(.Count), Type:=xlWorksheet)
Else
Set wksDest = .Item(nSheet) ' Keeps from having empty sheets
End If
wksDest.Name = wksSource.Cells(1, nTasks)
End With
With wksSource
wksDest.Cells(1, 1) = "E-mail address" ' Add header row to sheet
wksDest.Cells(1, 2) = .Cells(.UsedRange.Row, 2) ' Col B
wksDest.Cells(1, 3) = .Cells(.UsedRange.Row, 6) ' Col F
wksDest.Cells(1, 4) = .Cells(.UsedRange.Row, 8) ' Col H
nDestRow = 2
For nSourceRow = .UsedRange.Row + 1 To .UsedRange.Rows.Count
If .Cells(nSourceRow, nTasks).Value <> "" Then
wksDest.Cells(nDestRow, 1).FormulaR1C1 = _
.Cells(nSourceRow, nTasks).Value
wksDest.Cells(nDestRow, 2).FormulaR1C1 = _
.Range("B" & nSourceRow).Value
wksDest.Cells(nDestRow, 3).FormulaR1C1 = _
.Range("F" & nSourceRow).Value
wksDest.Cells(nDestRow, 4).FormulaR1C1 = _
.Range("H" & nSourceRow).Value
nDestRow = nDestRow + 1
End If
Next nSourceRow
End With
Next nTasks
wkb.SaveAs
End Sub

Infinite loop while gathering datasets from several worksheets

This is my first time to code in VBA.
I have several worksheets in a file and they are in order by dates.
So what I am trying to do is to collect data sets in a worksheet if they have the same period of time.
date1 value1
date2 value2
date3 value3
Since they are in order I just compare the first date values and if they are different it moves on to the next worksheet. If they are the same then copy the value and do the same process until it reaches the last worksheet.
However it copies one worksheet fine but after that Excel freezes.
I would be appreciated if you find any errors or give me other suggestions to do it.
Following is my code:
Sub matchingStock()
Dim sh1 As Worksheet, sh2 As Worksheet
' create short references to sheets
' inside the Sheets() use either the tab number or name
Set sh1 = Sheets("combined")
Dim col As Long
'since first column is for Tbill it stock price should place from the third column
col = 3
Dim k As Long
'go through all the stock worksheets
For k = Sheets("WLT").Index To Sheets("ARNA").Index
Set sh2 = Sheets(k)
' Create iterators
Dim i As Long, j As Long
' Create last rows values for the columns you will be comparing
Dim lr1 As Long, lr2 As Long
' create a reference variable to the next available row
Dim nxtRow As Long
' Create ranges to easily reference data
Dim rng1 As Range, rng2 As Range
' Assign values to variables
lr1 = sh1.Range("A" & Rows.Count).End(xlUp).Row
lr2 = sh2.Range("A" & Rows.Count).End(xlUp).Row
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
Application.ScreenUpdating = False
' Loop through column A on sheet1
For i = 2 To lr1
Set rng1 = sh1.Range("A" & i)
' Loop through column A on sheet1
For j = 2 To lr2
Set rng2 = sh2.Range("A" & j)
' compare the words in column a on sheet1 with the words in column on sheet2
'Dim date1 As Date
'Dim date2 As Date
'date1 = TimeValue(sh1.Range("A3"))
'date2 = TimeValue(sh2.Range("A3"))
sh1.Cells(1, col).Value = sh2.Range("A1").Value
' find next empty row
nxtRow = sh1.Cells(Rows.Count, col).End(xlUp).Row + 1
' copy the word in column A on sheet2 to the next available row in sheet1
' copy the value ( offset(0,1) Column B ) to the next available row in sheet1
sh1.Cells(nxtRow, col).Value = rng2.Offset(0, 6).Value
'when the date is different skip to the next worksheet
Set rng2 = Nothing
Next j
Set rng1 = Nothing
Next i
'sh3.Rows("1:1").Delete
Else
GoTo Skip
End If
Skip:
col = col + 1
Next k
End Sub
I cannot identify a specific error so this is a list of suggestions that may help you identify the error and may help improve your code.
Suggestion 1
Do you think the Else block of If-Then-Else-End-If is compulsory?
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
:
Else
GoTo Skip
End If
Skip:
is the same as:
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
:
End If
Suggestion 2
I do not like:
For k = Sheets("WLT").Index To Sheets("ARNA").Index
The value of property Index for a worksheet may not what you think it is. This may not give you the set or sequence of worksheets you expect. Do you want every worksheet except "Combined"? The following should be more reliable:
For k = 1 To Worksheets.Count
If Worksheets(k).Name <> sh1.Name Then
:
End If
Next
Suggestion 3
You use:
.Range("A" & Rows.Count)
.Range("A3")
.Cells(1, col).Value
.Cells(Rows.Count, col)
rng2.Offset(0, 6)
All these methods of identifying a cell or a range have their purposes. However, I find it confusing to use more than one at a time. I find .Cells(row, column) and .Range(.Cells(row1, column1), .Cells(row2, column2)) to be the most versatile and use them unless there is a powerful reason to use one of the other methods.
Suggestion 4
I cannot decypher what this code is attempting to achieve.
You say: "I have several worksheets in a file and they are in order by dates. So what I am trying to do is to collect data sets in a worksheet if they have the same period of time."
If you have set Worksheet("combined").Range("A3").Value to a particular date and you want to collect data from all those sheets with the same value in cell A3 then the outer For-Loop and the If give this effect. But if so, if does not matter how the worksheets are ordered. Also you start checking cell values from row 2 which suggests row 3 is a regular data row.
The outer loop is for each worksheet, the next loop is for each row in "combined" and the inner loop is for each row in the worksheet selected by the outer loop. The middle loop does not appear to do anything but set rng1 which is not used.
Perhaps you can add an explanation of what you are trying to achieve.
Suggestion 5
Are you trying to add an entire column of values from the source worksheets to "Combined". The macro below:
Identifies the next free row in column A of "Combined"
Identifies the last used row in column A of "Sheet2"
Assumes the first interesting row of "Sheet2" is 2.
Adds the entire used range of column A of "Sheet2" (complete with formatting) to the end of "Combined"'s column A in a single statement.
This may demonstrate a better way of achieving the effect you seek.
Sub Test()
Dim RngSrc As Range
Dim RngDest As Range
Dim RowCombNext As Long
Dim RowSrcFirst As Long
Dim RowSrcLast As Long
With Worksheets("Combined")
RowCombNext = .Cells(Rows.Count, "A").End(xlUp).Row + 1
Set RngDest = .Cells(RowCombNext, "A")
End With
With Worksheets("Sheet2")
RowSrcFirst = 2
RowSrcLast = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngSrc = .Range(.Cells(RowSrcFirst, "A"), .Cells(RowSrcLast, "A"))
End With
RngSrc.Copy Destination:=RngDest
End Sub

Copy cells from specified column, removing duplicates

I'm newbie in VBA, what I need to do is to copy rows from specified column into a column on the other worksheet, but I want to copy just one occurance of each word, for example
Column "F"
dog
dog
cat
dog
In the result I need to have new Worksheet called "Animals" with:
Column "A" Column "B"
1 dog
2 cat
Here is a sub routine that will do exactly what you want: slap a list of unique elements in Sheet1 column F into column A of sheet2 and rename the sheet "animals". You could tweak this so that instead of it changing the name of sheet2 it can create a new sheet if you like.
Sub UniqueList()
Application.ScreenUpdating = False
Dim lastRow As Long
Dim i As Long
Dim dictionary As Object
Set dictionary = CreateObject("scripting.dictionary")
Sheet1.Activate
lastRow = Sheet1.Cells(Rows.count, "F").End(xlUp).row
On Error Resume Next
For i = 1 To lastRow
If Len(cells(i, "F")) <> 0 Then
dictionary.Add cells(i, "F").Value, 1
End If
Next
Sheet2.range("a1").Resize(dictionary.count).Value = _
Application.Transpose(dictionary.keys)
Application.ScreenUpdating = True
MsgBox dictionary.count & " unique cell(s) were found and copied."
End Sub
How it works: I use a dictionary file, which will automatically take out any dupes, then slap the list of entries into sheet2.
Do you need to do this in VBA at all?
If you just want to get a unique copy of your list, select the unsorted, non-unique column contents including the header, then hit the Advanced... button on the Sort and Filter pane of the Data ribbon. You can ask it to copy to another location and tick Unique records only.
Recording this activity and looking at the VBA, this is how it looks:
Range("A1:A4").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range("B1"), Unique:=True
here is a solution:
Option Explicit
Sub copyNoDuplicates()
Dim rLastCell As Range
Dim cell As Range, i As Long
Dim cAnimals As Collection
Set cAnimals = New Collection
With ActiveWorkbook.Worksheets("Sheet1")
'Find last used cell
Set rLastCell = .Range("F65536").End(xlUp)
'Parse every animal and put it in a collection
On Error Resume Next
For Each cell In .Range("F2:F" & rLastCell.Row)
cAnimals.Add cell.Value, CStr(cell.Value)
Next cell
On Error GoTo 0
End With
With ActiveWorkbook.Worksheets("Sheet2")
For i = 1 To cAnimals.Count
.Range("A" & i).Value = i
.Range("B" & i).Value = cAnimals(i)
Next i
End With
End Sub