Excel VBA Combine duplicate rows and add quantities - vba

I have data that looks like this:
Col A | Col B | Col C
name 1| Item 1| 3
name 2| Item 3| 1
name 3| Item 2| 2
name 2| Item 3| 6
name 3| Item 2| 4
name 2| Item 3| 3
And I need a line of code to add the last column of quantities for duplicate rows and then delete the duplicate rows. So the above table should look like this:
Col A | Col B | Col C
name 1| Item 1| 3
name 2| Item 3| 10
name 3| Item 2| 6
I have tried multiple ways from other people's questions, but i keep getting "error: 400".
Here's two examples:
For Each a In tm.Range("B2", Cells(Rows.Count, "B").End(xlUp))
For r = 1 To Cells(Rows.Count, "B").End(xlUp).Row - a.Row
If a = a.Offset(r, 0) And a.Offset(0, 1) = a.Offset(r, 1) And a.Offset(0, 2) = a.Offset(r, 2) Then
a.Offset(0, 4) = a.Offset(0, 4) + a.Offset(r, 4)
a.Offset(r, 0).EntireRow.Delete
r = r - 1
End If
Next r
Next a
With Worksheets("Card Test")
With .Range("b2:e2").Resize(.Cells(.Rows.Count, 1).End(xlUp).Row)
.Copy
With .Offset(, .Columns.Count + 1)
.PasteSpecial xlPasteAll ' copy value and formats
.Columns(2).Offset(1).Resize(.Rows.Count - 1, 2).FormulaR1C1 = "=SUMIF(C1,RC1,C[-" & .Columns.Count + 1 & "])"
.Value = .Value
.RemoveDuplicates 1, xlYes
End With
End With
End With
Also I should mention that I have two worksheets and the button using the macro will be on a different sheet than the data. That seems to be causing issues too.

You could use a FOR loop to solve your issue:
Sub RemoveDuplicates()
Dim lastrow As Long
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
For x = lastrow To 1 Step -1
For y = 1 To lastrow
If Cells(x, 1).Value = Cells(y, 1).Value And Cells(x, 2).Value = Cells(y, 2).Value And x > y Then
Cells(y, 3).Value = Cells(x, 3).Value + Cells(y, 3).Value
Rows(x).EntireRow.Delete
Exit For
End If
Next y
Next x
End Sub

Create a code module in your workbook, by default 'Module1'. Paste the following 3 items into that module, with the Enum declaration at the very top. You can change the enumerations, like NumItem = 3 would make the column in which you have the item's name "C" and NumQty automatically 4 ("D") because it follows in the next line. Right now the columns are A, B, and C.
Private Enum Num
NumName = 1 ' Column Names
NumItem
NumQty
NumFirstRow = 2 ' First data row
End Enum
Sub CreateMergedList()
Dim Ws As Worksheet
Dim Comp As String, Comp1 As String
Dim R As Long, Rend As Long, Rsum As Long
Dim Qty As Single
Set Ws = Worksheets("Source")
Ws.Copy Before:=Sheets(1)
With Ws
' There is one caption row which is excluded from sorting
With .UsedRange
.Sort .Columns(NumName), Key2:=.Columns(NumItem), Header:=xlYes
Rend = .Rows.Count
End With
For R = NumFirstRow To Rend - 1
If Comp = vbNullString Then Comp = CompareString(Ws, R)
Comp1 = CompareString(Ws, R + 1)
If StrComp(Comp, Comp1) Then
Comp = vbNullString
Rsum = R + 1
Else
If Rsum = 0 Then Rsum = NumFirstRow
Qty = .Cells(Rsum, NumQty).Value
.Cells(Rsum, NumQty).Value = Qty + .Cells(R + 1, NumQty).Value
.Cells(R + 1, NumName).Value = ""
End If
Next R
For R = Rend To (NumFirstRow - 1) Step -1
If .Cells(R, NumName).Value = "" Then .Rows(R).Delete
Next R
End With
Application.DisplayAlerts = False
Worksheets(1).Delete
Application.DisplayAlerts = True
End Sub
Private Function CompareString(Ws As Worksheet, R As Long) As String
With Ws.Rows(R)
CompareString = .Cells(NumName).Value & "|" & .Cells(NumItem).Value
End With
End Function
At the top of the main procedure, change the name of the worksheet "Source" to whatever is the name of your own worksheet in which you have Names, Items and Quantities.
The code will first make a copy of the worksheet. Then it will sort it by name and item. After that it will combine the quantities and, finally, delete the superfluous rows.
At the end of the code the copy is deleted. If you want to be prompted to permit deletion, add an apostrophe at the start of the line "Application.DisplayAlerts = False" to make that command ineffective.
Call the procedure "CreateMergedList" from the Click event of whatever button you have for that purpose. Have fun!

you could use Dictionary object
Option Explicit
Sub main()
Dim cell As Range, dataRng As Range
Dim key As Variant
With Worksheets("Card Test")
Set dataRng = .Range("A1", .Cells(.Rows.count, "A").End(xlUp))
End With
With CreateObject("Scripting.Dictionary")
For Each cell In dataRng
key = cell.Value & "|" & cell.Offset(, 1).Value
.item(key) = .item(key) + cell.Offset(, 2).Value
Next
dataRng.Resize(, 3).ClearContents
dataRng.Resize(.count) = Application.Transpose(.Keys)
dataRng.Resize(.count).Offset(, 2) = Application.Transpose(.Items)
dataRng.Resize(.count).TextToColumns DataType:=xlDelimited, Other:=True, OtherChar:="|"
End With
End Sub

Related

Excel VBA script to filter and sum

I am using the code below and it works for filtering the unique name and totalting the value field. I recently have a need to expand upon the filtering of unique names to include other columns in the criteria. Please see the example output I am looking for. Any help would be appreciated.
Sub SUM()
Dim i, j, k As Integer
i = 2
j = 2
Range("D1").Value = "NAME"
Range("E1").Value = "VALUE"
'copy the first value of column A to column D
Range("D2").Value = Range("A2").Value
'cycle to read all values of column B and sum it to column E; will run until find a blank cell
While Range("A" & i).Value <> ""
'this check if actual value of column A is equal to before value of column A, if true just add the column B value to E
'else, look for the row in column D where is the same value of column A, if it doesn't exist code create the value
'in column D and E
If Range("A" & i).Value = Range("A" & i - 1).Value Then
Range("E" & j).Value = Range("E" & j).Value + Range("B" & i).Value
Else
flag = 1
While Range("D" & flag).Value <> ""
If Range("A" & i).Value = Range("D" & flag).Value Then
j = flag
Range("E" & j).Value = Range("E" & j).Value + Range("B" & i).Value
flag = Range("D1").End(xlDown).Row
Else
j = 0
End If
flag = flag + 1
Wend
If j = 0 Then
Range("D1").End(xlDown).Offset(1, 0).Value = Range("A" & i).Value
Range("E1").End(xlDown).Offset(1, 0).Value = Range("B" & i).Value
j = Range("E1").End(xlDown).Row
End If
End If
i = i + 1
Wend
MsgBox "End"
End Sub
Currently outputs like this:
Name Value Name Sum
A 1 A 13
A 2 B 7
B 1 C 3
B 3
C 2
A 1
B 2
A 3
B 1
A 2
A 4
C 1
I would like to have it export data like this example:
Name Code Date Value Name Code Date Sum
A 101 3/10/17 1 A 101 3/10/17 9
A 101 3/10/17 2 A 102 3/10/17 4
B 102 3/10/17 1 B 101 3/10/17 3
B 101 3/10/17 3 B 102 3/10/17 2
C 102 3/8/17 2 B 101 3/8/17 2
A 102 3/10/17 1 C 102 3/8/17 2
B 101 3/8/17 2 C 102 3/10/17 1
A 102 3/10/17 3
B 102 3/10/17 1
A 101 3/10/17 2
A 101 3/10/17 4
C 102 3/10/17 1
As long as your columns go A,B,C,D then F,G,H,I then below code should work. Let me know if it works for you.
Sub CountCodes()
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
Dim wbk As Workbook
Dim ws As Worksheet
Dim wsRow As Long, newRow As Long
Dim Names() As String
Dim Found As Boolean
Dim x As Integer, y As Integer, z As Integer, myCount As Integer, mySum As Integer
Dim Cell As Range
Set wbk = ThisWorkbook
Set ws = wbk.Worksheets(1)
ReDim Names(0 To 0) As String
ReDim Codes(0 To 0) As String
ReDim Dates(0 To 0) As String
newRow = 1
With ws
'Find last row of data
wsRow = .Range("A" & .Rows.Count).End(xlUp).Row
'Loop through Column A to fill array
For Each Cell In .Range(.Cells(2, 1), .Cells(wsRow, 1))
'Fill Names array
Found = (IsInArray(Cell.Value2, Names) > -1)
If Found = False Then
Names(UBound(Names)) = Cell.Value2
If Cell.Row <> wsRow Then
ReDim Preserve Names(0 To UBound(Names) + 1) As String
End If
End If
'Fill Codes array
Found = (IsInArray(Cell.Offset(0, 1).Value2, Codes) > -1)
If Found = False Then
Codes(UBound(Codes)) = Cell.Offset(0, 1).Value2
If Cell.Row <> wsRow Then
ReDim Preserve Codes(0 To UBound(Codes) + 1) As String
End If
End If
'Fill Dates array
Found = (IsInArray(Cell.Offset(0, 2).Value2, Codes) > -1)
If Found = False Then
Dates(UBound(Dates)) = Cell.Offset(0, 2).Value
If Cell.Row <> wsRow Then
ReDim Preserve Codes(0 To UBound(Dates) + 1) As String
End If
End If
Next
'Add Autofilter if off
If .AutoFilterMode = False Then
.Range("A1").AutoFilter
End If
For x = LBound(Names) To UBound(Names)
.Range("A1").AutoFilter Field:=1, Criteria1:=Names(x)
For y = LBound(Codes) To UBound(Codes)
.Range("B1").AutoFilter Field:=2, Criteria1:=Codes(y)
For z = LBound(Dates) To UBound(Dates)
.Range("C1").AutoFilter Field:=3, Criteria1:=Dates(z)
For Each Cell In .Range("A1:A" & wsRow).SpecialCells(xlCellTypeVisible)
myCount = myCount + 1
Next
If myCount > 1 Then
For Each Cell In .Range("D2:D" & wsRow).SpecialCells(xlCellTypeVisible)
mySum = mySum + Cell.Value2
Next
'Find last row in new data
newRow = newRow + 1
.Cells(newRow, 6) = Names(x)
.Cells(newRow, 7) = Codes(y)
.Cells(newRow, 8) = Dates(z)
.Cells(newRow, 9) = mySum
End If
myCount = 0
mySum = 0
Next z
Next y
Next x
.ShowAllData
End With
Erase Names
Erase Codes
Erase Dates
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Function IsInArray(stringToBeFound As String, arr As Variant) As Long
'http://stackoverflow.com/questions/10951687/how-to-search-for-string-in-an-array
'Boolean = (IsInArray(StringToFind, ArrayToSearch) > -1)
Dim i As Long
' default return value if value not found in array
IsInArray = -1
For i = LBound(arr) To UBound(arr)
If StrComp(stringToBeFound, arr(i), vbTextCompare) = 0 Then
IsInArray = i
Exit For
End If
Next i
End Function
I used a pivot table in Excel 2016 to generate this view, since you are not opposed to using a Pivot Table. If Excel can do this out of the box, and you are happy with how it looks and behaves, there is no real need for custom VBA in this case.
Just do the following:
Highlight your data, then insert > pivot table
I placed the pivot table in cell F1 of Sheet 1
Add Name, Code, and Date to the Rows of the pivot table
Add Value to the Values of the pivot table. It should default to Sum.
Click on the pivot table, then in the pivot table tools > design tab in the ribbon, go to the "Layout" ribbon group:
On the Report Layout Dropdown:
Click "Show in Tabular Form"
Click "Repeat All Item Labels"
On the Subtotals Dropdown:
Click "Do Not Show Subtotals"
On the Grand totals Dropdown:
Click "off for rows and columns"
You could use Dictionary object:
Option Explicit
Sub ListTotals()
Dim c As Range, dataRng As Range
Dim key As Variant
Set dataRng = Range("A2", Cells(Rows.Count, 1).End(xlUp))
With CreateObject("Scripting.Dictionary")
For Each c In dataRng
key = Join(c.Resize(,3), "|")
.Item(key) = .Item(key) + c.Offset(,4)
Next c
With dataRng.Resize(.Count)
.Offset(,5) = Application.Transpose(.Keys)
.Offset(,8) = Application.Transpose(.Items)
.Offset(,5).TextToColumns DataType:=xlDelimited, Other:=True, OtherChar:="|", FieldInfo:= Array(Array(1, 2), Array(3, 3))
End With
End With
End Sub

VBA- Delete row if value is not a Duplicate and keep all rows with duplicate values

I've been working on a VBA script for a while now that goes through the values of a column and deletes all rows with values appearing only once (pretty much the inverse of deleting duplicates).
Column Headers to make explanation easier
There are numbers in the 'VTR' column that appear more than once. most appear just once.
I'd like the macro to delete all rows where the number in the 'VTR' column appears only once.(in the case of one of these numbers appearing more than once, the difference lies at the 'AFTARTKRZ' column where the value can either be (GAPNK or GAPN2) or RSLNV or RSVNK. (GAPNK or GAPN2 are the same thing)
i.e a row can appear either once with AFTARTKRZ,
(GAPNK or GAPN2)
-OR twice
either (GAPNKorGAPN2), RSLNV
or (GAPNKorGAPN2), RSVNK
OR thrice
(GAPNK or GAPN2), RSLNV, RSVNK.
I'd like to delete all those that appear only once (GAPNKorGAPN2)
Furthermore, I'd like to then add the values of the 'AFTARTKRZ' values of the duplicates to 2 extra columns at the end.
i.e, when a (GAPNK or GAPN2) appears two or threee other times, I'd like to input the 'AFTARTKRZ' column value in the 2 last columns at the end.
Something like this should be the final result
VTR|AFTARTKRZ | Add1 | Add2
11 |GAPNK |RSLNV | RSVNK| - VTR appeared thrice
12 |GAPN2 |RSLNV | | - Appeared twice as (GAPNKorGAPN2), RSLNV
13 |GAPNK |RSVNK | | - Appeared twice as (GAPNKorGAPN2), RSVNK
14 |GAPN2 | |
15 |GAPNK | |
16 |GAPN2 | |
The relevant part begins at '~~~~ Work on A
Sub Test()
Dim wb As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Dim RowsToTestC As Range, Delrange As Range
Dim i As Long, Lrow As Long, Lop As Long
Set ws1 = ThisWorkbook.Worksheets(1)
ThisWorkbook.ActiveSheet.Name = "A"
ws1.Copy ThisWorkbook.Sheets(Sheets.Count)
ThisWorkbook.ActiveSheet.Name = "B"
Set ws2 = ThisWorkbook.Worksheets(2)
ws1.Copy ThisWorkbook.Sheets(Sheets.Count)
ThisWorkbook.ActiveSheet.Name = "C"
Set ws2 = ThisWorkbook.Worksheets(3)
'~~~~ Work on C
Worksheets("C").Activate
With ActiveSheet
ActiveSheet.Range("A:AQ").RemoveDuplicates Columns:=6, Header:=xlNo
End With
Worksheets("C").Activate
Application.ScreenUpdating = False
'~~> Delete all but RSV
For Lrow = Range("D" & Rows.Count).End(xlUp).Row To 2 Step -1
If Range("D" & Lrow).Value = "GAPNK" Or Range("D" & Lrow) = "GAPN2" Then
Rows(Lrow).EntireRow.Delete
End If
Next Lrow
'~~~~ Work on B
Worksheets("B").Activate
With ActiveSheet
ActiveSheet.Range("A:AQ").RemoveDuplicates Columns:=6, Header:=xlNo
End With
Worksheets("B").Activate
Application.ScreenUpdating = False
'~~> Delete all but GAP
For Lrow = Range("D" & Rows.Count).End(xlUp).Row To 2 Step -1
If Range("D" & Lrow).Value = "RSVNK" Or Range("D" & Lrow) = "RSLNV" Then
Rows(Lrow).EntireRow.Delete
End If
Next Lrow
'~~~~ Work on A
Worksheets("A").Activate
Range("AR1").Select
ActiveCell.FormulaR1C1 = "RSVNK"
Range("AS1").Select
ActiveCell.FormulaR1C1 = "RSLNV"
With ws1
'~~> Get the last row which has data in Col A
Lop = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Loop through the rows
For i = 2 To Lop
'~~> For for multiple occurances
If .Cells(i, 6).Value <> "" And .Cells(i, 4).Value <> "" Then
If Application.WorksheetFunction.CountIf(.Columns(6), .Cells(i, 6)) = 1 And _
Application.WorksheetFunction.CountIf(.Columns(4), .Cells(i, 4)) = 1 Then
'~~> Store thee row in a temp range
If Delrange Is Nothing Then
Set Delrange = .Rows(i)
Else
Set Delrange = Union(Delrange, .Rows(i))
End If
End If
End If
Next
End With
End Sub
The logic of your code isn't valid.
The condition If Application.WorksheetFunction.CountIf(.Columns(4), .Cells(i, 4)) = 1 will always be False because this is the column containing your AFTARTKRZ keys. I don't know how many rows of data you have, but even in the 10 row sample you gave us the result is always greater than 1.
I do think you're making this unnecessarily complicated. Aren't you just trying to populate two lists: one of GAPs and the other of RSVs? You then want to create a third list where the GAP entries have corresponding RSV entries?
That could be done in a couple of short routines. You could do away with all of your sheet copying and row deleting and simply write your three lists directly to your sheets.
The code below shows you how this could be done. I've created 4 sheets, so you may need to add another to your Workbook: Sheet1 is your summary list (A), Sheet2 is your GAP list (B), Sheet3 is your RSV list (C), and Sheet4 holds the raw data.
Hopefully this code can get you started:
Option Explicit
Public Sub RunMe()
Const AFTARTKRZ_COL As Long = 4
Const VTR_COL As Long = 6
Dim data As Variant
Dim GAPs As Collection
Dim RSVs As Collection
Dim multis As Collection
Dim vtrKey As String
Dim multi(0 To 1) As Long
Dim i As Long, r As Long, c As Long
Dim v As Variant
Dim countRSV As Long
Dim output() As Variant
'Name your sheets.
'If you have fewer than 3 sheets or
'sheets already names A, B, C then this
'will throw an error.
Sheet1.Name = "A"
Sheet2.Name = "B"
Sheet3.Name = "C"
'Initialise the 3 collections
Set GAPs = New Collection
Set RSVs = New Collection
Set multis = New Collection
'Read the data - I've put my dummy data on Sheet4
data = Sheet4.UsedRange.Value2
'Iterate rows and place row in relevant collection
For r = 1 To UBound(data, 1)
vtrKey = CStr(data(r, VTR_COL))
On Error Resume Next 'removes duplicate entries
Select Case data(r, AFTARTKRZ_COL)
Case Is = "GAPNK", "GAPN2": GAPs.Add r, vtrKey
Case Is = "RSLNV": RSVs.Add r, vtrKey & "|RSLNV"
Case Is = "RSVNK": RSVs.Add r, vtrKey & "|RSVNK"
End Select
On Error GoTo 0
Next
'Check if each GAP also has RSVs
For Each v In GAPs
vtrKey = CStr(data(v, VTR_COL))
countRSV = 0
If Exists(RSVs, vtrKey & "|RSLNV") Then countRSV = countRSV + 1
If Exists(RSVs, vtrKey & "|RSVNK") Then countRSV = countRSV + 2
If countRSV > 0 Then
multi(0) = CLng(v)
multi(1) = countRSV
multis.Add multi, vtrKey
End If
Next
'Write your outputs
'Sheet C
ReDim output(1 To RSVs.Count + 1, 1 To UBound(data, 2))
For c = 1 To UBound(data, 2)
output(1, c) = data(1, c)
Next
i = 2
For Each v In RSVs
For c = 1 To UBound(data, 2)
output(i, c) = data(v, c)
Next
i = i + 1
Next
With Sheet3
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
'Sheet B
ReDim output(1 To GAPs.Count + 1, 1 To UBound(data, 2))
For c = 1 To UBound(data, 2)
output(1, c) = data(1, c)
Next
i = 2
For Each v In GAPs
For c = 1 To UBound(data, 2)
output(i, c) = data(v, c)
Next
i = i + 1
Next
With Sheet2
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
'Sheet A
ReDim output(1 To multis.Count + 1, 1 To 5)
output(1, 1) = "VTR"
output(1, 2) = "AFTARTKRZ"
output(1, 3) = "Add1"
output(1, 4) = "Add2"
i = 2
For Each v In multis
r = v(0)
output(i, 1) = data(r, VTR_COL)
output(i, 2) = data(r, AFTARTKRZ_COL)
output(i, 2) = data(r, AFTARTKRZ_COL)
Select Case v(1)
Case 1
output(i, 3) = "RSLNV"
output(i, 5) = "Appeared twice as (GAPNK or GAPN2), RSLNV"
Case 2
output(i, 3) = "RSVNK"
output(i, 5) = "Appeared twice as (GAPNK or GAPN2), RSVNK"
Case 3
output(i, 3) = "RSLNV"
output(i, 4) = "RSVNK"
output(i, 5) = "VTR appeared thrice"
End Select
i = i + 1
Next
With Sheet1
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
End Sub
Private Function Exists(col As Collection, key As String) As Boolean
Dim v As Variant
On Error Resume Next
v = col(key)
On Error GoTo 0
Exists = Not IsEmpty(v)
End Function

VBA, Advanced Filter & Remove Duplicates

I have a list full of different paths in col A.
I have a list of details in B and C.
How can I on a new sheet: 1) pull each unique path, 2) for each path compile the values from B * C and remove the duplicates. 3) repeat the next path after these are done in the latest row.
I do have a faulty macro, but for the sake of being concise and accurate I will not post. Unless someone wants to read it, please reques
Any help would be greatly appreciated.
Here is what I have(I understand its long, i'll take try to clean it up abit) :
Sub FileDetail()
'Does not fill down, go to bottom to unleased fill down
'Skips unreadable files
'This Macro retrieves data from files picked. The data is based on header. Data is also filtered for unique values.
'You must make sure headers are in the first row and delimted.
Dim wb As Workbook, fileNames As Object, errCheck As Boolean
Dim ws As Worksheet, wks As Worksheet, wksSummary As Worksheet
Dim y As Range, intRow As Long, i As Integer
Dim r As Range, lr As Long, myrg As Range, z As Range
Dim boolWritten As Boolean, lngNextRow As Long
Dim intColNode As Integer, intColScenario As Integer
Dim intColNext As Integer, lngStartRow As Long
Dim lngLastNode As Long, lngLastScen As Long
Dim intColinstrument As Integer, lngLastinstrument As Long
'Skipped worksheet for file names
Dim wksSkipped As Worksheet
Set wksSkipped = ThisWorkbook.Worksheets("Skipped")
' Turn off screen updating and automatic calculation
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' Create a new worksheet, if required
On Error Resume Next
Set wksSummary = ActiveWorkbook.Worksheets("Unique data")
On Error GoTo 0
If wksSummary Is Nothing Then
Set wksSummary = ActiveWorkbook.Worksheets.Add(After:=ActiveWorkbook.Worksheets(ActiveWorkbook.Worksheets.Count))
wksSummary.Name = "Unique data"
End If
' Set the initial output range, and assign column headers
With wksSummary
Set y = .Cells(.Rows.Count, 3).End(xlUp).Offset(1, 0)
Set r = y.Offset(0, 1)
Set z = y.Offset(0, -2)
lngStartRow = y.Row
.Range("A1:E1").Value = Array("File Name", "Sheet Name", "Node", "Book", "Instrument")
End With
'get user input for files to search
Set fileNames = CreateObject("Scripting.Dictionary")
errCheck = UserInput.FileDialogDictionary(fileNames)
If errCheck Then
Exit Sub
End If
'''
For Each Key In fileNames 'loop through the dictionary
On Error Resume Next
Set wb = Workbooks.Open(fileNames(Key))
If Err.Number <> 0 Then
Set wb = Nothing ' or set a boolean error flag
End If
On Error GoTo 0 ' or your custom error handler
If wb Is Nothing Then
wksSkipped.Cells(wksSkipped.Cells(wksSkipped.Rows.Count, "A").End(xlUp).Row + 1, 1) = fileNames(Key)
Else
Debug.Print "Successfully loaded " & fileNames(Key)
wb.Application.Visible = False 'make it not visible
' more working with wb
' Check each sheet in turn
For Each ws In ActiveWorkbook.Worksheets
With ws
' Only action the sheet if it's not the 'Unique data' sheet
If .Name <> wksSummary.Name Then
boolWritten = False
''''''''''''''''''testing additional column..trouble here
' Find the Anchor Date
intColScenario = 0
On Error Resume Next
intColScenario = WorksheetFunction.Match("instrument.instrumentType", .Rows(1), 0)
On Error GoTo 0
If intColScenario > 0 Then
' Only action if there is data in column E
If Application.WorksheetFunction.CountA(.Columns(intColScenario)) > 1 Then
lr = .Cells(.Rows.Count, intColScenario).End(xlUp).Row
' Copy unique values from the formula column to the 'Unique data' sheet, and write sheet & file details
.Range(.Cells(1, intColScenario), .Cells(lr, intColScenario)).AdvancedFilter xlFilterCopy, , r, True
r.Offset(0, -2).Value = ws.Name
r.Offset(0, -3).Value = ws.Parent.Name
' Delete the column header copied to the list
r.Delete Shift:=xlUp
boolWritten = True
End If
End If
''''''''''''''''''''''''''''''''''''below is working'''''''''''''''''''''''
' Find the Desk column
intColNode = 0
On Error Resume Next
intColNode = WorksheetFunction.Match("book.reportingLine.pathName", .Rows(1), 0)
On Error GoTo 0
If intColNode > 0 Then
' Only action if there is data in column A
If Application.WorksheetFunction.CountA(.Columns(intColNode)) > 1 Then
lr = .Cells(.Rows.Count, intColNode).End(xlUp).Row
' Copy unique values from column A to the 'Unique data' sheet, and write sheet & file details (if not already written)
.Range(.Cells(1, intColNode), .Cells(lr, intColNode)).AdvancedFilter xlFilterCopy, , y, True
If Not boolWritten Then
y.Offset(0, -1).Value = ws.Name
y.Offset(0, -2).Value = ws.Parent.Name
End If
' Delete the column header copied to the list
y.Delete Shift:=xlUp
End If
End If
' Find the Intrument
intColinstrument = 0
On Error Resume Next
intColinstrument = WorksheetFunction.Match("instrument.instrumentType", .Rows(1), 0)
On Error GoTo 0
If intColinstrument > 0 Then
' Only action if there is data in column A
If Application.WorksheetFunction.CountA(.Columns(intColinstrument)) > 1 Then
lr = .Cells(.Rows.Count, intColinstrument).End(xlUp).Row
' Copy unique values from column A to the 'Unique data' sheet, and write sheet & file details (if not already written)
.Range(.Cells(1, intColinstrument), .Cells(lr, intColinstrument)).AdvancedFilter xlFilterCopy, , z, True
If Not boolWritten Then
z.Offset(0, -3).Value = ws.Name
z.Offset(0, -4).Value = ws.Parent.Name
End If
' Delete the column header copied to the list
z.Delete Shift:=xlUp
End If
End If
' Identify the next row, based on the most rows used in columns C & D
lngLastNode = wksSummary.Cells(wksSummary.Rows.Count, 3).End(xlUp).Row
lngLastScen = wksSummary.Cells(wksSummary.Rows.Count, 4).End(xlUp).Row
lngLastinstrument = wksSummary.Cells(wksSummary.Rows.Count, 5).End(xlUp).Row
lngNextRow = WorksheetFunction.Max(lngLastNode, lngLastScen) + 1
If (lngNextRow - lngStartRow) > 1 Then
' Fill down the workbook and sheet names
z.Resize(lngNextRow - lngStartRow, 2).FillDown
''''''''Optional if you want headers to be filled down.
'If (lngNextRow - lngLastNode) > 1 Then
' Fill down the last Node value
'wksSummary.Range(wksSummary.Cells(lngLastNode, 3), wksSummary.Cells(lngNextRow - 1, 3)).FillDown
'End If
'If (lngNextRow - lngLastScen) > 1 Then
' Fill down the last Scenario value
'wksSummary.Range(wksSummary.Cells(lngLastScen, 4), wksSummary.Cells(lngNextRow - 1, 4)).FillDown
'End If
End If
Set y = wksSummary.Cells(lngNextRow, 3)
Set r = y.Offset(0, 1)
Set z = y.Offset(0, -2)
lngStartRow = y.Row
End If
End With
Next ws
wb.Close savechanges:=False 'close the workbook do not save
Set wb = Nothing 'release the object
End If
Next 'End of the fileNames loop
Set fileNames = Nothing
' Autofit column widths of the report
wksSummary.Range("A1:E1").EntireColumn.AutoFit
' Reset system settings
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.Visible = True
End With
End Sub
So this code gets file name, sheet name, and columns I specify's data.
1) However I am having trouble adding additional columns to this. (I currently get 2 extracted columns), and also
2) I am having trouble putting it in a format where it columns are based upon each other. ex It will give me unique value for each path, but then not the unique values per sport.
Edit to include data ( I also would like to include a 4th and 5th column but kept it to 3 for simplicity):
+-------------------------------+------------+--------------+
| path | sport | Teams |
+-------------------------------+------------+--------------+
| stack/over/flow/larrybird | basketball | celtics |
+-------------------------------+------------+--------------+
| stack/over/flow/michaeljordan | basketball | bulls |
+-------------------------------+------------+--------------+
| stack/over/flow/tigerwoods | golf | pga |
+-------------------------------+------------+--------------+
| stack/over/flow/josebautista | baseball | bluejays |
+-------------------------------+------------+--------------+
| stack/over/flow/jordanspeith | golf | pga |
+-------------------------------+------------+--------------+
| stack/over/flow/kevinlove | basketball | timberwolves |
+-------------------------------+------------+--------------+
| stack/over/flow/lebronjames | basketball | cavs |
+-------------------------------+------------+--------------+
| stack/over/flow/stephencurry | basketball | warriors |
+-------------------------------+------------+--------------+
| stack/over/flow/larrybird | baseball | redsox |
+-------------------------------+------------+--------------+
| stack/over/flow/michaeljordan | baseball | whitesox |
+-------------------------------+------------+--------------+
| stack/over/flow/michaeljordan | chess | knight |
+-------------------------------+------------+--------------+
| stack/over/flow/michaeljordan | basketball | hornets |
+-------------------------------+------------+--------------+
| stack/over/flow/kevinlove | basketball | cavs |
+-------------------------------+------------+--------------+
| stack/over/flow/tigerwoods | golf | pga |
+-------------------------------+------------+--------------+
And expected result (I included fill down in this)
+-------------------------------+------------+--------------+
| path | sport | teams |
+-------------------------------+------------+--------------+
| stack/over/flow/larrybird | basketball | celtics |
+-------------------------------+------------+--------------+
| | baseball | red sox |
+-------------------------------+------------+--------------+
| stack/over/flow/tigerwoods | golf | pga |
+-------------------------------+------------+--------------+
| stack/over/flow/michaeljordan | basketball | bulls |
+-------------------------------+------------+--------------+
| | | hornets |
+-------------------------------+------------+--------------+
| | baseball | whitesox |
+-------------------------------+------------+--------------+
| | chess | knight |
+-------------------------------+------------+--------------+
| stack/over/flow/kevinlove | basketball | timberwolves |
+-------------------------------+------------+--------------+
| | | cavs |
+-------------------------------+------------+--------------+
| stack/over/flow/josebautista | baseball | bluejays |
+-------------------------------+------------+--------------+
It seems to be an issue for the 3rd (4th and 5th also) columns with getting unique values.
The easy way would be, to copy the whole range, sort it and then run some calculations:
Sub Macro1()
Application.ScreenUpdating = False
Dim str As String
With Sheet1
str = .Range(.Cells(.Rows.Count, 1).End(xlUp), .Cells(1, 3)).Address
.Range(str).Copy Sheet2.Cells(1, 1)
End With
Application.CutCopyMode = False
With Sheet2
.Activate
Dim str2 As String
str2 = .Range(str).Offset(1).Resize(.Range(str).Rows.Count - 1).Address
.Range(str2).Value = Evaluate("if(" & str2 & "="""",-1E+99," & str2 & ")")
.Sort.SortFields.Clear
.Sort.SortFields.Add .Range(str).Offset(1).Resize(, 1), 0, 1, , 0
.Sort.SortFields.Add .Range(str).Offset(1, 1).Resize(, 1), 0, 1, , 0
.Sort.SortFields.Add .Range(str).Offset(1, 2).Resize(, 1), 0, 1, , 0
.Sort.SetRange .Range(str).Offset(1)
.Sort.Header = 2
.Sort.Apply
.Range(str2).Value = Evaluate("if(" & str2 & "=-1E+99,""""," & str2 & ")")
Dim val As Variant, i As Long, rng2 As Range
val = .Range(str).Value
Set rng2 = .Range(str).Offset(.Range(str).Rows.Count).Resize(1)
For i = 3 To UBound(val)
If val(i - 1, 1) = val(i, 1) And val(i - 1, 2) = val(i, 2) And val(i - 1, 3) = val(i, 3) Then Set rng2 = Union(rng2, .Range(str).Rows(i))
Next
i = .Range(str).Rows.Count - rng2.Rows.Count
rng2.EntireRow.Delete xlShiftUp
With .Range(str).Offset(1).Resize(i - 1, 1)
.Value = Evaluate("if(" & .Address & "=" & .Offset(-1).Address & ",""""," & .Address & ")")
With .Offset(, 1)
.Value = Evaluate("if((" & .Address & "=" & .Offset(-1).Address & ")*(" & .Offset(, -1).Address & "=""""),""""," & .Address & ")")
End With
End With
End With
End Sub
Done by phone, may contain errors!
Changed a lot now, please copy the whole code and test it again.
EDIT
Ok, a completely different solution. Should be fast, but may not be very clear in the way it works :P
Sub Macro2()
Dim inVal As Variant, outVal() As Variant, orderArr() As Variant
Dim startRng As Range
Dim i As Long, j As Long, k As Long, iCount As Long
Set startRng = Sheet1.Range("A2:C2") 'upmost row-range of the range to be copied (exclude headers!)
With startRng.Parent
inVal = .Range(startRng, .Cells(.Rows.Count, startRng.Column).End(xlUp)).Value
End With
ReDim orderArr(1 To UBound(inVal))
For i = 1 To UBound(inVal)
iCount = 1
For j = 1 To UBound(inVal)
For k = 1 To UBound(inVal, 2)
If StrComp(inVal(i, k), inVal(j, k), 1) = 1 Then iCount = iCount + 1
If StrComp(inVal(i, k), inVal(j, k), 1) <> 0 Then Exit For
Next
Next
orderArr(i) = iCount
Next
k = 1
ReDim outVal(1 To UBound(inVal, 2), 1 To UBound(inVal))
For i = 0 To Application.Max(orderArr)
If IsNumeric(Application.Match(i, orderArr, 0)) Then
iCount = Application.Match(i, orderArr, 0)
For j = 1 To UBound(inVal, 2)
outVal(j, k) = inVal(iCount, j)
Next
k = k + 1
End If
Next
ReDim Preserve outVal(1 To UBound(inVal, 2), 1 To k - 1)
For i = 1 To UBound(outVal)
For j = UBound(outVal, 2) To 2 Step -1
If outVal(i, j - 1) = outVal(i, j) Then
If i = 1 Then
outVal(i, j) = ""
ElseIf outVal(i - 1, j) = "" Then
outVal(i, j) = ""
End If
End If
Next
Next
'upper left cell of the output-range
Sheet2.Range("A2").Resize(UBound(outVal, 2), UBound(outVal)).Value = Application.Transpose(outVal)
End Sub
Feel free to set the starting range (Sheet1.Range("A2:C2")) to Selection and then simply select the range and start the macro. Does work with any size (while VERY big ranges may freeze excel for some time).
As always: if you have any questions, just ask :)
One efficient solution would be to:
Fisrt copy the values with Range.Copy
Then sort the rows with Range.Sort
Then remove the duplicated rows with Range.RemoveDuplicates
Finally remove the duplicated branches with a loop
This procedure removes the duplicated rows and format as a tree view:
Sub RemoveDuplicates()
Dim rgSource As Range, rgTarget As Range, data(), r&, c&
' define the source, the target and the number of columns
Const columnCount = 3
Set rgSource = Range("Sheet1!A3")
Set rgTarget = Range("Sheet1!F3")
' copy the values to the targeted range
Set rgSource = rgSource.Resize(rgSource.End(xlDown).Row - rgSource.Row + 1, columnCount)
Set rgTarget = rgTarget.Resize(rgSource.Rows.Count, columnCount)
rgSource.Copy rgTarget
' sort the rows on each column
For c = columnCount To 1 Step -1
rgTarget.Sort rgTarget.Columns(c)
Next
' build the array of columns for RemoveDuplicates
Dim rdColumns(0 To columnCount - 1)
For c = 1 To columnCount: rdColumns(c - 1) = c: Next
' remove the duplicated rows
rgTarget.RemoveDuplicates rdColumns
Set rgTarget = rgTarget.Resize(rgTarget.End(xlDown).Row - rgTarget.Row + 1, columnCount)
' format as a tree view by removing the duplicated branches
data = rgTarget.Value
For r = UBound(data) To 2 Step -1
For c = 1 To columnCount - 1
If data(r, c) <> data(r - 1, c) Then Exit For
data(r, c) = Empty
Next
Next
rgTarget.Value = data
End Sub
If you don't mind having the results sorted, instead of in the original order, the following code will do that. It should "auto-adapt" to any number of columns.
(If you need the results in the original order, I would use Collections or Dictionaries and User Defined Object approach)
Your data should start in A1 (with Row 1 being the column labels) and you can see where, in the code, you define the Worksheets for your Source and Results data.
Since most of the "work" is done within a VBA array, rather than on the worksheet, it should run quite rapidly.
Option Explicit
Sub SortFormat()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vRes As Variant
Dim R As Range, C As Range
Dim V As Variant
Dim I As Long, J As Long
'Set source and results worksheets, ranges
Set wsSrc = Worksheets("Sheet1")
Set wsRes = Worksheets("Sheet2")
wsRes.Cells.Clear
Set rRes = wsRes.Cells(1, 1)
Application.ScreenUpdating = False
'Copy source data to results worksheet
Dim LastRow As Long, LastCol As Long
With wsSrc
LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set R = .Range(.Cells(1, 1), .Cells(LastRow, LastCol))
R.Copy rRes
Application.CutCopyMode = False
End With
'Go to Results sheet
With wsRes
.Select
.UsedRange.EntireColumn.AutoFit
End With
rRes.Select
'Sort the data
With wsRes.Sort.SortFields
.Clear
Set R = wsRes.UsedRange.Columns
For Each C In R
.Add Key:=C, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
Next C
End With
With wsRes.Sort
.SetRange R
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.Apply
End With
'Remove any completely duplicated rows
'Create array of columns
ReDim V(0 To R.Columns.Count - 1)
For I = 0 To UBound(V)
V(I) = I + 1
Next I
R.RemoveDuplicates Columns:=(V), Header:=xlYes
'Remove Duplicated items in each row
'Work in VBA array for more speed
vRes = R
For I = UBound(vRes, 1) To 3 Step -1
If vRes(I, 1) = vRes(I - 1, 1) Then vRes(I, 1) = ""
For J = 2 To UBound(vRes, 2)
If vRes(I, J) = vRes(I - 1, J) And _
vRes(I, J - 1) = "" Then vRes(I, J) = ""
Next J
Next I
R = vRes
Application.ScreenUpdating = True
End Sub
If you want to make a unique list of anything, use a Dictionary object.
Make sure to add a reference to the Scripting Runtime controls! Just some quick and dirty code (as in completely untested) based on your sample data:
Sub GetUniques()
Dim oDic as New Dictionary
Dim r as Integer
Dim strKey as String
Dim varValue(2) as Variant
'Get a unique list of Column A values
r = 3 'Your data starts on row 3
Do While Cells(r,1).value <> "" 'Run until the first blank line
strKey = Cells(r,1).value
varValue(0) = Cells(r,2).Value
varValue(1) = Cells(r,3).Value
If Not oDic.Exists(strKey) Then
oDic.Add strKey, varValue
End If
r = r +1
Loop
'Now display your list of unique values
Dim K as Variant
Dim myArray as Variant
r = 3 'We'll start on row 3 again but move over to column I (9)
For Each K in oDic.Keys
Cells(r,9).Value = K
myArray = oDic.Item(K)
Cells(r,10).Value = myArray(0)
Cells(r,11).Value = myArray(1)
r = r + 1
Next K
End Sub

VBA - Concatenate columns on the first column

I want to concatenate columns but in the first column in VBA, like that :
A | B | C |
sentence1 | sentence2 | sentence3 |
sentence4 | sentence5 | sentence6 |
sentence7 | sentence8 | sentence9 |
->
A | B | C
sentence1 sentence2 sentence3 | nothing | nothing
sentence4 sentence5 sentence6 | nothing | nothing
sentence7 sentence8 sentence9 | nothing | nothing
How can I do ?
Thanks in advance !
Dim tempval As String
Dim row As Integer, col As Integer
Application.ScreenUpdating = False
'loop through rows
For row = 1 To 3 Step 1
'clear temp string
tempval = ""
'loop through columns
For col = 1 To 3 Step 1
'save columnvalues in temp
tempval = tempval & Cells(row, col).Value
'delete cell value
Cells(row, col).Value = ""
Next col
'paste saved string into first cell
Cells(row, 1).Value = tempval
Next row
Application.ScreenUpdating = True
the following does what you ask and is a little more generic in that:
it takes into account all cells of column "A" with some text in it
it extends the range whose content is to be concatenated to all consecutive non blank cells in the given row
in other words this approach doesn't suffer neither from any possible variations of columns number to consider (they can be 3, as per your example, or more or less) nor from the condition of having all rows having the same number of cells filled
Option Explicit
Sub main()
Dim cell As Range
With Worksheets("mySheet").Columns("A").SpecialCells(xlCellTypeConstants, xlTextValues)
For Each cell In .Cells
cell.Value = Join(Application.Transpose(Application.Transpose(Range(cell, cell.End(xlToRight)))))
Range(cell.Offset(, 1), cell.End(xlToRight)).Clear
Next cell
.WrapText = False
.EntireColumn.AutoFit
End With
End Sub
Dim r As Range
For Each r In Sheet1.UsedRange
r(1, 1).Value = r(1, 1).Value & " " & r(1, 2).Value & " " & r(1, 3).Value
r(1, 2).Value = ""
r(1, 3).Value = ""
Next r

how to add multiple items to a key in vba

i'm new to vba and i was trying to make a program to do add multiple items to a key.
Eg:
Table
Name Date Time
XYZ 20 4
ABC 21 5
XYZ 22 6
and then if the names are repeated, then column values to the previous one...like:
Name Date Time Date Time
XYZ 20 4 22 6
ABC 21 5
i've done sorting and adding the sum of duplicate values for a single item value but i'm finding it hard to do this for multivalued item. So plz do help out!!
Thank you!!
Sub t()
Application.DisplayAlerts = False
Dim Tempsheet As Worksheet
Dim c As Range
Set Tempsheet = ThisWorkbook.Sheets.Add
With ThisWorkbook.Sheets("sheet1")
rng = .UsedRange.Address
.UsedRange.Sort key1:=.Range("b1"), order1:=xlAscending, key2:=.Range("c1"), order2:=xlAscending, Header:=xlYes
.UsedRange.Columns(1).Copy
Tempsheet.Range("a1").PasteSpecial (xlPasteValues)
Tempsheet.Columns(1).RemoveDuplicates Columns:=1, Header:=xlYes
For Each cell In Tempsheet.Range("a1:a" & Tempsheet.Range("a" & Rows.Count).End(xlUp).Row).Cells
i = i + 1
If i <> 1 Then
With .Range(rng)
Set c = .Find(what:=cell.Value, after:=.Range("a1"), LookIn:=xlValues, lookat:=xlWhole)
firstAddress = c.Address
If Not c Is Nothing Then
Do
j = j + 1
If j <> 1 Then
k = k + 1
.Range(c.Offset(0, 1), c.Offset(0, 2)).Copy
.Range(firstAddress).Offset(0, (k * 2) + 1).PasteSpecial (xlPasteValues)
.Range(firstAddress).Offset(-1, (k * 2) + 1) = "Date"
.Range(firstAddress).Offset(-1, (k * 2) + 2) = "Time"
c.EntireRow.ClearContents
End If
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
End If
j = 0
Next cell
.UsedRange.Columns(1).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End With
Tempsheet.Delete
End Sub
You are writing an elaborate program for something that is a basic part of the Excel functionality. It is called Pivot tables.
https://en.wikipedia.org/wiki/Pivot_table
http://www.excel-easy.com/data-analysis/pivot-tables.html