VBA - sort the data from excel - excel

I'm having an case what i don't know how to deal with that, can someone suggest or give me some idea to solve it ?
Summary: I have an Excel file with 3 columns (Name, user , password), this is an example. As you can see in column A , there are 2 cell with name jack , 1 is his user ID (row 2) , and the other is his password. I wish they can stored in the same row , sth like that cell A2 is jack , cell B2 is his user name (jck), and C2 is his pwd (123).
my code as below
Sub test()
Dim last_row As Long
Dim sh As Worksheet
Dim sh2 As Worksheet
Dim wb As Workbook
Set wb = ThisWorkbook
Set sh = wb.Sheets(1)
Set sh2 = wb.Sheets(2)
Dim rng As Range
last_row = sh.Range("A" & Rows.Count).End(xlUp).Row
last_row2 = sh2.Range("A" & Rows.Count).End(xlUp).Row
MsgBox last_row
For i = 2 To last_row - 1
For j = i + 1 To last_row
If sh.Range("A" & i).Value = sh.Range("A" & j).Value Then
If IsEmpty(Range("B" & i)) Then
sh2.Range("A" & i & ":C" & i).Value = sh.Range("A" & i & ":C" & i).Value
sh2.Range("B" & i).Value = sh.Range("B" & j).Value
End If
If IsEmpty(Range("C" & i)) Then
sh2.Range("A" & i & ":C" & i).Value = sh.Range("A" & i & ":C" & i).Value
sh2.Range("C" & i).Value = sh.Range("C" & j).Value
End If
End If
Next j
Next i
End Sub
Could you please help look ? Any assist will be appreciated.

Your main issue is that you use the same row counter for Sheet2 as you do for Sheet1. Since you will end up with half the number of rows with data in Sheet2, that will result in gaps in Sheet2. You will need a seperate row counter for Sheet2
Another issue is you have several unqualified Range references, so whether this works or not will depend on which sheet is active.
A third issue is you haven't declared all your variables.
You code, refactored
Sub test()
Dim last_row As Long
Dim last_row2 As Long
Dim sh As Worksheet
Dim sh2 As Worksheet
Dim wb As Workbook
Dim rng As Range
Dim i As Long, j As Long
Dim rw2 As Long
Set wb = ThisWorkbook
Set sh = wb.Sheets(1)
Set sh2 = wb.Sheets(2)
last_row = sh.Range("A" & sh.Rows.Count).End(xlUp).Row
last_row2 = sh2.Range("A" & sh2.Rows.Count).End(xlUp).Row
'MsgBox last_row
rw2 = 1
For i = 2 To last_row - 1
For j = i + 1 To last_row
If sh.Range("A" & i).Value = sh.Range("A" & j).Value Then
If IsEmpty(sh.Range("B" & i)) Or IsEmpty(sh.Range("C" & i)) Then
rw2 = rw2 + 1
sh2.Range("A" & rw2 & ":C" & rw2).Value = sh.Range("A" & i & ":C" & i).Value
If IsEmpty(sh.Range("B" & i)) Then
sh2.Range("B" & rw2).Value = sh.Range("B" & j).Value
ElseIf IsEmpty(sh.Range("C" & i)) Then
sh2.Range("C" & rw2).Value = sh.Range("C" & j).Value
End If
End If
End If
Next j
Next i
End Sub

Related

How to loop through all filter options in VBA?

I'd like to use a loop to go through each of the criteria in a filter, then calculate and output averages. My code below calculates the averages and outputs them on the next empty line of the second sheet, but unfortunately, this only works for the first filter selection. After each new iteration of the loop, nothing happens. Can anyone help?
source data
output
Option Explicit
Sub avg()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim cell As Range
Dim lastrow As Long
Dim RowCount As Long
Dim rng1 As Range
Dim nextrow As Long
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet2")
lastrow = ws1.Cells(Rows.Count, 1).End(xlUp).Row
RowCount = ws2.Cells(Rows.Count, 1).End(xlUp).Row
Set rng1 = ws1.Range("G2:G" & lastrow)
For Each cell In rng1.SpecialCells(xlCellTypeVisible)
With ws1.AutoFilter.Range
ws2.Range("A" & RowCount + 1) = ws1.Range("B" & .Offset(1, 0).SpecialCells(xlCellTypeVisible)(1).Row).Value2
ws2.Range("B" & RowCount + 1) = ws1.Range("F" & .Offset(1, 0).SpecialCells(xlCellTypeVisible)(1).Row).Value2
End With
ws2.Range("C" & RowCount + 1) = Application.WorksheetFunction.Average(ws1.Range("Y2:Y" & lastrow & ",Z2:Z" & lastrow & ",AA2:AA" & lastrow _
& ",AD2:AD" & lastrow & ",AE2:AE" & lastrow & ",AI2:AI" & lastrow & ",AK2:AK" & lastrow & ",AL2:AL" & lastrow).SpecialCells(xlCellTypeVisible))
ws2.Range("D" & RowCount + 1) = Application.WorksheetFunction.Average(ws1.Range("AF2:AF" & lastrow & ",AM2:AM" & lastrow & ",AP2:AP" & lastrow _
& ",AQ2:AQ" & lastrow & ",AR2:AR" & lastrow & ",AS2:AS" & lastrow & ",AT2:AT" & lastrow & ",AU2:AU" & lastrow).SpecialCells(xlCellTypeVisible))
ws2.Range("E" & RowCount + 1) = Application.WorksheetFunction.Average(ws1.Range("AW2:AW" & lastrow & ",AX2:AX" & lastrow & ",AY2:AY" & lastrow).SpecialCells(xlCellTypeVisible))
Next cell
End Sub
Few changes made in your macro. Note the newly added array for unique values of filter criteria and loop changed from each cells to array. lastrow and RowCount variables replaced by lastrow1 and lastrow2.
Sub avg()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim ws1 As Worksheet, ws2 As Worksheet
Dim cell As Range, rng1 As Range, rng1Crt As Range
Dim lastrow1 As Long, lastrow2 As Long, arr As Variant
Set ws1 = wb.Worksheets("Sheet1")
Set ws2 = wb.Worksheets("Sheet2")
lastrow1 = ws1.Cells(Rows.Count, 1).End(xlUp).Row
Set rng1 = ws1.Range("G1:G" & lastrow1)
rng1.AdvancedFilter Action:=xlFilterInPlace, Unique:=True
Set rng1Crt = rng1.Cells.SpecialCells(xlCellTypeVisible)
'Populating array with filter criteria values
ReDim arr(0 To rng1Crt.Cells.Count - 1)
i = 0
For Each cell In rng1Crt.Cells
arr(i) = cell.Value
i = i + 1
Next
ws1.ShowAllData
rng1.AutoFilter
For i = 1 To UBound(arr)
lastrow2 = ws2.Cells(Rows.Count, 1).End(xlUp).Row
rng1.AutoFilter Field:=1, Criteria1:=arr(i)
With ws1.AutoFilter.Range
ws2.Range("A" & lastrow2 + 1) = ws1.Range("B" & .Offset(1, 0).SpecialCells(xlCellTypeVisible)(1).Row).Value2
ws2.Range("B" & lastrow2 + 1) = ws1.Range("F" & .Offset(1, 0).SpecialCells(xlCellTypeVisible)(1).Row).Value2
End With
ws2.Range("C" & lastrow2 + 1) = Application.WorksheetFunction.Average(ws1.Range("Y2:Y" & lastrow1 & ",Z2:Z" & lastrow1 & ",AA2:AA" & lastrow1 _
& ",AD2:AD" & lastrow1 & ",AE2:AE" & lastrow1 & ",AI2:AI" & lastrow1 & ",AK2:AK" & lastrow1 & ",AL2:AL" & lastrow1).SpecialCells(xlCellTypeVisible))
ws2.Range("D" & lastrow2 + 1) = Application.WorksheetFunction.Average(ws1.Range("AF2:AF" & lastrow1 & ",AM2:AM" & lastrow1 & ",AP2:AP" & lastrow1 _
& ",AQ2:AQ" & lastrow1 & ",AR2:AR" & lastrow1 & ",AS2:AS" & lastrow1 & ",AT2:AT" & lastrow1 & ",AU2:AU" & lastrow1).SpecialCells(xlCellTypeVisible))
ws2.Range("E" & lastrow2 + 1) = Application.WorksheetFunction.Average(ws1.Range("AW2:AW" & lastrow1 & ",AX2:AX" & lastrow1 & ",AY2:AY" & lastrow1).SpecialCells(xlCellTypeVisible))
Next
End Sub

How to insert a specific value at the beginning of text in a Column using Excel VBA?

I'm trying to insert this character "-" at the beginning of each text in column F.
My F column looks like this:
BP850
BP851
BT100
GP160
GP161
I tried this code:
Option Explicit
Sub test()
Dim LastRow As Long, i As Long
Dim str As String
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
'Replace the first occurance
str = Replace(.Range("F" & i).Value, "", "-", 1, 1)
.Range("F" & i).Value = str
Next i
End With
End Sub
I expect:
-BP850
-BP851
-BT100
-GP160
-GP161
As an alternative, you could try to utilize .Evaluate. This prevents the need for any loop:
Option Explicit
Sub test()
Dim LastRow As Long
Dim rng As Range
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
Set rng = .Range("F1:F" & LastRow)
rng.Value = .Evaluate("""'-""&" & rng.Address)
End With
End Sub
Instead of using replace, just concatenate "-" before str
Sub test()
Dim LastRow As Long, i As Long
Dim str As String
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
'Replace the first occurance
str = "-" & .Range("F" & i).Value '<== EDIT
.Range("F" & i).Value = str
Next i
End With
End Sub
Your str = Replace(.Range("F" & i).Value, "", "-", 1, 1) has problem. Change it to following:
For i = 1 To LastRow
.Range("F" & i).Value = "-" & .Range("F" & i).Value
Next i
No need to use str variable here.
See if following approach helps.
It will also check
Cell is not blank
Cell doesn't begin with "-"
which should help in case of accidental rerun of the macro!
Option Explicit
Sub test2()
Dim LastRow As Long, i As Long
'Change sheet if needed
Application.ScreenUpdating = False
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
If Len(.Range("F" & i).Value) > 0 And Left(.Range("F" & i).Value, 1) <> "-" Then _
.Range("F" & i).Value = "-" & .Range("F" & i).Value
Next i
End With
Application.ScreenUpdating = True
End Sub
try this, ie. add "'-" in front of the value. Note the single quote before the -
For i = 1 To lastRow
If .Range("F" & i).Value <> "" Then
If Left(.Range("F" & i).Value, 1) <> "-" Then
.Range("F" & i).Value = "'-" & .Range("F" & i).Value
End If
End If
Next i
Option Explicit
Sub test()
Dim LastRow As Long, i As Long
Dim AdditionString As String, CellString As String, CompleteString As String
AdditionString = "'-"
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 1 To LastRow
CellString = .Range("A" & i).Value
CompleteString = AdditionString & CellString
.Range("A" & i).Value = CompleteString
Next i
End With
End Sub

How to duplicate a template, populate it with data from another sheet, and rename it from a range within the other sheet without creating Template(2)?

I have the following code to create copies of a template, populate it based on the data within each row of another worksheet and rename it based on the employee in that row. However, I continue to get a sheet named Template(2).
Option Explicit
Sub NewSheets()
Dim i As Integer
Dim ws As Worksheet
Dim sh As Worksheet
Set ws = Sheets("Template")
Set sh = Sheets("Employee_Data")
Application.ScreenUpdating = True
For i = 2 To Range("B" & Rows.Count).End(xlUp).Row
Sheets("Template").Copy After:=Sheets(Sheets.Count)
ActiveSheet.Name = sh.Range("B" & i).Value
ActiveSheet.Range("C1").Value = sh.Range("A" & i).Value
ActiveSheet.Range("C2").Value = sh.Range("G" & i).Value
ActiveSheet.Range("C3").Value = sh.Range("H" & i).Value
ActiveSheet.Range("C4").Value = sh.Range("I" & i).Value
ActiveSheet.Range("C5").Value = sh.Range("J" & i).Value
ActiveSheet.Range("C6").Value = sh.Range("S" & i).Value
ActiveSheet.Range("C7").Value = sh.Range("V" & i).Value
ActiveSheet.Range("C8").Value = sh.Range("W" & i).Value
ActiveSheet.Range("C9").Value = sh.Range("X" & i).Value
ActiveSheet.Range("C11").Value = sh.Range("L" & i).Value
ActiveSheet.Range("C12").Value = sh.Range("AH" & i).Value
ActiveSheet.Range("C13").Value = sh.Range("AJ" & i).Value
ActiveSheet.Range("C14").Value = sh.Range("AM" & i).Value
ActiveSheet.Range("C15").Value = sh.Range("AP" & i).Value
ActiveSheet.Range("C16").Value = sh.Range("AQ" & i).Value
ActiveSheet.Range("H1").Value = sh.Range("F" & i).Value
ActiveSheet.Range("H3").Value = sh.Range("K" & i).Value
ActiveSheet.Range("N1").Value = sh.Range("C" & i).Value
ActiveSheet.Range("N11").Value = sh.Range("N" & i).Value
Next i
End Sub
I did find code which would create the multiple copies of the template and rename them as required but I cannot figure out how to write the code needed to populate the template with the data from each row for the specific employee. That code is as follows:
Sub CreateSheetsFromAList()
' Example Add Worksheets with Unique Names
Dim MyRange As Range
Dim dic As Object, c As Range
Dim k As Variant, tmp As String
Set dic = CreateObject("scripting.dictionary")
Set MyRange = Sheets("Employee_Data").Range("B2")
Set MyRange = Range(MyRange, MyRange.End(xlDown))
Sheets("Template").Visible = True
For Each c In MyRange
tmp = Trim(c.Value)
If Len(tmp) > 0 Then dic(tmp) = dic(tmp) + 1
Next c
For Each k In dic.keys
If Not WorksheetExists(k) Then
Sheets("Template").Copy After:=Sheets(Sheets.Count)
ActiveSheet.Name = k ' renames the new worksheet
End If
Next k
Sheets("Template").Visible = False
End Sub
Public Function WorksheetExists(ByVal WorksheetName As String) As Boolean
On Error Resume Next
WorksheetExists = (Sheets(WorksheetName).Name <> "")
On Error GoTo 0
End Function
I know I can always delete the extra worksheet but it would be nice if I didn't have too do that as the current project has 13 different groups for this will need to be completed. Any help would be greatly appreciated.
Better to be a little more explicit, and reduce/remove reliance on ActiveSheet:
Option Explicit
Sub NewSheets()
Dim i As Integer
Dim ws As Worksheet, wb As Workbook
Dim sh As Worksheet, wsCopy as worksheet, v
Set wb = ActiveWorkbook
Set ws = wb.Sheets("Template")
Set sh = wb.Sheets("Employee_Data")
For i = 2 To sh.Range("B" & sh.Rows.Count).End(xlUp).Row
ws.Copy After:=wb.Sheets(wb.Sheets.Count)
Set wsCopy = wb.Sheets(wb.Sheets.Count) '<<<< get a reference to the copy
wsCopy.Name = sh.Range("B" & i).Value
wsCopy.Range("C1").Value = sh.Range("A" & i).Value
'EDIT: only copy value if not empty
v = sh.Range("AJ" & i).Value
If Len(v) > 0 Then wsCopy.Range("C13").Value = v
'...
'snipped for clarity
'...
wsCopy.Range("N11").Value = sh.Range("N" & i).Value
Next i
End Sub

Copy rows (not entire row) from one sheet to another

I am having trouble with the below code. "Backend" is the Source Sheet and "Availability" is the Target sheet. Any help is appreciated.
Sub CopyA()
Dim lr As Long, lr2 As Long, r As Long
lr = Sheets("Backend").Cells(Rows.Count, "AB").End(xlUp).Row
lr2 = Sheets("Availability").Cells(Rows.Count, "A").End(xlUp).Row
For r = lr To 2 Step -1
If Range("Backend!AB" & r).Value = "A" Then
Range("Availability!A" & lr2 + 1 & ":C" & lr2 + 1) =
Range("Backend!V" & r & ":X" & r).Value2
lr2 = Sheets("Availability").Cells(Rows.Count, "A").End(xlUp).Row
End If
Next r
End Sub
Based on your code I think you are trying to copy columns V:X from sheet Backend if column AB = A and paste the data into column A of sheet Availability.
This code achieves that:
Sub CopyData()
Dim lastRow As Long, rw As Long
lastRow = Sheets("Backend").Cells(Rows.Count, "AB").End(xlUp).Row
With Worksheets("Backend")
For rw = lastRow To 2 Step -1
If .Range("AB" & rw) = "A" Then
pasteRow = Worksheets("Availability").Cells(Rows.Count, "A").End(xlUp).Row + 1
Range("V" & rw & ":X" & rw).Copy Destination:=Worksheets("Availability").Range("A" & pasteRow & ":C" & pasteRow)
End If
Next rw
End With
End Sub
In your original code you are looping backwards with Step -1. A consequence of this is that the data pasted into Availability will be in reverse order. If you want the pasted data to appear in the order it is found on backend then use this code instead:
Sub CopyData2()
Dim copyRng As Range, cl As Range
Set copyRng = Worksheets("Backend").Range("AB2:AB" & Worksheets("Backend").Cells(Rows.Count, "AB").End(xlUp).Row)
With Worksheets("Availability")
For Each cl In copyRng
If cl = "A" Then
pasteRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
Worksheets("Backend").Range("V" & cl.Row & ":X" & cl.Row).Copy Destination:=.Range("A" & pasteRow & ":C" & pasteRow)
End If
Next cl
End With
End Sub

macro to create multiple sub totals in one column

Can anyone help me with this macro to create multiple sub totals in one column? Any help would be great. I have a group of numbers in column Y. which begins at row 16.
The data is listed on every three lines until the end of that section then there is a gap of around thirty lines then it beings again. I want to create a macro to count how many numbers >45 in each section. Put the total 2 rows below the last data point of each section. In column X on the same row place Number>45
Sub Sample()
Dim result As Long, firstrow As Long, lastrow As Long
Dim ws As Worksheet
Dim rng As Range
Set ws = ThisWorkbook.Sheets("Sheet1")
With ws
'~~> Find Lastrow in Col Y
lastrow = .Range("Y" & .Rows.Count).End(xlUp).Row
'~~> Set First row
firstrow = 16
'~~> Set your range
Set rng = .Range("Y" & firstrow & ":Y" & lastrow)
'~~> Put relevant values
.Range("x" & lastrow + 2).Value = "Total>45"
.Range("y" & lastrow + 2).Value = _
Application.WorksheetFunction.CountIf(rng, ">45")
End With
End Sub
try the below procedure
and loop backwards to ROW=1 like this:
Sub setTotals()
Dim iRow As Integer
Dim iLastRow As Integer
Dim sFormulaTargetAddress As String
iLastRow = ActiveSheet.Range("Y" & ActiveSheet.Rows.Count).End(xlUp).Row
iRow = iLastRow
Do Until iRow = 1
If Range("Y" & iRow).Value <> "" Then
'
' define the section
sFormulaTargetAddress = "Y" & Range("Y" & iRow).End(xlUp).Row & ":Y" & iRow & ""
'
' Put in the COUNTIF > 45 of the current section...
'
Range("Y" & iRow + 2).Formula = "=COUNTIF(" & sFormulaTargetAddress & ","">45"")"
' '
'Range("X" & iRow + 2).Formula = "=COUNTIF(" & sFormulaTargetAddress & ","">45"")"
Range("X" & iRow + 2).value="Numbers > 45"
'
' find the next section
iRow = Range("Y" & iRow).End(xlUp).Row
If Range("Y" & iRow) <> "" Then iRow = Range("Y" & iRow).End(xlUp).Row
Else
iRow = Range("Y" & iRow).End(xlUp).Row
End If
Loop
End Sub
HTH
Philip

Resources