I would like to copy column from excel "Book1" to another excel "Book2" by determined its cell value.
Let's say the header columns in Book1 are Name, Age, Gender, Address and Group. I want to copy the column "Name", "Age" and "Group" to "Book2". Below coding is what I've done to pull column data by cell coordinate.
Is it possible if I can pull the column from its header value?
Sub copyColumns()
Dim lr As Long, r As Long
Set src = ThisWorkbook.Worksheets("Sheet1")
Set tgt = Workbooks("Book2.xlsx").Worksheets("Sheet1")
lr = src.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lr
src.Cells(i, 1).copy
r = tgt.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
src.Paste Destination:=tgt.Cells(r, 1)
src.Cells(i, 2).copy
src.Paste Destination:=tgt.Cells(r, 2)
src.Cells(i, 5).copy
src.Paste Destination:=tgt.Cells(r, 3)
Next i
End Sub
There is number of solutions to this problem. For example, declare variables:
Dim rw As Range
Dim cl As Range
Dim sFields As String
Dim V
Dim j As Integer
Chose your column names for copying:
sFields = "Name|Age|Group"
V = Split(sFields, "|")
And then, inside your For i loop, make two another loops:
For Each cl In Intersect(src.Rows(i), src.UsedRange)
For j = 0 To UBound(V)
If cl.EntireColumn.Range("A1") = V(j) Then
tgt.Cells(i, j).Value = src.Cells(i, j).Value
End If
Next j
Next cl
Intersect(src.Rows(i), src.UsedRange) will chose all cells in row in the range that is actually used (that is, it will not loop through all 16 384 columns. All columns names are in one variable sFields, you can easily modify it. It is string separated with pipes, you will probably never use pipes (|) in your field names, so it is safe.
A few tips along the way:
Always declare every variable you want to use and user Option Explicit at the beginning of your module.
Dont copy every single cell, copy a range
See the updated code below:
Sub copyColumns2()
Dim src As Worksheet, tgt As Worksheet
Dim lr As Long, r As Long, I As Long, Col As Long
Dim ColsToCopy, ColToCopy, counter As Integer
ColsToCopy = Array("Name", "Age", "Group")
Set src = ThisWorkbook.Worksheets("Sheet1")
Set tgt = Workbooks("Book2.xlsx").Worksheets("Sheet1")
lr = src.Cells(Rows.Count, 1).End(xlUp).Row
For Col = 1 To 20
For Each ColToCopy In ColsToCopy
If src.Cells(2, Col).Value = ColToCopy Then
counter = counter + 1
src.Range(src.Cells(2, Col), src.Cells(lr, Col)).Copy tgt.Cells(2, counter)
Exit For
End If
Next ColToCopy
Next Col
End Sub
If you would like to copy the column by column header, you can use this function to get the letter of your header:
Function Letter(oSheet As Worksheet, name As String, Optional num As Integer)
If num = 0 Then num = 1
Letter = Application.Match(name, oSheet.Rows(num), 0)
Letter = Split(Cells(, Letter).Address, "$")(1)
End Function
Implementation:
Sub copycolum()
Dim ws As Worksheet, ws2 As Worksheet
Set ws = Sheets("Sheet1"): Set ws2 = Sheets("Sheet2"):
ws.Range(Letter(ws, "Gender") & "2:" & Letter(ws, "Gender") & ws.Range(Letter(ws, "Gender") & "1000000").End(xlUp).Row).Copy Destination:=ws2.Range(Letter(ws2, "Gender") & "2")
End Sub
Note that the function is set to default at row 1, in other words your header is in column 1, if you like you can change this to whatever row your data is in.
Related
Hope you you can help me here. I have a repetitive task every week, which I could do the same way every single time through Excel formulas, but I am looking for a more automated way of going about this.
What I want to achieve is to set-up a dynamic range that will look for multiple key words such as in this case "OA" & "SNC" and if it matches it will return the value in the column G & H respectively. At the same time it has to skip blank rows. What is the best way to go about this?
I figured it shouldn't be too hard, but I cannot figure it out.
As per image above, I want to consolidate the charges per category (OA & SNC) in the designated columns ("G" & "H") on row level.
My approach to the task
Procedure finds data range, loops through it's values, adding unique values to the dictionary with sum for specific row and then loads all these values along with sums per row.
Option Explicit
Sub CountStuff()
Dim wb As Workbook, ws As Worksheet
Dim lColumn As Long, lRow As Long, lColTotal As Long
Dim i As Long, j As Long
Dim rngData As Range, iCell As Range
Dim dictVal As Object
Dim vArr(), vArrSub(), vArrEmpt()
'Your workbook
Set wb = ThisWorkbook
'Set wb = Workbooks("Workbook1")
'Your worksheet
Set ws = ActiveSheet
'Set ws = wb.Worksheets("Sheet1")
'Number of the first data range column
lColumn = ws.Rows(1).Find("1", , xlValues, xlWhole).Column
'Number of the last row of data range
lRow = ws.Cells(ws.Rows.Count, lColumn).End(xlUp).Row
'Total number of data range columns
lColTotal = ws.Cells(1, lColumn).End(xlToRight).Column - lColumn + 1
'Data range itself
Set rngData = ws.Cells(1, lColumn).Resize(lRow, lColTotal)
'Creating a dictionary
Set dictVal = CreateObject("Scripting.Dictionary")
'Data values -> array
vArr = rngData.Offset(1, 0).Resize(rngData.Rows.Count - 1, _
rngData.Columns.Count).Value
'Empty array
ReDim vArrEmpt(1 To UBound(vArr, 1))
'Loop through all values
For i = LBound(vArr, 1) To UBound(vArr, 1)
For j = LBound(vArr, 2) To UBound(vArr, 2)
'Value is not numeric and is not in dictionary
If Not IsNumeric(vArr(i, j)) And _
Not dictVal.Exists(vArr(i, j)) Then
'Add value to dictionary
dictVal.Add vArr(i, j), vArrEmpt
vArrSub = dictVal(vArr(i, j))
vArrSub(i) = vArr(i, j - 1)
dictVal(vArr(i, j)) = vArrSub
'Value is not numeric but already exists
ElseIf dictVal.Exists(vArr(i, j)) Then
vArrSub = dictVal(vArr(i, j))
vArrSub(i) = vArrSub(i) + vArr(i, j - 1)
dictVal(vArr(i, j)) = vArrSub
End If
Next j
Next i
'Define new range for results
Set rngData = ws.Cells(1, lColumn + lColTotal - 1). _
Offset(0, 2).Resize(1, dictVal.Count)
'Load results
rngData.Value = dictVal.Keys
For Each iCell In rngData.Cells
iCell.Offset(1, 0).Resize(lRow - 1).Value _
= Application.Transpose(dictVal(iCell.Value))
Next
End Sub
I've used a simple custom function, possibly overkill as this could be done with worksheet formulae, but given that your ranges can vary in either direction...
Function altsum(r As Range, v As Variant) As Variant
Dim c As Long
For c = 2 To r.Columns.Count Step 2
If r.Cells(c) = v Then altsum = altsum + r.Cells(c - 1)
Next c
If altsum = 0 Then altsum = vbNullString
End Function
Example below, copy and formula in F2 across and down (or apply it one go with another bit of code).
I have 2 sheets. Sheet1 has 2 rows: column names and values.
Sheet 2 is a master sheet with all the possible column names in. I need to copy the values from sheet 1 into their appropriate column.
I think i can do this via a match function, and so far i have this:
Sub dynamic_paste()
Dim Columnname As String
Dim inputvalue As String
Dim starter As Integer
Dim i As Integer
starter = 0
For i = 1 To 4
'replace 4 with rangeused.rows.count?
Sheets("sheet1").Select
Range("a1").Select
ActiveCell.Offset(0, starter).Select
Columnname = ActiveCell
'sets columnname variable
ActiveCell.Offset(1, 0).Select
inputvalue = ActiveCell
'sets inputname variable
Sheets("sheet2").Select
'**Cells(0, WorksheetFunction.Match(Columnname, Rows(1), 0)).Select**
Range("a1").Offset(1, starter).Value = inputvalue
'inputs variable in the next cell along
starter = starter + 1
Next
End Sub
I need to find out how to use my columnname variable as the matching value, and then offset down to the first row that is empty - then change the value of that cell to the variable called inputvalue.
For extra points: I need to make sure the code doesnt break if they dont find a matching value, and if possible put any values that dont match into the end of the row?
What about this:
Dim LR As Long, X As Long, LC As Long, COL As Long
Dim RNG As Range, CL As Range
Option Explicit
Sub Test()
LR = Sheets(2).Cells.SpecialCells(xlCellTypeLastCell).Row 'Get last used row in your sheet
LC = Sheets(2).Cells(1, Sheets(2).Columns.Count).End(xlToLeft).Column 'Get last used column in your sheet
Set RNG = Sheets(2).Range(Sheets(2).Cells(1, 1), Sheets(2).Cells(1, LC))
'Loop through all the columns on your sheet with values
For X = 1 To Sheets(1).Cells(1, Sheets(1).Columns.Count).End(xlToLeft).Column
Set CL = RNG.Find(Sheets(1).Cells(1, X).Value, lookat:=xlWhole)
If Not CL Is Nothing Then
COL = CL.Column
Sheets(2).Cells(LR + 1, COL).Value = Sheets(1).Cells(2, X).Value 'Get the value on LR offset by 1
Else
Sheets(2).Cells(1, Sheets(2).Cells(1, Sheets(2).Columns.Count).End(xlToLeft).Column).Value = Sheets(1).Cells(1, X).Value
Sheets(2).Cells(LR + 1, Sheets(2).Cells(1, Sheets(2).Columns.Count).End(xlToLeft).Column).Value = Sheets(1).Cells(2, X).Value
End If
Next X
End Sub
This way you will avoid using select. Which is very recommandable!
This is Sheet1:
This is Sheet2:
This is the code:
Option Explicit
Sub DynamicPaste()
Dim col As Long
Dim wks1 As Worksheet: Set wks1 = Worksheets(1)
Dim wks2 As Worksheet: Set wks2 = Worksheets(2)
For col = 1 To 3
Dim currentRow As Long
currentRow = WorksheetFunction.Match(wks2.Cells(1, col), wks1.Columns(1))
wks2.Cells(2, col) = wks1.Cells(currentRow, 2)
Next col
End Sub
This is Sheet2 after the code:
This is a must-read - How to avoid using Select in Excel VBA
I'm not sure where I'm going wrong. I'm trying to compare values within a column ("B") to a cell referenced to ("A1"). If the values in Column "B" equal "A1" I want it to count up. When it gets to the end of Column "B" I'm trying to get it to loop back and compare values in column "B" with "A2", etc. For example:
So Far I've written two different codes one with a nested do while loop and a nested do while if loop but i cant get them to loop through the whole column
Sub CountDb()
Dim i As Long
Dim iRow As Long
Dim initial As Long
i = 1
iRow = 1
initial = 1
Do While Cells(iRow, "A").Value <> "" 'initial loop, whilst there are values in cell "A" continue the loop
Do While Cells(i, "B").Value = Cells(iRow, "A").Value 'nested while loop, comparing the first B1 and cell A1.
If True Then Cells(i, "C") = initial 'if they A1 and B1 are equal, print 1 in column C
initial = initial + 1 'and move on comparing A1 with B2
If False Then
i = i + 1 'if not satisfied, move on to cell B2 etc.
Loop
iRow = iRow + 1 'when you get to the end of column B, start again and compare values with A2 and B
Loop
End Sub
Sub CountDb()
Dim i As Long
Dim iRow As Long
Dim initial As Long
'same comments as above, just different methodology
i = 1
iRow = 1
initial = 1
Do While Cells(iRow, "A").Value <> ""
If Cells(i, "B").Value = Cells(iRow, "A").Value Then
Cells(i, "C") = initial
Else
initial = initial + 1
i = i + 1
End If
iRow = iRow + 1
Loop
End Sub
Any help would be appreciated. Thanks!
*EDIT - fixed up column references
**EDIT - applied comments to code
Try this instead:
Option Explicit
Sub test()
Dim sht As Worksheet
Dim lastrow As Long, i as integer, j as integer, initial as integer
Set sht = Workbooks("Book1").Worksheets("Sheet1") 'Don't forget to change this
lastrow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
For i = 1 To lastrow
initial = 1
lastrow = sht.Cells(sht.Rows.Count, "B").End(xlUp).Row
For j = 1 To lastrow
If Workbooks("Book1").Worksheets("Sheet1").Range("A" & i).Value = Workbooks("Book1").Worksheets("Sheet1").Range("B" & j).Value Then
Workbooks("Book1").Worksheets("Sheet1").Range("C" & j).Value = initial
initial = initial + 1
End If
Next j
Next i
End Sub
I prefer using For loops as opposed to Whiles, just because I can see the ranges being looped through more easily. Here we use nested For loops, the first to loop through column A, the second to loop through column B. If our value in column A equals our value in column B, we place the initial number in column C using our variable from the nested loop.
Notice how to make this work, we re-initialize our lastrow variable to make the ranges for our loops.
It is useful to use countif.
Sub test()
Dim rngOrg As Range, rngDB As Range
Dim Wf As WorksheetFunction
Dim vR() As Variant
Dim i As Long, n As Long
Set Wf = WorksheetFunction
Set rngOrg = Range("a1", Range("a" & Rows.Count).End(xlUp))
Set rngDB = Range("b1", Range("b" & Rows.Count).End(xlUp))
n = rngDB.Rows.Count
ReDim vR(1 To n, 1 To 1)
For Each Rng In rngDB
i = i + 1
If Wf.CountIf(rngOrg, Rng) Then
vR(i, 1) = Wf.CountIf(Range("b1", Rng), Rng)
End If
Next Rng
Range("c1").Resize(n) = vR
End Sub
Here is another method, this time using Find. This is probably quicker than the looping method since it leverages the in-built find function to skip to the next match.
I've commented the code below for clarity, but basically we loop through values in column A (using a For loop because they're less prone to disguised infinite looping than While) and look for them in column B.
Note: This looks a bit longer, but that's mainly because (a) I've added lots of comments and (b) I've used a With statement to ensure the ranges are fully qualified.
Sub countdb()
Dim c As Range, fnd As Range, listrng As Range, cnt As Long, addr As String
' Use with so that our ranges are fully qualified
With ThisWorkbook.Sheets("Sheet1")
' Define the range to look up in (column B in this case)
Set listrng = .Range("B1", .Range("B1").End(xlDown))
' Loop over values in the index range (column
For Each c In .Range("A1", .Range("A1").End(xlDown))
cnt = 0
' Try and find the c value
Set fnd = listrng.Find(what:=c.Value, lookat:=xlWhole, LookIn:=xlValues, after:=listrng.Cells(listrng.Cells.Count))
If Not fnd Is Nothing Then
' Store the address of the first find so we can stop when we find it again!
addr = fnd.Address
' Loop over all other matches in the range. By using a "Do ... Loop While"
' style loop, we ensure that the loop is run at least once!
Do
' Increase count and assign value to next column
cnt = cnt + 1
fnd.Offset(0, 1).Value = cnt
' Find next match after current
Set fnd = listrng.Find(what:=c.Value, lookat:=xlWhole, LookIn:=xlValues, after:=fnd)
Loop While fnd.Address <> addr
End If
Next c
End With
End Sub
The trick is in making the declarations transparent. After that the programming is very easy.
Sub CountMatches()
Dim Rng As Range ' "count" range (= column "B")
Dim Itm As String ' item from the "items' column (= "A")
Dim Rla As Long, Rlb As Long ' last row in columns A and B
Dim Ra As Long, Rb As Long ' row counters
Dim Counter As Long ' count matches
With ActiveSheet
' look for the last used rows
Rla = .Cells(.Rows.Count, "A").End(xlUp).Row
Rlb = .Cells(.Rows.Count, "B").End(xlUp).Row
' start looking for matches from row 2
Set Rng = .Range(.Cells(2, "B"), .Cells(Rlb, "B"))
' start looping in row 2
For Ra = 2 To Rla
Itm = .Cells(Ra, "A").Value
If Len(Trim(Itm)) Then ' skip if blank
' start comparing from row 2
For Rb = 2 To Rlb
' compare not case sensitive
If StrComp(.Cells(Rb, "B").Value, Itm, vbTextCompare) = 0 Then
Counter = Counter + 1
End If
Next Rb
.Cells(Ra, "C").Value = Counter
Counter = 0
End If
Next Ra
End With
End Sub
Now the question is whether the transparency that workred for me appears transparent to you. I hope it does. :-)
This should be significantly faster.
Sub CountMatches_2()
Dim Rng As Range ' "count" range (= column "B")
Dim Itm As String ' item from the "items' column (= "A")
Dim Rla As Long, Rlb As Long ' last row in columns A and B
Dim Ra As Long, Rb As Long ' row counters
With ActiveSheet
' look for the last used rows
Rla = .Cells(.Rows.Count, "A").End(xlUp).Row
Rlb = .Cells(.Rows.Count, "B").End(xlUp).Row
' start looking for matches from row 2
Set Rng = .Range(.Cells(2, "B"), .Cells(Rlb, "B"))
' start looping in row 2
For Ra = 2 To Rla
Itm = .Cells(Ra, "A").Value
If Len(Trim(Itm)) Then ' skip if blank
.Cells(Ra, "C").Value = Application.CountIf(Rng, Itm)
End If
Next Ra
End With
End Sub
This code presumes that each item in column A is unique. If it is not duplicates will be created which, however, it would be easy to eliminate either before or after they are created.
I am currently working on a macro and I had it working 100% like I wanted, but when I went to move the control group to a different sheet, I've started getting all sorts of problems. Here is the code:
Sub Duplicate_Count()
'Diclare Variables
Dim LastRow As Long
Dim value1 As String
Dim value2 As String
Dim counter As Long
Dim startRange As Long
Dim endRange As Long
Dim inColumn As String
Dim outColumn As String
Dim color As Long
counter = 0
Dim sht As Worksheet
Dim controlSht As Worksheet
Set sht = Worksheets("Sheet1")
Set controlSht = Worksheets("Duplicate Check")
'Find the last used row in column L
LastRow = sht.Cells(Rows.Count, "L").End(xlUp).Row
'set default ranges
startRange = 2
endRange = LastRow - 1
inColumn = "L"
outColumn = "N"
'check for user inputs
If controlSht.Cells(8, "B") <> "" Then
startRange = controlSht.Cells(8, "B")
End If
If controlSht.Cells(8, "C") <> "" Then
endRange = controlSht.Cells(8, "C")
End If
If controlSht.Cells(11, "C") <> "" Then
Column = controlSht.Cells(11, "C")
End If
If controlSht.Cells(14, "C") <> "" Then
Column = controlSht.Cells(14, "C")
End If
color = controlSht.Cells(17, "C").Interior.color
'Search down row for duplicates
Dim i As Long
For i = startRange To endRange
'Sets value1 and value2 to be compared
value1 = sht.Cells(i, inColumn).Value
value2 = sht.Cells(i + 1, inColumn).Value
'If values are not diferent then counter will not increment
counter = 1
Do While value1 = value2
sht.Cells(i, inColumn).Interior.color = color
sht.Cells(i + counter, inColumn).Interior.color = color
counter = counter + 1
value2 = sht.Cells(i + counter, inColumn).Value
Loop
'Ouput the number of duplicates on last duplicates row
If counter <> 1 Then
sht.Cells(i + counter - 1, outColumn) = counter
i = i + counter - 1
End If
Next i
End Sub
This is my first program so I apologize for all the mistakes. This code does exactly what I want except for finding the last row if there is no user input. It always says the last row is 1, when it should be 110460. I'm not sure if it's grabbing from the wrong sheet or if there is an error in my logic.
This should be easy to fix by just Activating the sheet first. I can't recall the exact syntax but since you tagged this macros just record a macro, then select a sheet and click on it somewhere. Then open up the macro it will say something like Sheets("sheet name".Activate. Or Sheets("sheet name").Select. Repeat that for each worksheet you want to run the macro on. To clarify, each time your macro finds the last row on 1 sheet, then you Activate or Select the next worksheet and find the last row again. Suppose this is being called in a loop through list of worksheet names.
I changed the "L" to an 11, and it all seems to work now. Why it wants it this way i have no clue, but it works.
I always do it like this.
Sub FindingLastRow()
'PURPOSE: Different ways to find the last row number of a range
'SOURCE: www.TheSpreadsheetGuru.com
Dim sht As Worksheet
Dim LastRow As Long
Set sht = ThisWorkbook.Worksheets("Sheet1")
'Ctrl + Shift + End
LastRow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
'Using UsedRange
sht.UsedRange 'Refresh UsedRange
LastRow = sht.UsedRange.Rows(sht.UsedRange.Rows.Count).Row
'Using Table Range
LastRow = sht.ListObjects("Table1").Range.Rows.Count
'Using Named Range
LastRow = sht.Range("MyNamedRange").Rows.Count
'Ctrl + Shift + Down (Range should be first cell in data set)
LastRow = sht.Range("A1").CurrentRegion.Rows.Count
End Sub
https://www.thespreadsheetguru.com/blog/2014/7/7/5-different-ways-to-find-the-last-row-or-last-column-using-vba
I have two for loops in vba that are iterating over column b and checking to see if the first word in the current cell is the same as the first word in any other cell and if so copying them into another column, therefore grouping similar items. But, when I go to copy and paste the matches it finds, it only copy and pastes the matches, not the original cells that it is comparing against. I would like to have the matches and the original cells as well in the grouping but I am unsure where to modify my code so it will do so. I am rather new to vba so any help would be greatly appreciated.
Sub FuzzySearch()
Dim WrdArray1() As String, WrdArray2() As String, i As Long, Count As Long, Rng1 As Range
Dim WS As Worksheet, positionx As Long, positiony As Long
Dim rng2 As Range
Set WS = ThisWorkbook.ActiveSheet
With WS
Set Rng1 = .Range("B2:B" & .Range("B" & .Rows.Count).End(xlUp).Row)
End With
For i = 1 To Rng1.Rows.Count
With Columns("B")
.Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas).Activate
End With
position = 1
For j = 1 To Rng1.Rows.Count
WrdArray1 = Split(ActiveCell.Value, " ")
ActiveCell.Offset(1).Activate
WrdArray2 = Split(ActiveCell.Value, " ")
If UBound(WrdArray2) < 0 Then
End
End If
If WrdArray1(0) = WrdArray2(0) Then
ActiveCell.Copy Destination:=ActiveSheet.Range("C" & position)
position = position + 1
Count = Count + 1
End If
Next j
Next i
End Sub
Given that you are using a mixture of arrays and Ranges it would probably be easier and less confusing to populate one of the arrays with the final output (including the comparator) within a loop and then transfer the array to the worksheet in a single command.
However, perhaps consider the following approach which lets Excel do all the 'heavy lifting'. It's the same number of code lines but I have annotated it for your information. This illustrates the filling of an array in a loop and transferring it to a Range. Change the various variables to suit your situation.
Sub grpAndCount()
Dim ws As Worksheet
Dim strow As Long, endrow As Long, stcol As Long
Dim coloffset As Long, r As Long
Dim newstr As String
Dim drng As Range
Dim strArr() As String
'Data start r/c
strow = 6 'Row 6
stcol = 2 'Col B
'Offset no of Cols from Data to place results
coloffset = 2
Set ws = Sheets("Sheet1")
With ws
'find last data row
endrow = Cells(Rows.Count, stcol).End(xlUp).Row
'for each data row
For r = strow To endrow
'get first word
newstr = Left(.Cells(r, stcol), InStr(.Cells(r, stcol), " ")-1)
'put string into array
ReDim Preserve strArr(r - strow)
strArr(r - strow) = newstr
Next r
'put array to worksheet
Set drng = .Range(.Cells(strow, stcol + coloffset), .Cells(endrow, stcol + coloffset))
drng = Application.Transpose(strArr)
'sort newly copied range
drng.Sort Key1:=.Cells(strow, stcol + coloffset), Order1:=xlAscending, Header:=xlNo
'provide a header row for SubTotal
.Cells(strow - 1, stcol + coloffset) = "Header"
'resize range to include header
drng.Offset(-1, 0).Resize(drng.Rows.Count + 1, 1).Select
'apply Excel SubTotal function
Application.DisplayAlerts = False
Selection.Subtotal GroupBy:=1, Function:=xlCount, Totallist:=Array(1)
Application.DisplayAlerts = True
'remove 'Header' legend
.Cells(strow - 1, stcol + coloffset) = ""
End With
End Sub