Excel VBA to copy list to new worksheet - vba

I have a list in Excel containing details of people This contains City, Address and name
I need to grab the City column and create a worksheet for each city, then copy the data from sheet1 to that new worksheet.
So if for example I have a city named Dublin, I need the macro to create a new worksheet named dublin, go to the list, grab all the cities named dublin, copy and paste them in the dublin worksheet (as well as the other columns of course)
I am using the macro form this link: http://www.mrexcel.com/forum/excel-questions/727407-visual-basic-applications-split-data-into-multiple-worksheets-based-column.html created by mirabeau.
The code is as follows:
Sub columntosheets()
Const sname As String = "Sheet1" 'change to whatever starting sheet
Const s As String = "A" 'change to whatever criterion column
Dim d As Object, a, cc&
Dim p&, i&, rws&, cls&
Set d = CreateObject("scripting.dictionary")
With Sheets(sname)
rws = .Cells.Find("*", , , , xlByRows, xlPrevious).Row
cls = .Cells.Find("*", , , , xlByColumns, xlPrevious).Column
cc = .Columns(s).Column
End With
For Each sh In Worksheets
d(sh.Name) = 1
Next sh
Application.ScreenUpdating = False
With Sheets.Add(after:=Sheets(sname))
Sheets(sname).Cells(1).Resize(rws, cls).Copy .Cells(1)
.Cells(1).Resize(rws, cls).Sort .Cells(cc), 2, Header:=xlYes
a = .Cells(cc).Resize(rws + 1, 1)
p = 2
For i = 2 To rws + 1
If a(i, 1) <> a(p, 1) Then
If d(a(p, 1)) <> 1 Then
Sheets.Add.Name = a(p, 1)
.Cells(1).Resize(, cls).Copy Cells(1)
.Cells(p, 1).Resize(i - p, cls).Copy Cells(2, 1)
End If
p = i
End If
Next i
Application.DisplayAlerts = False
.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End With
Sheets(sname).Activate
End Sub
The above is able to create worksheets for each city, but doesn't copy the data into the newly created worksheets. How can this be done? I have very limited knowledge of VBA and am totally lost on this.

Once all the sheets are created, you just need to scour the list in search for cities. For each line, look at the city, and write it in the corresponding sheet. The sheets need to have the same names as the cities for my code to work.
I assume you started in column A, row 1.
dim strCity as string
dim strAdd as string
dim strName as string
for i = 1 to Sheets("[TableSheet]").Cells(Rows.Count, "A").End(xlUp).row
strCity = Sheets("[TableSheet]").range("A" & i)
strAdd = Sheets("[TableSheet]").range("B" & i)
strName = Sheets("[TableSheet]").range("C" & i)
Sheets(strCity).Range("A" & i) = strCity
Sheets(strCity).Range("B" & i) = strAdd
Sheets(strCity).Range("C" & i) = strName
next
[tableSheet] of course is the name of the sheet with your information.If you don't udnerstand and have questions I can gladly answer.

thanks for your swift reply. I used it on a simple list and it worked fine. However, I applied it to a slightly more complex scenario and edited the code as follows:
Dim strDB As String
Dim strName As String
Dim strDate As String
Dim strHour As String
Dim strMin As String
Dim strGR As String
For i = 1 To Sheets("[TableSheet]").Cells(Rows.Count, "B").End(xlUp).Row
strDB = Sheets("[TableSheet]").Range("A" & i)
strName = Sheets("[TableSheet]").Range("B" & i)
strDate = Sheets("[TableSheet]").Range("C" & i)
strHour = Sheets("[TableSheet]").Range("D" & i)
strMin = Sheets("[TableSheet]").Range("E" & i)
strGR = Sheets("[TableSheet]").Range("F" & i)
Sheets(strName).Range("A" & i) = strDB
Sheets(strName).Range("B" & i) = strName
Sheets(strName).Range("C" & i) = strDate
Sheets(strName).Range("D" & i) = strHour
Sheets(strName).Range("E" & i) = strMin
Sheets(strName).Range("F" & i) = strGR
Next
I need to sort by column B. Whenever I run it I keep getting a runtime error '9' Subscript out of range. I know what this means but I can't find where I went wrong in the code.

Related

How to update existing data from Sheet1 to Sheet2 using Macro?

I just want to ask if someone do have a code for this.
I have a saved data in Sheet2 and I want to update it using the new data from Sheet1. In the below example, the code will search for Family "Oh" in Sheet2 and update its details using the updated information from Sheet1 when I click on the Update button. Here are the screenshots of
Sheet1:
and
Sheet2:
Tried this code but I can't get it to work correctly
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim valueToSearch As String
Dim i As Integer, t As Integer
Set lookUpSheet = Worksheets("sheet1")
Set updateSheet = Worksheets("sheet2")
'get the number of the last row with data in sheet1 and in sheet2
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'for every value in column A of sheet2
For i = 1 To lastRowUpdate
valueToSearch = updateSheet.Cells(i, 1)
'look the value in column A of sheet1
For t = 1 To lastRowLookup
'if found a match, copy column B value to sheet1 and proceed to the next value
If lookUpSheet.Cells(t, 1) = valueToSearch Then
updateSheet.Cells(i, 2) = lookUpSheet.Cells(t, 2)
Exit For
End If
Next t
Next i
End Sub
Thank you in advance for your help
The following should do what you expect, I've commented the code so you may understand what it is doing:
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim valueToSearch As String
Dim i As Long, t As Long
Set lookUpSheet = Worksheets("Sheet1")
Set updateSheet = Worksheets("Sheet2")
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'get the number of the last row with data in sheet1 and in sheet2
For i = 2 To lastRowLookup 'i = 2 to last to omit the first row as that row is for headers
valueFamily = lookUpSheet.Cells(i, 1) 'Family, 1 = Column A
valueDOB = lookUpSheet.Cells(i, 2) 'DOB, 2 = Column B
valueName = lookUpSheet.Cells(i, 3) 'Name, 3 = Column C
valueAge = lookUpSheet.Cells(i, 4) 'Age, 4 = Column D
'above get the values from the four column into variables
For t = 2 To lastRowUpdate 't = 2 to last to omit the first row as that row is for headers
If updateSheet.Cells(t, 1) = valueFamily And updateSheet.Cells(t, 2) = valueDOB And updateSheet.Cells(t, 3) = valueName Then
'if family, dob and name match, then
updateSheet.Cells(t, 4) = valueAge
'update age value
Exit For
End If
Next t
Next i
End Sub
This could be shortened without using the variables and comparing cells instead like below:
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim i As Long, t As Long
Set lookUpSheet = Worksheets("Sheet1")
Set updateSheet = Worksheets("Sheet2")
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'get the number of the last row with data in sheet1 and in sheet2
For i = 2 To lastRowLookup 'i = 2 to last to omit the first row as that row is for headers
For t = 2 To lastRowUpdate 't = 2 to last to omit the first row as that row is for headers
If updateSheet.Cells(t, 1) = lookUpSheet.Cells(i, 1) And updateSheet.Cells(t, 2) = lookUpSheet.Cells(i, 2) And updateSheet.Cells(t, 3) = lookUpSheet.Cells(i, 3) Then
'if family, dob and name match, then
updateSheet.Cells(t, 4) = lookUpSheet.Cells(i, 4)
'update age value
Exit For
End If
Next t
Next i
End Sub
The issue you were having is from the fact that you needed to have the IF Statement look at the first 3 cells instead of a single value, so with the AND between conditions you compare all three.
This is how to update to sql using adodb.
Sub UpdateSQL()
Dim Cn As Object
Dim strConn As String, Name As String
Dim Ws As Worksheet
Dim strSQL As String
Dim i As Integer
Dim vDB
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & ThisWorkbook.FullName & ";" & _
"Extended Properties=Excel 12.0;"
Set Ws = Sheets(1)
Name = Sheets(2).Name
With Ws
vDB = .Range("a2", .Range("d" & Rows.Count).End(xlUp))
End With
Set Cn = CreateObject("ADODB.Connection")
Cn.Open strConn
For i = 1 To UBound(vDB, 1)
strSQL = "UPDATE [" & Name & "$] set Age=" & vDB(i, 4) & " where Family = '" & vDB(i, 1) & "' AND DOB =#" & vDB(i, 2) & "# AND Name='" & vDB(i, 3) & "' "
Cn.Execute strSQL
Next i
Cn.Close
Set Cn = Nothing
End Sub

VBA get values from 2 workbooks where value matches?

Scenario
I have three workbooks
Master
Planner
Phonebook
In my master workbook, i have a value in cell I8 on sheet 1.
Master (Sheet 1)
I8 = 2
On sheet 2 i have the following empty columns:
Master (Sheet 2)
Column A (number) Column B (Supplier) Column C (Contact)
I intend to populate these columns with data from both planner workbook and phonebook workbook.
In my planner, I have a list of numbers in column A and suppliers in Column N.
Numbers Supplier
2 A
2 B
2 C
3 D
4 E
2 F
I am trying to copy all the suppliers from my planner workbook that match the value in cell I8 (in this instance it is 2).
I am pasting the numbers (2) in column A and pasting the supplier names into column B in master workbook.
My code already copies and pastes these values fine.
(I am also copying other values from planner into other columns in master - but for this question, these are not relevant).
So this part of my code works fine.
The problem
Once the suppliers have been pasted into column B in master workbook - I also want to copy the contact name for each supplier from my workbook phonebook.
My phonebook workbook has sheets A-Z and suppliers are listed under these sheets alphabetically.
Phonebook:
Supplier (Column A) Contact Name (Column C)
A Linda
Aa Dave
Aa Terry
AB James
A | B | C | D etc... <----- Sheets
I need to look in each sheet in column A of the phonebook for a supplier name that matches the supplier name in column B (master).
If the supplier name matches then I want to copy the contact name in column C over to master workbook column C.
My result should look like this
Master (Sheet 2)
Column A (number) Column B (Supplier) Column C (Contact)
2 A Linda
2 A Linda
Here's my code:
Option Explicit
Sub CreateAnnounce()
Dim WB As Workbook
Dim WB2 As Workbook
Dim i As Long
Dim i2 As Long
Dim j As Long
Dim LastRow As Long
Dim j2 As Long
Dim LastRow2 As Long
Dim ws As Worksheet
'Open Planner
On Error Resume Next
Set WB = Workbooks("2017 Planner.xlsx")
On Error GoTo 0
If WB Is Nothing Then 'open workbook if not open
Set WB = Workbooks.Open("G:\BUYING\Food Specials\2. Planning\1. Planning\1. Planner\8. 2017\2017 Planner.xlsx")
End If
'Open PhoneBook
On Error Resume Next
Set WB2 = Workbooks("Phone Book for Food Specials.xls")
On Error GoTo 0
If WB2 Is Nothing Then 'open workbook if not open
Set WB2 = Workbooks.Open("G:\BUYING\Food Specials\1. General\Phone Book\Phone Book for Food Specials.xls")
End If
' ======= Edit #2 , also for DEBUG ======
With WB.Worksheets(1)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
j = 2
For i = 1 To LastRow
' === For DEBUG ONLY ===
Debug.Print CInt(ThisWorkbook.Worksheets(1).Range("I8").Value)
If CInt(ThisWorkbook.Worksheets(1).Range("I8").Value) = .Range("A" & i).Value Then ' check if Week No equals the value in "A1"
ThisWorkbook.Worksheets(2).Range("A" & j).Value = .Range("A" & i).Value
ThisWorkbook.Worksheets(2).Range("B" & j).Value = .Range("N" & i).Value
ThisWorkbook.Worksheets(2).Range("H" & j).Value = .Range("K" & i).Value
ThisWorkbook.Worksheets(2).Range("I" & j).Value = .Range("L" & i).Value
ThisWorkbook.Worksheets(2).Range("J" & j).Value = .Range("M" & i).Value
ThisWorkbook.Worksheets(2).Range("K" & j).Value = .Range("G" & i).Value
ThisWorkbook.Worksheets(2).Range("L" & j).Value = .Range("O" & i).Value
ThisWorkbook.Worksheets(2).Range("M" & j).Value = .Range("P" & i).Value
ThisWorkbook.Worksheets(2).Range("N" & j).Value = .Range("W" & i).Value
ThisWorkbook.Worksheets(2).Range("O" & j).Value = .Range("Z" & i).Value
'Retrieve Contact Details for supplier
'Worksheet 1
'Retrieve Contact Details for supplier
With WB2.Worksheets(2)
LastRow2 = .Cells(.Rows.Count, "A").End(xlUp).Row
j2 = 2
For i2 = 1 To LastRow2
Dim rngToFill As Range
Set rngToFill = .Range("C2")
Do
Debug.Print ThisWorkbook.Worksheets(2).Range("B" & j2).Value
If ThisWorkbook.Worksheets(2).Range("B" & j2).Value Like .Range("A" & i2).Value Then ' check if Company equals the value in "B1 Phonebook"
ThisWorkbook.Worksheets(2).Range("C2").Value = .Range("C" & i2).Value
Set rngToFill = rngToFill.Offset(1, 0)
End If
Loop
Next i2
End With
'Retrieve Contact Details for supplier - END
End If
Next i
End With
End Sub
For some reason, the code is Copying/pasting just 1 single contact name on the first row into master workbook.
I am also aware that at the moment I am only looking across one sheet.
With WB2.Worksheets(2)
I need this code to obviously look across all sheets for all supplier contact names.
Please can someone show me where I am going wrong and how to get this code to work? Thanks in advance.
EDIT:
I have composed the code suggested by user #BjornBogers
'Retrieve Contact Details for supplier
Dim FoundCellRng As Range
Dim ContactValue As String
Dim SearchStr As String
For i2 = 1 To 26
'Assuming --> ThisWorkbook.Worksheets(2).Range("B1").Value is what you are looking for?
SearchStr = ThisWorkbook.Worksheets(2).Range("B2").Value
Set FoundCellRng = WB2.Worksheets(i2).Range("A2:A200").Find(SearchStr)
If (FoundCellRng Is Nothing) Then
'Didn't find anything
Else
'We found it
ContactValue = WB2.Worksheets(i2).Cells(FoundCellRng.Row, FoundCellRng.Column + 2).Value
ThisWorkbook.Worksheets(2).Range("C" & j).Value = ContactValue
Exit For
End If
Next i2
'Retrieve Contact Details for supplier - END
However, this does the same thing, only one contact name is being entered in the top row but nothing more.
EDIT 2:
With the code #R3uK provided, i seem to be getting the following issues:
Supplier names and other values are not copying across correctly.
The same value seems to repeat itself again and again in column I.
For some reason, this code creates another sheet? What is this sheet for?
The code is incredibly slow and i am having to wait 20 minutes or more.
Is there a way to speed this up?
I haven't tested this but you could try the following:
Dim FoundCellRng As Range
Dim ContactValue As String
Dim SearchStr As String
For i = 1 To 26
'Assuming --> ThisWorkbook.Worksheets(2).Range("B1").Value is what you are looking for?
SearchStr = ThisWorkbook.Worksheets(2).Range("B1").Value
Set FoundCellRng = WB2.Worksheets(i).Range("A1:A100").Find(SearchStr)
If (FoundCellRng Is Nothing) Then
'Didn't find anything
Else
'We found it
ContactValue = WB.Worksheets(i).Cells(FoundCellRng.Row, FoundCellRng.Column + 2).Value
Exit For
End If
Next i
Sub CreateAnnounce()
Dim WbMaster As Workbook
Dim wSMaster1 As Worksheet
Dim wSMaster2 As Worksheet
Dim wSMastTemp As Worksheet
Dim WbPlan As Workbook
Dim wSPlan1 As Worksheet
Dim WbPhone As Workbook
Dim wSPhone As Worksheet
Dim i As Long
Dim j As Long
Dim LastRow As Long
Dim rngToFill As Range
Dim rngToChk As Range
Set WbMaster = ThisWorkbook
Set wSMaster1 = WbMaster.Sheets(1)
Set wSMaster2 = WbMaster.Sheets(2)
Set wSMastTemp = WbMaster.Sheets.Add
'''Open Planner
Set WbPlan = GetWB("2017 Planner.xlsx", "G:\BUYING\Food Specials\2. Planning\1. Planning\1. Planner\8. 2017\2017 Planner.xlsx")
Set wSPlan1 = WbPlan.Sheets(1)
'''Open PhoneBook
Set WbPhone = GetWB("Phone Book for Food Specials.xls", "G:\BUYING\Food Specials\1. General\Phone Book\Phone Book for Food Specials.xls")
With wSPlan1
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
j = 2
For i = 1 To LastRow
'''Check if Week No equals the value in "A1"
If CInt(wSMaster1.Range("I8").Value) = .Range("A" & i).Value Then
wSMaster2.Range("A" & j).Value = .Range("A" & i).Value
wSMaster2.Range("B" & j).Value = .Range("N" & i).Value
wSMaster2.Range("H" & j & ":J" & j).Value = .Range("K" & i & ":M" & i).Value
wSMaster2.Range("K" & j).Value = .Range("G" & i).Value
wSMaster2.Range("L" & j & ":M" & j).Value = .Range("O" & i & ":P" & i).Value
wSMaster2.Range("N" & j).Value = .Range("W" & i).Value
wSMaster2.Range("O" & j).Value = .Range("Z" & i).Value
'''Store those infos for next results
wSMastTemp.Cells.Clear
wSMastTemp.Range("A1:O1").Value = wSMaster2.Range("A" & j & ":O" & j).Value
'''Retrieve Contact Details for supplier
Set rngToFill = wSMaster2.Range("C" & j)
For Each wSPhone In WbPhone.Sheets
With wSPhone
'''Define properly the Find method to find all
Set rngToChk = .Columns(1).Find(What:=wSMaster2.Range("B" & j).Value, _
After:=.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
'''If there is a result, keep looking with FindNext method
If Not rngToChk Is Nothing Then
FirstAddress = rngToChk.Address
Do
'''Transfer the cell value to the master
rngToFill.Value = rngToChk.Offset(, 2).Value
'''Go to next row on the template for next Transfer
Set rngToFill = rngToFill.Offset(1, 0)
'''Copy the Info from 1st row for the next result
wSMaster2.Range("A" & rngToFill.Row & ":O" & rngToFill.Row).Value = wSMastTemp.Range("A1:O1").Value
'''Look until you find again the first result in that sheet
Set rngToChk = .Columns(1).FindNext(rngToChk)
Loop While Not rngToChk Is Nothing And rngToChk.Address <> FirstAddress
Else
End If
End With 'wSPhone
Next wSPhone
'''Restart to fill from the next available row
j = rngToFill.Row
'''Clean Data that was there for the next result of this test
wSMaster2.Range("A" & j & ":O" & j).ClearContents
End If
Next i
End With
Application.DisplayAlerts = False
wSMastTemp.Delete
Application.DisplayAlerts = True
End Sub
Public Function GetWB(FileName As String, FileFullPath As String) As Workbook
On Error Resume Next
Set GetWB = Workbooks(FileName)
On Error GoTo 0
If GetWB Is Nothing Then 'open workbook if not open
Set GetWB = Workbooks.Open(FilePath)
DoEvents
End If
End Function

Convert headers and row level data to column level

I have very little experience working with VBA, so I'm having a hard time looking up what I am trying to do because I am having a hard time putting what I am trying to do into words.
I have been struggling to write a code to do the below task for the past few days.
Basically what I am trying to do is to convert a set of data to different format.
This what my source data looks like.
Data:
and I need it to look like this
FinalLook:
I've a already setup a code which is lengthy and incomplete.
FIRST PART
I started with retrieving a part of a data (AQ:BA) and then convert to the format in sheet2 with the below code.
Sub FirstPart()
Dim lastRow As Long
Dim Laaastrow As Long
Sheets("sheet2").Range("a2:A5000").ClearContents
lastRow = Sheets("Sheet1").Range("c" & Rows.Count).End(xlUp).Row
Sheets("Sheet2").Range("A2:A" & lastRow).Value = Sheets("Sheet1").Range("c5:c" & lastRow).Value
Sheets("Sheet2").Range("b2:l" & lastRow).Value = Sheets("Sheet1").Range("aq5:ba" & lastRow).Value
End Sub
But.. the problem i am facing with this code is that it pulls all the data, i do not want it to pull all the values, but only the ones which is not empty or 0. In other words, if AQ6:BA6 is empty, script should skip this particular row and go the next one.
SECOND PART (converting the sheet2 data to the final format)
Sub NormalizeSheet()
Dim wsSheet2 As Worksheet
Dim wsSheet4 As Worksheet
Dim strKey As String
Dim clnHeader As Collection
Dim lngColumnCounter As Long
Dim lngRowCounterSheet2 As Long
Dim lngRowCounterSheet4 As Long
Dim rngCurrent As Range
Dim varColumn As Variant
Set wsSheet2 = ThisWorkbook.Worksheets("Sheet2")
Set wsSheet4 = ThisWorkbook.Worksheets("Sheet4")
Set clnHeader = New Collection
wsSheet4.Range("c2:c5000").ClearContents
wsSheet4.Range("e2:e5000").ClearContents
wsSheet4.Range("g2:g5000").ClearContents
lngColumnCounter = 2
lngRowCounterSheet2 = 1
Set rngCurrent = wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter)
Do Until IsEmpty(rngCurrent.Value)
clnHeader.Add rngCurrent.Value, CStr(lngColumnCounter)
lngColumnCounter = lngColumnCounter + 1
Set rngCurrent = wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter)
Loop
lngRowCounterSheet2 = 2
lngRowCounterSheet4 = 1
lngColumnCounter = 1
Do While Not IsEmpty(wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter))
Set rngCurrent = wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter)
strKey = rngCurrent.Value
lngColumnCounter = 2
Do While Not IsEmpty(wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter))
Set rngCurrent = wsSheet2.Cells(lngRowCounterSheet2, lngColumnCounter)
If rngCurrent.Value = "NULL" Then
Else
wsSheet4.Range("c" & lngRowCounterSheet4).Offset(1, 0).Value = strKey
wsSheet4.Range("e" & lngRowCounterSheet4).Offset(1, 0).Value = clnHeader(CStr(lngColumnCounter))
wsSheet4.Range("g" & lngRowCounterSheet4).Offset(1, 0).Value = rngCurrent.Value
lngRowCounterSheet4 = lngRowCounterSheet4 + 1
End If
lngColumnCounter = lngColumnCounter + 1
Loop
lngRowCounterSheet2 = lngRowCounterSheet2 + 1
lngColumnCounter = 1
Loop
End Sub
I got this code from another thread posted here on stakcoverflow, i modified a bit to get this work.
The problem i am encountering here is that if Sheet2 B2 is empty, the codes doesnt check sheet C2 instead it skips the whole row, which is not right here.
I know this sounds complicated, and this approach of mine may not be even feasible.
Is there ANY OTHER WAY to do this? Is there any other way to get this in a single shot instead of breaking down the data and move each set of columns to sheet2 then to final format?
See how you get on with this. You'll have to adjust range references, and possibly sheet names
Sub x()
Dim r As Long, c As Range
With Sheet1
For r = 5 To .Range("A" & Rows.Count).End(xlUp).Row
For Each c In .Range(.Cells(r, "AQ"), .Cells(r, "BK")).SpecialCells(xlCellTypeConstants)
If c.Value > 0 Then
Sheet2.Range("A" & Rows.Count).End(xlUp)(2).Value = .Range("B1").Value
Sheet2.Range("B" & Rows.Count).End(xlUp)(2).Value = .Cells(r, 1).Value
Sheet2.Range("C" & Rows.Count).End(xlUp)(2).Value = .Cells(r, 2).Value
Sheet2.Range("D" & Rows.Count).End(xlUp)(2).Value = .Cells(3, c.Column).Value
Sheet2.Range("E" & Rows.Count).End(xlUp)(2).Value = .Cells(4, c.Column).Value
Sheet2.Range("F" & Rows.Count).End(xlUp)(2).Value = "(blank)"
Sheet2.Range("G" & Rows.Count).End(xlUp)(2).Value = c.Value
End If
Next c
Next r
End With
Sheet2.Range("A1").Resize(, 7) = Array("TOPHEADER", "HEADER1", "HEADER2", "FROM", "TO", "TYPE", "UNIT")
End Sub

Modifying VBA copy and paste code to search down rather than across

I have the following VBA code:
Sub test():
Dim NameValue As String, w1 As Worksheet, w2 As Worksheet
Dim i As Long, j As Long, k As Long, c As Long
Set w1 = Sheets("Sheet2"): Set w2 = Sheets("Sheet3")
GetNameValue: For i = 1 To w1.Range("A" & Rows.Count).End(xlUp).row
If w1.Range("A" & i) = "NAME:" Then
If InStr(1, NameValue, w1.Range("B" & i)) Then GoTo GetNext
j = i + 1: Do Until w1.Range("A" & j) = "DATE OF BIRTH:": j = j + 1: Loop
NameValue = Trim(NameValue & " " & w1.Range("B" & i) & "|" & w1.Range("B" & j))
c = c + 1: End If
GetNext: Next i: NameValue = NameValue & " "
For k = 1 To c
i = InStr(1, NameValue, "|"): j = InStr(i, NameValue, " ")
w2.Range("A" & k) = Left(NameValue, i - 1): w2.Range("B" & k) = Mid(NameValue, i + 1, j - i)
NameValue = Mid(NameValue, j + 1, Len(NameValue) - j)
Next k
End Sub
To break down what this code does:
1) Set the first sheet that should be searched and the second sheet (output sheet) that the results should be appended to.
2) Search the first column for a certain string "NAME:" and once found take the value in the second column, place it in the output sheet go look for "DATE OF BIRTH:". Once "DATE OF BIRTH:" is found put it beside the value for "NAME:" in the output sheet.
3) Repeat until there are no more entries.
I'm sure this is a very simple modification, but what I'd like to do is check whether a certain string exists, if it does grab the entry directly BELOW it, and then continue searching for the next string and associated entry just like the code does already.
Can anyone point me to what I would need to change in order to do this (and preferably why)?
In addition, how might I be able to extend this code to run over multiple sheets while depositing the results in a single sheet? Do I need to set up a range running over the worksheets w_1....w_(n-1) (with output sheet w_n possibly in a different workbook)?
Removed Line continuations in code:
Sub test()
Dim NameValue As String, w1 As Worksheet, w2 As Worksheet
Dim i As Long, j As Long, k As Long, c As Long
Set w1 = Sheets("Sheet2")
Set w2 = Sheets("Sheet3")
GetNameValue:
For i = 1 To w1.Range("A" & Rows.Count).End(xlUp).Row
If w1.Range("A" & i) = "NAME:" Then
If InStr(1, NameValue, w1.Range("B" & i)) Then GoTo GetNext
j = i + 1
Do Until w1.Range("A" & j) = "DATE OF BIRTH:"
j = j + 1
Loop
NameValue = Trim(NameValue & " " & w1.Range("B" & i) & "|" & w1.Range("B" & j))
c = c + 1
End If
GetNext:
Next i
NameValue = NameValue & " "
For k = 1 To c
i = InStr(1, NameValue, "|")
j = InStr(i, NameValue, " ")
w2.Range("A" & k) = Left(NameValue, i - 1)
w2.Range("B" & k) = Mid(NameValue, i + 1, j - i)
NameValue = Mid(NameValue, j + 1, Len(NameValue) - j)
Next k
End Sub
UPDATE: Just to make sure we're all on the same page about what the output would look like. Suppose we are searching for the entry below A and the entry beside C:
INPUT
A 1
B
y 3
z 4
t
d
s 7
C 8
A 1
Z
y 3
z 4
t
d
s 7
C 12
OUTPUT
B 8
Z 12
.
.
.
Assuming I understand your desire correctly, you can use the .Offset method with your current range to get to the cell below it. You would need to add a dim, so here's my stab at what you're trying to accomplish:
Sub test()
Dim NameValue As String, w1 As Worksheet, w2 As Worksheet
'new local variable
Dim newValue as string
Dim i As Long, j As Long, k As Long, c As Long
Set w1 = Sheets("Sheet2")
Set w2 = Sheets("Sheet3")
GetNameValue:
For i = 1 To w1.Range("A" & Rows.Count).End(xlUp).Row
'assuming your string is in column A
If w1.Range("A" & i) = "FIND ME" Then
newValue = w1.Range("A" & i).Offset(1,0).Value
End If
If w1.Range("A" & i) = "NAME:" Then
If InStr(1, NameValue, w1.Range("B" & i)) Then GoTo GetNext
j = i + 1
Do Until w1.Range("A" & j) = "DATE OF BIRTH:"
j = j + 1
Loop
NameValue = Trim(NameValue & " " & w1.Range("B" & i) & "|" & w1.Range("B" & j))
c = c + 1
End If
GetNext:
Next i
NameValue = NameValue & " "
For k = 1 To c
i = InStr(1, NameValue, "|")
j = InStr(i, NameValue, " ")
w2.Range("A" & k) = Left(NameValue, i - 1)
w2.Range("B" & k) = Mid(NameValue, i + 1, j - i)
NameValue = Mid(NameValue, j + 1, Len(NameValue) - j)
Next k
End Sub
Then you could do anything you desired with the newValue string, including putting it in w2 like so: w2.Range("D1").value = newValue
UPDATED ANSWER
I am now 89% sure I know what you are trying to accomplish :) thanks for your clarifying example.
To search a range for your search string, you need to set up a range you are looking in:
dim searchRange as range
dim w1,w2 as worksheet
Set w1 = Sheets("Sheet1")
Set w2 = Sheets("Sheet2")
set searchRange = w1.Range("A" & Rows.Count).End(xlUp).Row
Then you search the searchRange for both of your search strings (which I'm saying are "A" for the first and "C" for the second). As long as both strings are found in the searchRange, it will create a new Dictionary entry for the two values, having the value below "A" as the key and the value beside "C" as the item.
dim rng as range
dim valueBelowFirstSearch as string
dim resultsDictionary as object
dim i as integer
dim c, d as range
dim cAddress, dAddress as string
set resultsDictionary = CreateObject("scripting.dictionary")
with searchRange
set c = .Find("A", lookin:=xlValues)
set d = .Find("C", lookin:=xlValues)
if not c Is Nothing and not d Is Nothing then
cAddress = c.address
dAddress = d.address
resultsDictionary.add Key:=c.offset(1,0).value, Item:=d.value
Do
set c = .FindNext(c)
set d = .FindNext(d)
Loop While not c is nothing and not d is nothing and c.address <> cAddress and d.address <> dAddress
end if
end with
Now that we have all of the results in the resultsDictionary, we can now output the values into another place, which I'm choosing to be w2.
dim outRange as range
dim item as variant
set outRange = w2.Range("A1")
for each item in resultsDictionary
outRange.Value = item.key
set outRange = outRange.Offset(0,1)
outRange.Value = item.item
set outRange = outRange.Offset(1,-1)
next item
Can anyone point me to what I would need to change in order to do this
(and preferably why)?
Basically you need to change the parts of which NameValue is composed.
Originally you took the value beside the first match as w1.Range("B" & i) and now you want the value below the first match, which is w1.Range("A" & i + 1).
Originally it was:
Trim(NameValue & " " & w1.Range("B" & i) & "|" & w1.Range("B" & j))
Now you need something like this:
Trim(NameValue & " " & w1.Range("A" & i + 1) & "|" & w1.Range("B" & j))
In addition, how might I be able to extend this code to run over
multiple sheets while depositing the results in a single sheet?
(with output sheet w_n possibly in a different workbook)?
To achieve that you can e.g. create an array of Sheets and let the code run for each Sheet of this array. Note that the array might contain 1-N Sheets.
' Set array of sheets for just one sheet
Dim searchedSheets As Sheets
Set searchedSheets = Workbooks("SomeBook.xlsx").Sheets(Array("Sheet1"))
' Set array of sheets for more sheets, e.g. "Sheet1" and "Sheet2" and "Sheet3"
Dim searchedSheets As Sheets
Set searchedSheets = Workbooks("SomeBook.xlsx").Sheets(Array("Sheet1", "Sheet2", "Sheet3"))
' Finally set the second sheet where the results should be appended
' to sheet in the same workbook as the searched sheets
Dim outputSheet As Worksheet
Set outputSheet = Workbooks("SomeBook.xlsx").Worksheets("ResultSheet")
' Or set the second sheet where the results should be appended to sheet
' in a different workbook then the searched sheets belong to
Dim outputSheet As Worksheet
Set outputSheet = Workbooks("SomeOtherBook.xlsx").Worksheets("ResultSheet")
The complete code might look like this (tested with data you provided).
Option Explicit
Public Sub main()
' String to search below of it
Dim string1 As String
string1 = "A"
' String to search beside of it
Dim string2 As String
string2 = "C"
' Set the sheets that should be searched
Dim searchedSheets As Sheets
Set searchedSheets = Workbooks("SomeBook.xlsx").Sheets(Array("Sheet1", "Sheet2"))
' Set the second sheet (outputSheet sheet) that the results should be
' appended to external sheet in different book
Dim outputSheet As Worksheet
Set outputSheet = Workbooks("SomeOtherBook.xlsx").Worksheets("ResultSheet")
SearchFor string1, string2, searchedSheets, outputSheet
End Sub
Public Sub SearchFor( _
string1 As String, _
string2 As String, _
searchedSheets As Sheets, _
output As Worksheet)
Dim searched As Worksheet
Dim NameValue As String
Dim below As String
Dim beside As String
Dim i As Long
Dim j As Long
Dim k As Long
Dim c As Long
Dim rowsCount As Long
For Each searched In searchedSheets
rowsCount = searched.Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To rowsCount
' Search the first column for a 'string1'
If searched.Range("A" & i) = string1 Then
' once 'string1' was found grab the entry directly below it
below = searched.Range("A" & i + 1)
If InStr(1, NameValue, below) Then
' skip this 'below' result because it was found before
GoTo GetNext
End If
' Search the first column for a 'string2' starting at the
' position where 'below' was found
For j = i + 1 To rowsCount
If searched.Range("A" & j) = string2 Then
' once 'string2' was found grab the entry directly
' beside it
beside = searched.Range("B" & j)
Exit For
End If
Next j
' Append 'below' and 'beside' to the result and count the
' number of metches
NameValue = Trim(NameValue & " " & below & "|" & beside)
c = c + 1
End If
GetNext:
Next i
Next searched
' Write the output
NameValue = NameValue & " "
For k = 1 To c
i = InStr(1, NameValue, "|")
j = InStr(i, NameValue, " ")
output.Range("A" & k) = Left(NameValue, i - 1)
output.Range("B" & k) = Mid(NameValue, i + 1, j - i)
NameValue = Mid(NameValue, j + 1, Len(NameValue) - j)
Next k
End Sub
Note: I replaced the Do-Until loop with For-Next loop because the Do-Until might cause a Stack-Overflow :-) error if the string "DATE OF BIRTH:" does not exist in the first column. However I have tryied to keep your originall code structure so you still understand it. HTH.
Assuming that you want to find one value (Name:), then continue searching till to find the second one (Date Of Birth:)... Finally, you want to move these pair of data into another worksheet.
To achieve that, i'd suggest to use Dictionary object to get only distinct values. I strongly do not recommend to use string concatenation as you provided in your code!
Option Explicit
Sub Test()
Dim src As Worksheet, dst As Worksheet
Set dst = ThisWorkbook.Worksheets("Sheet2")
For Each src In ThisWorkbook.Worksheets
If src.Name = dst.Name Then GoTo SkipNext
NamesToList src, dst
SkipNext:
Next
End Sub
'needs reference to MS Scripting Runtime library
Sub NamesToList(ByVal srcWsh As Worksheet, ByVal dstWsh As Worksheet, _
Optional ByVal SearchFor As String = "NAME:", Optional ByVal ThenNextFor As String = "DATE OF BIRTH:")
Dim dic As Dictionary, i As Long, j As Long, k As Long
Dim sKey As String, sVal As String
On Error GoTo Err_NamesToList
Set dic = New Dictionary
i = 2
j = GetFirstEmpty(srcWsh)
Do While i < j
If srcWsh.Range("A" & i) = SearchFor Then
sKey = srcWsh.Range("B" & i)
If Not dic.Exists(sKey) Then
Do While srcWsh.Range("A" & i) <> ThenNextFor
i = i + 1
Loop
sVal = srcWsh.Range("B" & i)
dic.Add sKey, sVal
k = GetFirstEmpty(dstWsh)
With dstWsh
.Range("A" & k) = sKey
.Range("B" & k) = sVal
End With
'sKey = ""
'sVal = ""
End If
End If
SkipNext:
i = i + 1
Loop
Exit_NamesToList:
On Error Resume Next
Set dic = Nothing
Exit Sub
Err_NamesToList:
Resume Exit_NamesToList
End Sub
Function GetFirstEmpty(ByVal wsh As Worksheet, Optional ByVal sCol As String = "A") As Long
GetFirstEmpty = wsh.Range(sCol & wsh.Rows.Count).End(xlUp).Row + 1
End Function
Sample output:
Name DateOfBirth:
A 1999-01-01
B 1999-01-02
C 1999-01-03
D 1999-01-04
E 1999-01-05

Offset function on selective columns in Excel VBA

Bit new to VBA. It seems quite simple though; am not able to figure it out how to use Offset function and While/Do while loop here.
I am making an excel form where columns A to L will have values.
Out of which few columns are mandatory. Those are A, B, C, D, F, G, H, I, J, L.
Which means those can't be left blank and other columns can be blank.
My excel looks like below.
I have written a code where it checks whether mandatory columns have values or not.
The code is as below :
Dim celadr, celval As Variant
Dim cell As Variant
Dim LastRow As Long
LastRow = Range("A65536").End(xlUp).Row
On Error GoTo 0
shname = ActiveSheet.Name
Dim celArray, arr, Key1, KeyCell As Variant
celArray = ("A,B,C,D,F,G,H,I,J,L")
arr = Split(celArray, ",")
For Key1 = LBound(arr) To UBound(arr)
KeyCell = arr(Key1)
Range(KeyCell & "2:" & KeyCell & "" & LastRow).Select
'Selection.Clearformats
For Each cell In Selection
celadr = cell.Address
celval = cell.Value
If celval = "" Then
Range(celadr).Interior.Color = vbRed
strErr = Range(celadr).Value
Sheets("Observations").Range("A65536").End(xlUp).Offset(1, 0).Value = IIf(strErr = "", "Empty Found", strErr)
strstr = "'" & shname & "'!" & Range(celadr).Address(0, 0)
Sheets("Observations").Hyperlinks.Add Anchor:=Sheets("Observations").Range("A65536").End(xlUp), Address:="", SubAddress:= _
strstr, TextToDisplay:=IIf(strErr = "", "Empty Found", strErr)
End If
Next cell
Next Key1
The result of this code is;
1) between each two school records a row may be left blank.
My above code will color such all rows also in red background.
(It should not happen)
2) Columns B, C, D, F, G, H can have values only in the same row in which school_name is mentioned.
So, if following rows for same school are left blank then those also will be colored in red background.
(It should not happen).
So; I want to make small correction to code:
I want to add a condition to code:
"When there is a value in Column A; then only the above code should be exceuted."
I tried to achieve it as I have written in below Code. Still, am not upto.
I have commented all such lines of code which were giving me error (from below code):
Dim celadr, celval, celadr1, celval1 As Variant
Dim cell, cell1 As Variant
Dim LastRow As Long
LastRow = Range("A65536").End(xlUp).Row
On Error GoTo 0
shname = ActiveSheet.Name
Dim celArray, arr, Key1, KeyCell As Variant
'Range("A2:A" & LastRow).Select
'For Each cell1 In Selection
'celadr1 = cell1.Address
'celval1 = cell1.Value
'Do While Len(celval1) >= 1
celArray = ("A,B,C,D,F,G,H,I,J,L")
arr = Split(celArray, ",")
For Key1 = LBound(arr) To UBound(arr)
KeyCell = arr(Key1)
Range(KeyCell & "2:" & KeyCell & "" & LastRow).Select
'Selection.Clearformats
For Each cell In Selection
celadr = cell.Address
celval = cell.Value
' May be another loop over here to increment value in offset function according to column number.
If celval = "" Then 'And Offset Function Referring to column A, same row.
Range(celadr).Interior.Color = vbRed
strErr = Range(celadr).Value
Sheets("Observations").Range("A65536").End(xlUp).Offset(1, 0).Value = IIf(strErr = "", "Empty Found", strErr)
strstr = "'" & shname & "'!" & Range(celadr).Address(0, 0)
Sheets("Observations").Hyperlinks.Add Anchor:=Sheets("Observations").Range("A65536").End(xlUp), Address:="", SubAddress:= _
strstr, TextToDisplay:=IIf(strErr = "", "Empty Found", strErr)
End If
' End If
Next cell
Next Key1
' Loop
Can someone guide me how I can make correct use of offset function/while loops here?
Edit:
Assume, XYZ School don't have value for No. of Teachers
And
PQRS School don't have value for No. of students
My Current output is as in below image:
Where as Expected Output is:
I think the below code should work - try it out and let me know if there are any issues:
Sub Your_Macro()
Dim celArray, item As Variant
Dim LastRow, x As Long
LastRow = Cells(rows.Count, "A").End(xlUp).row
celArray = ("A,B,C,D,F,G,H,I,J,L")
celArray = Split(celArray, ",")
For x = 2 To LastRow
If Not IsEmpty(Cells(x, "A")) Then
For Each item In celArray
If IsEmpty(Cells(x, item)) Then
Cells(x, item).Interior.Color = vbRed
End If
Next item
End If
Next x
End Sub