Unexpected behaviour of "For Each wks In ActiveWindow.SelectedSheets", it affects more column that it should be - excel

i did this code that works pretty well exept the last part:
The behaviour of last part should be that ".Interior.Color" and ".Value" affected until the last populated column, instead it affects the first cell of many other columns. Any ideas?
Sub Sample_Workbook()
'Creation of new workbook
Application.ScreenUpdating = False
Workbooks.Add
Set wb = ActiveWorkbook
wb.SaveAs ThisWorkbook.Path & "etc.xlsx"
'following variable is declared for sending mail purpose
SourceWorkbook = ActiveWorkbook.Name
Set this = Workbooks("Sample")
Set wb = ActiveWorkbook
Set ws1 = wb.Sheets("Sheet1")
wb.Sheets.Add After:=Sheets(1)
Set ws2 = wb.Sheets(2)
wb.Sheets.Add After:=Sheets(2)
Set ws3 = wb.Sheets(3)
ws1.Name = "Sheet1"
ws2.Name = "Sheet2"
ws3.Name = "Sheet3"
'Model the new excel with the requirements:
Dim Population, Population2 As Range
Dim lastRow As Long, firstRow As Long
Dim sampleSize As Long
Dim unique As Boolean
Dim i As Long, d As Long, n As Long
'following function perfoms all the calculations and copy and pasting
doTheJob x, y, z, num, q
doTheJob x, y, z, num, q
doTheJob x, y, z, num, q
'copy and paste the remaining sheets from the sample files
Workbooks.Open ThisWorkbook.Path & "Sample2.xlsx"
Sheets("Sheetx").Copy After:= _
Workbooks(SourceWorkbook).Sheets(6)
Workbooks("Sample2.xlsx").Close SaveChanges:=False
Application.ScreenUpdating = True
Application.CutCopyMode = False
ws1.Select
wb.Close SaveChanges:=True
End Sub
'these will make the variable available to all modules of this macro Workbook
Public SourceWorkbook As String
Public this, wb As Workbook
Public data As Range
Public output As Range
Public ws1, ws2, ws3 As Worksheet
Public LastCol As Long
Public wks As Worksheet
Public iCol As Long
'FUNCTION
Sub doTheJob(x As String, y As String, z As String, num As Integer, q As String)
'beginning logic.
this.Worksheets(x).Activate
Set Population = Range("a3", Range("a3").End(xlDown))
sampleSize = this.Worksheets("SNOW Reports").Range(y).Value
Set r = Population
lastRow = r.Rows.Count + r.Row - 1
firstRow = r.Row
For i = 1 To sampleSize
Do
unique = True
n = Application.WorksheetFunction.RandBetween(firstRow, lastRow)
For d = 1 To i - 1
'wb.Sheets(z).Activate
If wb.Sheets(z).Cells(d + 1, 50) = n Then
unique = False
Exit For
End If
Next d
If unique = True Then
Exit Do
End If
Loop
Set data = this.Worksheets(x).Range("a" & n, Range("a" & n).End(xlToRight))
Set output = wb.Worksheets(z).Range("A" & i + 1)
output.Resize(data.Rows.Count, data.Columns.Count).Value = data.Value
'THE NEXT LINE IS JUST FOR DELETEING LAST COLUMN PURPOSE
wb.Worksheets(z).Cells(1, 50) = "REF COL"
wb.Worksheets(z).Cells(i + 1, 50) = n
this.Worksheets(x).Activate
Next i
'delete REF COL:
With wb.Sheets(z)
.Columns(50).Delete
End With
'copy and paste header:
Set data = this.Worksheets(x).Range("a2", Range("a2").End(xlToRight))
Set output = wb.Sheets(z).Range("A1")
output.Resize(data.Rows.Count, data.Columns.Count).Value = data.Value
'_________________________________________________________________________________________________________
'copy and paste into new sheet with recorded macro
wb.Activate
Sheets.Add(After:=Sheets(num)).Name = q
wb.Worksheets(z).Cells.Copy Destination:=wb.Worksheets(q).Range("A1")
'create columns and add color and text dinamically
For Each wks In ActiveWindow.SelectedSheets
With wks
For iCol = .Cells.SpecialCells(xlCellTypeLastCell).Column To 2 Step -1
.Columns(iCol).Insert
With Cells(1, iCol)
.Interior.Color = 65535
.Value = Cells(1, iCol - 1) & " - Comparison"
End With
Next iCol
End With
Next wks
End Sub

If I understand what you're aiming to do, the following does what you want.
The code could be approached differently (and possibly made more efficient), if the larger context was known
However, I sense this is just a stage in your development, so have stayed with your approach (wherever reasonable).
' I suggest this goes to the top of the sub (no need for public declaration)
' Note the shorthand declaration: 'lgRow&' is the same as `lgRow as Long'
Dim lgRow&, lgCol&, lgLastRow&
' Replaces the code starting with the next comment
'create columns and add color and text dynamically
For Each wks In ActiveWindow.SelectedSheets
With wks
For lgCol = .Cells.SpecialCells(xlCellTypeLastCell).Column To 2 Step -1
' Insert a column (not sure why you're not doing this after the last column also)
.Columns(lgCol).Insert
' Get last row with data in the column 1 to the left
With .Columns(lgCol - 1)
lgLastRow = .Cells(.Cells.Count).End(xlUp).Row
End With
' In the inserted column:
' o Set cell color
' o Set value to corresponding cell to the left, appending ' - Comparison'
For lgRow = 1 To lgLastRow
With .Cells(lgRow, lgCol)
.Interior.Color = 65535
.Value = .Offset(0, -1) & " - Comparison"
End With
Next lgRow
Next lgCol
End With
Next wks
Note 1: Not sure of the reason, but your code inserts the 'comparison columns' after each column, except the last column (of the copied data). If I understand your intent correctly, I'd assume you want to do this for the last column also. If that's true:
'change this line
For lgCol = .Cells.SpecialCells(xlCellTypeLastCell).Column To 2 Step -1
'To:
For lgCol = .Cells.SpecialCells(xlCellTypeLastCell).Column + 1 To 2 Step -1
Note 2: My code changes write <cell value> & " - Comparison" to all cells in each column, down to the last non-blank cell in each 'compared' column (including blank cells above that). If you want to do that write for all rows in the copied data range (whether cells are blank or not) you could simplify the code by placing the following:
' Insert this:
lgLastRow = .Cells.SpecialCells(xlCellTypeLastCell).Row
'above line:
For lgCol = ....
And remove this:
' Get last row with data in the column 1 to the left
With .Columns(iCol - 1)
lgLastRow = .Cells(.Cells.Count).End(xlUp).Row
End With
Other Note / Pointers:
Recommend Option Explicit at the top of all modules (just saves a lot of debugging due to typos)
There's no need (and it's not good practice) to declare Public variables that are only used locally in a given Sub or Function. Instead, declare same locally (usually at the top of the Sub or Function).
It's good practice to use the leading characters of variable names to ID the data type. Can be any length, but is commonly 1, 2 or 3 chars (coder's preference). e.g. Above I used lg to ID long data types. Similarly, I use in for Integer, st for String, rg for Range, etc.

Related

Find the maximum consecutive repeated value on the bases of two columns

I need the expert help in VBA as I am new. Actually I am looking for Vba code for Consecutive Count on the bases of two column (Serial Number and Alert Code) on button click event. The Column row are not fixed (dynamically change). The Consecutive count is maximum repeat count for Alert Code per Serial number. This should display in output worksheet as per max repeat Alert count per Serial number
Input Worksheet:
Expected Output :
The repeat count work as below pattern from Input sheet (Just for reference only).
Mine source code as below but this does not reference the 1st Column Serial Number (This only work for One column like AlertCode) :
Sub ConsecutiveCount()
Dim lr As Long, c As Range, a As Long
Application.ScreenUpdating = False
lr = Worksheets("Count2").Cells(Rows.Count, 1).End(xlUp).Row
For Each c In Range("B2:B" & lr)
If c.Value <> c.Offset(1).Value Then
a = Cells(c.Row, 3).End(xlUp).Row
' Range(Cells(c.Row, 4), Cells(c.Row, 4).End(xlUp).Offset(1)).Value = c.Row - a
Cells(c.Row, 3).Value = c.Row - a
Else
End If
Next c
Application.ScreenUpdating = True
End Sub
Current Output (Serial number not included)
Screenshot(s) / here(♪) refers:
Named ranges/setup
First, define a couple of named ranges to assist with referencing / formulating in VBA:
Name: range_data: dynamic range that references the two columns of interest (here, col 1&2 in Sheet1):
Refers to: =Sheet1!$D$3:OFFSET(Sheet1!$E$3,COUNTA(Sheet1!$E$3:$E$99995)-1,0,1,1)
Name: range_summary_startcell: a static range that references the desired upper-left cell of the output table / summary.
Refers to: =Sheet1!$G$3
The summary table itself shall comprise a number of rows (depending upon range_data) and 3 columns (given the input/Q) - this will be produced by the macro (code below) and can be seen in screenshot above (G3:I5) - the macro functions shall determine the appropriate dimensions automatically
Code
With these two named ranges (i.e. 'range_data' & 'range_summary_startcell') defined, the following VB code produces the desired output per your Q:
Sub Macro_Summary()
'
'JB_007 07/01/2022
'
'
Application.ScreenUpdating = True
Range("range_summary_startcell").Select
ActiveCell.Formula2R1C1 = "=UNIQUE(range_data)"
ActiveSheet.Calculate
x = ActiveCell.End(xlDown).Row
Set range_count = ActiveCell.Offset(0, 2)
range_count.Select
range_count.Formula2R1C1 = _
"=COUNTIFS(INDEX(range_data,0,2),RC[-1],INDEX(range_data,0,1),RC[-2])"
Selection.AutoFill Destination:=Range(range_count, range_count.Offset(x - range_count.Row))
ActiveSheet.Calculate
End Sub
Caveats: assumes you have Office 365 compatible version of Excel
GIF - Running Macro
Notes (♪) saved as macro-free workbook for your own security if you wish to download underlying workbook - otherwise identical to screenshot(s) in this proposed soln.
Sub ConsecutiveCount()
Dim srcLastRow As Long, cntConsec As Long, i As Long
Dim rng As Range
Dim srcArr() As Variant
Dim srcSht As Worksheet
Dim destsht As Worksheet
Dim destArr() As Variant
Dim combID As String
Dim splitID As Variant
Application.ScreenUpdating = False
Set srcSht = Worksheets("Input")
Set destsht = Worksheets("Output")
With srcSht
srcLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1 ' include 1 blank line
srcArr = .Range(.Cells(2, "A"), .Cells(srcLastRow, "B"))
End With
Dim dict As Object
Dim dKey As Variant
Set dict = CreateObject("Scripting.dictionary")
cntConsec = 0
For i = LBound(srcArr) To UBound(srcArr)
cntConsec = cntConsec + 1
If i <> UBound(srcArr) Then
If srcArr(i, 1) <> srcArr(i + 1, 1) Or srcArr(i, 2) <> srcArr(i + 1, 2) Then
combID = srcArr(i, 1) & "|" & srcArr(i, 2)
If dict.Exists(combID) Then
' check if sum is more
If dict(combID) < cntConsec Then ' If new max for combination
dict(combID) = cntConsec
End If
Else
' add to dictionary
dict(combID) = cntConsec
End If
cntConsec = 0
End If
End If
Next i
ReDim destArr(1 To dict.Count, 1 To 3)
i = 0
For Each dKey In dict.keys
splitID = Split(dKey, "|")
i = i + 1
destArr(i, 1) = splitID(0)
destArr(i, 2) = splitID(1)
destArr(i, 3) = dict(dKey)
Next dKey
destsht.Range("A2").Resize(UBound(destArr), 3).Value = destArr
Application.ScreenUpdating = True
End Sub

Moving rows from one worksheet to specific worksheets based on keywords found in string in a specific column in master worksheet

I have an Excel worksheet called "Main" which includes a set amount of columns, one of which contains a listing of different codes (CVE's) regarding patches that need to be installed on worksheets based on criteria from the internet.
The codes to search for are not in a set format, other than being in strings containing the code.
I manually created a number of worksheets based on keywords in these strings, that will eventually, contain all the lines from the master sheet, but only those defined by the name of the keyword I want.
For example, I have a worksheet named "Microsoft" that should contain all the rows from the master sheet that refer to Microsoft CVE's, based on a search of the string and finding the word "Microsoft". Same for Adobe and so on.
I created a script to copy the rows, as well as create a new Index sheet that lists the amount of rows found for each keyword that have been copied from the master sheet to the relevant sheet.
And this is where I get lost.
I have 18 worksheets which are also keywords. I can define a single keyword and then copy everything over from the main worksheet for one keyword.
I need a loop (probably a loop within a loop) that reads the worksheet names as defined in the Index, searches for all the relevant rows that contain a CVE regarding that keyword, and then copy the row over to the relevant worksheet that I created into the relevant row on that worksheet.
For example, if I have copied two rows, the next one should be written to the next row and so on, until I have looped through all the worksheet (keyword) names and have reached the empty row after the last name in the Index sheet.
My code, set for only one keyword for a limited run to test works.
I need to loop through all the keywords and copy all the data.
In the end, I want to copy the relevant row from the master worksheet (Main) to the relevant worksheet (based on keyword worksheet name in the Index worksheet), and delete the row after it was copied from the master worksheet.
I should end up with all the data split into the relevant worksheets and an empty (except for headers) master worksheet.
This is what I have so far (from various examples and my own stuff).
Public WSNames() As String
Public WSNum() As Long
Public I As Long
Public ShtCount As Long
Sub MoveBasedOnValue()
Dim CVETitle As String
Dim xRg As Range
Dim xCell As Range
Dim A As Long
Dim B As Long
Dim C As Long
Dim D As Long
Dim CountCop As Long
A = Worksheets("Main").UsedRange.Rows.Count
A = A + 1
'Create an index of the worksheet names to work with for moving the data and counting the lines in the WS
ReadWSNames
B = Worksheets(WSNames(2)).UsedRange.Rows.Count
B = B + 1 'Place under the last row for start
'Range to read and scan from
Set xRg = Worksheets("Main").Range("E5:E" & A)
On Error Resume Next
Application.ScreenUpdating = False
'For C = 1 To xRg.Count
For C = 1 To 5
'Read in the string to search from the Main WS
CVETitle = CStr(xRg(C).Value)
'Find if the word we want exists in the string
If InStr(1, CVETitle, WSNames(2)) > 0 Then
xRg(C).EntireRow.Copy Destination:=Worksheets(WSNames(2)).Range("A" & B + 1)
CountCop = Worksheets("Index").Range("B3").Value
CountCop = CountCop + 1
Worksheets("Index").Range("B3").Value = CountCop
'xRg(C).EntireRow.Delete
'If CStr(xRg(C).Value) = WSNames(2) Then
'C = C - 1
'End If
B = B + 1
End If
Next
Application.ScreenUpdating = True
End Sub
Sub ReadWSNames()
ReDim WSNames(1 To ActiveWorkbook.Sheets.Count)
ReDim WSNum(1 To ActiveWorkbook.Sheets.Count)
Dim MyIndex As Worksheet
ShtCount = Sheets.Count
'Read sheetnames and number of lines in each WS into arrays and clear the sheets other than the main one
If Not IndexExists("Index") Then
For I = 1 To ShtCount
WSNames(I) = Sheets(I).Name
If WSNames(I) <> "Main" Then ActiveWorkbook.Worksheets(WSNames(I)).Range("5:10000").EntireRow.Delete
WSNum(I) = Worksheets(WSNames(I)).UsedRange.Rows.Count
WSNum(I) = WSNum(I) - 3
Next I
'Add an index worksheet before the main worksheet and make sure one doesn't exist
Worksheets.Add Before:=Worksheets(1)
ActiveSheet.Name = "Index" 'Give new Ws a name
Application.DefaultSheetDirection = xlLTR 'Make direction suited to English
'Write headers and set parameters
Range("A1").Value = "WS Names"
Range("B1").Value = "Count"
With Range("A1:B1")
.Font.Size = 14
.Font.Bold = True
.Font.Color = vbBlue
End With
Columns("A:B").AutoFit
Columns("B:B").HorizontalAlignment = xlCenter
'Write data from arrays into Index WS
ActiveCell.Offset(1, 0).Select
For I = 1 To ShtCount 'Write values to Index WS
ActiveCell.Value = WSNames(I) 'Write Worksheet name
ActiveCell.Offset(0, 1) = WSNum(I) 'Write number of rows already existing in Ws
ActiveCell.Offset(1, 0).Select 'Move one cell down
Next I
Worksheets("Index").Activate 'Make Index the active ws
Range("A2").Select 'Select first cell to read data from
I = 1
X = 2
Do While Not IsEmpty(Range("A" & X)) 'Read values back into array to make sure i's all there
WSNames(I) = ActiveCell.Value
WSNum(I) = ActiveCell.Offset(0, 1).Value
ActiveCell.Offset(1, 0).Select 'Move one cell down
I = I + 1
X = X + 1
Loop
Worksheets("Main").Activate 'Make Main the active ws
Else 'If Index exists, simply read the data into the arrays
Worksheets("Index").Activate 'Make Index the active ws
Range("A2").Select 'Select first cell to read data from
I = 1
X = 2
Do While Not IsEmpty(Range("A" & X)) 'Read values back into array to make sure i's all there
WSNames(I) = ActiveCell.Value
WSNum(I) = ActiveCell.Offset(0, 1).Value
ActiveCell.Offset(1, 0).Select 'Move one cell down
I = I + 1
X = X + 1
Loop
Worksheets("Main").Activate 'Make Main the active ws
Exit Sub
End If
End Sub
Function IndexExists(sSheet As String) As Boolean
On Error Resume Next
sheetExist = (ActiveWorkbook.Sheets(sSheet).Index > 0)
End Function
Because the CVE strings are not the same, it is not possible to sort them, so there can be a CVE for Microsoft in one row and then a few rows of other CVEs, and the Microsoft again and so on.
I tried to post picture examples of the Index worksheet, the worksheet names, and an example of the data in the lines, but I don't have enough reputation.
So, a few examples (out of over 7,000 lines) of the string data is that is searched for the keyword (column E):
*[MS20-DEC] Microsoft Windows Cloud Files Mini Filter Driver Elevation of Privilege Vulnerability - CVE-2020-17134 [APSB16-04]
*Adobe Flash Player <20.0.0.306 Remote Code Execution Vulnerability - CVE-2016-0964 [MS21-JUN] *
*Microsoft Kerberos AppContainer Security Feature Bypass Vulnerability - CVE-2021-31962
*McAfee Agent <5.6.6 Local Privilege Escalation Vulnerability - CVE-2020-7311
*7-Zip <18.00 and p7zip Multiple Memory Corruption Vulnerabilities - CVE-2018-5996
Scan the sheets for a word and then scan down the strings in sheet Main for that word. Scan up the sheet to delete rows.
update - muliple words per sheet
Option Explicit
Sub SearchWords()
Const COL_TEXT = "E"
Const ROW_TEXT = 5 ' first line of text
Dim wb As Workbook
Dim ws As Worksheet, wsMain As Worksheet, wsIndex As Worksheet
Dim arData(), arDelete() As Boolean
Dim lastrow As Long, i As Long, n As Long, r As Long
Dim word As String, txt As String
Dim t0 As Single: t0 = Timer
Dim w
' create index if not exists
CreateIndex
Set wb = ActiveWorkbook
With wb
Set wsMain = .Sheets("Main")
Set wsIndex = .Sheets("Index")
End With
' copy strings into array for speed
With wsMain
lastrow = .Cells(.Rows.Count, COL_TEXT).End(xlUp).Row
If lastrow < ROW_TEXT Then
MsgBox "No text found in column " & COL_TEXT, vbCritical
Exit Sub
End If
arData = .Cells(1, COL_TEXT).Resize(lastrow).Value2
ReDim arDelete(1 To lastrow)
End With
' scan main for each keyword in index
i = 2 ' index row
Application.ScreenUpdating = False
For Each ws In wb.Sheets
If ws.Name <> "Index" And ws.Name <> "Main" Then
'word = ws.Name
lastrow = ws.Cells(ws.Rows.Count, "E").End(xlUp).Row
For Each w In Split(ws.Name, "+")
word = Trim(w)
For r = ROW_TEXT To UBound(arData)
txt = arData(r, 1)
If InStr(1, txt, word, vbTextCompare) > 0 Then
lastrow = lastrow + 1
wsMain.Rows(r).Copy ws.Cells(lastrow, 1)
arDelete(r) = True
n = n + 1
End If
Next
Next
' update index
wsIndex.Cells(i, 1) = ws.Name
wsIndex.Cells(i, 2) = lastrow - 1
i = i + 1
End If
Next
' delete or highlight rows
' scan upwards
For r = UBound(arDelete) To ROW_TEXT Step -1
If arDelete(r) = True Then
wsMain.Cells(r, COL_TEXT).Interior.Color = vbYellow
'wsMain.Rows(r).Delete 'uncomment to delete
End If
Next
Application.ScreenUpdating = True
MsgBox n & " lines copied", vbInformation, Format(Timer - t0, "0.0 secs")
End Sub
Sub CreateIndex()
Dim ws As Worksheet, bHasIndex As Boolean
For Each ws In Sheets
If ws.Name = "Index" Then bHasIndex = True
Next
' create index
If Not bHasIndex Then
Worksheets.Add(before:=Sheets(1)).Name = "Index"
End If
' format index
With Sheets("Index")
.Cells.Clear
With .Range("A1:B1")
.Value2 = Array("WS Names", "Count")
.Font.Size = 14
.Font.Bold = True
.Font.Color = vbBlue
End With
.Columns("A:B").AutoFit
.Columns("B:B").HorizontalAlignment = xlCenter
End With
End Sub

How do i copy and paste data to worksheets that i created in VBA using for loop?

Im trying to copy and paste my data and assign them into different worksheets.For example, if column F is martin 1, the entire row that has martin1 will be paste to worksheets("Index1"). Same thing for Charlie 1 and it will be paste to worksheets("Index2"). However, I faced with a object defined error here as shown in my code below. Any ideas how to solve it?
Sub SaveRangewithConsecutiveDuplicateValuestoNewSheet()
'Define all variables
Dim wb As Workbook, ws As Worksheet, sCel As Range, rwNbr As Long
Set wb = ThisWorkbook 'Set workbook variable
Set ws = wb.Worksheets("Sheet1") 'set worksheet variable using workbook variable
Set sCel = ws.Cells(1, 6) 'Set the first start cell variable to test for duplicate values
Dim i As Integer
Dim site_i As Worksheet
For i = 1 To 3
Set site_i = Sheets.Add(after:=Sheets(Worksheets.count))
site_i.Name = "Index" & CStr(i)
Next i
Application.DisplayAlerts = False
For rwNbr = 2 To ws.Cells(ws.Rows.count, 6).End(xlUp).Offset(1).Row Step 1 'Loop
If ws.Cells(rwNbr, 6).Value = "Martin1" Then
ws.Range(sCel, ws.Cells(rwNbr, 6)).EntireRow.Copy Destination:=Sheets("Index1").Range("A1")
ElseIf ws.Cells(rwNbr, 6).Value = "Charlie1" Then
ws.Range(sCel, ws.Cells(rwNbr - ws.UsedRange.Rows.count, 6)).EntireRow.CopyDestination:=Sheets("Index2").Range("A1") '<----application defined or object defined error here
End If
Next rwNbr
Application.DisplayAlerts = True
End Sub
This is the link to my worksheet. https://www.dropbox.com/home?preview=Sample+-+Copy.xlsm
The final output should look something like this...
If your raw data does not have a header row then I would use a loop to gather up your target cells and copy them accordingly.
You will need to update your 3 target values inside Arr to Charlie1, Martin1, etc.
Macro Steps
Loop through each name in Arr
Loop through each row in Sheet1
Add target row to a Union (collection of cells)
Copy the Union to the target sheet where target Sheet Index # = Arr position + 1
Sub Filt()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim cs As Worksheet
Dim Arr: Arr = Array("Value1", "Value2", "Value3")
Dim x As Long, Target As Long, i As Long
Dim CopyMe As Range
'Create 3 Sheets, move them to the end, rename
For x = 1 To 3
Set cs = ThisWorkbook.Sheets.Add(After:=Sheets(ThisWorkbook.Sheets.Count))
cs.Name = "Index" & x
Next x
lr = ws.Range("F" & ws.Rows.Count).End(xlUp).Row
'Loop through each name in array
For Target = LBound(Arr) To UBound(Arr)
'Loop through each row
For i = 1 To lr
'Create Union of target rows
If ws.Range("F" & i) = Arr(Target) Then
If Not CopyMe Is Nothing Then
Set CopyMe = Union(CopyMe, ws.Range("F" & i))
Else
Set CopyMe = ws.Range("F" & i)
End If
End If
Next i
'Copy the Union to Target Sheet
If Not CopyMe Is Nothing Then
CopyMe.EntireRow.Copy Destination:=ThisWorkbook.Sheets("Index" & Target + 1).Range("A1")
Set CopyMe = Nothing
End If
Next Target
End Sub
Tested and working as expected on my end, however....
If you had headers this would be much easier with a copy/paste. If you run the same macro on same book twice this will break for many reasons such as having duplicated sheet names, breaking the relationship between Sheet Index # = Arr Position + 1, etc...

To extract certain location cell values from mutiple worksheets in Excel along with worksheet name

I have encountered a problem during my work.
There are over one hundred worksheets in my excel, and I would like to extract values from certain location (I25:K25, I50:K50, I95:K95) along with the worksheet name on the beside for every worksheet.
I would like to have these extracted values pasted on a new worksheet.
Does anyone know if there is any excel formula or excel macro I could use to achieve the goal?
I'm not proficient with formulas, but it would certainly be doable with VBA.
Look into For Each..Next loops, which I think you should use to go through all sheets.
Next, the .Name property will extract the sheet's name for you. You can save this to a variable and fill a cell with.
Getting values from one cell to another is as easy as
.Sheets(1).Range("A1:B1").Value = .Sheets(2).Range("A1:B1").Value
Note that SO is not a free code writing service, so I won't go as far as writing the entire procedure for you. If you have some code but encounter problems, come back to us.
Useful links:
looping through sheets
Copying cell values
Workbook and -sheet objects
This code loop all sheets except sheet called Results, code sheet name in column A and range values in columns B:D.
Option Explicit
Sub test()
Dim ws As Worksheet, wsResults As Worksheet
Dim Lastrow As Long
With ThisWorkbook
Set wsResults = .Worksheets("Results")
For Each ws In .Worksheets
If ws.Name <> "Results" Then
Lastrow = wsResults.Cells(wsResults.Rows.Count, "A").End(xlUp).Row
wsResults.Range("A" & Lastrow + 1 & ":A" & Lastrow + 3).Value = ws.Name
ws.Range("I25:K25").Copy wsResults.Range("B" & Lastrow + 1)
ws.Range("I50:K50").Copy wsResults.Range("B" & Lastrow + 2)
ws.Range("I95:K95").Copy wsResults.Range("B" & Lastrow + 3)
End If
Next ws
End With
End Sub
Ranges to New Master Worksheet
Workbook
Download
(Dropbox)
Adjust the values in the constants (Const) section to fit your
needs.
The code will only affect the workbook containing it.
The code will delete a possible existing worksheet named after
cTarget, but will only read from all other worksheets. Then it will
create a worksheet named after cTarget and write the read data to it.
To run the code, go to the Developer tab and click Macros and
click RangesToNewMasterWorksheet.
Sub RangesToNewMasterWorksheet()
' List of Source Row Range Addresses
Const cRowRanges As String = "I25:K25, I50:K50, I95:K95"
Const cTarget As String = "Result" ' Target Worksheet Name
Const cHead1 As String = "ID" ' 1st Column Header
Const cHead2 As String = "Name" ' 2nd Column Header
Const cHead As Long = 2 ' Number of First Header Columns
Const cRange As String = "Rng" ' Range (Area) String
Const cColumn As String = "C" ' Column String
Const cFirstCell As String = "A1" ' Target First Cell Range Address
Dim wb As Workbook ' Source/Target Workbook
Dim ws As Worksheet ' Current Source/Target Worksheet
Dim rng As Range ' Current Source/Target Range
Dim vntT As Variant ' Target Array
Dim vntA As Variant ' Areas Array
Dim vntR As Variant ' Range Array
Dim NoA As Long ' Number of Areas
Dim NocA As Long ' Number of Area Columns (in Target Array)
Dim i As Long ' Area Counter
Dim j As Long ' Area Column Counter
Dim k As Long ' Target Array Row Counter
Dim m As Long ' Target Array Column Counter
' Speed Up.
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' Create a reference to ThisWorkbook i.e. the workbook containing this code.
Set wb = ThisWorkbook
' Task: Delete a possibly existing instance of Target Worksheet.
Application.DisplayAlerts = False
On Error Resume Next
wb.Worksheets(cTarget).Delete
On Error GoTo 0
Application.DisplayAlerts = True
' Handle unexpected error.
On Error GoTo UnExpected
' Task: Calculate size of Target Array.
' Create a reference to the 1st worksheet. (Note: Not sheet.)
For Each ws In wb.Worksheets
Exit For
Next
' Create a reference to the Source Row Range (in 1st worksheet.
Set rng = ws.Range(cRowRanges)
With rng
NoA = .Areas.Count
ReDim vntA(1 To NoA)
' Calculate Number of Area Columns (NocA).
For i = 1 To NoA
With .Areas(i)
' Write number of columns of current Area (i) to Areas Array.
vntA(i) = .Columns.Count
NocA = NocA + vntA(i)
End With
Next
End With
' Resize Target Array.
' Rows: Number of worksheets + 1 for headers.
' Columns: Number of First Header Columns + Number of Area Columns.
ReDim vntT(1 To wb.Worksheets.Count + 1, 1 To cHead + NocA)
' Task: Write 'Head' (headers) to Target Array.
vntT(1, 1) = cHead1
vntT(1, 2) = cHead2
k = cHead
For i = 1 To NoA
For j = 1 To vntA(i)
k = k + 1
vntT(1, k) = cRange & i & cColumn & j
Next
Next
' Task Write 'Body' (all except headers) to Target Array.
k = 1
For Each ws In wb.Worksheets
k = k + 1
vntT(k, 1) = k - 1
vntT(k, 2) = ws.Name
Set rng = ws.Range(cRowRanges)
m = cHead
For i = 1 To NoA
vntR = rng.Areas(i)
For j = 1 To vntA(i)
m = m + 1
vntT(k, m) = vntR(1, j)
Next
Next
Next
' Task: Copy Target Array to Target Worksheet.
' Add new worksheet to first tab (1).
Set ws = wb.Sheets.Add(Before:=wb.Sheets(1))
ws.Name = cTarget
' Calculate Target Range i.e. resize First Cell Range by size of
' Target Array.
Set rng = ws.Range(cFirstCell).Resize(UBound(vntT), UBound(vntT, 2))
rng = vntT
' Task: Apply Formatting.
' Apply formatting to Target Range.
With rng
.Columns.AutoFit
' Apply formatting to Head (first row).
With .Resize(1)
.Interior.ColorIndex = 49
With .Font
.ColorIndex = 2
.Bold = True
End With
.BorderAround xlContinuous, xlThin
.Borders(xlInsideVertical).LineStyle = xlContinuous
End With
' Apply formatting to Body (all except the first row).
With .Resize(rng.Rows.Count - 1).Offset(1)
.Interior.ColorIndex = xlColorIndexNone
With .Font
.ColorIndex = xlColorIndexAutomatic
.Bold = False
End With
.Borders(xlEdgeLeft).LineStyle = xlContinuous
.Borders(xlEdgeRight).LineStyle = xlContinuous
.Borders(xlInsideVertical).LineStyle = xlContinuous
End With
End With
MsgBox "The program finished successfully.", vbInformation, "Success"
ProcedureExit:
' Speed Down.
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
Exit Sub
UnExpected:
MsgBox "An unexpected error occurred. Error '" & Err.Number & "': " _
& Err.Description, vbCritical, "Error"
GoTo ProcedureExit
End Sub

Normalizing Excel Grid Intersection data into a flat list

I am trying to get Excel data, which was mapped using a grid/matrix mapping into a de-normalized for so that i can enter the data into a database.
How do you copy data in a grid from one excel sheet to the other as follow illustrated below.
I was trying something like this... but as you can see, i am far off!
Sub NormaliseList(mySelection As Range)
Dim cell As Range
Dim i As Long
i = 1
For Each cell In mySelection
If cell <> "" Then
Sheets(2).Range("A" & i).Value = cell(cell.Row, 1).Value
Sheets(2).Range("B" & i).Value = cell.Value
Sheets(2).Range("C" & i).Value = cell(1, cell.Column).Value
i = i + 1
Next cell
End Sub
For Reference. I Updated my code..
Simply add the code, assign macro shortcut to the function
Select the range that contains the intersection data (not the row and column data)
Run macro (Beware, sheet 2 will have data added in normalised form)
If there are multiple headings that are needed i figured i would consolidate into one column then perform a "text to columns" after processing.
Sub NormaliseList()
' to run - assign macro shortcut to sub - Select Intersection data (not row and column headings and run)
Dim Rowname, ColumnName, IntValue As String
Dim x, cntr As Integer
Dim test As Boolean
cntr = 0
For x = 1 To Selection.Count
If Selection(x).Value <> "" Then
cntr = cntr + 1
Rowname = ActiveSheet.Cells(Selection.Cells(x).Row, Selection.Column - 1)
ColumnName = ActiveSheet.Cells(Selection.Row - 1, Selection.Cells(x).Column)
IntValue = Selection(x).Value
test = addrecord(Rowname, ColumnName, IntValue, cntr)
End If
Next x
End Sub
Function addrecord(vA, vB, vC As String, rec As Integer) As Boolean
'Make sure that you have a worksheet called "Sheet2"
Sheets("Sheet2").Cells(rec, 1) = vA
Sheets("Sheet2").Cells(rec, 2) = vB
Sheets("Sheet2").Cells(rec, 3) = vC
End Function
I've got two posts, with usable code and downloadable workbook, on doing this in Excel/VBA on my blog:
http://yoursumbuddy.com/data-normalizer
http://yoursumbuddy.com/data-normalizer-the-sql/
Here's the code:
'Arguments
'List: The range to be normalized.
'RepeatingColsCount: The number of columns, starting with the leftmost,
' whose headings remain the same.
'NormalizedColHeader: The column header for the rolled-up category.
'DataColHeader: The column header for the normalized data.
'NewWorkbook: Put the sheet with the data in a new workbook?
'
'NOTE: The data must be in a contiguous range and the
'rows that will be repeated must be to the left,
'with the rows to be normalized to the right.
Sub NormalizeList(List As Excel.Range, RepeatingColsCount As Long, _
NormalizedColHeader As String, DataColHeader As String, _
Optional NewWorkbook As Boolean = False)
Dim FirstNormalizingCol As Long, NormalizingColsCount As Long
Dim ColsToRepeat As Excel.Range, ColsToNormalize As Excel.Range
Dim NormalizedRowsCount As Long
Dim RepeatingList() As String
Dim NormalizedList() As Variant
Dim ListIndex As Long, i As Long, j As Long
Dim wbSource As Excel.Workbook, wbTarget As Excel.Workbook
Dim wsTarget As Excel.Worksheet
With List
'If the normalized list won't fit, you must quit.
If .Rows.Count * (.Columns.Count - RepeatingColsCount) > .Parent.Rows.Count Then
MsgBox "The normalized list will be too many rows.", _
vbExclamation + vbOKOnly, "Sorry"
Exit Sub
End If
'You have the range to be normalized and the count of leftmost rows to be repeated.
'This section uses those arguments to set the two ranges to parse
'and the two corresponding arrays to fill
FirstNormalizingCol = RepeatingColsCount + 1
NormalizingColsCount = .Columns.Count - RepeatingColsCount
Set ColsToRepeat = .Cells(1).Resize(.Rows.Count, RepeatingColsCount)
Set ColsToNormalize = .Cells(1, FirstNormalizingCol).Resize(.Rows.Count, NormalizingColsCount)
NormalizedRowsCount = ColsToNormalize.Columns.Count * .Rows.Count
ReDim RepeatingList(1 To NormalizedRowsCount, 1 To RepeatingColsCount)
ReDim NormalizedList(1 To NormalizedRowsCount, 1 To 2)
End With
'Fill in every i elements of the repeating array with the repeating row labels.
For i = 1 To NormalizedRowsCount Step NormalizingColsCount
ListIndex = ListIndex + 1
For j = 1 To RepeatingColsCount
RepeatingList(i, j) = List.Cells(ListIndex, j).Value2
Next j
Next i
'We stepped over most rows above, so fill in other repeating array elements.
For i = 1 To NormalizedRowsCount
For j = 1 To RepeatingColsCount
If RepeatingList(i, j) = "" Then
RepeatingList(i, j) = RepeatingList(i - 1, j)
End If
Next j
Next i
'Fill in each element of the first dimension of the normalizing array
'with the former column header (which is now another row label) and the data.
With ColsToNormalize
For i = 1 To .Rows.Count
For j = 1 To .Columns.Count
NormalizedList(((i - 1) * NormalizingColsCount) + j, 1) = .Cells(1, j)
NormalizedList(((i - 1) * NormalizingColsCount) + j, 2) = .Cells(i, j)
Next j
Next i
End With
'Put the normal data in the same workbook, or a new one.
If NewWorkbook Then
Set wbTarget = Workbooks.Add
Set wsTarget = wbTarget.Worksheets(1)
Else
Set wbSource = List.Parent.Parent
With wbSource.Worksheets
Set wsTarget = .Add(after:=.Item(.Count))
End With
End If
With wsTarget
'Put the data from the two arrays in the new worksheet.
.Range("A1").Resize(NormalizedRowsCount, RepeatingColsCount) = RepeatingList
.Cells(1, FirstNormalizingCol).Resize(NormalizedRowsCount, 2) = NormalizedList
'At this point there will be repeated header rows, so delete all but one.
.Range("1:" & NormalizingColsCount - 1).EntireRow.Delete
'Add the headers for the new label column and the data column.
.Cells(1, FirstNormalizingCol).Value = NormalizedColHeader
.Cells(1, FirstNormalizingCol + 1).Value = DataColHeader
End With
End Sub
You’d call it like this:
Sub TestIt()
NormalizeList ActiveSheet.UsedRange, 1, "Name", "Count", False
End Sub

Resources