Home > Software design >  How to fill missing rows using the day of the week and time as keys in VBA?
How to fill missing rows using the day of the week and time as keys in VBA?

Time:08-26

Assumptions

  • The EventLists sheet contains a list of events by day of the week and start time.
  • The Master sheet contains a list of events by date.
  • The Master sheet contains only one month's data.
  • The Master sheet is sorted by date ASC, time ASC.
  • The day of the week is divide from 05:00 to 28:59
  • The Excel version is 2019.

What I want to do

For the Master sheet, if there is a time that does not exist in the EventLists sheet,

I would like to add a row and embed the target time.

I would like to make it look like the Expected Result sheet shown in the sample URL.

(Background color is not necessary.)

Sample URL

https://docs.google.com/spreadsheets/d/e/2PACX-1vTJAr87dZ_92NxxR-eS5gUrKW9dmb5liaw4748eb730EHGrotcQTBQS9LDcBkZKauyWKeYfFuUo3Abk/pubhtml

Implement

Sub appendPositionToMaster()
  Dim ws As Worksheet
  Dim ws2 As Worksheet
  Dim lastRowNum As Long
  Dim lastRowNum2 As Long
  Dim i As Integer
  Dim j As Integer
  Dim lastStartTime
  Dim currentStartTime
  Dim weekName As String
  Dim startTime
  Dim weekName2 As String
  Dim startTime2

  Set ws = Worksheets("EventLists")
  Set ws2 = Worksheets("Master")

  lastRowNum = ws.Range("A" & Rows.Count).End(xlUp).row
  lastRowNum2 = ws2.Range("A" & Rows.Count).End(xlUp).row

  For i = 2 To lastRowNum
    ws.Activate

    weekName = Cells(i, 1).Value
    startTime = Cells(i, 3).Value

    ' Convert Serial To Number (0.208333333 -> "5:00" -> 500)
    startTime = Val(Format(startTime, "hmm"))

    For j = 2 To lastRowNum2
      ws2.Activate

      weekName2 = Cells(j, 2).Value

      If weekName = weekName2 Then
        lastStartTime = currentStartTime
        currentStartTime = Cells(j, 3).Value

        If lastStartTime <> currentStartTime Then
          startTime2 = currentStartTime
          startTime2 = Val(startTime2)

          ' [TEST] If current start time is not in array
          arr = Array(502, 510, 606, 630, 800, 930, 1025, 1130, 1145, 1155, 1355, 1455, 1550, 1650, 1815, 1954, 2000, 2154, 2200, 2359, 2454, 2559, 2629)
          result = Filter(arr, startTime)

          If UBound(result) = -1 And startTime > startTime2 Then
            MsgBox startTime & " " & startTime2 & " " & j
            Rows(j).Insert
            Exit For
          End If
        End If
      End If
    Next j
  Next i
End Sub

Anybody help?

P.S.

@FaneDuru

what to match the EventLists data with Master data

I want to add a row based on two keys.

The day of the week (line A) and the start time (line C) of the event list.

If the day of the week in the event list matches the day of the week on the Master sheet,

and the start time in the event list does not exist on the Master sheet, add a row and insert the start time.

For example, on the Master sheet,

the first start time for 20220901 is 502,

Date Week StartTime Type
20220901 Thu 502 B
20220901 Thu 502 B
20220901 Thu 502 A
20220901 Thu 502 A
20220901 Thu 502 A
20220901 Thu 502 A

but the Event List has an earlier start time of 500, so I want to add a row with a start time of 500 above 502.

Date Week StartTime Type
20220901 Thu 500 ADD
20220901 Thu 502 B
20220901 Thu 502 B
20220901 Thu 502 A
20220901 Thu 502 A
20220901 Thu 502 A
20220901 Thu 502 A

Also, the Master sheet has start times of 1650 and 1815,

Date Week StartTime Type
20220901 Thu 1650 A
20220901 Thu 1650 A
20220901 Thu 1815 A
20220901 Thu 1815 A

but the event list has 1753 in between, so add a row with a start time of 1753 above 1815.

Date Week StartTime Type
20220901 Thu 1650 A
20220901 Thu 1650 A
20220901 Thu 1753 ADD
20220901 Thu 1815 A
20220901 Thu 1815 A

Also add 2734-2855 under 2629

Date Week StartTime Type
20220901 Thu 2629 A
20220901 Thu 2734 ADD
20220901 Thu 2737 ADD
20220901 Thu 2825 ADD
20220901 Thu 2855 ADD

I want this to be inserted in an iterative process for all dates on the Master sheet,

and eventually the rows will be added as on the Master(Expected) sheet.

It would be too much work to write everything out, so the Master(Expected) sheet only contains one day.

Did I get my point across to you to some extent? I am sorry that I am not good at English and do not understand your detailed intention.

CodePudding user response:

I wrote the following code. It works for the given example data. I am not 100% sure that I understand your question but if you could give me some advice on what you wanted more, I can update it accordingly.

Sub AddMissingStartTime()
    Dim wsMaster As Worksheet
    Dim wsLists As Worksheet
    Dim lastrowMaster As Long
    Dim lastrowLists As Long
    Dim key0 As String
    Dim key1 As String
    Dim d0 As Date
    Dim d1 As Date
    Dim t0 As Date
    Dim t1 As Date
    Dim t0str As String
    Dim t1str As String
    Dim i As Long
    Dim rngInsert As Range

    Set wsMaster = Worksheets("Master")
    Set wsLists = Worksheets("EventList")

    With wsMaster
        lastrowMaster = .Cells(Rows.Count, "A").End(xlUp).Row

        For i = 2 To lastrowMaster
            key0 = .Cells(i, "A").Value
            d0 = .Cells(i, "A").Value
            t0 = .Cells(i, "C").Value

            With wsLists
                lastrowLists = .Cells(Rows.Count, "A").End(xlUp).Row

                For j = 2 To lastrowLists
                    key1 = .Cells(j, "A").Value
                    d1 = .Cells(j, "A").Value
                    t1 = .Cells(j, "C").Value

                    If key0 = key1 Then
                        t0str = Format(t0, "HH:mm")
                        t1str = Format(t1, "HH:mm")

                        If t0str &lt;&gt; t1str Then
                            If t0str &lt; t1str Then
                                Set rngInsert = .Range("C" &amp; j)
                                rngInsert.EntireRow.Insert
                                .Cells(j, "C") = t0
                                .Cells(j, "D") = "ADD"
                            End If
                        End If
                    End If
                Next
            End With
        Next
    End With
End Sub

CodePudding user response:

Please test the next solution and send some feedback.

It uses a dictionary to firstly extract the unique StartTime values in the necessary format and then keep the rows number to be inserted for each case. The code should be fast enough, most of processing being done in memory, using arrays. I commented all code lines which could not be easily understood:

Sub appendPositionToMaster()
 Dim wb As Workbook, ws As Worksheet, wsM As Worksheet, lastR As Long, lastRM As Long
 Dim arrT, arrDic, arrM, mtch, i As Long, j As Long, k As Long, L As Long, dict As Object
 Dim rngMT As Range, arrRows, rngIns As Range, boolFirst As Boolean, boolFirstLine As Boolean
  
 Set wb = ActiveWorkbook
 Set ws = wb.Worksheets("EventLists")
 Set wsM = Worksheets("Master")

  lastR = ws.Range("A" & ws.rows.count).End(xlUp).row
  lastRM = wsM.Range("A" & wsM.rows.count).End(xlUp).row
  arrM = wsM.Range("A2:C" & lastRM).Value2 'place the rangge in an array for faster iteration
  
  arrT = ws.Range("C2:C" & lastR).Value2   'the Time column array (from EventLists)
  
  'place UNIQUE time records from EventLists in dictionary, as keys:
  Set dict = CreateObject("Scripting.Dictionary") 'create the dictionary
  For i = 1 To UBound(arrT)
        dict(val(Replace(arrT(i, 1), ":", ""))) = 1
  Next i
  arrDic = dict.Keys: dict.RemoveAll   'place the dictionary keys in an array and empty the dictionary to reuse it

  BubbleSort arrDic 'sort the array Ascending
  Debug.Print Join(arrDic, "|"): 'just to visually see in Immediate Window the array of UNIQUE reference time values
  
  Set rngMT = wsM.Range("C2:C" & lastRM) 'The range of StartTime from Master
  
  For i = 0 To UBound(arrDic)                          'iterate between the UNIQUE time values from EventLists
        mtch = Application.match(arrDic(i), rngMT, 0)  'check if the time value exists in Master C:C column
        If IsError(mtch) Then                          'if it does not exist:
            For j = 1 To UBound(arrM)                  'iterate between arrM elements:
                If arrM(j, 3) > arrDic(i) Then         'if iterated time value is greater than the time value from the array:
                    If j = 1 Then                      'only if first array row is greater than reference time:
                        dict(arrDic(i)) = dict(arrDic(i)) & "|" & j   1 'create the dictionary key as time value and item as row number
                        boolFirstLine = True                            'variable used to copy format for the second row (as header, after inserting a row)
                    End If
                    For k = 2 To UBound(arrM)                      'iterating again between the arrM items:
                        If arrM(k, 3) = arrM(j, 3) And arrM(k, 3) <> arrM(k - 1, 3) Then 'find the case of the reference value first occurrence
                            dict(arrDic(i)) = dict(arrDic(i)) & "|" & k   1                             'add the row number to the dictionary item
                        End If
                    Next k
                   
                    arrRows = Split(dict(arrDic(i)), "|")              'place the dictionary current processed item in an array
                    arrRows(0) = "$$##": arrRows = filter(arrRows, "$$##", False)  'eliminate the first (empty) array element
                    Set rngIns = wsM.Range("A" & Join(arrRows, ",A"))              'build the range of rows numbers to be inserted at once
                    rngIns.EntireRow.Insert                                                                    'insert all the necessary rows per reference value, at once
                    
                    If boolFirstLine Then                                                        'in case of an insertion of the second row:
                        boolFirstLine = False                                                    'reinitialize the boolean variable to work only once
                        wsM.Range("A3:H3").Copy                                   
 'copy the format of the third range
                        wsM.Range("A2").PasteSpecial xlPasteFormats     'and paste it in the second row
                    End If
                    
                    lastRM = wsM.Range("A" & wsM.rows.count).End(xlUp).row  'recalculate the last row after rows insertion
                    arrM = wsM.Range("A2:D" & lastRM).Value2                      
    'place the necessary range in an array, for faster processing
                    Set rngMT = wsM.Range("C2:C" & lastRM)                                  'reset the reference range
                    For L = 1 To UBound(arrM)                                                                        'iterate between the array rows, to find the empty ones:
                            If arrM(L, 1) = "" Then                                                                        'if the row is empty:
                                If Not boolFirst Then                                                                      'first time (second row) the necessary data are copied from below
                                    arrM(L, 1) = CStr(arrM(L   1, 1)): arrM(L, 2) = arrM(L   1, 2)
                                    arrM(L, 3) = arrDic(i): arrM(L, 4) = "Add"
                                    boolFirst = True 'reinitialize the boolean variable
                                Else                                                                                                   'for the next times the necessary data are copied from above
                                    arrM(L, 1) = CStr(arrM(L   1, 1)): arrM(L, 2) = arrM(L   1, 2)
                                    arrM(L, 3) = arrDic(i): arrM(L, 4) = "Add"
                                End If
                            End If
                    Next L
                    
                    'drop the content of the processed array at once:
                    wsM.Range("A2").Resize(UBound(arrM), UBound(arrM, 2)) = arrM
                    Exit For    'exit the loop
                End If
            Next j
        End If
  Next i
  MsgBox "Ready..."
End Sub

Private Sub BubbleSort(arr)  'function to sort a 1D array
    Dim i As Long, j As Long, temp
    For i = LBound(arr) To UBound(arr) - 1
        For j = i   1 To UBound(arr)
            If arr(i) > arr(j) Then
                temp = arr(i): arr(i) = arr(j)
                arr(j) = temp
            End If
        Next j
    Next i
End Sub
  • Related