Infinite loop while gathering datasets from several worksheets - vba

This is my first time to code in VBA.
I have several worksheets in a file and they are in order by dates.
So what I am trying to do is to collect data sets in a worksheet if they have the same period of time.
date1 value1
date2 value2
date3 value3
Since they are in order I just compare the first date values and if they are different it moves on to the next worksheet. If they are the same then copy the value and do the same process until it reaches the last worksheet.
However it copies one worksheet fine but after that Excel freezes.
I would be appreciated if you find any errors or give me other suggestions to do it.
Following is my code:
Sub matchingStock()
Dim sh1 As Worksheet, sh2 As Worksheet
' create short references to sheets
' inside the Sheets() use either the tab number or name
Set sh1 = Sheets("combined")
Dim col As Long
'since first column is for Tbill it stock price should place from the third column
col = 3
Dim k As Long
'go through all the stock worksheets
For k = Sheets("WLT").Index To Sheets("ARNA").Index
Set sh2 = Sheets(k)
' Create iterators
Dim i As Long, j As Long
' Create last rows values for the columns you will be comparing
Dim lr1 As Long, lr2 As Long
' create a reference variable to the next available row
Dim nxtRow As Long
' Create ranges to easily reference data
Dim rng1 As Range, rng2 As Range
' Assign values to variables
lr1 = sh1.Range("A" & Rows.Count).End(xlUp).Row
lr2 = sh2.Range("A" & Rows.Count).End(xlUp).Row
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
Application.ScreenUpdating = False
' Loop through column A on sheet1
For i = 2 To lr1
Set rng1 = sh1.Range("A" & i)
' Loop through column A on sheet1
For j = 2 To lr2
Set rng2 = sh2.Range("A" & j)
' compare the words in column a on sheet1 with the words in column on sheet2
'Dim date1 As Date
'Dim date2 As Date
'date1 = TimeValue(sh1.Range("A3"))
'date2 = TimeValue(sh2.Range("A3"))
sh1.Cells(1, col).Value = sh2.Range("A1").Value
' find next empty row
nxtRow = sh1.Cells(Rows.Count, col).End(xlUp).Row + 1
' copy the word in column A on sheet2 to the next available row in sheet1
' copy the value ( offset(0,1) Column B ) to the next available row in sheet1
sh1.Cells(nxtRow, col).Value = rng2.Offset(0, 6).Value
'when the date is different skip to the next worksheet
Set rng2 = Nothing
Next j
Set rng1 = Nothing
Next i
'sh3.Rows("1:1").Delete
Else
GoTo Skip
End If
Skip:
col = col + 1
Next k
End Sub

I cannot identify a specific error so this is a list of suggestions that may help you identify the error and may help improve your code.
Suggestion 1
Do you think the Else block of If-Then-Else-End-If is compulsory?
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
:
Else
GoTo Skip
End If
Skip:
is the same as:
If sh1.Range("A3").Value = sh2.Range("A3").Value Then
:
End If
Suggestion 2
I do not like:
For k = Sheets("WLT").Index To Sheets("ARNA").Index
The value of property Index for a worksheet may not what you think it is. This may not give you the set or sequence of worksheets you expect. Do you want every worksheet except "Combined"? The following should be more reliable:
For k = 1 To Worksheets.Count
If Worksheets(k).Name <> sh1.Name Then
:
End If
Next
Suggestion 3
You use:
.Range("A" & Rows.Count)
.Range("A3")
.Cells(1, col).Value
.Cells(Rows.Count, col)
rng2.Offset(0, 6)
All these methods of identifying a cell or a range have their purposes. However, I find it confusing to use more than one at a time. I find .Cells(row, column) and .Range(.Cells(row1, column1), .Cells(row2, column2)) to be the most versatile and use them unless there is a powerful reason to use one of the other methods.
Suggestion 4
I cannot decypher what this code is attempting to achieve.
You say: "I have several worksheets in a file and they are in order by dates. So what I am trying to do is to collect data sets in a worksheet if they have the same period of time."
If you have set Worksheet("combined").Range("A3").Value to a particular date and you want to collect data from all those sheets with the same value in cell A3 then the outer For-Loop and the If give this effect. But if so, if does not matter how the worksheets are ordered. Also you start checking cell values from row 2 which suggests row 3 is a regular data row.
The outer loop is for each worksheet, the next loop is for each row in "combined" and the inner loop is for each row in the worksheet selected by the outer loop. The middle loop does not appear to do anything but set rng1 which is not used.
Perhaps you can add an explanation of what you are trying to achieve.
Suggestion 5
Are you trying to add an entire column of values from the source worksheets to "Combined". The macro below:
Identifies the next free row in column A of "Combined"
Identifies the last used row in column A of "Sheet2"
Assumes the first interesting row of "Sheet2" is 2.
Adds the entire used range of column A of "Sheet2" (complete with formatting) to the end of "Combined"'s column A in a single statement.
This may demonstrate a better way of achieving the effect you seek.
Sub Test()
Dim RngSrc As Range
Dim RngDest As Range
Dim RowCombNext As Long
Dim RowSrcFirst As Long
Dim RowSrcLast As Long
With Worksheets("Combined")
RowCombNext = .Cells(Rows.Count, "A").End(xlUp).Row + 1
Set RngDest = .Cells(RowCombNext, "A")
End With
With Worksheets("Sheet2")
RowSrcFirst = 2
RowSrcLast = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngSrc = .Range(.Cells(RowSrcFirst, "A"), .Cells(RowSrcLast, "A"))
End With
RngSrc.Copy Destination:=RngDest
End Sub

Related

List table name AND cell in last row and column

I am looking to list all table names in a sheet, together with the table's corresponding cell in the last row and column. The below code finds the table names in sheet "A1.6Laster" (Except table "Lastkategori") and then lists them in sheet "A1.6.5Lastkombinationer".
Since I can add/delete tables i sheet "A1.6Laster", the list is first deleted/cleared.
In other words; the below code work fine listing the names of the tables, but in the column next to the name list I want each table's corresponding cell in the last row and column to be listed as well. Do I need to add some code in the For Each loop?
Any input is welcome, and please ask if you need further information!
Sub Laster()
Dim tbl As ListObject
Dim wsSummary As Worksheet
Dim ws As Worksheet
Dim lRow As Long
Dim SearchText As String
Dim GCell As Range
SearchText = "Laster"
Set GCell = Worksheets("A1.6.5Lastkombinationer").Cells.Find(SearchText).Offset(0)
Set wsSummary = Worksheets("A1.6.5Lastkombinationer")
Set ws = Worksheets("A1.6Laster")
With Worksheets("A1.6.5Lastkombinationer").ListObjects("Laster").DataBodyRange
If .Rows.Count > 1 Then
.Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).Rows.Delete
End If
End With
Worksheets("A1.6.5Lastkombinationer").ListObjects("Laster").DataBodyRange.Rows(1).ClearContents
lRow = GCell.Row
For Each tbl In Worksheets("A1.6Laster").ListObjects
If tbl.Name <> "Lastkategori" Then
lRow = lRow + 1
With wsSummary
.Cells(lRow, "A") = tbl.Name
End With
End If
Next tbl
ws.ListObjects("Lastkategori").ListColumns(1).DataBodyRange.Copy
wsSummary.ListObjects("Laster").DataBodyRange(1, 1).End(xlDown).Offset(1).PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End Sub
I am assuming when you say last row and column means the bottom right hand corner cell of each table.
Add the following snippet within With wsWsummary ... End With. What it does is it takes the range of cells for each table's data range and gets the last row's last column and spits out the data in that cell into the column next to the table's name.
Dim r As Range
Dim last As Range
Set r = tbl.DataBodyRange
Set last = r.Cells(r.Rows.Count, r.Columns.Count)
ws.Cells(lRow, "B").Value = last

Remove all rows that contain value VBA

I've been working on this for longer than I'd like to admit. I'm comparing two Worksheets (A & B). I'm looping through A-Column("B") and for each value in that column I'm checking it against B-Column("C"). If there's a match, I want to delete the entire row.
I've done it a number of different ways and I just can't get it to work. This is the original:
Option Explicit
Sub Purge()
Dim wipWS As Worksheet
Dim invWS As Worksheet
Dim C As Range
Dim SourceLastRow As Long
Dim DestLastRow As Long
Dim LRow As Long
Dim D As Range
Set wipWS = Worksheets("Work in Process")
Set invWS = Worksheets("Inventory Allocation")
With wipWS
' find last row in Work in Process Column B
SourceLastRow = .Cells(.Rows.count, "E").End(xlUp).Row
' find last row in Inventory Allocation Column C
DestLastRow = invWS.Cells(invWS.Rows.count, "B").End(xlUp).Row
' define the searched range in "Inventory Allocation" sheet
Set C = invWS.Range("B1:B" & DestLastRow)
Set D = wipWS.Range("E1:E" & SourceLastRow)
' allways loop backwards when deleting rows or columns
' choose 1 of the 2 For loops below, according to where you want to delete
' the matching records
' --- according to PO request delete the row in Column B Sheet A
' and the row in Column C in "Inventory Allocation" worksheet
' I am looping until row 3 looking at the PO original code
For LRow = SourceLastRow To 1 Step -1
' found a match between Column B and Column C
If Not IsError(Application.Match(.Cells(LRow, "E"), C, 0)) Then
.Cells(LRow, 2).EntireRow.Delete Shift:=xlUp
invWS.Cells(Application.Match(.Cells(LRow, "E"), C, 0), 2).EntireRow.Delete Shift:=xlUp
End If
Next LRow
End With
End Sub
It works, except it leaves 1 row left (that should be deleted). I think I know why it's happening, except I have no idea of how to do it. I've tried a For loop and it works, except I have to set a range (eg., "A1:A200") and I want it to only loop through based on the number of rows.
Any help would be greatly appreciated!
Instead of running 2 loops, you can run 1 For loop in yout Worksheets("Work in Process"), scanning Column B, and then just use the Match function to search for that value in the entire C range - which is Set to Worksheets("Inventory Allocation") at Column C (untill last row that has data).
Note: when deleting rows, allways use a backward loop (For loop counting backwards).
Code
Option Explicit
Sub Purge()
Dim wipWS As Worksheet
Dim invWS As Worksheet
Dim C As Range
Dim SourceLastRow As Long
Dim DestLastRow As Long
Dim LRow As Long
Set wipWS = Worksheets("Work in Process")
Set invWS = Worksheets("Inventory Allocation")
With wipWS
' find last row in Sheet A Column B
SourceLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
' find last row in Sheet B Column C
DestLastRow = invWS.Cells(invWS.Rows.Count, "C").End(xlUp).Row
' define the searched range in "Inventory Allocation" sheet
Set C = invWS.Range("C1:C" & DestLastRow)
' allways loop backwards when deleting rows or columns
' choose 1 of the 2 For loops below, according to where you want to delete
' the matching records
' --- according to PO request delete the row in Column B Sheet A
' and the row in Column C in "Inventory Allocation" worksheet
' I am looping until row 3 looking at the PO original code
For LRow = SourceLastRow To 3 Step -1
' found a match between Column B and Column C
If Not IsError(Application.Match(.Cells(LRow, "B"), C, 0)) Then
.Cells(LRow, 2).EntireRow.Delete Shift:=xlUp
invWS.Cells(Application.Match(.Cells(LRow, "B"), C, 0), 3).EntireRow.Delete Shift:=xlUp
End If
Next LRow
End With
End Sub
You are comparing two Worksheets (A & B). You want to loop through A-Column("B") and for each value in that column, check against B-Column("C").
So you can have a counter (ie. bRow) to keep track of which row you are looking at in worksheet B
Dim cell as Range
Dim bRow as Integer
bRow = 1
With Worksheets("A")
For Each cell in Range(.Range("B1"), .Range("B1").End(xlDown))
If (cell.Value = Worksheets("B").Range("C" & bRow).Value0 Then
cell.EntireRow.Delete Shift:=xlUp
Worksheets("B").Range("C" & bRow).EntireRow.Delete Shift:=xlUp
Else
bRow = bRow + 1
End If
Next cell
End WIth
So I finally figured this out. It's not pretty and I'm sure there is a more elegant way of doing this, but here it is.
Option Explicit
Public Sub purWIP()
Dim wipWS As Worksheet
Dim invWS As Worksheet
Dim P As Range
Dim i As Integer, x As Integer
Set wipWS = Worksheets("Work in Process")
Set invWS = Worksheets("Inventory Allocation")
' Start by checking conditions of a certain row
For x = wipWS.UsedRange.Rows.count To 1 Step -1
With wipWS
' For each cell in wipWS I'm going to check whether a certain condition exists
For Each P In wipWS.Range(.Cells(6, 5), .Cells(wipWS.Rows.count, 5).End(xlUp))
'If it does, then I'm going to check the Inventory Allocation Worksheet to see if there's a match
If (IsDate(P.Offset(0, 7))) Then
invWS.Activate
' I do a for loop here and add 1 to i (this was the part that fixed it)
For i = invWS.UsedRange.Rows.count + 1 To 3 Step -1
If invWS.Cells(i, "B") = P.Offset(0, 0) Then
invWS.Rows(i).EntireRow.Delete Shift:=xlUp
End If
Next
P.EntireRow.Delete Shift:=xlUp
End If
Next
End With
Next
wipWS.Activate
End Sub

Select cells that fit in the range of the counter and concatenate what is selected from the range

I've been working on a Macro that i need to copy, concatenate what has been selected through the counter. e.g. is below
excel snapshot example
so what i want to do is set a count in column c from 1 to "infinite" because each worksheet varies to go up to 10 or hundreds and when the counter hits a value of 1 again to stop concatenate column D what is in the range from 1 to "the last digit it stopped before hitting 1 again" and paste it on a different sheet. I know little to nothing on VBA but I understand the copy and paste to different sheet part. I'm just stuck on the counter and the concatenate part. Here is the code i have so far(i edited it to resemble the example for better reference)
'select counter/concatenate
Sheets(1).Select
Columns("C").EntireColumn
Do
Columns("C").Count
For i = 1 To 9999
Loop While (i <= 1)
If i = 1 Then
select.columns("D")
after the count is where i am stuck. this count is what I've come up with looking at different variations of counters.
I suggest you Forget about column and use just one cell for easier understanding. A cell is a reference that allows you to refer to any other cells on the sheet by using Offsets. You may use two Loops, the outer one crawling the columns, the inner one working downward until it finds 1
Dim i As Long ' note that in VBA integer Overflows at 65535 rows
Dim s As String
Set aCell = Worksheet("Sheet1").Range("D1")
While aCell.Column < 255
i = 0
s = ""
While Not aCell.Offset(i, 0).Value = 1
s = s & aCell.Offset(1, 0).Value
Wend
' paste s somewhere by using range.value = s
Set aCell = aCell.Offset(0, 1)
Wend
By specifying the workbook and worksheet before the range, you may refer to the proper cell without being dependent on the active worksheet or range.
Hope this works for you.
You can try this (not tested):
Dim s As String, firstAddr as String
Dim f as range, iniCell As Range
With Worksheet("MySheet") '<--| change "MySheet" to your actual sheet name
With .Range("C1", .Cells(.Rows.Count, 3).End(xlUp))
Set f = .Find(What:=1, LookAt:=xlWhole, LookIn:=xlValues, After:=.Cells(.Rows.Count, 1))
If Not f Is Nothing Then
firstAddr = f.Address
Set iniCell = f
Set f = FindNext(f)
Do While f.Address <> firstAddr
s = s & Join(Range(iniCell, f.Offset(-1)).Offset(, 1), "")
' here code to paste s somewhere
Set iniCell = f
Set f = FindNext(f)
Loop
End If
End With
End With
Here's one I actually tested, using some random data in columns C and D.
You'll have to modify a little to get exactly where you want the data to go, but should get you in the right direction.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
Dim s As String
Dim lastRow As Long
Dim c As Integer
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = ThisWorkbook.Sheets("Sheet1")
Set ws2 = ThisWorkbook.Sheets("Sheet2")
lastRow = ws1.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
'This will get an accurate last row
c = 1
For i = 1 To lastRow
s = s & ws1.Cells(i, 4).Value 'Build the string
If ws1.Cells(i + 1, 3).Value = 1 Or ws1.Cells(i + 1, 3).Value = "" Then
ws2.Cells(c, 1).Value = s
s = ""
c = c + 1
'If the next cell to check is 1 or blank, then copy the values to the next cell in order on sheet2
End If
Next
End Sub
Walking through it, lastRow is set using the last row in the sheet with a value in it. Then, c is set to one, although you could set this to the last available row in ws2 using the same process. After that it just steps through the rows from 1 To LastRow building strings and transferring the value to ws2 when it's about to hit a 1.

Excel Moving Data From Rows Into a Column

Sorry, I feel like this is probably super basic, but I am trying to use Excel and VBA to move data from multiple cells per row into an empty column in a specific order. Some of the cells might not have data so I have to check that as well and skip empty ones with something along the lines of Value <> Empty.
Basically, what I am trying to do is take a table that looks like this (with empty column A):
B1 C1 D1 E1
B2 C2 D2 [E2empty]
B3 C3 D3 E3
And set it up like this in column A:
B1
C1
D1
E1
B2
C2
D2
B3
C3
D3
E3
It would be entered in one row at a time into the new column.
I guess I am trying to figure out how to say the following in code
In Row 1, check if cell B is empty. If not, move Value to column A, first avaible cell,
next cell in row 1, (repeat).
Next Row( do the same as row 1.)
So I was thinking of using For i = 1 To rwcnt where rwcnt is defined by CountA(Range("B:B"))
To do the rows in order and then doing a similar thing inside that for-statement for cells (Maybe j = B To E?).
So my overall goal is to scan my range (MyRange = ActiveSheet.Range("B1:E" & rwcnt)) and move everything into column A in the order described at the top, but I don't know how to move data to column A in sequence. Any advice on how to accomplish this would be very helpful.
Loop through all the used rows, looping columns starting at B in that row. Check if the cell is not empty. Write it to A next cell.
In you VBA IDE go to the tools menu and selecte references. Select "Microsoft scripting runtime"
Dim lRow As Long
Dim lRowWrite as long
Dim lCol As Long
Dim ws As Excel.Worksheet
Dim ts As TextStream
Dim fs As FileSystemObject
'Create the text file to write to
Set fs = New FileSystemObject
Set ts = fs.CreateTextFile("C:\Temp\test.txt", True, False)
Application.ScreenUpdating = False
Set ws = Application.ActiveSheet
lRowWrite = 1
lRow = 1
'Loop through all the rows.
Do While lRow <=ws.UsedRange.Rows.count
'Loop through all the columns
lCol = 2
Do While lCol <=ws.UsedRange.Columns.count
'Check if it is empty
If not isempty(ws.Cells(lRow, lCol)) Then
'Not empty so write it to the text file
ts.WriteLine ws.Cells(lRow, lCol)
End If
lCol = lCol + 1
Loop
lRow = lRow + 1
ws.Range("A" & lRow).Activate
Loop
Application.ScreenUpdating = True
ts.Close: Set ts = Nothing
Set fs = Nothing
Try this:
Sub test()
Dim lastCol As Long, lastRow As Long, k As Long, i As Long, colALastRow As Long
Dim rng As Range
Dim ws As Worksheet
'Columns(1).Clear ' uncomment this if you want VB to force Col. A to be cleared
Application.ScreenUpdating = False
Set ws = ActiveSheet
lastCol = ws.UsedRange.Columns.Count 'This will get the last column
lastRow = ws.UsedRange.Rows.Count 'this will get the last used row
k = 2 'Set k to 2, to start in Col B
colALastRow = 1 'This starts at 1, since your entire Column A is empty
With ws
For i = 1 To lastRow
lastCol = .Cells(i, 2).End(xlToRight).Column
Set rng = .Range(.Cells(i, 2), .Cells(i, lastCol))
' rng.Select
rng.Copy
.Range(.Cells(colALastRow, 1), .Cells(colALastRow + (lastCol), 1)).PasteSpecial Paste:=xlPasteAll, operation:=xlNone, skipblanks:=False, Transpose:=True
Application.CutCopyMode = False
colALastRow = .Cells(1, 1).End(xlDown).Row + 1
Next i
End With
Application.ScreenUpdating = True
MsgBox ("Done!")
End Sub
Edit: Changed the lastCol, lastRow, etc. from Integer to Long, since there will be over 32,767 rows.
Edit 2: I commented out rng.Select. This is because there's no reason to select it for the Macro. I only had it there, because as I worked through the macro (using F8), I wanted to make sure it was grabbing the right ranges. It is, so you can comment this out. It might even make it run slightly faster :)

copy part of a row to another sheet unless already been copied. Source rows can change row

I basically need to copy rows from one sheet to another. The rows which I need to copy are on sheet 2 , columns A to N and I need to copy the rows that has the unique value in column N.
Column N will just be =M1 or =M2 depending on which row has data on it.
Hence I will use a worksheet calculate event to try capture this.
The tricky part is that each time new values exist in say the last row on sheet 2 and then N gets filled form M. I don't want the previously copied rows to be copied. It may also be the case that the entire contents of the rows change places or that one of the rows disappears and the other row will fill its gap on sheet 2. So I need to avoid the calculate event recognizing this from the =M line. i.e if it has already been copied but gets recalcuated - -I don't need it to be copied again.
I guess one way to do this would be toi lookup if the N column value exists in the N column value on sheet 1. Because if that row disappears then it will be on the sheet 1. it will because I have other formulas putting it there.
My starting point for dong this is the code below and I have this set in the worksheet code of sheet 1
Private Sub Worksheet_Calculate()
Dim i As Long
Dim lr1 As Long, lr2 As Long
Dim Delta As String
Dim wks1 As Worksheet, wks2 As Worksheet
Set wks1 = ActiveSheet
Set wks2 = Worksheets("Sheet2") 'change to suit
lr1 = wks1.Cells(Rows.Count, "N").End(xlUp).Row
For i = 2 To lr1
lr2 = wks2.Cells(Rows.Count, "A").End(xlUp).Row + 1
wks1.Cells(i, "N").EntireRow.Copy Destination:=wks2.Cells(lr2, "A")
Next i
End Sub
I also have this working which I would need to incorporate into that worksheet calcualte
Sub updt()
Dim sh1 As Worksheet, sh2 As Worksheet, lr As Long, rng As Range
Set sh1 = Sheets(1) 'Edit sheet name
Set sh2 = Sheets(2) 'Edit sheet name
lr = sh1.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = sh1.Range("A2:A" & lr)
For Each c In rng
If WorksheetFunction.CountIf(sh2.Range("A:A"), c.Value) = 0 Then
sh2.Range("A" & sh2.Cells(Rows.Count, 1).End(xlUp).Row)(2) = c.Value
End If
Next
End Sub
The fastest method is pretty sure to copy all wanted rows an then call the remove dublicates function which is almost instantanios. You can simply set your unique col as indicator