I have a range with several series of dates and values
Input
Output
And i need this output, a series of dates ( using the min date and max date from input ).
If output date matches with the input date of a series then set the value of this day if not set a 0. I have tried all kind of loops but i have 40 series o dates and values ( 80 columns x 2000 rows ) and i can't get anything fast.
Please, test the next code. You must take care that the format in the analyzed range to be the same as the one in the built range (dd/mm/yyyy). It returns the processed array in another sheet (sh1). I used the next sheet. If it is empty in your case, you can use the code as it is. There must not exist other records in the first row, except the last Valuex. The code can be adapted to search this header type, but it is not the object of the solution:
Sub CentralizeDateValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from min to max:
arrD1 = Evaluate("TEXT(DATE(" & Year(minD) & "," & month(minD) & ",row(" & Day(minD) & ":" & NoD & ")),""dd/mm/yyyy"")")
Debug.Print Join(Application.Transpose(arrD1), "|") 'just to visually check it.
arrD2 = arrD1 'clone the built dates array
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(CStr(arrGen(i, j)), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2)).Value = arrD2
Sub CentralizeDateLongValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from long numbers, corespondent to min and max dates:
arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop
arrD2 = arrD1 ''clone the built dates arary
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(arrGen(i, j), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
.Value2 = arrD2
.Columns(1).NumberFormat = "dd/mm/yyyy"
End With
'put headers:
Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
sh1.Range("A1").Resize(1, UBound(arrHd) + 1).Value = arrHd: sh1.Activate
MsgBox "Ready..." & vbCrLf & _
" (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub
End Sub
It returns in "A1" of the next sheet the header and in "A2" the processed array.
Please, send some feedback after testing it. I am curious how much it takes for a big range. I tested it on a small range, but solution must run on any range...
Edited:
Please, test the following version. It uses a Long numbers array, corresponding to the necessary Dates range. This allows using value2 to create the global array, which allows a (little) faster iteration and does no need the CStr conversion. Not date format dependent, too:
Sub CentralizeDateLongValues()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
Set sh = ActiveSheet
Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
lastR = sh.UsedRange.rows.Count 'last row
lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column 'last column
'extract the odd columns number in an array:
arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
Debug.Print Join(arrOddCols, "|"): 'just to visually check it. Comment the line after understanding what the above line does
'obtain the columns letters array:
ReDim arrCols(1 To UBound(arrOddCols))
For i = 1 To UBound(arrOddCols)
arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
Next i
strCols = Join(arrCols, "1,") & "1": Debug.Print strCols 'just to visually check it.
Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
minD = WorksheetFunction.min(rngD)
maxD = WorksheetFunction.Max(rngD)
NoD = maxD - minD + 1 'number the days in the range betweenthe min and max dates
'build a continuous date array from long numbers, corespondent to min and max dates:
arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop
arrD2 = arrD1 ''clone the built dates arary
ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols) + 1) 'add the necessary columns for Values
StartTime = Timer 'start the timer to count the time spent by the following code.
arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
For i = 1 To UBound(arrGen)
For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
If arrGen(i, j) <> "" Then
col = col + 1
mtch = Application.match(arrGen(i, j), arrD1, True)
If IsNumeric(mtch) Then
arrD2(mtch, col) = arrGen(i, j + 1)
Else
arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
End If
End If
Next j
col = 1 'reinitialize the variable to set the column where the value to be placed
Next i
'drop the processed array content at once
Dim rngBlank As Range
With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
.Value2 = arrD2
.Columns(1).NumberFormat = "dd/mm/yyyy"
.EntireColumn.AutoFit
.Borders(xlEdgeLeft).Weight = xlThin
.Borders(xlEdgeTop).Weight = xlThin
.Borders(xlEdgeBottom).Weight = xlThin
.Borders(xlEdgeRight).Weight = xlThin
.Borders(xlInsideVertical).Weight = xlThin
.Borders(xlInsideHorizontal).Weight = xlThin
.BorderAround Weight:=xlThick
On Error Resume Next 'for the case (even imporbable) that no any blank cell will exist...
Set rngBlank = .SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
End With
If Not rngBlank Is Nothing Then rngBlank.Value = 0
'put headers:
Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
With sh1.Range("A1").Resize(1, UBound(arrHd) + 1)
.Value = arrHd
.Font.Bold = True
.EntireColumn.AutoFit
.Borders(xlInsideVertical).Weight = xlThin
.BorderAround Weight:=xlThick
End With
sh1.Activate
MsgBox "Ready..." & vbCrLf & _
" (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub
Please, send some feedback after testing it...
I have used the code below to duplicate columns based on the iteration number and to paste the required data in the required columns.
Sub collerinfo(endroit As Variant, iterat As Variant, Mot As String, DateDeb As Variant, DateFin As
Variant, nbjours As Double, Ref As Variant)
Dim iteration As Integer
Dim it As Integer
Dim recherche As String
Dim Line As Range
Dim NumDebut As Integer
Dim NumFin As Integer
Dim NumDernier As Integer
Dim dercol As Integer
iteration = CInt(iterat)
Select Case Mot
Case "CP"
'max iteration = 4
If iteration > 4 Then
MsgBox "Le " & iteration & "ième " & Mot & " du matricule " & Ref & " n'a pas pu être inscrit sur le fichier Excel"
Exit Sub
End If
If iteration > 1 Then
recherche = "Début CP (date)"
Set Line = Sheets("Navette").Rows("2").Find(What:=recherche, LookIn:=xlValues, lookat:=xlWhole)
If Not Line Is Nothing Then
NumDebut = Line.Column
End If
recherche = "Fin CP (choix)"
Set Line = Sheets("Navette").Rows("2").Find(What:=recherche, LookIn:=xlValues, lookat:=xlWhole)
If Not Line Is Nothing Then
NumFin = Line.Column
End If
'comprendre ce bout de code
dercol = Sheets("Navette").Cells(1, Cells.Columns.Count).End(xlToLeft).Column
For NumDernier = dercol To 1 Step -1
If Sheets("Navette").Cells(2, NumDernier) = "Fin CP (choix)" Then Exit For
Next NumDernier
If (NumDernier - NumDebut + 1) / 4 < iteration Then
Sheets("Navette").Select
Range(Columns(NumDebut), Columns(NumFin)).Select
Selection.Copy
Columns(NumDernier + 1).Select
Selection.Insert Shift:=xlToRight
End If
End If
Dim ResCP As Variant
ResCP = Application.Match("Début CP (date)", Sheets("Navette").Rows(2), 0)
Sheets("Navette").Cells(endroit, ResCP + (iteration - 1) * 4).Value = DateDeb
Sheets("Navette").Cells(endroit, (ResCP + 1) + (iteration - 1) * 4).Value = nbjours
Sheets("Navette").Cells(endroit, (ResCP + 2) + (iteration - 1) * 4).Value = DateFin
Case "RTT"
If iteration > 4 Then
MsgBox "Le " & iteration & "ième " & Mot & " du matricule " & Ref & " n'a pas pu être inscrit sur le fichier Excel"
Exit Sub
End If
' revoir code
If iteration > 1 Then
recherche = "Début RTT (date)"
Set Line = Sheets("Navette").Rows("2").Find(What:=recherche, LookIn:=xlValues, lookat:=xlWhole)
If Not Line Is Nothing Then
NumDebut = Line.Column
End If
recherche = "Fin RTT (choix)"
Set Line = Sheets("Navette").Rows("2").Find(What:=recherche, LookIn:=xlValues, lookat:=xlWhole)
If Not Line Is Nothing Then
NumFin = Line.Column
End If
'comprendre ce bout de code
dercol = Sheets("Navette").Cells(1, Cells.Columns.Count).End(xlToLeft).Column
For NumDernier = dercol To 1 Step -1
If Sheets("Navette").Cells(2, NumDernier) = "Fin RTT (choix)" Then Exit For
Next NumDernier
If (NumDernier - NumDebut + 1) / 4 < iteration Then
Sheets("Navette").Select
Range(Columns(NumDebut), Columns(NumFin)).Select
Selection.Copy
Columns(NumDernier + 1).Select
Selection.Insert Shift:=xlToRight
End If
End If
End Select
End Sub
After data has been pasted, how do I restore the sheet, i.e, delete the added columns and data?
For instance, after adding the columns, the headers look like this :
A A1 A2 A A1 A2 A A1 A2 B B1 B2 B B1 B2
And in the end, I want it to be as follows :
A A1 A2 B B1 B2
Any suggestions ?
Try this. I have assumed the headers are in row 1 so might need adjusting.
Sub x()
Dim r As Range, i As Long
Set r = Range(Cells(1, 1), Cells(1, Columns.Count).End(xlToLeft))
For i = r.Count To 2 Step -1
If IsNumeric(Application.Match(r.Cells(i), r.Resize(, i - 1), 0)) Then 'header is found in the range to the left so delete this one
r.Cells(i).Delete shift:=xlToLeft 'just the cell
'r.Cells(i).entirecolumn.Delete 'whole column
End If
Next i
End Sub
Let us assume that the Headers appears in row 1. Try the below:
Option Explicit
Sub Macro1()
Dim LastColumn As Long, i As Long
Dim Columns As String
Columns = ""
With ThisWorkbook.Worksheets("Sheet1")
'Find last column of row 1
LastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
'Loop columns
For i = 1 To LastColumn
'Check if the value appears twice
If WorksheetFunction.CountIf(.Range(.Cells(1, 1), .Cells(1, i)), .Cells(1, i).Value) > 1 Then
'Pass the dublicate value in a split converting the column number the dublicate found into a letter
If Columns = "" Then
Columns = Split(.Cells(1, i).Address, "$")(1) & ":" & Split(.Cells(1, i).Address, "$")(1)
Else
Columns = Columns & "," & Split(.Cells(1, i).Address, "$")(1) & ":" & Split(.Cells(1, i).Address, "$")(1)
End If
End If
Next i
'If the Columns are not empty delete the imported columns
If Columns <> "" Then
.Range(Columns).Delete Shift:=xlToLeft
End If
End With
End Sub
I am trying to identify a specific range in column-A and concatenate two cells within the specific range and delete the empty cell. I have been successful in putting a code together and it does the job very well. But, I don't know how to loop it to identify next range. Any help would be appreciated.
As per below image and code, First, I am finding and selecting a range between two (MCS) in column-A with a condition that, if the rows are more than 8 between two MCS. Then I am concatenating first 2 cells immediately after MCS and delete the empty row.
The below code works well for first range but I am unable to loop to identify next range from row 22 to 32 and perform concatenations.
I dont know how to loop in column-A and select ranges and concatenate. Any help would be much appreciated. Thanks
Sub MergeStem()
Dim findMCS1 As Long
Dim findMCS2 As Long
Dim myCount As Integer
Dim myStems As Long
Dim mySelect As Range
Dim c As Range
findMCS1 = Range("A:A").Find("MCS", Range("A1")).Row
findMCS2 = Range("A:A").Find("MCS", Range("A" & findMCS1)).Row
myCount = Range("A" & findMCS1 + 1 & ":A" & findMCS2 - 1).Cells.Count
Range("B1").Value = myCount
MsgBox "Number of rows =" & myCount
Set mySelect = Selection
If myCount > 8 Then
myStems = Range("A" & findMCS1 + 2 & ":A" & findMCS2 - 9).Select
Set mySelect = Selection
For Each c In mySelect.Cells
If firstcell = "" Then firstcell = c.Address(bRow, bCol)
sArgs = sArgs + c.Text + " "
c.Value = ""
Next
Range(firstcell).Value = sArgs
End If
Columns("A").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Application.ScreenUpdating = True
End Sub
Can you try this? Ordinarily, Find would be the way to go but because you are deleting rows it's hard to keep track of which cells you've found.
Sub x()
Dim r As Long, n1 As Long, n2 As Long
With Range("A1", Range("A" & Rows.Count).End(xlUp))
For r = .Count To 1 Step -1
If .Cells(r).Value = "MCS" Then
If n1 = 0 Then
n1 = .Cells(r).Row
Else
n2 = .Cells(r).Row
End If
If n1 > 0 And n2 > 0 Then
If n1 - n2 > 9 Then
.Cells(r + 1).Value = .Cells(r + 1).Value & .Cells(r + 2).Value
'.Cells(r + 2).EntireRow.Delete
'Call procedure to delete row
End If
n1 = n2
n2 = 0
End If
End If
Next r
End With
End Sub
I have this macro that I use to sum total entries by date. The macro can be run two or three times per day which causes a problem as it wants to sum by date. When I run it and it places the total in the last cell in column E, I want that total number to remain the next time I run the macro, but right now it erases the last entry and sums the new total plus the previous total. How can this be changed? I hope this makes sense the way I am explaining it.
Sub Sum_TodaysDate()
Dim sh As Worksheet
Set sh = ActiveSheet
Dim LastRow As Long, iCount As Long
Dim icell As Range
Dim dSplit As Variant
Dim dIndex As Date
LastRow = sh.Range("D" & Rows.Count).End(xlUp).Row
iCount = 0
For Each icell In sh.Range("D2:D" & LastRow)
dSplit = Split(icell.Value, " ")
dIndex = Format(dSplit(0), "mm/dd/yyyy")
If dIndex = Date Then
iCount = iCount + 1
icell.Offset(0, 1).Value = "|"
End If
Next icell
sh.Range("E" & LastRow).Value = iCount
sh.Range("E" & LastRow).Font.Color = vbRed
Application.ScreenUpdating = True
ThisWorkbook.Save
End Sub
If you wish to leave anything that is currently in column E untouched by the macro, so that previous counts remain in place, change the line that says:
icell.Offset(0, 1).Value = "|"
to
If IsEmpty(icell.Offset(0, 1)) Then
icell.Offset(0, 1).Value = "|"
Endif
If you want the new count to only be the count of records added since the previous time the macro was run (and the data is in date order), this could be modified to be:
If IsEmpty(icell.Offset(0, 1)) Then
icell.Offset(0, 1).Value = "|"
Else
iCount = 0
Endif
I have the following VBA Macro -
Sub CopyData()
Application.ScreenUpdating = False
Dim CRow As Integer
Dim CColBRange As String
Dim PColBRange As String
Dim Continue As Boolean
'Select Sheet1
With Sheets("KG9New")
.Select
'Initialize variables
Continue = True
CRow = 1
While Continue = True
CRow = CRow + 1
'Test B2:
If CRow = 2 And Cells(CRow, 2).Value = 0 Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
CRow = CRow + 1
End If
CColBRange = "B" & CStr(CRow)
PColBRange = "B" & CStr(CRow - 1)
'Break loop upon finding blank cell.
If Len(Range(CColBRange).Value) = 0 Then
Continue = False
End If
'Copy first instance of each changing Value in MachineRunning.
If Range(CColBRange).Value <> Range(PColBRange).Value Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
Wend
End With
Application.ScreenUpdating = True
End Sub
Basically, This scans through Column B of my table and copies values across to a new sheet when the value changes from a 1 to a 0 or 0 to 1.
My issue is that this assumes that the first value (in B2) will be a 1. I would like it to return Row 2 values if B2=0.
I tried changing the initialized CRow to 1, but this returns row 2 whether it is a 1 or 2 (due to it being different from the header, I guess).
Could somebody help me out please?
Change your CRow to 1, like you thought. You can't test B2 if you are never at that cell. Then you just need to do an IF statement.
Sub CopyData()
Application.ScreenUpdating = False
Dim CRow As Integer
Dim CColBRange As String
Dim PColBRange As String
Dim Continue As Boolean
'Select Sheet1
Sheets("KG9New").Select
'Initialize variables
Continue = True
CRow = 1
While Continue = True
CRow = CRow + 1
'Test B2:
If CRow=2 and Cells(CRow, 2).value = 0 Then
CRow = 3
End if
CColBRange = "B" & CStr(CRow)
PColBRange = "B" & CStr(CRow - 1)
'Break loop upon finding blank cell.
If Len(Range(CColBRange).Value) = 0 Then
Continue = False
End If
'Copy first instance of each changing Value in MachineRunning.
If Range(CColBRange).Value <> Range(PColBRange).Value Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
Wend
Application.ScreenUpdating = True
End Sub
The additional If statement just tests to see if we are on row 2 and if that value is 0. If it is, then change CRow to 3 and it will continue on.
I also removed the superfluous With block. I couldn't see anywhere else in the Macro where that format was being used.