More effective/elegant way of copying used ranges - excel

I'm trying to copy certain columns from a sheet to another (around 15).
My current method is less than ideal (I think).
Sheets(2).Range("A:A").Value = Sheets(1).Range("C:C").Value
Sheets(2).Range("C:C").Value = Sheets(1).Range("G:G").Value
Sheets(2).Range("D:D").Value = Sheets(1).Range("T:T").Value
It looks repetitive and more importantly it copies the entire column, which slows down the process by loading the empty rows until the end of the sheet as well.
I'm trying to figure out the best possible way to copy the columns just up to the last used column.
This is my current idea, but the empty cells in the second sheet are filled with the not available error value, which defeats the purpose.
lastRow = Sheets(1).Range("C" & Sheets(1).Rows.Count).End(xlUp).Row
Sheets(2).Range("A:A").Value = Sheets(1).Range("C1:C" & lastRow).Value
Any function that I'm probably not aware of? Thank you!
(And yes, this must be done in VBA. Ask my boss 😃.)

As your source range is smaller, you have to adjust the size of your target range.
Sub copyData()
Dim arrColumns(1, 2) As String
'mapping source column : target column
arrColumns(0, 0) = "C": arrColumns(1, 0) = "A"
arrColumns(0, 1) = "G": arrColumns(1, 1) = "C"
arrColumns(0, 2) = "I": arrColumns(1, 2) = "D"
With ThisWorkbook
Dim wsSource As Worksheet: Set wsSource = .Worksheets(1)
Dim wsTarget As Worksheet: Set wsTarget = .Worksheets(2)
End With
Dim lastRow As Long
With wsSource
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
End With
'now we can do the copying
Dim i As Long
For i = 0 To UBound(arrColumns, 2)
wsTarget.Columns(arrColumns(1, i)).Resize(lastRow).Value = wsSource.Columns(arrColumns(0, i)).Resize(lastRow).Value
Next
End Sub
Furthermore I like to have mapping arrays at the beginning of such a sub.
In case your boss wants another column to be copied - or copy one to another target column this is much easier to handle.

To make it simple, maybe this can work for you:
Sub test()
Dim i As Long
Dim SourceArray As Variant
Dim DestinyArray As Variant
Dim lastRow As Long
'all ranges must be same size to work so all of them will use same lastRow
SourceArray = Array("A1:A", "C1:C", "D1:D")
DestinyArray = Array("C1:C", "G1:G", "T1:T")
lastRow = Sheets(1).Range("C" & Sheets(1).Rows.Count).End(xlUp).Row
For i = LBound(SourceArray) To UBound(SourceArray) Step 1
Sheets(2).Range(DestinyArray(i) & lastRow).Value = Sheets(1).Range(SourceArray(i) & lastRow).Value
Next i
Erase SourceArray
Erase DestinyArray
End Sub
Easy to update if you need to copy more columns, but remember this code will copy always same quantity of cells so if you are copying ranges of different size, then it won't work.

Related

Copy Current DataGridView Row Into Excel

I’m trying to copy the current row from the DataGridView into Excel but can’t seem to find the correct syntax for it to work. Hopefully someone can help me out with the below code.
Thanks in advance.
Public Sub CopyRowToExcel()
Dim xlApp As Excel.Application
Dim WB As Excel.Workbook
Dim WS As Excel.Worksheet
Dim LR As Integer
Dim RowNum As Integer
xlApp = GetObject(, Constants.ExcelApp)
WB = xlApp.Workbooks("Products")
WS = WB.Sheets("DataSheet")
LR = WS.Range("A" & WS.Rows.Count).End(Excel.XlDirection.xlUp).Row + 1
RowNum = DataGridView1.CurrentRow.Index
WS.Range("A" & LR & ":L" & LR).Value = DataGridView1.Rows(RowNum).Cells(1 To 12).Value
End Sub
You do not say if the grid has a data source. If you do not have a data source for the grid, then I suggest you use one. It will make things easier in numerous ways, and this is an example.
Assuming the grid has a DataTable as a DataSource, then you should be able to write a whole row to the Excel file similar to what you appear to be asking with…
DataGridView1.Rows(RowNum).Cells(1 To 12).Value
When you set a ranges value in Excel like…
WS.Range("A" & LR & ":L" & LR).Value =
… will work as long as the right-hand side of the “=” is an array of the same dimensions. In this case where the range is a “single” row, then the array needs to be a ONE (1) dimensional array. Then, if the range is larger than the array, it will fill the missing array part(s) with something like… “#N/A” … And if the range is less than the size of the array, then it will ignore the extra values in the array.
If the range dimension is not the same as the array dimension… you will get an error.
Therefore if the grid used a DataTable and you wanted to append the selected row to the end of an existing worksheet, then you should be able to do something like…
Dim excelRange As String = "A" & LR & ":L" & LR
Dim drv As DataRowView = DataGridView1.SelectedRows(0).DataBoundItem
xlWorksheet.Range(excelRange, Type.Missing).Value2 = drv.Row.ItemArray.ToArray()
In my small tests, this worked as described above.

Compare two data ranges and copy entire row into worksheet VBA

i have found many very similar questions in the forum, but somehow nothing fits what i am looking for.
I have two ranges (a & b) which i'd like to compare and if values do not match, i'd like to copy the entire row to a predefined worksheet. The purpose is to find rows / values that have been changed vs. previous edit.
Dim a, b as range
Dim ws1,ws2,ws3 as worksheet
Dim last_row, last_row2 as integer 'assume last_row =15, last_row2=12
Dim i, j, k as integer
last_row=15
last_row2=12
' the orignal range is not massive, but at 500x 6 not small either
Set a=ws1.range("I5:S"& last_row)
Set b=ws2.range("H2:R"& last_row2)
I have seen different approaches when it comes to addressing each item of the range and don't know which would be quickest / best (loop or for each ).
The main if-statement would look something like this:
'assume i, j are the used as counters running across the range
k = 1
If Not a(i).value=b(j).value then
a(i)EntireRow.copy
ws3.row(k).paste
k = k + 1
end if
The solution cannot be formula based, as I need to have ws3 saved after each comparison.
Any help on this is much appreciated. Thanks!
If you have the ability to leverage Excel Spill Ranges, you can achieve what you want without VBA. Here's a web Excel file that shows all rows in first sheet where column A does not equal column b.
=FILTER(Sheet1!A:ZZ,Sheet1!A:A<>Sheet1!B:B)
If VBA is required, this routine should work. It's not optimal for handling values (doesn't use an array), but it gets it done.
Sub listDifferences()
Dim pullWS As Worksheet, pushWS As Worksheet
Set pullWS = Sheets("Sheet1")
Set pushWS = Sheets("Sheet2")
Dim aCell As Range
For Each aCell In Intersect(pullWS.Range("A:A"), pullWS.UsedRange).Cells
If aCell.Value <> aCell.Offset(0, 1).Value Then
Dim lastRow As Long
lastRow = pushWS.Cells(Rows.Count, 1).End(xlUp).Row
pushWS.Rows(lastRow + 1).Value = aCell.EntireRow.Value
End If
Next aCell
End Sub
This is the small for-loop I ended up using.
Thanks for your input!
For i = 1 To rOutput.Cells.Count
If Not rOutput.Cells(i) = rBackUp.Cells(i) Then
' Debug.Print range1.Cells(i)
' Debug.Print range2.Cells(i)
rOutput.Cells(i).EntireRow.Copy wsChangeLog.Rows(k)
k = k + 1
End If
Next i

Copy last row between A and I to row below

I'm trying to look for the last row of data between column A and I and then duplicate the value to the row below which is empty.
Every time I run it, Excel crashes
Sub insert_row()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
LastRow = LastRow
Dim lastrow_start As String
Dim lastrow_end As String
lastrow_start = "A" & LastRow
lastrow_end = "I" & LastRow
Dim lastrowregion As String
lastrowregion = lastrow_start & ":" & lastrow_end
Dim lastrowrange As Range
Set lastrowrange = Range(lastrowregion)
Dim rng As Range
Set rng = Range(lastrow_start)
Do While (rng.Value <> "")
rng.Offset(1).insert
lastrowrange.Copy rng.Offset(1)
Set lastrowrange = rng.Offset(2)
Loop
End Sub
Is it just copying too much and causing a crash? It's only nine columns and they're all text apart from one cell which is a shape (button).
You are trying to set a String to a range object. To get the range use:
Set rng = Range(lastrowregion)
The Range you are getting is A2:I2. So your Do While will error because rng.Value is actually returning an Array. You could either loop through either the Range or the Array at that point if you intended on it being multiple cells.
If the goal is simply to copy the last row of data down one row then this method can be much simpler. You can simply set the Offset to equal the value of the last row. Since they are the same size it will just work.
To show this I used CurrentRegion but you could also do it with your A2:I2 Range.
Public Sub copyLastRowDown()
Dim region As Range
Set region = ThisWorkbook.Worksheets("Sheet1").Range("A1").CurrentRegion
With region.Rows(region.Rows.Count)
.Offset(1).Value = .Value
End With
End Sub
Additional Notes
Use Option Explicit to ensure all variables are explicitly declared.
Declare and assign variables next to where they are going to be used, but place them in a reasonable place.
Do not use underscore case as this has special meaning with events and interfaces.

Copy/Paste dynamic range

Starting from Sheet "DATA" range B4:Hx, where x is my last row taking by a row count. I need to copy this range and paste it as values on sheet "bat" starting at A1.
Going forward I need to offset columns in 6. So my second copy will be I4:Ox and so one copying appending into bat sheet.
I know where I must stop and I'm informing it using the Funds value.
The first error I'm having is when I try set Column2 = Range("H" & bottomD) value that is giving me "overflow".
And sure I don't know yet if my For loop would work.
Sub Copy_bat()
Dim bottomD As Integer
Dim Column1 As Integer
Dim Column2 As Integer
Dim i As Integer
Dim Funds As Integer
Funds = Sheets("bat").Range("u3").Value
Sheets("DATA").Activate
bottomD = Range("A" & Rows.Count).End(xlUp).Row
Column1 = Range("B4")
Column2 = Range("H" & bottomD)
For i = 1 To Funds
Range(Column1 & ":" & Column2).Copy
Sheets("Data").Cells(Rows.Count, "A").End(xlUp)(2).PasteSpecial Paste:=xlPasteValues, SkipBlanks:=True, Transpose:=False
Column1 = Colum1.Range.Offset(ColumnOffset:=6)
Column2 = Colum2.Range.Offset(ColumnOffset:=6)
Next i
End Sub
Always use Option Explicit at the beginning of every module to prevent from typos. Always! You had typos at the bottom - Colum1 and Colum2.
Avoid Activate and Select (you had Sheets("DATA").Activate) - better performance, smaller error chance. Instead, you should always explicitly tell VBA which sheet you are referring to.
While pasting values you can simply do something like Range2.value = Range1.value. No need to .Copy and then .Paste.
I did my best to understand what you need. From my understanding you did not use Range data type, while you needed that. This caused you errors.
Option Explicit
Sub Copy_bat()
Dim bottomD As Integer
Dim i As Integer
Dim Funds As Integer
Dim rngArea As Range
Funds = Sheets("bat").Range("u3").Value
With Sheets("Data")
bottomD = .Range("A" & .Rows.Count).End(xlUp).Row
Set rngArea = Range(.Range("B4"), .Range("H" & bottomD))
End With
For i = 1 To Funds
Sheets("bat").Cells(Rows.Count, "A").End(xlUp)(2).Resize(rngArea.Rows.Count, rngArea.Columns.Count).Value = _
rngArea.Value
Set rngArea = rngArea.Offset(, 7)
Next
End Sub
I made one rngArea variable of type Range instead of 2 variables (Column1 and Column2). This code takes info from "Data" sheet and puts that to "bat" sheet. Then offsets to right by 7(!) columns in "Data" sheet and puts data in "bat" sheet below the data that was put previously.

Excel VBA, faster, cleaner way to find matching values/index match and return value from another column?

The code I've written below to replace some index match formulas in a sheet. It seems to work well enough, but I think the loop is a bit clumsy and may be prone to errors. Does anyone have any recommended improvements?
Sub match_SIC_code_sheet_loop()
'sic code needs to match value in column j or a in sic code sheet, '
'if not available = met10 works, but probably needs a bit more
'debugging to make it robust.
Dim ws As Integer
Dim lastrow As Long
Dim lastrow_sic As Long
Dim output_wb As Workbook
Dim SIC_sheet As Worksheet
Dim Demand_CAT As String
Dim sic_DMA As String
Dim i As Integer
Dim row As Integer
Dim WS_count As Long
Dim x As String
Dim y As String
Set output_wb = Workbooks("DMA_customers_SICTEST.xlsx") 'use thisworkbook instead
Set SIC_sheet = Workbooks("DMA_metered_tool_v12_SICTEST.xlsm").Sheets("SIC codes")
With SIC_sheet 'count the number of SIC codes to search through
lastrow_sic = .Range("j" & .Rows.Count).End(xlUp).row
End With
With output_wb 'count the no. of sheets in the generated customer workbook
WS_count = output_wb.Worksheets.Count
End With
With output_wb
For ws = 1 To WS_count 'loop through each sheet in the customer workbook
With output_wb.Sheets(ws)
y = output_wb.Sheets(ws).Name
lastrow = .Range("a" & .Rows.Count).End(xlUp).row ' number of rows in the
'current customer sheet
For i = 2 To lastrow 'data starts in row 2; sic code in column 9
sic_DMA = .Cells(i, 9).Text 'the lookup value
With SIC_sheet
'SIC codes start in row 2, if the sic code matches,
'the correct demand category is appointed, if the sic code does not
'match, then MET_10 is given as the default value.
For row = 2 To lastrow_sic
x = .Cells(row, 3).Text
If x = sic_DMA Then
Demand_CAT = .Cells(row, 10).Text
Exit For
Else
Demand_CAT = "MET_10"
End If
Next row
output_wb.Sheets(ws).Cells(i, 23).Value = Demand_CAT
End With
Next i
End With
Next ws
End With
output_wb.Save
End Sub
Thanks
For starters you could break that long procedure into a few smaller methods. For example you could have a ProcessSheet procedure into which you pass each sheet under :
For ws = 1 To WS_count 'loop through each sheet in the customer workbook
That would definitely help readability etc. If you're still not satisfied then continue breaking the loop into smaller logical procedures. Just don't go too crazy.
Apart from that some error checking and value validation would go a long way in a deeply nested loop. For example ensure that various calculated variables such as 'lastrow' are correct or within a valid threshold etc.
Finally instead of hardcoded values sprinkled through your long loop like magically camoflauged debug-from-hell-where's-waldo fairies; prefer instead a few meaningfully named Const variable alternatives i.e.
Private Const SIC_START_ROW = 2

Resources