Advanced sorting in excel - excel

I have a data in excel in the format:
Description Name Percent
Always A 52
Sometimes A 23
Usually A 25
Always B 60
Sometimes B 30
Usually B 15
Always C 75
Sometimes C 11
Usually C 14
I want to sort this data:
For each name the sequence of description has to be same (eg: always followed by sometimes followed by usually) but for three names A, B and C, I want to sort the always percent from smallest to largest. Eg: I want the above example to look like this after sorting:
Description Name Percent
Always C 75
Sometimes C 11
Usually C 14
Always B 60
Sometimes B 30
Usually B 15
Always A 52
Sometimes A 23
Usually A 25
The always percent of name C was highest and always percent of name A was lowest. I hope I was able to explain it. I would really appreciate your help regarding the same.

Here's a vba routine to perform this sort:
Select the data on the sheet and run SortList
Important: this code assumes that the Always, Sometimes, Usually data is grouped by Name (as in your sample data)
Method:
Sub SortList()
Dim dat As Variant
Dim rng As Range
Dim newDat() As Variant
Dim always() As Long
Dim i As Long
Set rng = Selection
If rng.Columns.Count <> 3 Then
MsgBox "Select a range with 3 columns", vbCritical + vbOKOnly
Exit Sub
End If
If StrComp(rng.Cells(1, 1), "Description", vbTextCompare) = 0 Then
Set rng = rng.Offset(1, 0).Resize(rng.Rows.Count - 1, 3)
End If
dat = rng
ReDim always(1 To UBound(dat, 1) / 3)
For i = 1 To UBound(dat)
If StrComp(dat(i, 1), "Always", vbTextCompare) = 0 Then
always(i \ 3 + 1) = i
End If
Next
QuickSort dat, always, LBound(always, 1), UBound(always, 1)
ReDim newDat(1 To UBound(dat, 1), 1 To 3)
For i = 1 To UBound(always)
newDat((i - 1) * 3 + 1, 1) = dat(always(i), 1)
newDat((i - 1) * 3 + 1, 2) = dat(always(i), 2)
newDat((i - 1) * 3 + 1, 3) = dat(always(i), 3)
' Assumes original data is sorted in name order
newDat((i - 1) * 3 + 2, 1) = dat(always(i) + 1, 1)
newDat((i - 1) * 3 + 2, 2) = dat(always(i) + 1, 2)
newDat((i - 1) * 3 + 2, 3) = dat(always(i) + 1, 3)
newDat((i - 1) * 3 + 3, 1) = dat(always(i) + 2, 1)
newDat((i - 1) * 3 + 3, 2) = dat(always(i) + 2, 2)
newDat((i - 1) * 3 + 3, 3) = dat(always(i) + 2, 3)
Next
rng = newDat
End Sub
Private Sub QuickSort(ByRef dat As Variant, ByRef Field() As Long, ByVal LB As Long, ByVal UB As Long)
Dim P1 As Long, P2 As Long, Ref As Variant, TEMP As Long
P1 = LB
P2 = UB
Ref = dat(Field((P1 + P2) / 2), 3)
Do
Do While dat(Field(P1), 3) > Ref
P1 = P1 + 1
Loop
Do While dat(Field(P2), 3) < Ref
P2 = P2 - 1
Loop
If P1 <= P2 Then
TEMP = Field(P1)
Field(P1) = Field(P2)
Field(P2) = TEMP
P1 = P1 + 1
P2 = P2 - 1
End If
Loop Until (P1 > P2)
If LB < P2 Then Call QuickSort(dat, Field, LB, P2)
If P1 < UB Then Call QuickSort(dat, Field, P1, UB)
End Sub
The Quicksort is adapted from this answer by Konrad Rudolph

It might be easier with ADO:
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim i As Integer
strFile = "C:\Docs\Book2.xlsm"
''Note that if HDR=No, F1,F2 etc are used for column names,
''if HDR=Yes, the names in the first row of the range
''can be used.
''Comment out the connection string, as appropriate.
''This is the Jet 4 connection string, for < 2007:
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
''ACE, for 2007 -
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
''Late binding, so no reference is needed
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT s1.[Description], s1.[Name], s1.[Percent] " _
& "FROM [Sheet3$] s1 " _
& "INNER JOIN (SELECT s.Name, s.Percent " _
& "FROM [Sheet3$] s " _
& "WHERE s.Description='Always') As s2 " _
& "ON s1.Name = s2.Name " _
& "ORDER BY s2.Percent DESC, s1.Description"
rs.Open strSQL, cn, 3, 3
''Pick a suitable empty worksheet or location for the results
With Worksheets("Sheet4")
For i = 1 To rs.Fields.Count
.Cells(1, i) = rs.Fields(i - 1).Name
Next
.Cells(2, 1).CopyFromRecordset rs
End With
''Tidy up
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing

Sort by Description. Add this formula to column D =RANK(VLOOKUP(INDIRECT("B"&ROW()),B:C, 2, FALSE),C:C ) and sort column D with Smallest to Largest.

Related

Calculating Portfolio from one data series

I am supposed to make two market portfolios from the reversal strategy from the data given of value weighted market returns. However, I am stuck at how to proceed.
Sub REV1()
Dim c As Integer, r As Integer, g As Integer, x As Integer
Application.ScreenUpdating = False
lr = Sheets("VWMR").Cells(Rows.Count, 1).End(xlUp).Row
lc = Sheets("MRM").Cells(1, Columns.Count).End(xlToLeft).Column
Sheets.Add after:=Sheets(Sheets.Count)
Sheets(ActiveSheet.Name).Name = "REV1"
ReDim r1(lr - 2) As Variant
ReDim r2(lr - 2) As Variant
ReDim r3(lr - 2) As Variant
ReDim r4(lr - 2) As Variant
ReDim r5(lr - 2) As Variant
ReDim r6(lr - 2) As Variant
Columns("A:C").ColumnWidth = 20
For h = 1 To 2
B = 2
x = 2
For r = 2 To lr - 2 - h
Set n = Range(Sheets("VWMR").Cells(x, 2), Sheets("VWMR").Cells(x, lc))
Set m = Range(Sheets("VWMR").Cells(x + h, 2), Sheets("VWMR").Cells(x + h, lc)) _
cn = Application.WorksheetFunction.Count(n)
cm = Application.WorksheetFunction.Count(m)
If cn > 10 And cm > 10 Then
D2 = Application.WorksheetFunction.Percentile(n, 0.1)
D3 = Application.WorksheetFunction.Percentile(n, 0.9)
r2(r) = Application.WorksheetFunction.AverageIfs(m, n, "<=" & D2)
r3(r) = Application.WorksheetFunction.AverageIfs(m, n, ">=" & D3)
Sheets("REV1").Cells(B + h - 1, h + 1).Value = r2(r) - r3(r)
Sheets("REV1").Cells(B, 1).Value = Sheets("VVMR").Cells(B + 1, 1).Value
End If
B = B + 1
x = x + 1
Next
Sheets("REV1").Cells(1, h + 1).Value = "MOM" & h
Next
Sheets("REV1").Cells(1, 1).Value = "Dates"
Application.ScreenUpdating = True
Set a1 = Range(Sheets("REV1").Cells(2, 2), Sheets("REV1").Cells(lr, 2))
D = Application.WorksheetFunction.Average(a1)
MsgBox "The annual reversal returns are " & Format(Exp(D) - 1, "") & "."
End Sub
This is the code I tried to take out one portfolio first but this is not working.

How can I increment column value in excel VBA?

I have 2 excel sheets one is mapping and other is soneri. I want to increment the values of Column D in soneri sheet which was get by lookup function. What is the mistake in my code?
soneri sheet
mappingsheet
Outcome
Column D
Only first 2 rows are correct of my outcome else are wrong.
Expected Outcome
Below is my code
"WORKING CODE EDITED"
Sub ButtonClick()
Dim soneriWs As Worksheet, mappingWs As Worksheet
Dim sonerilastrow As Long, mappinglastrow As Long, i As Long
Dim datarange As Range, assetrange As Range, b As Range
Dim entry As Variant
Set soneriWs = ThisWorkbook.Worksheets("Soneri")
Set mappingWs = ThisWorkbook.Worksheets("Mapping")
sonerilastrow = soneriWs.Range("I" & soneriWs.Rows.Count).End(xlUp).Row
mappinglastrow = mappingWs.Range("A" & mappingWs.Rows.Count).End(xlUp).Row
Set datarange = mappingWs.Range("A2:B" & mappinglastrow)
Set assetrange = soneriWs.Range("I2:I" & sonerilastrow)
i = 0
For Each entry In assetrange
Set b = entry.Offset(, -5)
b = Application.WorksheetFunction.VLookup(entry, datarange, 2, False)
If entry = entry.Offset(-1) Then
i = i + 1
b = Left(b, Len(b) - 1) & (Right(b, 1) + i)
Else
i = 0
End If
Next entry
End Sub
Rows.Count returns that number of rows for the active sheet. Try changing these two lines:
sonerilastrow = soneriWs.Range("I" & Rows.Count).End(xlUp).Row
mappinglastrow = mappingWs.Range("A" & Rows.Count).End(xlUp).Row
To this:
sonerilastrow = soneriWs.Range("I" & soneriWs.Rows.Count).End(xlUp).Row
mappinglastrow = mappingWs.Range("A" & mappingWs.Rows.Count).End(xlUp).Row
Also remember to clear any errors that might occur, otherwise you can run into trouble. Insert this before the Sub returns:
If Err <> 0 Then Err.Clear
I see you removed your "on error" statement.
I would also recommend that you force variable decalarations, as I can see you use undeclared variables, which will also get you into trouble sooner or later. Insert this as the first line in all modules:
Option Explicit
EDIT:
Please post test data "as text" next time to help people help you.
Here is a solution.
I uncommented your if statement, as it seem to not update the first record.
Sub ButtonClick()
Dim soneriWs As Worksheet, mappingWs As Worksheet
Dim sonerilastrow As Long, mappinglastrow As Long, x As Long, b As String, c As String
Dim Dct As Object
Dim Cnt As Long
Dim CntTxt As String
Dim PreTxt As String
Dim Idx As Long
Dim datarange As Range
Set soneriWs = ThisWorkbook.Worksheets("Soneri")
Set mappingWs = ThisWorkbook.Worksheets("Mapping")
sonerilastrow = soneriWs.UsedRange.Rows.Count
mappinglastrow = mappingWs.UsedRange.Rows.Count
Set Dct = CreateObject("Scripting.Dictionary")
Set datarange = mappingWs.Range("A2:B" & mappinglastrow)
For x = 2 To sonerilastrow
b = Application.WorksheetFunction.VLookup(soneriWs.Range("I" & x).Value, datarange, 2, False)
Idx = InStr(b, "-")
PreTxt = Left(b, Idx)
CntTxt = Mid(b, Idx + 1)
If Dct.Exists(b) Then
Cnt = Dct(b) + 1
Else
Cnt = Val(CntTxt)
End If
Dct(b) = Cnt
'If x > 2 Then
c = PreTxt & Format(Cnt, "0000")
' Use this instead, if you want to preserve the number of characters
'c = PreTxt & Format(Cnt, String(Len(CntTxt), "0"))
soneriWs.Range("D" & x).Value = c
'End If
Next x
End Sub
If you are new to VBA I recommend that you learn how to use the Scripting.Dictionary.
Your loop is only made for a single match of the Asset class.
There are a few problems here, but the if x > 2 approach would really only work if there was only one counter. Then we could substitute + 1 with something like + x - 2 (since we start at 3 for this part of the code).
But what you need is a counter that resets each time there is a new Asset class.
n = 1
For x = 2 To sonerilastrow
b = Application.WorksheetFunction.VLookup( _
soneriWs.Range("I" & x).Value, datarange, 2, False)
soneriWs.Range("D" & x).Value = b
If x > 2 Then
If Not Left(b, 7) = Left(soneriWs.Range("D" & x -1).Value, 7) then
n = 1
else
c = Left(b, 7) & Format(Val(Right(b, 4)) + n, "-0000")
soneriWs.Range("D" & x).Value = c
n = n + 1
End if
End If
Next x
Another way of writing it would be
Dim soneriWs As Worksheet, mappingWs As Worksheet
Dim sonerilastrow As Long, mappinglastrow As Long, i As Long
Dim datarange As Range, assetrange As Range, b As Range
Dim entry As Variant
Set soneriWs = ThisWorkbook.Worksheets("Soneri")
Set mappingWs = ThisWorkbook.Worksheets("Mapping")
sonerilastrow = soneriWs.Range("I" & soneriWs.Rows.Count).End(xlUp).Row
mappinglastrow = mappingWs.Range("A" & mappingWs.Rows.Count).End(xlUp).Row
Set datarange = mappingWs.Range("A2:B" & mappinglastrow)
Set assetrange = soneriWs.Range("I2:I" & sonerilastrow)
i = 0
For Each entry In assetrange
Set b = entry.Offset(, -5)
b = Application.WorksheetFunction.VLookup(entry, datarange, 2, False)
If entry = entry.Offset(-1) Then
i = i + 1
b = Left(b, Len(b) - 1) & (Right(b, 1) + i)
Else
i = 0
End If
Next entry
But it's using much the same approach.
These however expect the data to be sorted on the "I" column, since the counter will reset if there is another asset in between.
If you want it to work even when not sorted, you could use something like countIf, like so: (Replacing the loop)
Set assetrange = soneriWs.Range("I2:I" & sonerilastrow)
assetrange.Offset(, -5).Clear
For Each entry In assetrange
Set b = entry.Offset(, -5)
b = Application.WorksheetFunction.VLookup(entry, datarange, 2, False)
i = Application.WorksheetFunction.CountIf(assetrange.Offset(, -5), Left(b, 5) & "*")
b = Left(b, Len(b) - 1) & (Right(b, 1) + i - 1)
Next entry

Redundancy in logic for excel VBA

Please see the image attached -
My requirement is -
"If status null and Ref No. not unique then
check value2. If value2 not present, check value1 and take average
Example: For ref number = 1, calculated value is (50+10)/2 = 30 "
"if status is selected or Ref no is unique then
copy from value2, if not present then copy from value1
Example: For Ref No 3, value is 100 and for Ref No 4, value is 20
Total value= 100+30+20 = 150
My attempt
For I = 2 To lrow 'sheets all have headers that are 2 rows
'unique
If Application.WorksheetFunction.CountIf(ws.Range("A" & fRow, "A" & lrow), ws.Range("A" & I)) = 1 Then
If (ws.Range("AW" & I) <> "") Then 'AW has value2
calc = calc + ws.Range("AW" & I).Value
Else: calc = calc + ws.Range("AV" & I).Value 'AV has value1
End If
'not unique
Else
'selected
If ws.Range("AY" & I) = "Selected" Then 'AY has status (Selected/Null)
If (ws.Range("AW" & I) <> "") Then
calc = calc + ws.Range("AW" & I).Value
Else: calc = calc + ws.Range("AV" & I).Value
End If
'not selected
Else
If (ws.Range("AW" & I) <> "") Then
calc1 = calc1 + ws.Range("AW" & I).Value
Else: calc1 = calc1 + ws.Range("AV" & I).Value
End If
calc1 = calc1/Application.WorksheetFunction.CountIf(ws.Range("A" & fRow, "A" & lrow), ws.Range("A" & I))
End If
End If
My problem is -
Getting the Ref No 3 twice in my logic.
Not able to calculate the correct average.
How can I get the correct output? Thanks.
Using a SQL statement against the worksheet
If I understand your requirements, they are as follows:
For each Ref no, you want
the average of
value2 if it exists, otherwise value1
where the status is selected, or
there is no status = selected for this Ref no
I would open an ADODB Recordset against the data, with the following SQL:
SELECT [Ref no], Avg(Iif(value2 IS NOT NULL, value2, value1)) AS Result
FROM Sheet1
LEFT JOIN (
SELECT DISTINCT [Ref No]
FROM Sheet1
WHERE status = "selected"
) t1 ON Sheet1.[Ref no] = t1.[Ref no]
WHERE Sheet1.status="selected" OR t1.[Ref no] IS NULL
GROUP BY [Ref no]
Using nested Scripting.Dictionary
If SQL is not your thing, then you could something like the following:
'Define names for the columns; much easier to read row(RefNo) then arr(0)
Const refNo = 1
Const status = 3
Const value1 = 5
Const value2 = 6
'For each RefNo, we have to store 3 pieces of information:
' whether any of the rows are selected
' the sum of the values
' the count of the values
Dim aggregates As New Scripting.Dictionary
Dim arr() As Variant
arr = Sheet1.UsedRange.Value
Dim maxRow As Long
maxRow = UBound(arr, 1)
Dim i As Long
For i = 2 To maxRow 'exclude the column headers in the first row
Dim row() As Variant
row = GetRow(arr, i)
'Get the current value of the row
Dim currentValue As Integer
currentValue = row(value1)
If row(value2) <> Empty Then currentValue = row(value2)
'Ensures the dictionary always has a record corresponding to the RefNo
If Not aggregates.Exists(row(refNo)) Then Set aggregates(row(refNo)) = InitDictionary
Dim hasPreviousSelected As Boolean
hasPreviousSelected = aggregates(row(refNo))("selected")
If row(status) = "selected" Then
If Not hasPreviousSelected Then
'throw away any previous sum and count; they are from unselected rows
Set aggregates(row(refNo)) = InitDictionary(True)
End If
End If
'only include currently seleced refNos, or refNos which weren't previously selected,
If row(status) = "selected" Or Not hasPreviousSelected Then
aggregates(row(refNo))("sum") = aggregates(row(refNo))("sum") + currentValue
aggregates(row(refNo))("count") = aggregates(row(refNo))("count") + 1
End If
Next
Dim key As Variant
For Each key In aggregates
Debug.Print key, aggregates(key)("sum") / aggregates(key)("count")
Next
with the following two helper functions:
Function GetRow(arr() As Variant, rowIndex As Long) As Variant()
Dim ret() As Variant
Dim lowerbound As Long, upperbound As Long
lowerbound = LBound(arr, 2)
upperbound = UBound(arr, 2)
ReDim ret(1 To UBound(arr, 2))
Dim i As Long
For i = lowerbound To upperbound
ret(i) = arr(rowIndex, i)
Next
GetRow = ret
End Function
Function InitDictionary(Optional selected As Boolean = False) As Scripting.Dictionary
Set InitDictionary = New Scripting.Dictionary
InitDictionary.Add "selected", selected
InitDictionary.Add "sum", 0
InitDictionary.Add "count", 0
End Function
Explanation of SQL
For each Ref no, you want
Group the records by Ref no, using the GROUP BY clause
the average of
We'll return both the Ref no and the average -- SELECT [Ref no], Avg(...)
value2 if it exists, otherwise value1
Iif(value2 IS NOT NULL, value2, value1)
where the status is selected, or
WHERE Sheet1.status="selected" OR
there is no status = selected for this Ref no
We get a list of (unique -- DISTINCT) Ref nos that have status = "selected":
SELECT DISTINCT [Ref No]
FROM Sheet1
WHERE status = "selected"
and give it a name (AS t1) so we can refer to it separately from the main list (Sheet1)
Then we connect, or join (JOIN) that sublist to the main list, where the [Ref no] is the same in both (ON Sheet1.[Ref no] = t1.[Ref no]).
A simple JOIN is an INNER JOIN, where the records on both sides of the connection have to match. What we want in this case, is the records on the main list which do not match the records in the sublist. In order to see such records, we can use a LEFT JOIN, which displays all the records on the left side, and only those records on the right side that match.
We can then filter out the records that do match, using OR t1.[Ref no] IS NULL.
There must be a more concise way, but I think this does what you want. It is based on your example, so data in A1:F6 so will need amending.
Sub x()
Dim v2() As Variant, v1, i As Long, n As Long, d As Double
v1 = Sheet1.Range("A1:F6").Value
ReDim v2(1 To UBound(v1, 1), 1 To 5) 'ref/count/null/value null/value selected
With CreateObject("Scripting.Dictionary")
For i = 2 To UBound(v1, 1)
If Not .Exists(v1(i, 1)) Then
n = n + 1
v2(n, 1) = v1(i, 1)
v2(n, 2) = v2(n, 2) + 1
If v1(i, 3) = "" Then
v2(n, 3) = v2(n, 3) + 1
v2(n, 4) = IIf(v1(i, 6) = "", v1(i, 5), v1(i, 6))
ElseIf v1(i, 3) = "selected" Then
v2(n, 5) = IIf(v1(i, 6) = "", v1(i, 5), v1(i, 6))
End If
.Add v1(i, 1), n
ElseIf .Exists(v1(i, 1)) Then
v2(.Item(v1(i, 1)), 2) = v2(.Item(v1(i, 1)), 2) + 1
If v1(i, 3) = "" Then
v2(.Item(v1(i, 1)), 3) = v2(.Item(v1(i, 1)), 3) + 1
If v1(i, 6) = "" Then
v2(.Item(v1(i, 1)), 4) = v2(.Item(v1(i, 1)), 4) + v1(i, 5)
Else
v2(.Item(v1(i, 1)), 4) = v2(.Item(v1(i, 1)), 4) + v1(i, 6)
End If
Else
If v1(i, 6) = "" Then
v2(.Item(v1(i, 1)), 5) = v2(.Item(v1(i, 1)), 5) + v1(i, 5)
Else
v2(.Item(v1(i, 1)), 5) = v2(.Item(v1(i, 1)), 5) + v1(i, 6)
End If
End If
End If
Next i
End With
For i = LBound(v2, 1) To UBound(v2, 1)
If v2(i, 2) > 1 And v2(i, 3) = v2(i, 2) Then
d = d + v2(i, 4) / v2(i, 2)
End If
If v2(i, 2) > 1 And v2(i, 3) < v2(i, 2) Then
d = d + v2(i, 5) / (v2(i, 2) - v2(i, 3))
End If
If v2(i, 2) = 1 And v2(i, 3) = v2(i, 2) Then
d = d + v2(i, 4) / v2(i, 2)
End If
Next i
MsgBox "Total = " & d
End Sub

Numbering filtered rows

I tried to write a Macro, that does the following:
I have a table with many rows and columns, including one column that holds names
like "J63 System" or "J28 System" specifing which part of a machine every part in a row belongs to. Now I filter for one system and look at the parts: I have one empty column and want to number all the parts with the same part-number, everytime beginning from 1 whenever a new partnumber appears.
but the macro doesnt work correctly and I cant figure out why:
Option Explicit
Dim i As Integer, n As Integer, k As Integer
Dim system As String
Dim part0 As String, part1 As String
Sub temato()
n = 887
k = 888
Do
part0 = Cells(n, 2)
part1 = Cells(k, 2)
If Cells(k, 36) = "J64 Tail Rotor" Then
If part1 = part0 Then
Cells(k, 3) = Cells(k - 1, 3).Value + 1
n = n + 1
k = k + 1
Else
Cells(k, 3) = 1
n = n + 1
k = k + 1
End If
Else
k = k + 1
Debug.Print n
Debug.Print k
Do
'n bleibt
part1 = Cells(k, 2)
If Cells(k, 36) = "J64 Tail Rotor" Then
If part1 = part0 Then
Cells(k, 3) = Cells(n, 3).Value + 1
n = k
k = k + 1
Else
Cells(k, 3) = 1
n = k
k = k + 1
End If
Else
k = k + 1
End If
Loop While Cells(k, 36) <> "J64 Tail Rotor"
End If
Loop While k <= 1260
End Sub
`
Add a reference to Microsoft ActiveX Data Objects 6.1 Library then copy this macro:
Dim oConn As ADODB.Connection, rs As ADODB.Recordset, sSheet as String
Dim sWorkbookName as String
sWorkbookName = ThisWorkbook.FullName
connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=""" &
sWorkbookName & """;Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX = 1"""
sSheet="myDataSheet1"
oConn.Open connString
'just an example of SQL, you have to customize it
sSQL = "SELECT [FIELD1], [FIELD2] FROM [" & sSheet & "$] " &
" WHERE [FIELD1] Like ""*yourmatch"" ORDER BY [FIELD1] ASC"
rs.Open sSQL, oConn, adOpenStatic, adLockOptimistic, adCmdText
'dump results on a temporary sheet or on the data sheet in an empty column
ThisWorkbook.Worksheets("tmp_sheet").Range("A2").CopyFromRecordset rs
rs.Close
oConn.Close
Set rs = Nothing
Set oConn = Nothing
Once you post the table structure and specify the desired result I shall write the SQL query

IF duplicate cell value found in column then return value

I need to track a person in a data sheet to determine from which location to which location the person moved.
If a person appears more then one time in Column J that means the person has changed the location and the location value is in Column L. For this I have the following code:
=IF(J18=J19;IF(COUNTIF(J:J;J18)>1; "From "&L18 &" to "& IF(J18=J19;L19;"");"");"")
The problem is if the person changes the location more than two times. In Column O to Column AA I have the months of the year which determines the location of the person.
How can I modify this code to do the above:
=IF(J18=J19;IF(COUNTIF(J:J;J18)>1; "From "&L18 &" to "& IF(J18=J19;L19;"");"");"")
Here is a User Defined Function (aka UDF) to accomplish the task.
Function my_Travels(nm As Range, loc As Range, cal As Range)
Dim n As Long, cnt As Long, v As Long, vLOCs As Variant, vTMPs As Variant
Dim iLOC As Long, sTMP As String
my_Travels = vbNullString '"no travels"
cnt = Application.CountIf(nm.EntireColumn, nm(1))
If Application.CountIf(nm, nm(1)) = cnt And cnt > 1 Then
Set loc = loc.Rows(1).Resize(nm.Rows.Count, loc.Columns.Count)
Set cal = cal.Rows(1).Resize(nm.Rows.Count, cal.Columns.Count)
'seed the array
ReDim vLOCs(1 To cnt, 1 To cnt)
For v = LBound(vLOCs, 1) To UBound(vLOCs, 1)
vLOCs(v, 1) = cal.Columns.Count + 1
vLOCs(v, 2) = cal.Columns.Count + 1
Next v
'collect the values into the array
For n = 1 To nm.Rows.Count
If nm.Cells(n, 1).Value2 = nm.Cells(1, 1).Value2 Then
iLOC = Application.Match(1, Application.Index(cal, n, 0), 0)
For v = LBound(vLOCs, 1) To UBound(vLOCs, 1)
If vLOCs(v, 1) = cal.Columns.Count + 1 Then
vLOCs(v, 1) = iLOC
vLOCs(v, 2) = n
Exit For
End If
Next v
End If
Next n
'sort the values in the array
For v = LBound(vLOCs, 1) To (UBound(vLOCs, 1) - 1)
For n = (v + 1) To UBound(vLOCs, 1)
If vLOCs(v, 1) > vLOCs(n, 1) Then
vTMPs = Array(vLOCs(v, 1), vLOCs(v, 2))
vLOCs(v, 1) = vLOCs(n, 1)
vLOCs(v, 2) = vLOCs(n, 2)
vLOCs(n, 1) = vTMPs(0)
vLOCs(n, 2) = vTMPs(1)
Exit For
End If
Next n
Next v
'concatenate the locations from the array
For v = LBound(vLOCs) To (UBound(vLOCs) - 1)
sTMP = sTMP & "From " & loc.Cells(vLOCs(v, 2), 1) & " to " & loc.Cells(vLOCs(v + 1, 2), 1) & "; "
Next v
'truncate the string and return it
sTMP = Left(sTMP, Len(sTMP) - 2)
my_Travels = sTMP
End If
End Function
The Locations and the Calendar cells only need to be defined by the first row. Each has its height (i.e. rows) redefined to maintain consistency with the list of names.
    
In AB2 (as above) the formula is,
=my_Travels(J2:J$8, L2, O2:AA2)
Fill down as necessary.

Resources