I'm having trouble with some VBA for Excel 2010. I have a list of names that have different serial numbers associated with them. The following code will look at a name in column A, look it up in the names dictionary for an array of serial numbers associated with this name, and print out each number in a new column.
Names Dictionary:
Names("Jane B") = [111112, 22222]
Output:
|Joe A | 11111
|Jane B | 111112| 22222 |
|Jim C | 11111 | 121212 | 1122112
Code:
Dim name, counter
For i = 2 To Worksheets("Contacts").UsedRange.Rows.Count
name = Worksheets("Contact").Cells(i, 1)
counter = 0
If names.Exists(name) Then
For Each serial In names(name)
Worksheets("Contact").Cells(i, 2+counter).Value = serial
counter = counter + 1
Next serial
End If
Next i
So far, so good. But the output format isn't good for inputting into Access. Instead, I'd like to have the following format:
|Joe A | 11111
|Jane B | 111112
|Jane B | 22222
|Jim C | 11111
|Jim C | 121212
|Jim C | 1122112
Here's my code:
Dim name, counter
For i = 2 To Worksheets("Contact").UsedRange.Rows.Count
name= Worksheets("Contact").Cells(i, 1)
counter = 0
If names.Exists(name) Then
For Each serial In names(name)
Worksheets("Contact").Cells(i + counter, 2).Value = serial
Worksheets("Contact").Cells(i + counter, 1).Value = name
Worksheets("Contact").Cells(i + counter + 1, 1).EntireRow.Insert
counter = counter + 1
Next serial
End If
Next i
This is where I run into a problem. My output looks like this:
|Joe A | 11111
|Joe A | 1700
|Joe A | 1700
|Joe A | 1700
|Joe A | 1700
|Joe A | 1700
|Joe A | 1700
While the numbers are all made up, the 1700 output is actually what is outputting, although that doesn't relate to any serial number (???).
Can anyone spot what's off in my code?
Thank you all for your time and consideration.
With gratitude,
Zac
Try this: Use a new sheet (example: "NewContactSheet").
Instead of inserting rows to the current contact sheet, which makes you insert a row then scan the next row (the one you just inserted) and insert it again and again.
Then scan the contact sheet one row at a time, and compare to the dictionary exactly as you are. Then, one serial at a time per Name, you add cell 1 and 2 on the new sheet and increment the row.
Without the dictionary to test with, and based on the original post saying "So far so good"...
Sub SerialNameMover()
Dim name As String
Dim counter As Integer
Dim lastContactRow As Integer
Dim newSheet As String
Dim nRow As Integer
Dim i As Integer
newSheet = "NewContactSheet"
nRow = 2
lastContactRow = Worksheets("Contact").UsedRange.Rows.Count
For i = 2 To lastContactRow
name = Sheets("Contact").Cells(i, 1)
If Names.Exists(name) Then
For Each serial In Names(name)
Sheets(newSheet).Cells(nRow, 1) = name
Sheets(newSheet).Cells(nRow, 2) = serial
nRow = nRow + 1
Next serial
End If
Next i
End Sub
Related
Given two columns (A and B), one with a text and one with an integer, such as:
A | B
pen | 3
pen | 5
How could I fill the columns C, D, E [...] with the concatenation of the given string on each row with all integers starting from 1 until the specified number?
The desired output for the given example would be:
A | B | C | D | E | F | G
pen | 3 | pen01 | pen02 | pen03 | |
pen | 5 | pen01 | pen02 | pen03 | pen04 | pen05
With a simple vba sub this can be achieved:
Sub CreateValues
With ActiveSheet
Dim LastRow as Long: LastRow = .Range("A" & .Rows.Count).End(xlup).Row
For i = 1 To LastRow
Max_Num = .Cells(i, 2)
For j = 1 to Max_Num
.Cells(i, j + 2) = .Cells(i, 1) & Format(j, "00")
Next j
Next i
End With
End Sub
If you are looking for a formula solution without needing to resort to VBA, you can use this formula in C1 and drag in both dimensions:
=IF(COLUMNS($C1:C1)<=$B1,CONCATENATE($A1,TEXT(COLUMNS($C1:C1),"00")),"")
I have a large data set which looks like this:
Employee ID |Job| Function| Level|Date of change
1 | x | a | A1 | 01/05/2014
1 | y | a | A1 | 02/04/2015
1 | y | a | A2 | 25/08/2015
1 | z | a | A3 | 27/12/2015
1 | z | c | A3 | 01/03/2016
2 | t | b | B1 | 12/05/2013
2 | v | b | B1 | 13/04/2014
2 | w | b | B3 | 12/01/2016
Each row contains a change in either job, function or level.
I need to create a table which puts together the latest change in level for each employee (so for employee 1, it would be row 4). So far I have used a combination of conditional formatting and pivots but I was wondering if there is a way to do this quicker in VBA.
Thanks!
Without VBA
This assumes that there are genuine dates in column E with format dd/mm/yyyy, In G1 enter the Array Formula:
=MAX(IF(A:A=1,E:E,""))
This gives the latest date for employee 1
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key.
Then in G2 enter:
=SUMPRODUCT(--(A1:A9=1)*(E1:E9=G1)*(ROW(1:9)))
This gives the row number of the record you are interested in.
From there you can use INDEX() to get any information from that row.
NOTE:
The formulas in G1 and G2 can be combined into a single cell if desired.
EDIT#1:
The same set of formulas should work with text values for the employee id as well as numbers:
Not sure this is the best solution, but since this is a one-off exercise and it did the trick I used this:
VBA to find all the rows where there was a change in level, and write a "yes" in column "F" where applicable:
Sub JLChange()
Dim Data As Worksheet
Dim i As Long
Dim lastrow As Long
Set Data = ThisWorkbook.Worksheets("Data")
lastrow = Data.Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lastrow
If Cells(i + 1, 4).Value <> Cells(i, 4).Value And_
Cells(i + 1,1).Value = Cells(i, 1).Value Then
Cells(i + 1, 6).Value = "Yes"
Else
Cells(i + 1, 6).Value = "No"
End If
Next i
End Sub
For all the records in column "F", I used the formulas suggested by Gary's student to get the very last change.
Alternatively, you can copy-paste this database of changes in a new sheet, sort by ID and by date of change from newest to oldest, then use vlookup to get the first entry for each ID.
I'm trying to combine rows in a DataTable based on their shared ID. The table data looks something like this:
Member | ID | Assistant | Content
---------------------------------------------
16 | 1234 | jkaufman | 1/1/2015 - stuff1
16 | 1234 | jkaufman | 1/2/2015 - stuff2
16 | 4321 | mhatfield | 1/3/2015 - stuff3
16 | 4321 | mhatfield | 1/4/2015 - stuff4
16 | 4321 | mhatfield | 1/5/2015 - stuff5
16 | 5678 | psmith | 1/6/2015 - stuff6
I want to combine rows based on matching IDs. There are two steps I could use some clarification on. The first is merging the rows. The second is combining the Content columns so that the contents aren't lost. For the example above, here's what I want:
Member | ID | Assistant | Content
-------------------------------------------------------------------------------------------
16 | 1234 | jkaufman | 1/1/2015 - stuff1 \r\n 1/2/2015 - stuff2
16 | 4321 | mhatfield | 1/3/2015 - stuff3 \r\n 1/4/2015 - stuff4 \r\n 1/5/2015 - stuff5
16 | 5678 | psmith | 1/6/2015 - stuff6
My eventual goal is copy the DataTable to an Excel spreadsheet so I'm not sure sure if the \r\n is the correct newline character but that's the least of my concerns at this point.
Here's my code right now (EDIT: updated to current code):
Dim tmpRow As DataRow
dtFinal = dt.Clone()
Dim i As Integer = 0
While i < dt.Rows.Count
tmpRow = dtFinal.NewRow()
tmpRow.ItemArray = dt.Rows(i).ItemArray.Clone()
Dim j As Integer = i + 1
While j <= dt.Rows.Count
If j = dt.Rows.Count Then 'if we've iterated off the end of the datset
i = j
Exit While
End If
If dt.Rows(i).Item("ID") = dt.Rows(j).Item("ID") Then 'if we've found another entry for this id
'append change to tmpRow
tmpRow.Item("Content") = tmpRow.Item("Content").ToString & Environment.NewLine & dt.Rows(j).Item("Content").ToString
Else 'if we've run out of entries to combine
i = j
Exit While
End If
j += 1
End While
'add our combined row to the final result
dtFinal.ImportRow(tmpRow)
End While
When I export the final table to Excel, the spreadsheet is blank so I'm definitely doing something wrong.
Any help would be fantastic. Thanks!
I see various problems with your approach (with both versions; but the second one seems better). That's why I have preferred to write a whole working code to help transmit my ideas clearly.
Dim dtFinal As DataTable = New DataTable
For Each col As DataColumn In dt.Columns
dtFinal.Columns.Add(col.ColumnName, col.DataType)
Next
Dim oldRow As Integer = -1
Dim row As Integer = -1
While oldRow < dt.Rows.Count - 1
dtFinal.Rows.Add()
row = row + 1
oldRow = oldRow + 1
Dim curID As String = dt.Rows(oldRow)(1).ToString()
Dim lastCol As String = ""
While (oldRow < dt.Rows.Count AndAlso dt.Rows(oldRow)(1).ToString() = curID)
lastCol = lastCol & dt.Rows(oldRow)(3).ToString() & Environment.NewLine
oldRow = oldRow + 1
End While
oldRow = oldRow - 1
For i As Integer = 0 To 2
dtFinal.Rows(row)(i) = dt.Rows(oldRow)(i)
Next
dtFinal.Rows(row)(3) = lastCol
End While
Note that trying to come up with the most "elegant" solution or to maximise the given in-built functionalities might not be the best way to face certain situations. In the problem you propose, for example, I think that it is better going step by step (and reducing code size/improving elegance only after a properly working version is in place). This is the kind of code I have tried to create here: a simple one delivering what is expected (I think that this is the exact functionality you want; in any case, bear in mind that I am including a simplistic code which you are expected to take as a mere help to understand the point).
I find the VB syntax clunky compared to how it would be in C#, but you may prefer this Linq with grouping solution:
Dim merge = (From rw In dt.Rows.OfType(Of DataRow)()
Group rw By
New With {.fld1 = rw(0)}.fld1,
New With {.fld2 = rw(1)}.fld2,
New With {.fld3 = rw(2)}.fld3 Into Group).
Select(Function(x)
Return New With {.Member = x.fld1,
.ID = x.fld2,
.Assistant = x.fld3,
.Content = String.Join("", x.Group.Select(Function(y)
Return String.Join("", y.ItemArray)
End Function))}
End Function)
I'm traiting an 40M Excel file, I'm thinking of doing some deletes.
For example, I have the data in Sheet 1, and the primary key which I want to delete in sheet2.
Sheet 1
Column A | Column B | ...
0047 | a | ...
0048 | b | ...
0051 | c | ...
Sheet 2
Column A
0047
0051
Would you please tell me how do write a VBA script which delete line 0047 and 0051 in Sheet 1?
I'm new to VBA scripting.
try the code below. Loops through the rows until you find the values you are after using the line below you can delete rows:
Rows(i - j).Delete
Complete code:
Sub main()
Dim i As Integer
Dim j As Integer
j = 0
For i = 2 To 1000
If (Cells(i - j, 1) = "0047") Or (Cells(i - j, 1) = "0051") Then
Rows(i - j).Delete
j = j + 1
End If
Next i
End Sub
Simply, the data looks like this:
ID | Value | Test | Score |
1 | 30 | a | b |
1 | 40 | c | d |
2 | 30 | d | a |
2 | 40 | e | c |
... for 130,000 lines. The value is always 30 or 40, i'd like to replace the 40, Test, and Score and put it in the row with 30 to look like this.
ID | Value | Test | Score | | | |
1 | 30 | a | b | 40 | c | d |
2 | 30 | d | a | 40 | e | c |
I can't seem to wrap my head around the offset-pasting of values. I've tried with Select.Copy,etc -and I have read nothing but bad things about going that route due to performance issues. Any help would be appreciative!
Thanks,
Dan
EDIT: Update -- So I've got it to work, but it will only go through 40 rows before I receive an overflow error. I know this is not optimized or at all the best way to do this - but it was the only thing I could do to figure it out. any input GREATLY appreciative.
I found out that there are a few instances where only 1 ID exists, thus I am checking to see if there are two IDs, if so - copy/paste, otherwise continue to the next row.
Sub Macro4()
' Macro4 Macro
Application.ScreenUpdating = False
Range("H6:K6").Select
Application.CutCopyMode = False
Selection.Copy
Range("L5").Select
ActiveSheet.Paste
Dim r As Integer, ID As Integer, validation As Integer
r = 5
While r < 400
Range("V1:X1").Select
Selection.ClearContents
Range("V1").Select
ID = Cells(r, 1).Value
Selection.Value = ID
Range("W1").Select
ActiveCell.FormulaR1C1 = "=countifs(C[-22],RC[-1])"
validation = Selection.Value
If validation > 1 Then
Cells(r + 1, 8).Select
Range(Selection, Cells(r + 1, 11)).Select
Selection.Copy
Cells(r, 12).Select
ActiveSheet.Paste
r = r + 2
End If
If validation = 1 Then
r = r + 1
End If
Wend
Application.ScreenUpdating = True
End Sub
Assuming ID is in A1, formulae should do it:
in E2 40
in F2 =C3
in G2 =D3
Copy down to suit (ensure 40 does not autoincrement), Select F:G, Paste Special, Values... over the top, filter to select 40 in ColumnB and delete selected rows.
I got it to work by changing:
Dim r As Integer, ID As Integer, validation As Integer
to:
Dim r As Integer, ID as Double, validation As Integer
I read somewhere that double holds significantly larger values -- appreciate all the help, regardless!