check for duplicates in diffrent worksheets and thereafter print value - vba

I have searched but i cant seem to figure out how to print specified value in the column next to where i find my duplicate. What i have since earlier are code that first specify the diffrent ranges and thereafter look if a duplicate is found in sheet Y from sheet X. Sheet Le is this weeks information and sheet Be is the last weeks information.
IF i find a duplicate in the specified range i want to on my Delivery sheet print in column A next to the duplicate either Delivered or not delivered depending on if my output from function compareAEO print true or false.
The conditions that i am looking for are that if the we can find the same value that are in column B in sheet (Le) on sheet (Be) it will then check if the text in column F has changed. IF SO then it shall print in column A on sheet (Le) = Delivered. Otherwise not delivered.
It then checks to se if the dates in column M is the same. IF not then it shall print Replanned in column A on sheet (Le).
Shortly
IF value in cell on column B, Sheet (Le) = Value in column B, Sheet (Be) then
value in column A on sheet Le = "Delivered" Else "not deliverd".
Then
If value in cell in column M, Sheet (Le) <> If value in cell in column M, Sheet (Be) then value in column A, Sheet(Le) = "replanned"
This is how my data looks like,
Sheet (Le)
Col B Col F Col M
PZ2408 X13 2017-02-13
PZ2345 X30 2017-02-23
PZ2463 X45 2017-02-25
PZ2513 X13 2017-02-10
PZ2533 X70 2017-02-05
PZ2561 X60 2017-02-20
For sheet (Be) my data looks like this
Col B Col F Col M
PZ2408 X30 2017-02-13
PZ2345 X30 2017-02-23
PZ2463 X30 2017-02-25
PZ2513 X13 2017-02-05
PZ2533 X13 2017-02-10
PZ2561 X60 2017-02-17
After the code has done its course i would like it to show for example,
Sheet (Le)
col A Col B Col F Col M
Delivered PZ2408 X13 2017-02-13
Not Delivered PZ2345 X30 2017-02-23
Delivered PZ2463 X45 2017-02-25
replanned PZ2513 X13 2017-02-10
Delivered PZ2533 X70 2017-02-05
replanned PZ2561 X60 2017-02-20
Bascilly my Not delivered, delivered and Replanned statements does not work and my brain does not work.
Can SO help save my day?
Sub checkASMT()
Dim rng1 As Range
Dim rng2 As Range
Dim lastRowSource As Long
Dim lastRowTarget As Long
Dim row As Long
Dim ASMT As String
'Looping trough Range
With ThisWorkbook.Worksheets("Le")
lastRowTarget = .Range("B" & .Rows.Count).End(xlUp).row
For i = 29 To lastRowTarget
ASMT = .Range("b" & i).value
'Define range and see if we can find duplicates
With ThisWorkbook.Worksheets("Be")
lastRowSource = .Range("B" & .Rows.Count).End(xlUp).row
Set rng1 = .Range("B3", "B" & lastRowSource)
row = findValueInRangeReturnRow(rng1, ASMT)
'Check FAX
If compareAEO(i, row, "FAX") = True Then
'Debug.Print compareASMT(i, row, "FAX")
Worksheets("Le").Cells(i, ASMT).value = "Not Delivered"
Else
.Worksheets("Le").Cells(i, ASMT).value = "delivered"
'Check if dax are correct
If compareAEO(i, row, "DAX") = False Then
.Worksheets("Le").ASMT.Offset(0, 1).value = "Replan"
End If
End With
Next i
End With
End Sub
here are my first function
Function findValueInRangeReturnRow(rng As Range, value As Variant) As Long
Set c = rng.Find(value, LookIn:=xlValues)
If Not c Is Nothing Then
findValueInRangeReturnRow = c.row
End If
End Function
My second function that checks if duplicates are found in specified ranges.
Function compareAEO(rad1 As Variant, rad2 As Variant, typeCOMPARE As String) As Boolean
Dim col1 As String
Dim col2 As String
Select Case typeCOMPARE
Case "FAX"
col1 = "F"
col2 = "F"
Case "DAX"
col1 = "M"
col2 = "M"
End Select
If ThisWorkbook.Worksheets("Le").Range(col1 & rad1).value = ThisWorkbook.Worksheets("Be").Range(col2 & rad2).value Then
compareAEO = True
Else
compareAEO = False
End If
End Function

You were getting the last row of both pages in each loop. It is only necessary to get them once at the top, outside the loop. Same for the range you were setting. You can see that I put them at the top, before the loop.
I don't really know what you were using ASMT for. It looks like you were trying to use it as a range in some of your coding instead of range("B" & I). I used strings in the "B" column of Le to compare to the "B" column of Be when I tested it.
It works for me. You'll have to change it to suit your needs. You don't need all the functions, what they accomplished are all within this subroutine.
Sub checkASMT()
Dim rng1 As Range
Dim rng2 As Range
Dim lastRowLE As Long
Dim lastRowBe As Long
Dim row As Long
Dim ASMT As String
Dim LEws As Worksheet
Dim tmpRng As Range
Set LEws = Worksheets("Le")
lastRowLE = Sheets("Le").Cells(ActiveSheet.Rows.Count, "B").End(xlUp).row
lastRowBe = Sheets("Be").Cells(ActiveSheet.Rows.Count, "B").End(xlUp).row
Set rng1 = Sheets("Be").Range("B3", "B" & lastRowBe)
For i = 29 To lastRowLE
Set tmpRng = Sheets("Le").Range("b" & i)
ASMT = tmpRng.Value
Set c = rng1.Find(ASMT, LookIn:=xlValues)
If Not c Is Nothing Then
row = c.row
If ThisWorkbook.Worksheets("Le").Range("F" & i).Value = ThisWorkbook.Worksheets("Be").Range("F" & row).Value Then
' Worksheets("Le").Cells(i, ASMT).Value = "Not Delivered"
' Did you intend to use ASMT as the column number?
' I'm going to hard code that as column 27 for my purposes. You can change it if you need to
LEws.Cells(i, 27).Value = "Not Delivered" ' column 27 is "AA"
Else
LEws.Cells(i, 27).Value = "Delivered"
End If
If ThisWorkbook.Worksheets("Le").Range("M" & i).Value = ThisWorkbook.Worksheets("Be").Range("M" & row).Value Then
' .Worksheets("Le").ASMT.Offset(0, 1).Value = "Replan"
' again I don't understand the reference to ASMT. That is a string value - unless it is a numeric value in the string
' I'm going to assume that you intended for "Replan" to go into column C on row i
Else
LEws.Range("C" & i).Value = "Replan"
End If
End If
Next i
End Sub

Try this; place data in single sheet from B to G (Le then Be); place this formula in column H
=IF(VLOOKUP(E2,B$2:D$7,2,FALSE)=F2,IF(G2<D2,"replanned","Not Delivered"),"delivered")
tweak this formula to suit your needs to make it work across sheets

Related

Partial string match then return value

I'm working on a way to quickly code bank transactions. I have one tab of bank data downloaded (sheet 1) and I want to search the descriptions (column B) for a partial match with sheet 2, column A. Then if match found, return the value from sheet 2, column B to sheet 1 column D; and sheet 2, column C to sheet 1, column E.
Sheet 1
Column A Column B Column C Column D Column E
11/1/17 Transfer from Account 60617829-D 276 {acct} {location}
11/1/17 Transfer from Account 60692022-D 551.46 {acct} {location}
Sheet 2
Column A Column B (acct) Column C (location)
60617829-D 10430 03
60692022-D 10490 09
I was trying to use a solution similar to "Find and Get" described here: Excel Formula/VBA to search partial strings in other sheet
However, the following code returns the first value from sheet 2 to all values on sheet 1 without properly matching them. I think my error is in how I'm trying to use an array when it may not be necessary but I am at a loss.
Sub findAndGet()
Dim sh1, sh2 As Worksheet
Dim tempRow1, tempRow2 As Integer
Dim strList() As String
Dim name As String
Dim index As Integer
'Set sheets
Set sh1 = Sheets("list")
Set sh2 = Sheets("search")
'Set the start row of Sheet1
tempRow1 = 1
'Loop all row from starRow until blank of column A in Sheet1
Do While sh1.Range("A" & tempRow1) <> ""
'Get name
name = sh1.Range("B" & tempRow1)
'Split by space
strList = Split(Trim(name), " ")
'Set the start row of Sheet2
tempRow2 = 1
'Reset flag
isFound = False
'Loop all row from startRow until blank of column A in Sheet2
Do While sh2.Range("A" & tempRow2) <> ""
For index = LBound(strList) To UBound(strList)
'If part of name is found.
If InStr(UCase(sh2.Range("A" & tempRow2)), UCase(strList(index))) > 0 Then
'Set true to search flag
isFound = True
'exit do loop
Exit Do
End If
Next index
'Increase row
tempRow2 = tempRow2 + 1
Loop
'If record is found, set output
If isFound Then
'set account
sh1.Range("D" & tempRow1) = sh2.Range("B" & tempRow2)
'set location
sh1.Range("E" & tempRow1) = sh2.Range("C" & tempRow2)
End If
'Increase row
tempRow1 = tempRow1 + 1
Loop
End Sub
If formula solution is acceptable then assuming that data begins on both sheets on row number 2.
In cell D2 of Sheet1 insert following formula and copy down.
=LOOKUP(2^15,SEARCH(Sheet2!$A$2:$A$3,Sheet1!B2,1),Sheet2!$B$2:$B$3)
In cell E2 of Sheet1 insert following formula and copy down.
=LOOKUP(2^15,SEARCH(Sheet2!$A$2:$A$3,Sheet1!B2,1),Sheet2!$C$2:$C$3)

Excel VBA Macro to concatinate two columns using column header id name

I am really new in writing Excel macros through VBA. I want to concatenate two columns in my Excel worksheet. I have data in columns A, B & C and I want to concatenate B & C column to D column. This is the code I wrote:
Sub FINAL()
'
' FINAL Macro
'
'
Columns("D:D").Select
Selection.Insert Shift:=xlToRight
Range("D2").Select
ActiveCell.FormulaR1C1 = "SO::LI"
Range("D3").Select
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = "=RC[-2]&""::""&RC[-1]"
Range("D3").Select
Dim lastRow As Long
lastRow = Range("C" & Rows.Count).End(xlUp).Row
Range("D3").AutoFill Destination:=Range("D3:D" & lastRow)
End Sub
This works fine. But This works only when my data is in columns A, B & C.
When the data is in different columns, such as E, F & G, this does not work.
So what I want is to find the column using column header name and concatenate data.
Style S/O L/I
392389 265146 40
558570 300285 10
558570 300285 20
After concatenation:
Style S/O L/I SO::LI
392389 265146 40 265146::40
558570 300285 10 300285::10
558570 300285 20 300285::20
You can find a column header by using the worksheet MATCH function inside VBA, here I'll put it in to a variable called c1
c1 = Application.WorksheetFunction.Match("S/O", Range("1:1"), 0)
It's best to wrap this identification in a potential error handler, because if there IS no match then you'll get a run-time error
If Application.WorksheetFunction.CountIf(Range("1:1"), "S/O") > 0 Then
c1 = Application.WorksheetFunction.Match("S/O", Range("1:1"), 0)
Else
MsgBox ("Couldn't find ""S/O"" header!")
Exit Sub
End If
Here it uses the worksheet function COUNTIF to make sure there is at least one instance of "S/O" - if there isn't then the subroutine ends.
After that, you've identified your S/O column so can carry on with the rest of the code as usual - if you assume the columns are always consecutive then you can use c1 + 1 to mean "L/I" column and c1 + 2 to mean the CONCAT column
Below is a fully working version of the code:
Private Sub CommandButton1_Click()
Dim c1 As Long
Dim lastRow As Long
' If instance of "S/O" exists then find the column number else show error message and end
If Application.WorksheetFunction.CountIf(Range("1:1"), "S/O") > 0 Then
c1 = Application.WorksheetFunction.Match("S/O", Range("1:1"), 0)
Else
MsgBox ("Couldn't find ""S/O"" header!")
Exit Sub
End If
' Get last row for formula based on the "S/O" column header in c1
lastRow = Cells(Rows.Count, c1).End(xlUp).Row
' add 2 to c1 to make the c1 variable contain column number for SO::LI
c1 = c1 + 2
' use FormulaR1C1 as usual to flood the whole column from row 2 to lastRow
Range(Cells(2, c1).Address, Cells(lastRow, c1).Address).FormulaR1C1 = "=RC[-2]&""::""&RC[-1]"
End Sub
You may give this a try...
The code will find the headers in row2 and concatenate the columns.
Sub Final()
Dim FirstCell As Range, SecondCell As Range
Dim lr As Long, r As Long, c As Long
Application.ScreenUpdating = False
'Assuming that the Row2 is the Header Row, if not change it.
Set FirstCell = Rows(2).Find("S/O")
If FirstCell Is Nothing Then
MsgBox "A column with the header S/O was not found.", vbExclamation
Exit Sub
End If
Set SecondCell = Rows(2).Find("L/I")
If SecondCell Is Nothing Then
MsgBox "A column with the header L/I was not found.", vbExclamation
Exit Sub
End If
r = FirstCell.Row + 1
c = SecondCell.Column + 1
Set FirstCell = FirstCell.Offset(1)
Set SecondCell = SecondCell.Offset(1)
lr = Cells(Rows.Count, SecondCell.Column).End(xlUp).Row
Columns(c).Insert
Range(Cells(r, c), Cells(lr, c)).Formula = "=" & FirstCell.Address(0, 0) & "&""::""&" & SecondCell.Address(0, 0) & ""
Application.ScreenUpdating = True
End Sub
Use FormulaR1C1 instead is useful
Sub mergeColumn()
Dim col As Integer
Dim tr As Long
Application.ScreenUpdating = False
On Error Resume Next
col = Rows(1).Find(What:="S/O").Column
On Error GoTo 0
If col <> 0 Then ' if not found, it goes to 0
tr = Cells(Rows.Count, col).End(xlUp).Row
Range(Cells(1, col + 2), Cells(tr, col + 2)).Value = "=RC[-2] & ""::"" & RC[-1]"
End If
Application.ScreenUpdating = True
End Sub

Using Vlookup In VBA With Filter Conditions

What I am trying to accomplish here is
1) Is to iterate the values in column O and for the ones that are not null - filter the worksheet titled DATA to only show values where Column B = X and use VLOOKUP() to return the lookup values to the corresponding row in Column P
2) If column O is null then filter the sheet titled DATA to only show values where Column B <> X and use VLOOKUP() to return the lookup values to the corresponding row in Column P.
I attempted the syntax below but I am getting an error of
Method 'Rarnge' of object '_Worksheet' failed
What do I need to do differently in my code below to get the syntax to return the values I desire?
Dim destSheet As Worksheet: Set destSheet = Sheets("Main")
For i = Range("A" & Rows.Count).End(3).Row To 2 Step -1
If Not IsEmpty(Cells(i, "O").Value) Then
Sheets("Data").Select
Selection.AutoFilter
ActiveSheet.Range("$A:$C").AutoFilter Field:=2, Criteria1:="<>"
Sheets("Main").Select
Application.CutCopyMode = False
form2 = "=IFERROR(VLOOKUP(RC[-15],Lookup!C[-15]:C[-13],3,FALSE),"""")"
destSheet.Range("P:P" & lr).Formula = form2
Else
Sheets("Data").Select
Selection.AutoFilter
Sheets("Main").Select
Application.CutCopyMode = False
form3 = "=IFERROR(VLOOKUP(RC[-15],Lookup!C[-15]:C[-13],3,FALSE),"""")"
destSheet.Range("P:P" & lr).Formula = form3
End If
Next i
It was a bit hard to understand for me what you are trying to do, please correct me if I get you wrong.
First of all selecting sheets is not a preferred method while running macros, unless you intentionally do it, so avoid it.
Secondly, you don't need to filter anything, you can control it by checking conditions within your code. You don't do things physically, you do them theoretically within your code, and display the output.
Have a look at this code and ask wherever you need help to understand.
Sub VLookups()
Dim destSheet As Worksheet: Set destSheet = Sheets("Main")
Dim i As Long
Dim myVal As Variant
Set lookupRange = Sheets("Data").Range("$A:$C") 'This is your lookup range
For i = 2 To Range("O" & Rows.Count).End(xlUp).Row 'Iterate from 2nd row till last row
myVal = Application.VLookup(destSheet.Cells(i, "A").Value, lookupRange, 2, False)
If IsError(myVal) Then GoTo Skip 'If searched value not found then skip to next row
If Not IsEmpty(Cells(i, "O").Value) Then 'If Cell in Column O not empty
If myVal = "YOUR X VALUE FOR COLUMN B" Then 'If Your Column B X value exists
destSheet.Cells(i, "P").Value = Application.VLookup(destSheet.Cells(i, "A").Value, _
lookupRange, 3, False) 'Column P Cell is populated by Data Sheet Column C Cell
End If
Else 'If Cell in Column O empty
If myVal <> "YOUR X VALUE FOR COLUMN B" Then 'If Your Column B X value not exists
destSheet.Cells(i, "P").Value = Application.VLookup(destSheet.Cells(i, "A").Value, _
lookupRange, 3, False) 'Column P Cell is populated by Data Sheet Column C Cell
End If
End If
Skip:
Next i
End Sub

Referencing a particular cell value when there are two string matches in VBA

I am trying to create a predictive algorithm in VBA that would search for strings in a particular row from a data source, and return a value based on the row number. This is the first step in the workflow, and in its simplest form, there are 2 tables as shown below:
Source Table:
Output Table:
This is what I'm trying to do:
Pick up the strings in Row 1 of Output Table (Blue,Black) and search for them in Rows 1,2,3,4 of Source Table.
If both strings match in a single row, the 'Input' cell from that particular row is copied to Row 1 in Output Table in the 'Output' column.
Example (2nd iteration):
From Output Table Row 2, strings Ivory,Green,Grey are picked up and queried in all rows of Source Table. If any 2 out of 3 strings match in a single row on Source Table, the Input cell of that row is copied.
In this case, Ivory and Green match in Row 1, and also in Row 4. Either input cell would work, but for the sake of having a rule, lets take the last match (Row 4). So '1,8' would be copied to Row 2 on Output Table.
This the flow I am currently using, but I'm getting an incorrect output:
For i = 2 To 5
For j = 1 To 4
For k = 2 To 5
For l = 1 To 5
If Cells(i, j).Value = Worksheets("SourceTable").Cells(k, l).Value And Cells(i,j).Value <> "" Then
For a = 1 To 5
For b = 1 To 4
If Cells(i, b).Value = Worksheets("SourceTable").Cells(k, a).Value And Cells(i, b).Value <> "" Then
Cells(i, 15).Value = Worksheets("SourceTable").Cells(k, 5).Value
GoTo iLoop
End If
Next b
Next a
End If
Next l
Next k
Next j
iLoop:
Next i
Both tables would have around half a million rows, and I am trying to figure out how to reduce the number of loops and make it work at the same time. Any suggestions would be appreciated, this would help me save a lot of man-hours and automate a major chunk of the process. Thanks!
Sub macro()
lastRowOut = Sheets("OutputTable").Range("A" & Rows.Count).End(xlUp).Row
lastRowSou = Sheets("SourceTable").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To lastRowOut
For j = 2 To lastRowSou
If checkRow(j, i) >= 2 Then
Sheets("OutputTable").Cells(i, 5) = Sheets("SourceTable").Cells(j, 6)
Exit For
End If
Next j
Next i
End Sub
Function checkRow(sRow, i)
lastCol = Split(Sheets("OutputTable").Cells(i, Columns.Count).End(xlToLeft).Address, "$")(1)
counter = 0
For Each cell In Sheets("OutputTable").Range("A" & i & ":" & lastCol & i)
If Not Sheets("SourceTable").Range("A" & sRow & ":" & "E" & sRow).Find(cell.Value) Is Nothing Then
counter = counter + 1
End If
Next cell
checkRow = counter
End Function
Quite a few things are unclear so here were the assumptions I made:
Two or more of the cells in a row in the OutputTable have to be matched for the prediction to be made.
The first rows of both the Output and Source sheet contain "Col1, Col2" etc.
You seem to not mind whether we use the first or last matching row (from the source sheet) so I went with the first.
That's 3 loops instead of 6..
you can try this
Option Explicit
Sub main()
Dim row As Range
With Worksheets("OutputTable")
For Each row In .Range("D2", .Cells(.Rows.count, 1).End(xlUp)).Rows '<--| change "D" to "OutputTable" sheet last "col" column index (i.e. the one before "Output" column)
SearchSource row
Next
End With
End Sub
Sub SearchSource(rng As Range)
Dim cell As Range, row As Range
Dim nFounds As Long
With Worksheets("SourceTable")
For Each row In .Range("E2", .Cells(.Rows.count, 1).End(xlUp)).Rows '<--| change "E" to "SourceTable" sheet last "col" column index (i.e. the one before "Input" column)
nFounds = 0
For Each cell In rng.SpecialCells(xlCellTypeConstants)
If Not row.Find(what:=cell.Value, lookat:=xlWhole, LookIn:=xlValues) Is Nothing Then nFounds = nFounds + 1
If nFounds = 2 Then Exit For
Next
If nFounds = 2 Then rng.Cells(, rng.Columns.count + 1).Value = row.Cells(, row.Columns.count + 1).Value
Next
End With
End Sub
'Try this:
'First declare some variables:
'the number of rows of the Output table
Dim OrNum as integer
'the number of columns of the Output table
Dim OcNum as integer
'the number of rows of the Source table
Dim SrNum as integer
'the number of columns of the Source table
Dim ScNum as integer
'some dummy variables for the loops
Dim rO as integer, cO as integer
Dim rS as integer, cS as integer
And then declare a boolean variable (just for later on)
Dim bool as boolean
'Then assume the output table has it's first cell at the most 'top and the most left of the output table, which is taken to 'be the cell Z1 in the following Code
'Begin with this first cell of the Output table and get each 'value in a way, that you move first (inner loop) over the 'columns by fixing the row Index (rO) of the Output table and then (outer loop) get down to each and every row like this:
For rO = 0 to OrNum - 1
For cO = 0 to OcNum - 1
Range("Z1").Offset(rO, cO)
Next
Next
'Now you don't have only strings so you will need to check, 'if the value in the cell is a string or a number. There is VBA 'function, that can help. It's called IsNumeric. It will give 'True if the value is a numeric value. If we have a string, then it will give False. With the Function IsEmpty() you can also check if a cell is empty or not. If a cell is empty, then the function IsEmpty will return True.
For rO = 0 to OrNum - 1
For cO = 0 to OcNum - 1
bool = IsNumeric(Range("Z1").Offset(rO, cO).Value)
bool = bool Or IsEmpty (Range("Z1").Offset(rO, cO).Value)
If bool=False then
'we have a string!
'do something
End if
Next
Next

Modifying VBA code to place n entries below an entry rather than just the entry itself

I have the following VBA code:
Sub test()
Dim w1 As Worksheet
Dim w2 As Worksheet
Dim k As Long
Dim c As Range
Dim d As Range
Dim strFA As String
Set w1 = Sheets("a")
Set w2 = Sheets("b")
w2.Cells.Clear
k = 1
With w1.Range("A:A")
Set c = .Cells.Find("Name:" After:=.Cells(.Cells.Count), lookat:=xlWhole)
strFA = ""
While Not c Is Nothing And strFA <> c.Address
If strFA = "" Then strFA = c.Address
If IsError(Application.Match(c.Offset(1, 0).value, w2.Range("A:A"), False)) Then
Set d = .Cells.Find("Birthday:", c, , xlWhole)
w2.Range("A" & k).value = c.Offset(0, 1).value
w2.Range("B" & k).value = d.Offset(1, 0).value
k = k + 1
End If
Set c = .Cells.Find("Name:", After:=c, lookat:=xlWhole)
Wend
End With
End Sub
The short version of what this code does is as follows:
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 "Birthday:" Once "Birthday:" is found put the value below it beside "NAME:" in the output sheet.
3) Repeat until there are no more entries.
I'm wondering how I might extend this code so that rather than searching for the value below "Birthday:" we instead search for n entries below birthday and place each one beside the value for "NAME:" in succession so that the result looks like:
Col1 Col2 Col3 Col4
James 10 15 1974
Where the input looks similar to:
Col1 Col2 Col3 Col4
Name: James
Something
Birthday:
10
15
1974
Please let me know if anything is unclear. You can assume that the three values after Birthday: always appear directly after and that James is always in the column directly beside Name: but no assumptions can be made about how far apart Name: is from Birthday:, how many blank spaces are present, and so on.
You want a loop that has an incremented index. for each row you go down you write to one column to the right. Something like this.
lRow = 4
lCol = 2
'Loop through reading the birthday.
Do While lRow <= n
'Here we are writing a column to the right each time we come through.
ws.Range(lCol & k)
lCol = lCol + 1
lRow = lRow + 1
ws.Range("A" & lRow).Activate
Loop
So after you have the name and you are ready to read the birthday cells. Read them like that and write them to the cells on the current row.