Copy rows based on multiple cells values - vba

I'm trying to copy rows from a significant amount of worksheets.
I have multiple lines that relate to a certain document, depending on the versions.
Therefore, some lines have the same reference, same name but a different version/date of creation. I'd like to copy to another sheet (Sheet2 for instance) the latest version of every document.
I've tried so far with a few while loops to check all lines and a if to check the value of the date but I failed to make it work, and I wonder if it's an efficient way of doing it.
Here is a picture of my problem and a part of the code I wrote :
Dim Name as String
Dim Dates as Date
With Sheets(Sheet1)
Application.DisplayAlerts = False
Name = Cells(1,3) 'Initialise Name
Dates = Cells(1,5) 'Initialise Dates
LineCopy = 1 'The line we'll copy
Line = 1 'The line we use to check the sheet
While Name <> "" 'if the name is not empty, ie there are no documents left
While Sheets(Sheet1).StrComp(Name, .Cells(Line, 3)) = True 'WHile you are working with a same name document
If .Cells(Line, 5) > Dates Then 'If the document is older, then choose it.
Dates = .Cells(Line, 5)
Else
LineCopy = Line 'If there are no older documents, then it's the one to copy
Sheets(Sheet1).Range("A" & LineCopy & ":" & "E" & LineCopy).Copy ' Copy the oldest document
Sheets(Sheet2).Paste
End If
Line = Line + 1 ' Increment the Line in the second while to check every line
Wend
Name = .Cells(LineCopy + 1, 6) 'After the first while, let's change name to the second document and do it all over again.
Wend

Unless you need the formating I think copy and paste should be avoided.
The below code assumes the data is sorted on column A. If not another aproach is needed.
Edit: Adapted to comment that there may be blank rows.
Dim max_date As Date
Dim max_row As Long
Dim old_sheet As Worksheet
Dim new_sheet As Worksheet
Dim counter As Long
Dim last_name as String
Set new_sheet = Sheets("Sheet2") 'adjust name to result sheet
counter = 1
For x = 1 To 5 ' the sheets you should loop thru
Set old_sheet = Sheets(x)
end_row = old_sheet.Cells(old_sheet.Rows.Count, 1).End(xlUp).Row
For i = 2 To end_row 'loop all rows
If old_sheet.Cells(i, 5) > max_date Then 'if the date is larger, sve the date and the row
max_date = old_sheet.Cells(i, 5)
max_row = i
End If
if old_sheet.cells(i,j)<>"" then last_name = old_sheet.cells(i,j)
If (old_sheet.Cells(i + 1, 1) <> "" and old_sheet.Cells(i + 1, 1) <> last_name) or i = end_row Then
For j = 1 To 4
new_sheet(counter, j) = old_sheet(max_row, j) 'add the data to the new sheet
Next j
max_date = DateValue("01/01/1970") 'reset the date value
counter = counter + 1 'new row to move the data to
End If
Next i
Next x

Related

Excel Macro Copy Range Paste offset based on cell value

I have two sheets "Data" - which has raw data and "Report" - as Report form .
Report sheet first 5 rows has info.
Data Sheet there 6 columns of Data available (SlNo Name Desig Place gender Category)
Report sheet has first 5 columns only (SlNo Name Desig Place gender)
Report sheet range C5 is dropdown box (List from Category column of Data sheet).
So based on this C5 value get details from Data sheet and paste in Report sheet.
I tried the following code but it pastes the whole row when I want to paste only Name,Desig,Place,gender details in offset and loop...
Sub ViewBtn()
Dim SCHL As String
Dim x As Long
x = 2
Do While Cells(x, 1) <> ""
Sheets("Report").Range(Cells(x, 1).Address, Cells(x, 5).Address).ClearContents
x = x + 1
Loop
Dim id As String
id = ActiveSheet.Range("C5").Value
x = 2
Sheets("Data").Select
Category = id
Do While Cells(x, 1) <> ""
If Cells(x, 4) = Category Then
Worksheets("Data").Rows(x).Copy
Worksheets("Report").Activate
erow = Sheets("Report").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
ActiveSheet.Paste Destination:=Worksheets("Report").Rows(erow)
End If
Worksheets("Data").Activate
x = x + 1
Loop
Application.CutCopyMode = False
Worksheets("Report").Activate
End Sub
Here is some sample code to do what I think you are asking for. It is not necessarily the shortest or cleverest way to do it, but everything is done step by step so I hope it is clear enough to follow easily.
Option Explicit
Private Sub viewBtn_Click()
'// Set references to worksheets
Dim wsReport As Worksheet: Set wsReport = Sheets("Report")
Dim wsData As Worksheet: Set wsData = Sheets("Data")
'// Get the category to be reported
Dim sCategory As String
sCategory = wsReport.Range("C5")
'// Reference first line of the report, in row 8
Dim rFirstReportLine As Range
Set rFirstReportLine = wsReport.Range("A8:E8")
'// Reference the line of the report to be written
Dim rReportLine As Range
Set rReportLine = rFirstReportLine
'// Clear the old report area
Do While rReportLine.Cells(1, 1) <> ""
rReportLine.Clear
Set rReportLine = rReportLine.Offset(1, 0)
Loop
'// Reset to first line of the report
Set rReportLine = rFirstReportLine
'// Find the first cell, if any, that matches the category
Dim rMatch As Range
Set rMatch = wsData.Range("F:F").Find(sCategory, , , xlWhole)
'// Get reference to top data row of data sheet, just the cols to be copied
Dim rDataRow As Range: Set rDataRow = wsData.Range("A1:E1")
'// check for at least one match
If Not rMatch Is Nothing Then
'// Save the address of the first match for checking end of loop with FindNext
Dim sFirstMatchAddress As String: sFirstMatchAddress = rMatch.Address
Do
'// 1) .. copy data row to the report line
rDataRow.Offset(rMatch.Row - 1).Copy rReportLine
'// 2) .. move the report line down
Set rReportLine = rReportLine.Offset(1, 0)
'// 3) .. find the next match on category
Set rMatch = wsData.Range("F:F").FindNext(rMatch)
'// 4) .. exit when we have looped around
Loop Until rMatch.Address = sFirstMatchAddress
End If
'// Display the number of entries found at the end of the report
With rReportLine
Dim nEntryCount As Integer: nEntryCount = .Row - rFirstReportLine.Row
.Cells(1, 1) = nEntryCount & IIf(nEntryCount = 1, " Entry", " Entries")
.Font.Italic = True
.Font.Color = vbBlue
End With
'// Make sure the report sheet is displayed
wsReport.Activate
End Sub
With this data
Get this result

VBA Comparing two excel rows and deleting similar cells

I am trying to make an excel code that will compare the used range of Rows 1 and 2 of the same worksheet and delete any similar cells and move the remaining (unique values) cells to Row 1 beginning at A1.
eg) If row 1 contains these values (commas inidicate diff cells): a, b, c
and row 2 contains: a, b, c, d, e
I want the code to compare the two rows and end up with row 1 being: d, e (in columns A and B), after the code is complete. Any help would be appreciated.
Im new to VBA so im having trouble on some syntax that I would appreciate if some pros could help me out.
Get the used number of columns for rows 1 and 2 as integers. eg) maxCol1 = 3, maxCol2 = 5
Create a for loop that goes from i = 1 To maxCol2 and compares row 1 to row 2. if they are equal, make them both "", if there is something in row 2 but not in row 1, set that value to cell A1.
basically just need help on setting step 1 up.
With the help of the link posted in the comment, I figured it out! Thanks to those who helped. The code compares row 2 from row 1 and deletes any similar cell values and posts the unique values into row 1 and also into a new worksheet.
Sub CompareAndDelete()
'This code will compare the rows of each sheet and delete any old alerts that have already been emailed out
' it will then call SaveFile IF new alerts have been found
Dim row1() As Variant, row2() As Variant, newRow As Variant
Dim coll As Collection
Dim i As Long
Dim maxCol1 As Integer
Dim maxCol2 As Integer
'Find max number of columns for old and new alert
With ActiveSheet
maxCol1 = .Cells(1, .Columns.Count).End(xlToLeft).Column
maxCol2 = .Cells(2, .Columns.Count).End(xlToLeft).Column
End With
'Redimensionalize arrays
ReDim row1(0 To (maxCol1 - 1))
ReDim row2(0 To (maxCol2 - 1))
'Assign row1/row2 string values into arrays
For r = 0 To (maxCol1 - 1)
row1(r) = Cells(1, r + 1).Value
Next
For s = 0 To (maxCol2 - 1)
row2(s) = Cells(2, s + 1).Value
Next
ReDim newRow(LBound(row1) To Abs(UBound(row2) - UBound(row1)) - 1)
'Create a collection to load all row1/row2 values into
Set coll = New Collection
'Empty Collection for each run through
Set coll = Nothing
'Set collection to New before using
Set coll = New Collection
For i = LBound(row1) To (UBound(row1))
coll.Add row1(i), row1(i)
Next i
For i = LBound(row2) To (UBound(row2))
On Error Resume Next
coll.Add row2(i), row2(i)
If Err.Number <> 0 Then
coll.Remove row2(i)
End If
On Error GoTo 0
Next i
'Copy Row 2 and Paste it to Row 1
ActiveWorkbook.ActiveSheet.Rows(2).Copy
Range("A1").Select
ActiveSheet.Paste
'Now values are stored in collection, delete row 2
'Rows(2).EntireRow.ClearContents
'Paste only the new alerts onto a new worksheet that is designated for new alerts
For i = LBound(newRow) To UBound(newRow)
newRow(i) = coll(i + 1) 'Collections are 1-based
'Debug.Print newRow(i)
ActiveWorkbook.Sheets("Sheet" & index + 4).Select
ActiveWorkbook.Sheets("Sheet" & index + 4).Cells(1, i + 1).Value = newRow(i)
Next i
'if NEW alerts have been found, call SaveFile
If IsEmpty(ActiveWorkbook.Sheets("Sheet" & index + 4).Cells(1, 1)) = False Then
Call SaveFile
End If
End Sub
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Why do my VBA code sometimes work and most of the times it doesn't?

Sub UpdateCSAH()
Dim S As String
Dim R As Long
Dim RR As Long
Dim CC As Long
Dim i As Long
Dim j As Long
Dim csah() As String 'an array that stores the CSAH sites
ReDim csah(1 To 100, 1 To 7)
Dim Ran As Range
Dim Ran1 As Range
Set Ran = Worksheets("Current Sites").Range("A1").CurrentRegion 'Ran is the region that has values
RR = 1 'row number in csah
CC = 1 'column number in csah
'check each value in Ran to see if its Route section has "CSAH"
For Each cell In Ran
R = cell.row
S = CStr(Cells(R, 4).value)
If InStr(S, "CSAH") > 0 Then 'check if "CSAH" is in the Route section
If CC > 7 Then 'reset the column number and go to the next row when reach the end of the column
CC = 1
RR = RR + 1
End If
csah(RR, CC) = cell.value
CC = CC + 1
End If
Next cell
Worksheets("CSAH Sites").Select
Range("A2:G100").Select
Selection.ClearContents
'assign each array values to cells in sheet"CSAH Sites"
i = 1
j = 1
For i = 1 To UBound(csah, 1)
For j = 1 To UBound(csah, 2)
Cells(i + 1, j) = csah(i, j)
Next j
Next i
'format the CSAH Sites values
Set Ran1 = Worksheets("CSAH Sites").Range("A1").CurrentRegion
For Each cell In Ran1
If cell.row = 1 Then
With cell.Font
.Color = -11489280
End With
ElseIf cell.row Mod 2 = 0 Then
With cell.Interior
.Color = 10092441
End With
End If
Next cell
End Sub
I have an Excel worksheet named "Current Sites" that has some data. If the 4th column has the word "CSAH", I want to store the values of that row into an array and assign those values to cells in the worksheet named "CSAH Sites". My code sometimes works (the 1st time you click), and most of times it doesn't work or doesn't work properly.
Please help me out! Thanks A Bunch!!
It looks like you want to check every row of data in the "Current Sites" sheet and if column 4 includes the "CSAH" text, then write the first 7 columns of data for that entry to the "CSAH Sites" sheet and add some colour to the even-numbered rows.
To check every row of data, you can read down just one column and use either the Offset or the Cells method to see the values of neighbouring cells. In your code you were "touching" every cell and each time you were then looking at the value in column 4 and also checking to see if the code had gone past column 7. That slows things down a lot and makes the code hard to understand.
You can also assign the values from a range of cells directly to another range of cells without using variables or an array.
See if this does what you want:
Sub UpdateCSAH()
Dim currentSitesRange As Range
Dim thisSiteRange As Range
Dim outputCell As Range
Dim numRowsOfData As Long
Const NUM_COLUMNS_OF_DATA As Integer = 7
Set currentSitesRange = Worksheets("Current Sites").Range("A1")
numRowsOfData = currentSitesRange.CurrentRegion.Rows.Count
Set currentSitesRange = currentSitesRange.Resize(RowSize:=numRowsOfData) 'currentSitesRange is the region that has values
Worksheets("CSAH Sites").Range("A2:G100").ClearContents
Set outputCell = Worksheets("CSAH Sites").Range("A2")
For Each thisSiteRange In currentSitesRange.Cells
' Look for "CSAH" in the Route section (column D)
If InStr(1, thisSiteRange.Offset(ColumnOffset:=3).Value, "CSAH", vbTextCompare) > 0 Then
' Found "CSAH" so write NUM_COLUMNS_OF_DATA columns of data to CSAH Sites sheet
outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value = thisSiteRange.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value
' Format the even-numbered rows
If outputCell.Row Mod 2 = 0 Then
With outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Interior
.Color = 10092441
End With
End If
Set outputCell = outputCell.Offset(RowOffset:=1)
End If
Next thisSiteRange
End Sub

How to remain selected word form excel, and remove the rest

I need to deal with 14K rows of data within Excel.
I have a list of word need to be remain, and need to delete the rest. Due to large amount of data, it's to hard to find and replace the word one by one. I get some idea about put word list into another excel file and load it to check each column of data file(same as excel).
Is vba my correct approach or can this be solved using Excel out of the box?
This should get you started!
Sub CutWords() 'Cuts specific strings out from a specified sheet.
'---Variables---
Dim pos As Integer
Dim val As String
Dim word As String
Dim source As Worksheet
Dim target As Worksheet
Dim list As Worksheet
Dim startRow As Integer
Dim columns As Integer
Dim i As Long
Dim j As Long
Dim k As Long
Dim l As Long
'---Customize---
Set source = ThisWorkbook.Sheets(1) 'This sheet contains the data
Set target = ThisWorkbook.Sheets(2) 'Trimmed data will end up here
Set list = ThisWorkbook.Sheets(3) 'List of words to look for
startRow = 2 'The first row to be trimmed in data
columns = 2 'The number of columns to be trimmed in data
'---Logic---
Application.ScreenUpdating = False 'Saves us a bit of time
target.Cells.ClearContents 'Clearing the target sheet
i = startRow 'i will act as the "row counter" for our source sheet
l = 1 'l will act as the "row counter" for our target sheet
Do While i <= source.Range("A" & source.Rows.Count).End(xlUp).Row 'Looping until
'we hit the last row with data in "A" column.
j = 1 'j will act as the "column counter" for our source sheet
Do While j <= columns 'Checking all columns
k = 1 'k will act as the "row counter" for our word list sheet
Do While k <= list.Range("A" & list.Rows.Count).End(xlUp).Row 'Looping
'until we hit the last row with data in "A" column - these are the words
word = list.Range("A" & k).Value 'Get the word.
val = source.Cells(i, j).Value 'Get the value to check.
pos = InStr(val, word) 'Check for match, 0 = no match
If pos > 0 Then 'Match found
target.Cells(l, j).Value = val 'The values will be in the same
'position as they were in the source (minus the ignored rows).
'It should be quite simple to remove the empty space if needed.
End If
k = k + 1 'Next word
Loop
j = j + 1 'Next column
Loop
l = l + 1 'Next target row
i = i + 1 'Next source row
Loop
Application.ScreenUpdating = True 'Make sure to restore the value
End Sub
Insert the code in a new code module and it should be ready to go, though I admit that I didn't do too much testing. Certainly not the fastest or fanciest approach - but a simple one.
EDIT: Even partial matches will be seen as matches. Your word in the word list could be "Hello" and a phrase like "Hello World!" would still be considered a match. If you'd like to have exact matches only, you'll need to compare the string directly instead of using InStr.
If val = word Then 'Match found
By default, the first sheet should contain your data, starting from column "A". It may have headers. The second sheet will be where the cut list goes after you run the macro. The third sheet will have the list of words you want to cut. The words must be in column "A".
HTH

Finding the first empty row to paste a row from another sheet

I am working on a macro for copying rows for different locations to sheets specific to the locations from a master sheet.
I have everything working except finding the last row when the cell I am checking contains a '0' and shows as an empty string match. I need to either find a better way to paste to the first empty row, or to find out if the cell being checked is truly empty.
Here is the macro code:
Sub MoveDataToSheets()
'
' MoveDataToSheets Macro
' Macro written 2/25/2011 by Jim Snyder
'
Dim rowCount As Integer, sheetIndex As Integer, LastRow As Integer
Dim ExcelLastCell As Range
' Prevent screen updates from slowing execution
Application.ScreenUpdating = False
rowCount = ActiveCell.CurrentRegion.Rows.Count
' Process each row once copying row to matching location tab
For currentRow = 1 To rowCount
' Determine which sheet the row goes to
Select Case (Cells(currentRow, "B").Value)
Case "ALTAVISTA"
sheetIndex = 2
Case "AN"
sheetIndex = 3
Case "Ballytivnan"
sheetIndex = 4
Case "Casa Grande"
sheetIndex = 5
Case "Columbus - Devices (DE)"
sheetIndex = 6
Case "Columbus - Nutrition"
sheetIndex = 7
Case "Fairfield"
sheetIndex = 8
Case "Granada"
sheetIndex = 9
Case "Guangzhou"
sheetIndex = 10
Case "NOLA"
sheetIndex = 11
Case "Process Research Operations (PRO)"
sheetIndex = 12
Case "Richmond"
sheetIndex = 13
Case "Singapore"
sheetIndex = 14
Case "Sturgis"
sheetIndex = 15
Case "Zwolle"
sheetIndex = 16
Case Else
sheetIndex = 1
End Select
' Only if the row cotains a valid location, copy it to location sheet
If (sheetIndex > 1) Then
Sheets(1).Activate ' Activate the sheet being copied from
ActiveSheet.Rows(currentRow).Copy ' Copy from master sheet
Set sheet = Worksheets(sheetIndex) ' Designate target sheet
Set ExcelLastCell = sheet.Cells.SpecialCells(xlLastCell) ' Find the last used row
LastRow = ExcelLastCell.Row
If (sheet.Rows(LastRow).Cells(LastRow, 5).Value = "") Then
sheet.Paste Destination:=sheet.Cells(LastRow, 1) ' Paste into first row
Else
sheet.Paste Destination:=sheet.Cells(LastRow + 1, 1) ' Paste in first empty row
End If
Sheets(1).Activate ' Activate the sheet being copied from
End If
Next
Application.ScreenUpdating = True
End Sub
Chip Pearson's site has an example of what you need. You can do this on each page prior to paste.
Finding The Last Used Cell In A Range
Assuming your using Excel 2003 and/or you won't have more than 65,536 on any sheet:
LastRow = Sheet.Range("A65536").End(xlUp).Row
For just finding the last cell that has a value in a column:
Dim LastRow As Long
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
You can then use LastRow as a standard row reference. I often use it to find the last row then add 1, which actually gives me the first empty cell in that column. Example:
Cells(LastRow + 1, 6).Value = Cells(10, 3).Value 'Or whatever value you want
The CPearson site linked above by datatoo however does list many more ways to find "last cells" if you are looking for more complicated solutions.
Edit: Just tested to be sure: this does detect 0s or nulls ( ="" ) in the column.