I'm really new in VBA coding, currently I have a spreadsheet contains groups and accounts at different levels, following are a simple example:
Group codes are all numbers and Account codes start with 3 letters, say ABC follow by 2 or 3 numbers, so an example would be ABC100, first 2 letter of account codes are always the same, i.e "AB" in the example, so another account code might be ABS80. Group/Account codes are in a separate column corresponding to the groups/accounts.
My aim is to set up a macro which would provide me with a summary, in a separate tab called say results, of all the groups (ONLY) above a given Account/Group in the hierarchy tree, with the subject Account/Group on the bottom.
So to illustrate using the above example. If the subject Account is ABC100, then after running the macro, I would expect to see in the results tab:
So far, I managed to get the macro to find the position of the subject account in the hierarchy and copy that row into the results" tab. But I'm stuck on the next step which is to extract only the direct upper level groups (at the same time ignore the accounts & groups in between) and paste them into the results tab.
I know that I need to use the loop and tried For Next with If Then statement in between, but keeps getting error messages. Really appreciate if someone could lead me to right direction on which loop to use.
Thanks! Below are my current codes:
Sub SearchRelevantAccGp()
'
' This macro finds the account or group and provides a summary of all affected groups
' within the Hierarchy
Dim searchvalue As Variant
searchvalue = Sheets("Dashboard").Range("B2")
Dim hierarchy As Integer
Sheets("Main Tree").Select
cells.Find(What:=searchvalue, After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
hierarchy = ActiveCell.Offset(0, 5)
Dim startref As Variant
startref = "I" & ActiveCell.Row
Dim rownumber As Integer
rownumber = ActiveCell.Row
ActiveCell.EntireRow.Select
Selection.Copy
Sheets("Result").Select
Rows(hierarchy).Select
ActiveSheet.Paste
Sheets("Main Tree").Select
Range(startref).Select
For i = rownumber To 2 Step -1
If cells(i - 1, 9).Value - 1 = cells(i, 9).Value And cells(i - 1, 3).Value = "Group" Then
Rows(i).Select
Selection.Copy
Sheets("Result").Select
Rows(hierarchy - 1).Select
ActiveSheet.Paste
End If
Next i
End Sub
Consider no For loop or If logic and simply use SQL which you can in Excel for PC using the Jet/ACE SQL Engine (Windows .dll files). Because your worksheet represents a table we can run various WHERE logic to output to results tab with CopyFromRecordset method:
SQL (embedded below, adjust SheetName and column headers as needed)
SELECT [Type], [Account / Group ID], [Account / Group Name], [Hierarchy Position]
FROM SheetName$
WHERE (([Type] = 'Group' AND [Account / Group Name] NOT LIKE '%dupe%')
OR ([Account / Group ID] = 'ABC100'))
AND ([Hierarchy Position] <= (SELECT Max([Hierarchy Position])
FROM SheetName$ sub
WHERE sub.[Account / Group ID] = 'ABC100'))
VBA (connects to last saved instance of current workbook)
Sub RunSQL()
Dim conn As Object, rs As Object
Dim strConnection As String, strSQL As String
Dim i As Integer
Set conn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
' CONNECTION STRINGS (TWO VERSIONS -ODBC/OLEDB)
strConnection = "DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" _
& "DBQ=C:\Path\To\Workbook.xlsm;"
' strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" _
' & "Data Source=C:\Path\To\Workbook.xlsm';" _
' & "Extended Properties=""Excel 12.0 Xml;HDR=YES;"";"
' OPEN DB CONNECTION
conn.Open strConnection
strSQL = "SELECT [Type], [Account / Group ID], [Account / Group Name], [Hierarchy Position]" _
& " FROM SheetName$" _
& " WHERE (([Type] = 'Group' AND [Account / Group Name] NOT LIKE '%dupe%')" _
& " OR ([Account / Group ID] = 'ABC100'))" _
& " AND ([Hierarchy Position] <= (SELECT Max([Hierarchy Position])" _
& " FROM SheetName$ sub" _
& " WHERE sub.[Account / Group ID] = 'ABC100'))"
' OPEN RECORDSET OF SQL RESULTS
rs.Open strSQL, conn
' OUTPUT DATA TO EXISTING SHEET
With ThisWorkbook.Worksheets("results")
' COLUMN HEADERS
For i = 1 To rs.Fields.Count
.Cells(1, i) = rs.Fields(i - 1).Name
Next i
' DATA ROWS
.Range("A2").CopyFromRecordset rs
End With
rs.Close: conn.Close
Set rs = Nothing: Set conn = Nothing
Exit Sub
End Sub
Try this. This used a variant array.
Sub test()
Dim vDB, vR()
Dim Ws As Worksheet, toWs As Worksheet
Dim r As Long, i As Long, n As Long, j As Integer
Set Ws = ActiveSheet
Set toWs = Sheets(2)
vDB = Ws.Range("a1").CurrentRegion
r = UBound(vDB, 1)
For i = 2 To r
If InStr(vDB(i, 3), "Group Level") Or vDB(i, 1) = "ABC100" Then
n = n + 1
ReDim Preserve vR(1 To 4, 1 To n)
For j = 1 To 4
vR(j, n) = vDB(i, j)
Next j
End If
Next i
With toWs
.UsedRange.Clear
.Range("a1").Resize(1, 4) = Ws.Range("a1").Resize(1, 4).Value
.Range("a2").Resize(n, 4) = WorksheetFunction.Transpose(vR)
.Columns.AutoFit
End With
End Sub
This traverses the hierarchy backwards in "Results" which is a full copy of worksheet "Dashboard"
Hides all rows, then unhides each relevant row, to avoid copying and pasting data
Option Explicit
Public Sub ShowHierarchy()
Dim ws As Worksheet, found As Range, r As Long, nextR As Long
Set ws = ThisWorkbook.Worksheets("Results")
Set found = ws.UsedRange.Columns(2).Find(What:="ABC10", LookAt:=xlWhole)
If Not found Is Nothing Then 'ABC100 was found so we continue
ws.UsedRange.EntireRow.Hidden = True 'hide all rows on Results sheet
r = found.Row: nextR = -1 'get found row, and move up to next row
If r > 1 Then 'make sure it wasn't found on row 1
ws.Rows(1).Hidden = False 'unhide header row
ws.Cells(1).Activate 'update display (scroll to top row)
found.EntireRow.Hidden = False 'unhide found row
Dim foundLvl As Long, nextLvl As Long, lvlRng As Range
foundLvl = Val(found.Offset(0, 2)) 'get current level from column D
nextLvl = foundLvl 'establish initial (minimum) level
Application.ScreenUpdating = False 'turn off display
While nextLvl > 1 'loop while level is greater than 1
Set lvlRng = found.Offset(nextR, 2) 'get next level from column D
If Not IsError(lvlRng) Then 'check for errors (#N/A, #DIV/0!, etc)
nextLvl = Val(lvlRng) 'set next level
If nextLvl < foundLvl Then 'compare levels
If LCase(lvlRng.Offset(0, -3)) = "group" Then 'check Group in Col A
foundLvl = nextLvl 'set next minimum levele
lvlRng.EntireRow.Hidden = False
End If
End If
End If
nextR = nextR - 1 'move up to the next row, and repeat
Wend
Application.ScreenUpdating = True 'turn display back on
End If
End If
End Sub
Before
After
Related
I'm doing work to automate a ledger of mine based on the FIFO accounting principle (First in First out), where anything that would be referred to as a contra balance would be subtracted from the first entry, then the second, until that variable is zero (or if there is residual begin a new accounting line).
Generally what I have been doing to add a new position to this ledger (not removing any balance simply creating a line item is this...
Tickerstring = TTB 'TTB is the user defined input for the ticker
tickercolumn = HBWS.Cells.Find(What:="Ticker").Column 'Use this to identify
what column the ticker field is
Set TickerResult = HBWS.Cells.Find(What:=TickerString, LookIn:=xlValues)
If Not TickerResult Is Nothing Then
tickerRow = TickerResult.Row
Else
End If 'Identifies the row which the actual Ticker is in i.e. the TTB
HBWS.Cells(tickerRow, tickercolumn) = TTB
I use that same concept to define the amount of Shares, and whether they are long and short. Inserting Userform inputs into the respective cells.
My question is, say I run that code 3 times and now have 3 lines items that look like this
AAPL 300 Long
AAPL 100 Long
AAPL 100 Long
Then I want to add a new position for 600 short, which would go through the FIFO accounting process and remove 300 from the first row, 100 from the second, 100 from the third, then create a new line with the 100 short. How would I go about doing that?
I would imagine that I would be subtracting from a user defined variable i.e take 300 out of the first row, now my defined variable is left at 300 (when it started at 600).
Basically i think the best way to describe this would be how do I subtract from a variable based on current workbook values, then continue using this in my sub procedure.
EDIT: Editing my post for clarity
i have the following entry in my spreadsheet
I want to run my macro to take my short position indicated in the below userform subtract it from currently in my spread sheet then create a residual line representing what is left in the short position
The end state should look like this
Let me know if you need additional info
Requirements:
Maintain a ledger of shares transactions, generated from an user form input (one at a time).
Calculate & show the net position of the shares, using the FIFO inventory valuation method.
Proposed Solution:
The requirements can be achieved using:
A ListObject to contain the ledger of transactions and to calculate the end position after each transaction.
A PivotTable to show the end position of the shares (and any other report needed).
The figure below shows the proposed ListObject and PivotTable
ListObject Fields:
Input from user form
Ticker : Share symbol.
L/S : Share position (Long\Short).
Lots : Quantity of shares.
Calculated by VBA procedure
L/S.Net: Net share position (Long\Short).
Qty: Net share quantity (absolute value).
Lots.Net: Net share quantity.
T: Record Type (P: Prior \ R: Residual), used to flag the latest transaction of a share.
TimeStamp: Record date & time of posting, used to apply the FIFO valuation method.
VBA Procedure:
See explanations\coments inserted in the procedure.
Option Private Module
Option Compare Text
Option Explicit
Option Base 1
Rem Updated 20180504_121918
Sub ListObject_Stocks_Ledger_FIFO(vRcrd As Variant)
Dim aFlds As Variant, vFld As Variant
aFlds = [{"Ticker","L/S","Lots","T","TimeStamp","Lots.Net","L/S.Net","Qty"}]
Dim lo As ListObject, pt As PivotTable
Dim sTicker As String, lCnt As Long, lPos As Long
Dim lRow As Long, bCol As Byte, b As Byte
Dim sFml As String
Dim vValue As Variant
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Rem Set Objects
With ThisWorkbook.Worksheets("Sht(0)") 'change as required
Application.Goto .Cells(1), 1
Set pt = .PivotTables("ptPositions")
Set lo = .ListObjects("loPositions")
End With
With lo
Rem Set ListObject New Row
lRow = 1 + .ListRows.Count
Select Case lRow
Case 1
Rem ListObject with zero records
.HeaderRowRange.Cells(2, 1).Value2 = "!NEW"
Case Else
vFld = "Ticker"
sTicker = vRcrd(1)
bCol = .ListColumns(vFld).Index
lCnt = WorksheetFunction.CountIfs(.DataBodyRange.Columns(bCol), sTicker)
Rem Flag prior Ticker records
Select Case lCnt
Case 0
Rem New Ticker - NO ACTION
Case 1
Rem Ticker with only one prior record
lPos = WorksheetFunction.Match(sTicker, .DataBodyRange.Columns(bCol), 0)
.ListColumns("T").DataBodyRange.Cells(lPos).Value2 = "P"
Case Else
Rem Ticker with only one prior record
.Range.AutoFilter Field:=bCol, Criteria1:=sTicker
.ListColumns("T").DataBodyRange.SpecialCells(xlCellTypeVisible).Value2 = "P"
.Range.AutoFilter
End Select: End Select
Rem Add New Record
For Each vFld In aFlds
b = 1 + b
bCol = .ListColumns(vFld).Index
Rem Set Field Value\Formula
sFml = vbNullString
vValue = vbNullString
Select Case vFld
Case "Ticker", "L/S", "Lots": vValue = vRcrd(b)
Case "T": vValue = "R"
Case "TimeStamp": vValue = CDbl(Now)
Case "L/S.Net"
sFml = "=IF(NOT(EXACT([#T],'R')),CHAR(39)," & vbLf _
& "IF([#[Lots.Net]]<0,'Short',IF([#[Lots.Net]]>0,'Long','Zero')))"
Case "Qty"
sFml = "=IF(NOT(EXACT([#T],'R')),CHAR(39)," & vbLf _
& "ABS([#[Lots.Net]]))"
Case "Lots.Net"
sFml = "=IF(NOT(EXACT([#T],'R')),CHAR(39),SUM(" & vbLf _
& "SUMIFS([Lots],[Ticker],[#Ticker],[L/S],'Long',[TimeStamp],'<='&[#TimeStamp])," & vbLf _
& "-SUMIFS([Lots],[Ticker],[#Ticker],[L/S],'Short',[TimeStamp],'<='&[#TimeStamp])))"
End Select
Rem Apply Field Value\Formula
Select Case vbNullString
Case Is <> vValue
.DataBodyRange.Cells(lRow, bCol).Value2 = vValue
Case Is <> sFml
sFml = Replace(sFml, Chr(39), Chr(34))
With .DataBodyRange.Columns(bCol)
.Formula = sFml
.Value2 = .Value2
End With: End Select: Next: End With
Rem Sort ListObject
With lo.Sort
With .SortFields
.Clear
.Add Key:=lo.ListColumns("Ticker").DataBodyRange, _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
.Add Key:=lo.ListColumns("TimeStamp").DataBodyRange, _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
End With
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Rem Refresh PivotTable
pt.PivotCache.Refresh
Application.EnableEvents = False
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = False
End Sub
Use this procedure to simulate the posting from the user form:
Sub ListObject_Stocks_Ledger_FIFO_TEST()
Dim aDATA As Variant, vRcrd As Variant
aDATA = Array( _
Array("AAPL", "Long", "300"), _
Array("AAPL", "Long", "100"), _
Array("AAPL", "Long", "100"), _
Array("AAPL", "Short", "600"), _
Array("BCS", "Long", "300"), _
Array("BCS", "Long", "100"), _
Array("BCS", "Short", "500"), _
Array("Test", "Long", "100"), _
Array("Test", "Long", "200"), _
Array("Test", "Long", "300"), _
Array("Test", "Short", "400"))
For Each vRcrd In aDATA
Call ListObject_Stocks_Ledger_FIFO(vRcrd)
: Stop
Next
End Sub
This could be an example of what you want to do, hopefully is usefull to you:
Sub test()
Dim reduce_amount As String
reduce_amount = Val(InputBox("Number:"))
Dim cell As Range
For Each cell In Selection
cell_value = Mid(cell.Value, 6, 3)
If IsNumeric(cell_value) Then
reduce_amount = reduce_amount - cell_value
End If
Next cell
If reduce_amount > 0 Then
Selection.End(xlDown).Offset(1, 0).Value = "AAPL " & reduce_amount & " Long"
End If
End Sub
I think you should make it so each transaction stands on it's own (unless you have a good reason to do it otherwise). I never store "state" in a cell if possible. What about keeping track of each bucket. Here's an example
Public Sub AddLots(ByVal Ticker As String, ByVal Lot As Double)
Dim rCell As Range
Dim LotRemains As Double
Dim dc As Scripting.Dictionary
Dim dToTake As Double
Dim ThisTicker As String, ThisLS As String, ThisLot As Double, ThisBucket As Long, ThisTotal As Double
Dim lo As ListObject
Dim aOutput() As Variant
Dim MaxBucket As Long
Dim i As Long
LotRemains = Lot
Set dc = New Scripting.Dictionary
Set lo = Sheet1.ListObjects(1)
For Each rCell In lo.ListColumns(1).DataBodyRange.Cells
'Store this row's values
ThisTicker = rCell.Value: ThisLS = rCell.Offset(0, 1).Value: ThisLot = rCell.Offset(0, 2).Value
ThisBucket = rCell.Offset(0, 3).Value: ThisTotal = rCell.Offset(0, 4).Value
'if the ticker is the same
If ThisTicker = Ticker Then
'if it's going the opposite way of our transaction
If (Lot > 0 And ThisLS = "Short") Or _
(Lot < 0 And ThisLS = "Long") Then
'if there's still something left in the bucket
If ThisTotal <> 0 Then
If Abs(ThisTotal) >= Abs(LotRemains) Then
dToTake = LotRemains
Else
dToTake = -ThisTotal
End If
'store this bucket
dc.Add ThisTicker & "|" & ThisBucket, dToTake
'reduce the amount left to test
LotRemains = LotRemains - dToTake
'stop looking if we've used it all up
If LotRemains = 0 Then Exit For
End If
End If
End If
Next rCell
'this is an array we'll write out to the worksheet
ReDim aOutput(1 To dc.Count + IIf(LotRemains <> 0, 1, 0), 1 To 4)
'for every bucket we saved, put it in the array
For i = 1 To dc.Count
aOutput(i, 1) = Ticker
aOutput(i, 2) = IIf(Lot > 0, "Long", "Short")
aOutput(i, 3) = Abs(dc.Items(i - 1))
aOutput(i, 4) = Split(dc.Keys(i - 1), "|")(1)
Next i
'if we couldn't use it all up, get the next bucket number
If LotRemains <> 0 Then
For Each rCell In lo.ListColumns(1).DataBodyRange.Cells
If rCell.Value = Ticker Then
If rCell.Offset(0, 3).Value > MaxBucket Then
MaxBucket = rCell.Offset(0, 3).Value
End If
End If
Next rCell
'then add a new bucket to the array
aOutput(dc.Count + 1, 1) = Ticker
aOutput(dc.Count + 1, 2) = IIf(Lot > 0, "Long", "Short")
aOutput(dc.Count + 1, 3) = Abs(LotRemains)
aOutput(dc.Count + 1, 4) = MaxBucket + 1
End If
'write out the new transactions to the worksheet
lo.ListRows.Add.Range.Resize(UBound(aOutput, 1), UBound(aOutput, 2)).Value = aOutput
End Sub
Start here
Then run AddLots "BCS", 400 and get
Then run AddLots "BCS", -1000 and get
Then use a pivot table to see where you are by ticker, by bucket, or whatever
The formula in the last column of the table is
=SUMPRODUCT(([Ticker]=[#Ticker])*([Bucket]=[#Bucket])*([LS]="Long")*([Lots]))-SUMPRODUCT(([Ticker]=[#Ticker])*([Bucket]=[#Bucket])*([LS]="Short")*([Lots]))
I looked at all of the answers posted and took a few ideas from each. I used the below code to control the variable and then I have other code that kind of compiles everything using the final variable.
The section of code that clears out the other lots though and defines the variable is below
Dim rCell As Range
Dim reduce_amount As Variant
Dim HBWS As Worksheet
Dim TickerTotalString As String
TickerTotalString = "Total " & TTB
Set HBWS = Sheets("Hedgebook")
Dim FormulaWS As Worksheet
Set FormulaWS = Sheets("Formula_Template")
LastHBR = HBWS.Cells(HBWS.Rows.Count, "B").End(xlUp).Row
ClastHBC = HBWS.Cells(3, HBWS.Columns.Count).End(xlToLeft).Column
LastFWSR = FormulaWS.Cells(FormulaWS.Rows.Count, "B").End(xlUp).Row
CLASTFWSC = FormulaWS.Cells(3, FormulaWS.Columns.Count).End(xlToLeft).Column
Tickercolumn = HBWS.Cells.Find(What:="Ticker").Column
Datecolumn = HBWS.Cells.Find(What:="Date&Time Booked").Column
LScolumn = HBWS.Cells.Find(What:="L/S").Column
Lotscolumn = HBWS.Cells.Find(What:="Lots").Column
Conversioncolumn = HBWS.Cells.Find(What:="Conversion Cents").Column
Borrowcolumn = HBWS.Cells.Find(What:="Borrow (bps)").Column
Set Tickerresult = HBWS.Cells.Find(What:=TickerTotalString, LookIn:=xlValues)
If Not Tickerresult Is Nothing Then
Tickerrow = Tickerresult.Row
Else
End If
reduce_amount = LTB 'Userform input that defines the total lots
If reduce_amount > 0 Then
For Each rCell In HBWS.Range(Cells(3, Tickercolumn), Cells(LastHBR, Tickercolumn))
If rCell.Value = TTB And rCell.Offset(0, -1).Value <> TickerTotalString And reduce_amount > 0 Then
Cell_value = rCell.Offset(0, 3).Value
If reduce_amount < Cell_value Then
rCell.Offset(0, 3).Value = Cell_value - reduce_amount
ElseIf reduce_amount > Cell_value Then
rCell.Offset(0, 3).Value = 0
reduce_amount = reduce_amount - Cell_value
ElseIf reduce_amount = Cell_value Then
reduce_amount = 0
rCell.Offset(0, 3).Value = 0
End If
End If
Next
End If
I just want to ask if someone do have a code for this.
I have a saved data in Sheet2 and I want to update it using the new data from Sheet1. In the below example, the code will search for Family "Oh" in Sheet2 and update its details using the updated information from Sheet1 when I click on the Update button. Here are the screenshots of
Sheet1:
and
Sheet2:
Tried this code but I can't get it to work correctly
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim valueToSearch As String
Dim i As Integer, t As Integer
Set lookUpSheet = Worksheets("sheet1")
Set updateSheet = Worksheets("sheet2")
'get the number of the last row with data in sheet1 and in sheet2
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'for every value in column A of sheet2
For i = 1 To lastRowUpdate
valueToSearch = updateSheet.Cells(i, 1)
'look the value in column A of sheet1
For t = 1 To lastRowLookup
'if found a match, copy column B value to sheet1 and proceed to the next value
If lookUpSheet.Cells(t, 1) = valueToSearch Then
updateSheet.Cells(i, 2) = lookUpSheet.Cells(t, 2)
Exit For
End If
Next t
Next i
End Sub
Thank you in advance for your help
The following should do what you expect, I've commented the code so you may understand what it is doing:
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim valueToSearch As String
Dim i As Long, t As Long
Set lookUpSheet = Worksheets("Sheet1")
Set updateSheet = Worksheets("Sheet2")
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'get the number of the last row with data in sheet1 and in sheet2
For i = 2 To lastRowLookup 'i = 2 to last to omit the first row as that row is for headers
valueFamily = lookUpSheet.Cells(i, 1) 'Family, 1 = Column A
valueDOB = lookUpSheet.Cells(i, 2) 'DOB, 2 = Column B
valueName = lookUpSheet.Cells(i, 3) 'Name, 3 = Column C
valueAge = lookUpSheet.Cells(i, 4) 'Age, 4 = Column D
'above get the values from the four column into variables
For t = 2 To lastRowUpdate 't = 2 to last to omit the first row as that row is for headers
If updateSheet.Cells(t, 1) = valueFamily And updateSheet.Cells(t, 2) = valueDOB And updateSheet.Cells(t, 3) = valueName Then
'if family, dob and name match, then
updateSheet.Cells(t, 4) = valueAge
'update age value
Exit For
End If
Next t
Next i
End Sub
This could be shortened without using the variables and comparing cells instead like below:
Sub FindValues()
Dim lookUpSheet As Worksheet, updateSheet As Worksheet
Dim i As Long, t As Long
Set lookUpSheet = Worksheets("Sheet1")
Set updateSheet = Worksheets("Sheet2")
lastRowLookup = lookUpSheet.Cells(Rows.Count, "A").End(xlUp).Row
lastRowUpdate = updateSheet.Cells(Rows.Count, "A").End(xlUp).Row
'get the number of the last row with data in sheet1 and in sheet2
For i = 2 To lastRowLookup 'i = 2 to last to omit the first row as that row is for headers
For t = 2 To lastRowUpdate 't = 2 to last to omit the first row as that row is for headers
If updateSheet.Cells(t, 1) = lookUpSheet.Cells(i, 1) And updateSheet.Cells(t, 2) = lookUpSheet.Cells(i, 2) And updateSheet.Cells(t, 3) = lookUpSheet.Cells(i, 3) Then
'if family, dob and name match, then
updateSheet.Cells(t, 4) = lookUpSheet.Cells(i, 4)
'update age value
Exit For
End If
Next t
Next i
End Sub
The issue you were having is from the fact that you needed to have the IF Statement look at the first 3 cells instead of a single value, so with the AND between conditions you compare all three.
This is how to update to sql using adodb.
Sub UpdateSQL()
Dim Cn As Object
Dim strConn As String, Name As String
Dim Ws As Worksheet
Dim strSQL As String
Dim i As Integer
Dim vDB
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & ThisWorkbook.FullName & ";" & _
"Extended Properties=Excel 12.0;"
Set Ws = Sheets(1)
Name = Sheets(2).Name
With Ws
vDB = .Range("a2", .Range("d" & Rows.Count).End(xlUp))
End With
Set Cn = CreateObject("ADODB.Connection")
Cn.Open strConn
For i = 1 To UBound(vDB, 1)
strSQL = "UPDATE [" & Name & "$] set Age=" & vDB(i, 4) & " where Family = '" & vDB(i, 1) & "' AND DOB =#" & vDB(i, 2) & "# AND Name='" & vDB(i, 3) & "' "
Cn.Execute strSQL
Next i
Cn.Close
Set Cn = Nothing
End Sub
I found a macro to count & Highlight Duplicates. My friend & I have tried to get it working, but have not been able to get it working. Column A ( Name), Column L ( Yes - Filter), Column M (Duplicate). In addition to getting the code working, we are trying to get the macro to filter on Yes in column L, and Print Duplicate in Column M. Please advise what is wrong & how to fix the problem. Here is the code:
Option Explicit
Sub Find_Duplicates()
Dim col As Integer
Dim rng As String
col = 3
rng = "A5:A"
HighLightDuplicates rng, col
ShowMaxOnly rng, col
End Sub
Sub HighLightDuplicates(ByVal rng As String, ByVal col As Integer)
Dim i, j As Integer
Dim temp As Variant
Range(rng).Select
Dim Count As Integer
Count = 1
For i = 1 To Selection.Count
temp = Range(Left(rng, 1) & i)
For j = i + 1 To Selection.Count
If temp = Range(Left(rng, 1) & j) And temp <> "" Then
Count = Count + 1 'increase the number of duplicates
'highlight the duplicates
Range(Left(rng, 1) & i).Interior.Color = RGB(0, 100, 255)
Range(Left(rng, 1) & j).Interior.Color = RGB(0, 100, 255)
End If
Next
'show the number of duplicates
If Count > 1 Then
Cells(i, col) = Count & " duplicates"
End If
'reset count
Count = 1
Next
End Sub
'show only the biggest number of duplicates in a group
Sub ShowMaxOnly(ByVal rng As String, ByVal col As Integer)
Dim i, j As Integer
Dim temp As Variant
Range(rng).Select
i = 1
For i = 1 To Selection.Count
temp = Range(Left(rng, 1) & i)
For j = i + 1 To Selection.Count
If temp = Range(Left(rng, 1) & j) And temp <> "" Then
Cells(j, col) = "" 'remove the smaller numbers duplicates
End If
Next
Next
End Sub
Maybe this would work for you.
It adds the formula and then uses conditional formatting to highlight the cells.
Public Sub Find_Duplicates()
Dim wrkSht As Worksheet 'The worksheet that you're lookin for duplicates in.
Dim rng As Range 'The range containing the duplicates.
Dim Col As Long 'The last column containing data +1
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
With wrkSht
'Reference to whole data range.
Set rng = .Range("A5:L25")
'If the sheet is blank an error will be thrown when trying to find the last column.
'This code looks for the last column - you could just set Col to equal the last column number + 1.
On Error Resume Next
Col = .Cells.Find("*", , , , xlByColumns, xlPrevious).Column
Err.Clear
On Error GoTo 0
If Col = 0 Then Col = 1
'Place a COUNTIF formula in the last column.
rng.Offset(, Col).Columns(1).FormulaR1C1 = "=COUNTIF(" & rng.Columns(1).Address(ReferenceStyle:=xlR1C1) & ",RC" & rng.Column & ") & "" duplicates."""
With rng
'Add conditional formatting to first column in range: If the COUNTIF formula is showing >1 then highlight cell.
With .Columns(1)
'This formula is =VALUE(LEFT($M5,FIND(" ",$M5)-1))>1.
'It returns only the number from the duplicate count and checks it is higher than 1.
.FormatConditions.Add Type:=xlExpression, Formula1:= _
"=VALUE(LEFT(" & rng.Offset(, Col).Cells(1).Address(RowAbsolute:=False) & ",FIND("" ""," & _
rng.Offset(, Col).Cells(1).Address(RowAbsolute:=False) & ")-1))>1"
.FormatConditions(.FormatConditions.Count).SetFirstPriority
.FormatConditions(.FormatConditions.Count).Interior.Color = RGB(0, 100, 255)
End With
'Apply filter to your range.
.AutoFilter
.AutoFilter Field:=12, Criteria1:="Yes"
End With
End With
End Sub
Updated code:
This will always place the last column as 1 to the right of the range set in the variable rng.
Public Sub Find_Duplicates()
Dim wrkSht As Worksheet 'The worksheet that you're lookin for duplicates in.
Dim rng As Range 'The range containing the duplicates.
Dim rLastCol '1 column to right of rng.
Dim Col As Long 'The last column containing data +1
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
With wrkSht
'Reference to whole data range.
Set rng = .Range("A5:L25")
Set rLastCol = rng.Offset(, 1).Columns(rng.Columns.Count)
'Place a COUNTIF formula in the last column.
rLastCol.FormulaR1C1 = "=COUNTIF(" & rng.Columns(1).Address(ReferenceStyle:=xlR1C1) & ",RC" & rng.Column & ") & "" duplicates."""
With rng
'Add conditional formatting to first column in range: If the COUNTIF formula is showing >1 then highlight cell.
With .Columns(1)
'This formula is =VALUE(LEFT($M5,FIND(" ",$M5)-1))>1.
'It returns only the number from the duplicate count and checks it is higher than 1.
.FormatConditions.Add Type:=xlExpression, Formula1:= _
"=VALUE(LEFT(" & rLastCol.Cells(1).Address(RowAbsolute:=False) & ",FIND("" ""," & _
rLastCol.Cells(1).Address(RowAbsolute:=False) & ")-1))>1"
.FormatConditions(.FormatConditions.Count).SetFirstPriority
.FormatConditions(.FormatConditions.Count).Interior.Color = RGB(0, 100, 255)
End With
End With
End With
End Sub
I have an excel file that contains serial numbers(sn) of spare parts and production dates (pd) among many other extra data.
So far in order to find some extra data that refer to a specific sn I used the search function in excel. However a sn can have several pd and thus i had sometimes to click on the search button for more than a hundred times....
pd is always in a column on the left of the column where sn is. But there are more than 200 columns and their position isnt fixed...i.e. sometimes pd is in column 22 and sn in column 23 but sometimes pd is in column 66 and sn in column 67. Always in neighboring cells with pd on the left.
So far I have the following code:
Sub FindBoard()
Dim LastRow As Long
Dim LastColumn As Long
Dim LastCell As Range, NextCell As Range
Dim r As Long
Dim m As Long
Dim sthlh As Long
With Worksheets("Sheet3")
' Find LastRow. Works Best. 1st and last cells can be empty
If WorksheetFunction.CountA(Cells) > 0 Then
'Search for any entry, by searching backwards by Rows.
LastRow = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
'Search for any entry, by searching backwards by Columns.
LastColumn = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
'MsgBox "Last Cell" & vbCrLf & vbCrLf & Cells(LastRow, LastColumn).Address
MsgBox "The Last Row is: " & vbCrLf & vbCrLf & LastRow
MsgBox "The Last Column is: " & vbCrLf & vbCrLf & LastColumn
End If
' Number of columns based on actual size of log range NOT MyAr(n)
Set NextCell = Worksheets("Sheet3").Cells(LastRow + 1, (LastColumn - 10))
End With
For r = 1 To LastRow
'For sthlh = 2 To LastColumn**
If Cells(r, "AP") = "0600263" Then
If Cells(r, "AO") = "4112" Then
Exit For
End If
End If
'Next sthlh**
Next r
If r > LastRow Then
MsgBox " not found"
Else
' found in row
MsgBox "The board u r looking for is in row: " & vbCrLf & vbCrLf & r
Rows(r).Select
End If
End Sub
I try to add the two lines with the double asterix ** in order not to use spcific columns like I do in my code but to have sth like this:
.....
For r = 1 To LastRow
For sthlh = 2 To LastColumn
If Cells(r, sthlh ) = "0600263" Then
If Cells(r, sthlh-1) = "4112" Then
Exit For
End If
End If
Next sthlh
Next r
.....
where 4112 is pd
and 0600263 is sn
My aim is to iterate through Iterate through every PAIR OF COLUMNS of the excel sheet and when i find the sn to check if the pd is the desired one. If yes to select the row so that i can see the extra data i want.
Any idea where I m doing it wrong???
Thanks in advance!!!
Try this:
Option Explicit
Sub FindBoard()
Dim LastRow As Long, LastColumn As Long
Dim r As Long
Dim sthlh As Long
Dim found As Boolean
Dim Paire As Range
LastRow = Sheets("Sheet3").Range("A" & Rows.Count).End(xlUp).Row
LastColumn = Sheets("Sheet3").Cells(1, Columns.Count).End(xlToLeft).Column
found = False
r = 0
Do Until r > LastRow Or found = True
r = r + 1
For sthlh = 2 To LastColumn
If Cells(r, sthlh) = "0600263" Then
If Cells(r, sthlh - 1) = "4112" Then
found = True
Set Paire = Range(Cells(r, sthlh - 1), Cells(r, sthlh))
Exit For
End If
End If
Next sthlh
Loop
If found = False Then
MsgBox " not found"
Else
' found in row
MsgBox "The board u r looking for is in row: " & vbCrLf & vbCrLf & Paire.Address
Paire.Select
End If
End Sub
While showing details of pivottable with VBA method:
Range("D10").ShowDetail = True
I would like to choose only the columns I want, in a specified order I want. Let's say in source data of pivot table I have 10 columns (col1, col2, col3, ... , col10), and while expanding details with VBA I want to show just 3 columns (col7, col2, col5).
Is it possible to do it in SQL style like:
SELECT col7, col2, col5 from Range("D10").ShowDetail
I tuned this as a function so that you can get the sheet reference like this
Set DetailSheet = test_Przemyslaw_Remin(Range("D10"))
Here is the function :
Public Function test_Przemyslaw_Remin(RangeToDetail As Range) As Worksheet
Dim Ws As Worksheet
RangeToDetail.ShowDetail = True
Set Ws = ActiveSheet
Ws.Range("A1").Select
Ws.Columns("H:J").Delete
Ws.Columns("F:F").Delete
Ws.Columns("C:D").Delete
Ws.Columns("A:A").Value = Ws.Columns("D:D").Value
Ws.Columns("D:D").Clear
Set test_Przemyslaw_Remin = Ws
End Function
Solution with Headers' names
Results will be shown in the order set in the string in the ScanHeaders function
Public Sub SUB_Przemyslaw_Remin(RangeToDetail As Range)
Dim Ws As Worksheet, _
MaxCol As Integer, _
CopyCol As Integer, _
HeaD()
RangeToDetail.ShowDetail = True
Set Ws = ActiveSheet
HeaD = ScanHeaders(Ws, "HeaderName1/HeaderName2/HeaderName3")
For i = LBound(HeaD, 1) To UBound(HeaD, 1)
If HeaD(i, 2) > MaxCol Then MaxCol = HeaD(i, 2)
Next i
With Ws
.Range("A1").Select
.Columns(ColLet(MaxCol + 1) & ":" & ColLet(.Columns.Count)).Delete
'To start filling the data from the next column and then delete what is before
CopyCol = MaxCol + 1
For i = LBound(HeaD, 1) To UBound(HeaD, 1)
.Columns(ColLet(CopyCol) & ":" & ColLet(CopyCol)).Value = _
.Columns(HeaD(i, 3) & ":" & HeaD(i, 3)).Value
CopyCol = CopyCol + 1
Next i
.Columns("A:" & ColLet(MaxCol)).Delete
End With
End Sub
The scan headers function, that will return a array with in row : Header's Name,
Column number, Column letter :
Public Function ScanHeaders(aSheet As Worksheet, Headers As String, Optional Separator As String = "/") As Variant
Dim LastCol As Integer, _
ColUseName() As String, _
ColUse()
ColUseName = Split(Headers, Separator)
ReDim ColUse(1 To UBound(ColUseName) + 1, 1 To 3)
For i = 1 To UBound(ColUse)
ColUse(i, 1) = ColUseName(i - 1)
Next i
With Sheets(SheetName)
LastCol = .Cells(1, 1).End(xlToRight).Column
For k = LBound(ColUse, 1) To UBound(ColUse, 1)
For i = 1 To LastCol
If .Cells(1, i) <> ColUse(k, 1) Then
If i = LastCol Then MsgBox "Missing data : " & ColUse(k, 1), vbCritical, "Verify data integrity"
Else
ColUse(k, 2) = i
Exit For
End If
Next i
ColUse(k, 3) = ColLet(ColUse(k, 2))
Next k
End With
ScanHeaders = ColUse
End Function
And the function to get the Column's letter from the Column's number :
Public Function ColLet(x As Integer) As String
With ActiveSheet.Columns(x)
ColLet = Left(.Address(False, False), InStr(.Address(False, False), ":") - 1)
End With
End Function
Yes, I have finally done it. This collection of three subs allows you make SQL statements on just used ShowDetail on PivotTable.
After running Range("D10").ShowDetail = True run macro RunSQLstatementsOnExcelTable
Just adjust the SQL according to your needs:
select [Col7],[Col2],[Col5] from [DetailsTable] where [Col7] is not null
Just leave [DetailsTable] as it is. It will be changed automatically into ActiveSheet with details data.
Calling the sub DeleteAllWhereColumnIsNull is optional. This approach is the same as delete from table WHERE Column is null in SQL but it guarantees that the key column will not lose its formatting. Your formatting is read from the first eight rows and it will be turned into text i.e. if you have NULLs in the first rows. More about corrupt formatting of ADO you may find here.
You do not have to enable references to ActiveX libraries using the macros. It is important if you want to distribute your files.
You may experiment with different connection strings. There are three different left just in case. All of them worked for me.
Sub RunSQLstatementsOnExcelTable()
Call DeleteAllWhereColumnIsNull("Col7") 'Optionally delete all rows with empty value on some column to prevent formatting issues
'In the SQL statement use "from [DetailsTable]"
Dim SQL As String
SQL = "select [Col7],[Col2],[Col5] from [DetailsTable] where [Col7] is not null order by 1 desc" '<-- Here goes your SQL code
Call SelectFromDetailsTable(SQL)
End Sub
Sub SelectFromDetailsTable(ByVal SQL As String)
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
ActiveSheet.UsedRange.Select 'This stupid line proved to be crucial. If you comment it, then you may get error in line oRS.Open
Dim InputSheet, OutputSheet As Worksheet
Set InputSheet = ActiveSheet
Worksheets.Add
DoEvents
Set OutputSheet = ActiveSheet
Dim oCn As Object
Set oCn = CreateObject("ADODB.Connection")
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
Dim oRS As Object
Set oRS = CreateObject("ADODB.Recordset")
Dim strFile As String
strFile = ThisWorkbook.FullName
'------- Choose whatever connection string you like, all of them work well -----
Dim ConnString As String
ConnString = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & strFile & ";HDR=Yes';" 'works good
'ConnString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile & ";Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";" 'IMEX=1 data as text
'ConnString = "Provider=Microsoft.Jet.OLEDB.4.0;excel 8.0;DATABASE=" & strFile 'works good
'ConnString = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=" & strFile 'works good
Debug.Print ConnString
oCn.ConnectionString = ConnString
oCn.Open
'Dim SQL As String
SQL = Replace(SQL, "[DetailsTable]", "[" & InputSheet.Name & "$] ")
Debug.Print SQL
oRS.Source = SQL
oRS.ActiveConnection = oCn
oRS.Open
OutputSheet.Activate
'MyArray = oRS.GetRows
'Debug.Print MyArray
'----- Method 1. Copy from OpenRowSet to Range ----------
For intFieldIndex = 0 To oRS.Fields.Count - 1
OutputSheet.Cells(1, intFieldIndex + 1).Value = oRS.Fields(intFieldIndex).Name
Next intFieldIndex
OutputSheet.Cells(2, 1).CopyFromRecordset oRS
ActiveSheet.ListObjects.Add(xlSrcRange, Application.ActiveSheet.UsedRange, , xlYes).Name = "MyTable"
'ActiveSheet.ListObjects(1).Range.EntireColumn.AutoFit
ActiveSheet.UsedRange.EntireColumn.AutoFit
'----- Method 2. Copy from OpenRowSet to Table ----------
'This method sucks because it does not prevent losing formatting
'Dim MyListObject As ListObject
'Set MyListObject = OutputSheet.ListObjects.Add(SourceType:=xlSrcExternal, _
'Source:=oRS, LinkSource:=True, _
'TableStyleName:=xlGuess, destination:=OutputSheet.Cells(1, 1))
'MyListObject.Refresh
If oRS.State <> adStateClosed Then oRS.Close
If Not oRS Is Nothing Then Set oRS = Nothing
If Not oCn Is Nothing Then Set oCn = Nothing
'remove unused ADO connections
Dim conn As WorkbookConnection
For Each conn In ActiveWorkbook.Connections
Debug.Print conn.Name
If conn.Name Like "Connection%" Then conn.Delete 'In local languages the default connection name may be different
Next conn
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
Sub DeleteAllWhereColumnIsNull(ColumnName As String)
Dim RngHeader As Range
Debug.Print ActiveSheet.ListObjects(1).Name & "[[#Headers],[" & ColumnName & "]]"
Set RngHeader = Range(ActiveSheet.ListObjects(1).Name & "[[#Headers],[" & ColumnName & "]]")
Debug.Print RngHeader.Column
Dim ColumnNumber
ColumnNumber = RngHeader.Column
ActiveSheet.ListObjects(1).Sort.SortFields.Clear
ActiveSheet.ListObjects(1).HeaderRowRange(ColumnNumber).Interior.Color = 255
ActiveSheet.ListObjects(1).ListColumns(ColumnNumber).DataBodyRange.NumberFormat = "#,##0.00"
With ActiveSheet.ListObjects(1).Sort
With .SortFields
.Clear
'.Add ActiveSheet.ListObjects(1).HeaderRowRange(ColumnNumber), SortOn:=xlSortOnValues, Order:=sortuj
.Add RngHeader, SortOn:=xlSortOnValues, Order:=xlAscending
End With
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
'Delete from DetailsTable where [ColumnName] is null
On Error Resume Next 'If there are no NULL cells, just skip to next row
ActiveSheet.ListObjects(1).ListColumns(ColumnNumber).DataBodyRange.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Err.Clear
ActiveSheet.UsedRange.Select 'This stupid thing proved to be crucial. If you comment it, then you will get error with Recordset Open
End Sub
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim PTCll As PivotCell
On Error Resume Next
Set PTCll = Target.PivotCell
On Error GoTo 0
If Not PTCll Is Nothing Then
If PTCll.PivotCellType = xlPivotCellValue Then
Cancel = True
Target.ShowDetail = True
With ActiveSheet
ActiveSheet.Range("A1").Select
ActiveSheet.Columns("A:B").Select
Selection.Delete Shift:=xlToLeft
ActiveSheet.Columns("E:F").Select
Selection.Delete Shift:=xlToLeft
ActiveSheet.Columns("F:I").Select
Selection.Delete Shift:=xlToLeft
ActiveSheet.Columns("J:R").Select
Selection.Delete Shift:=xlToLeft
ActiveSheet.Columns("H:I").Select
Selection.NumberFormat = "0.00"
ActiveSheet.Columns("H:I").EntireColumn.AutoFit
Selection.NumberFormat = "0.0"
Selection.NumberFormat = "0"
ActiveSheet.Cells.Select
ActiveSheet.Cells.EntireColumn.AutoFit
ActiveSheet.Range("A1").Select
End With
End If
End If
End Sub