Use Range instead of PivotSelect --> Set rng = Selection - vba

I know I can select a specific column in a pivot table in VBA using this:
pt.PivotSelect "Costs Alternative['1']", xlDataOnly, True
Set rng = Selection
Now, this is a bit annoying and not quite as fast and resilient as just going straight for:
Set rng = pt.PivotFields("Alternative").PivotFields("1").PivotItems("Costs").DataRange
My pivot table looks roughly like this:
+-------+-------------+--------+
| | Alternative | Values |
| | 1 | |
| Price | ft | Costs |
+-------+-------------+--------+
| 6.58 | 6.00 | 39.48 |
| 2.00 | 2.00 | 4.00 |
| 0.30 | 6.00 | 1.80 |
| Total | 14.00 | 45.28 |
+-------+-------------+--------+
Keep in mind that only one Alternative is listed in the data but in my actual pivot there are a lot more alternatives.
Is there a way to reference the pivot result value cell similar to the above?

here is a bunch of test code that i used to identify ranges in a pivot table
it just does .Select on each range so that the range is visible on the worksheet
some of the ranges are single cells, so .Select can be replaced by .Value
just step code with F8 key and watch the different ranges in the pivot table show up.
Sub pivot()
Dim piv As pivotTable
Set piv = ActiveSheet.PivotTables("PivotTable3")
piv.TableRange1.Select
piv.TableRange2.Select
piv.DataBodyRange.Select
piv.RowRange.Select
piv.ColumnRange.Select
piv.PageRange.Select
piv.PageRangeCells.Select
Dim aaa As Variant
aaa = piv.DataBodyRange.Value ' array
aaa = piv.DataBodyRange.Value2 ' array
aaa = piv.DataBodyRange.Formula ' array
piv.DataBodyRange.PivotField.LabelRange.Select
piv.DataBodyRange.PivotField.DataRange.Select
piv.DataBodyRange.Next.PivotField.LabelRange.Select
piv.DataBodyRange.Next.PivotField.DataRange.Select
piv.DataBodyRange.PivotItem.LabelRange.Select
piv.DataBodyRange.PivotItem.DataRange.Select
piv.PivotFields("Price").LabelRange.Select
piv.PivotFields("Price").DataRange.Select
piv.PivotFields("ft").LabelRange.Select
piv.PivotFields("ft").DataRange.Select
piv.PivotFields("Costs").LabelRange.Select
piv.PivotFields("Costs").DataRange.Select
End Sub

Related

VBA AutoFilter with symbol (*) Not Working

I am trying to filter excel data using AutoFilter, but somehow it is not working.
The data I want to filter represent time and are in format of 00:00 through 23:59.
Specifically, I am trying to filter by the hour, so like "00" through "23", or "00:" through "23:", whichever works.
However, it is not working and none of them gets filtered.
Assuming there is a table like
|ID|Date| time |
| 0 | 7/5 | 00:00|
| 1 | 7/5 | 00:20|
| 2 | 7/5 | 00:35|
| 3 | 7/5 | 00:47|
| 4 | 7/5 | 00:58|
| 5 | 7/5 | 01:03|
| 6 | 7/5 | 01:15|
| 7 | 7/5 | 01:27|
| 8 | 7/5 | 01:48|
I tested the following macro:
Selection.Autofilter Field:=3, Criteria1:="=00*"
hoping that I could get the top 5 rows of the data as their times begin with "00". I looked for information on how to set "start with" in AutoFilter, and I found that star (*) can be used. However, this does not work and none of the rows gets filtered.
Interestingly, if I tried the following:
Selection.Autofilter Field:=3, Criteria1:="00:20"
it worked fine and it filtered the second row only where the time is "00:20".
Why is my first macro with * not working? I tried many different ways to no avail. Can someone tell me why this is not working?
Excel treats time values as decimal numbers. One approach would be is to insert a helper column called "Hour" and put the following formula in D2
=HOUR(C2)
and copy it down.
Now filter the data like below...
Range("$A$1:$D1").Autofilter Field:=4, Criteria1:="=0"

Dynamically Resizing Table to last row?

I have a table that I would like to resize dynamically in VBA.
My current code is this:
Sub resizedata()
Dim ws As Worksheet
Dim ob As ListObject
Dim Lrow1 As Long
Lrow1 = Sheets("Sheet4").Cells(Rows.Count, "J").End(xlUp).Row
Set ws = ActiveWorkbook.Worksheets("Sheet4")
Set ob = ws.ListObjects("Table28")
ob.Resize ob.Range.Resize(Lrow1)
End Sub
I would like to add one condition onto this though...
The table should resize to before the first 0 in column J.
For instance:
+-------+--------+-------+
|Date(I)|Hours(J)| Sal(K)|
+-------+--------+-------+
| Aug | 150000 | 12356 |
| Sep | 82547 | 8755 |
| Oct | 92857 | 98765 |
| Nov | 10057 | 45321 |
| Dec | 0 | 0 |
| Jan | 0 | 0 |
+-------+--------+-------+
The above table's last row should be the November row because December is the first 0 value in column J.
Can anyone assist in revising my existing code?
Something like:
With Sheets("Sheet4")
Lrow1 = .Cells(.Rows.Count, "J").End(xlUp).Row
Do While .Cells(Lrow1, "J").Value=0
Lrow1 = Lrow1 - 1
Loop
End With
This should help you to automatically resize the table.
With Worksheets("sheet-name").ListObjects("table-name")
.Resize .Range(1, 1).CurrentRegion
End With
Is there a particular reason you want to do this in VBA? You can simply create a defined name and use the following to create a self-adjusting range:
OFFSET(Sheet!$A$1,0,0,COUNTA(Sheet!$A:$A),COUNTA(Sheet!$1:$1))
OFFSET(reference, rows, cols, [height], [width])
The top line shows typical usage, 2nd line is the official syntax.
Go into Name Manager on the Formulas tab, click new, give it a name and paste the code into Refers to:.
One caveat, it looks as though your data has 0's where there are no values. If that is truly the case, you'll have to do a different test to determine height.
One benefit of this method is that it is always calculated. If the data changes then the size definition of your named range adjusts as needed.
I hope this is helpful, I use this a lot...

Excel/VBA/Conditional Formatting: Dictionary of Dictionaries

I've got an Excel workbook that obtains data from an MS SQL database. One of the sheets is used to check the data against requirements and to highlight faults. In order to do that, I've got a requirements sheet where the requirement is in a named range; after updating the data I copy the conditional formatting of the table header to all data rows. That works pretty nicely so far. The problem comes when I have more than one set of requirements:
An (agreeably silly) example could be car racing, where requirements may exist for driver's license and min/max horsepower. When looking at the example, please imagine there are a few thousand rows and 71 columns presently...
+-----+--------+----------------+------------+---------+
| Car | Race | RequirementSet | Horsepower | License |
+-----+--------+----------------+------------+---------+
| 1 | Monaco | 2 | 200 | A |
+-----+--------+----------------+------------+---------+
| 2 | Monaco | 2 | 400 | B |
+-----+--------+----------------+------------+---------+
| 3 | Japan | 3 | 200 | C |
+-----+--------+----------------+------------+---------+
| 4 | Japan | 3 | 300 | A |
+-----+--------+----------------+------------+---------+
| 5 | Japan | 3 | 350 | B |
+-----+--------+----------------+------------+---------+
| 6 | Mexico | 1 | 200 | A |
+-----+--------+----------------+------------+---------+
The individual data now needs to be checked against the requirements set in another sheet:
+-------------+---------------+---------------+---------+
| Requirement | MinHorsepower | MaxHorsepower | License |
+-------------+---------------+---------------+---------+
| 1 | 200 | 250 | A |
+-------------+---------------+---------------+---------+
| 2 | 250 | 500 | B |
+-------------+---------------+---------------+---------+
| 3 | 250 | 400 | A |
+-------------+---------------+---------------+---------+
In order to relate back to my present situation, I am only looking at either the Monaco, Japan or Mexico Race, and there is only 1 record in the requirements sheet, where the value in e.g. Cell B2 is always the MinHorsepower and the value in C2 is always the MaxHorsepower. So these cells are a named range that I can access in my data sheet.
Now however I would like to obtain all races at once, and refer conditional formatting formulas to the particular requirement.
Focussing on "Horsepower" in Monaco (requirement set 2), I can now find out that the min Horsepower is 250 and the max is 500 - so I will colour that column for car 1 as red and for car 2 as green.
The formula is programatically copied from the header row (the first conditional format rule is if row(D1) = 1 then do nothing)
I can't decide what the best approach to the problem is. Ideally, the formula is readable, something like `AND(D2 >= MinHorsepower; D2 <= MaxHorsepower) - I cannot imagine it to be maintainable if I had to use Vlookup combined with Indirect and Match to match a column header in requirements for that particular requirement - especially when it comes to combining criteria like in the HP example with min and max above.
I am wondering if I should read the requirements table into a dictionary or something in VBA, and then use a function like
public function check(requirementId as int, requirement$)
which then in Excel I could use like =D2 >= check(c2, "MinHorsepower")
Playing around with this a little bit it appears to be pretty slow as opposed to the previous system where I could only have one requirement. It would be fantastic if you could help me out with a fresh approach to this problem. I'll update this question as I go along; I'm not sure if I managed to illustrate the example really well but the actual data wouldn't mean anything to you.
In any case, thanks for hanging in until here!
Edit 29 October 2016
I have found a solution as basis for mine. Using the following code I can add my whole requirements table to a dictionary, and access the requirement.
Using a class clsRangeToDictionary (based on Tim Williams clsMatrix)
Option Explicit
Private m_array As Variant
Private dictRows As Object
Private dictColumns As Object
Public Sub Init(vArray As Variant)
Dim i As Long
Set dictRows = CreateObject("Scripting.Dictionary")
Set dictColumns = CreateObject("Scripting.Dictionary")
'add the row keys and positions. Skip the first row as it contains the column key
For i = LBound(vArray, 1) + 1 To UBound(vArray, 1)
dictRows.Add vArray(i, 1), i
Next i
'add the column keys and positions, skipping the first column
For i = LBound(vArray, 2) + 1 To UBound(vArray, 2)
dictColumns.Add vArray(1, i), i
Next i
' store the array for future use
m_array = vArray
End Sub
Public Function GetValue(rowKey, colKey) As Variant
If dictRows.Exists(rowKey) And dictColumns.Exists(colKey) Then
GetValue = m_array(dictRows(rowKey), dictColumns(colKey))
Else
Err.Raise 1000, "clsRangeToDictionary:GetValue", "The requested row key " & CStr(rowKey) & " or column Key " & CStr(colKey) & " does not exist"
End If
End Function
' return a zero-based array of RowKeys
Public Function RowKeys() As Variant
RowKeys = dictRows.Keys
End Function
' return a zero-based array of ColumnKeys
Public Function ColumnKeys() As Variant
ColumnKeys = dictColumns.Keys
End Function
I can now read the whole RequirementSet table into a dictionary and write a helper to obtain the particular requirement roughly so:
myDictionaryObject.GetValue(table1's RequirementSet, "MinHorsePower")
If someone could help me figure out how to put this into an answer giving the credit due to Tim Williams that'd be great.

Is there a way to perform a cross join or Cartesian product in excel?

At the moment, I cannot use a typical database so am using excel temporarily. Any ideas?
The
You have 3 dimensions here: dim1 (ABC), dim2 (123), dim3 (XYZ).
Here is how you make a cartesian product of 2 dimensions using standard Excel and no VBA:
1) Plot dim1 vertically and dim2 horizontally. Concatenate dimension members on the intersections:
2) Unpivoting data. Launch pivot table wizard using ALT-D-P (don't hold ALT, press it once). Pick "Multiple consolidation ranges" --> create a single page.. --> Select all cells (including headers!) and add it to the list, press next.
3) Plot the resulting values vertically and disassemble the concatenated strings
Voila, you've got the cross join. If you need another dimension added, repeat this algorithm again.
Cheers,
Constantine.
Here is a very easy way to generate the Cartesian product of an arbitrary number of lists using Pivot tables:
https://chandoo.org/wp/generate-all-combinations-from-two-lists-excel/
The example is for two lists, but it works for any number of tables and/or columns.
Before creating the Pivot table, you need to convert your value lists to tables.
Using VBA, you can. Here is a small example:
Sub SqlSelectExample()
'list elements in col C not present in col B
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
con.Open "Driver={Microsoft Excel Driver (*.xls)};" & _
"DriverId=790;" & _
"Dbq=" & ThisWorkbook.FullName & ";" & _
"DefaultDir=" & ThisWorkbook.FullName & ";ReadOnly=False;"
Set rs = New ADODB.Recordset
rs.Open "select ccc.test3 from [Sheet1$] ccc left join [Sheet1$] bbb on ccc.test3 = bbb.test2 where bbb.test2 is null ", _
con, adOpenStatic, adLockOptimistic
Range("g10").CopyFromRecordset rs '-> returns values without match
rs.MoveLast
Debug.Print rs.RecordCount 'get the # records
rs.Close
Set rs = Nothing
Set con = Nothing
End Sub
Here's a way using Excel formulas:
| | A | B | C |
| -- | -------------- | -------------- | -------------- |
| 1 | | | |
| -- | -------------- | -------------- | -------------- |
| 2 | Table1_Column1 | Table2_Column1 | Table2_Column2 |
| -- | -------------- | -------------- | -------------- |
| 3 | A | 1 | X |
| -- | -------------- | -------------- | -------------- |
| 4 | B | 2 | Y |
| -- | -------------- | -------------- | -------------- |
| 5 | C | 3 | Z |
| -- | -------------- | -------------- | -------------- |
| 6 | | | |
| -- | -------------- | -------------- | -------------- |
| 7 | Col1 | Col2 | Col3 |
| -- | -------------- | -------------- | -------------- |
| 8 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 9 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 10 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 11 | ... | ... | ... |
| -- | -------------- | -------------- | -------------- |
Formula1: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(3*3), 3), 1)))
Formula2: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(3) , 3), 2)))
Formula3: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(1) , 3), 3)))
One* general formula to rule them all!
The result
The formula
MOD(CEILING.MATH([index]/PRODUCT([size of set 0]:[size of previous set]))-1,[size of current set])+1
This formula gives the index (ordered position) of each element in the set, where set i has a size of n_i. Thus if we have four sets the sizes would be [n_1,n_2,n_3,n_4].
Using that index one can just use the index function to pick whatever attribute from the set (imagine each set being a table with several columns one could use index([table of the set],[this result],[column number of attribute]).
Explanation
The two main components of the formula explained, the cycling component and the partitioning component.
Cycling component
=MOD([partitioning component]-1, [size of current set])+1
Cycles through all the possible values of the set.
The modulo function is required so the result will "go around" the size of the set, and never "out of bounds" of the possible values.
The -1 and +1 help us go from one-based numbering (our set indexes) to zero-based numbering (for the modulo operation).
Partitioning component
CEILING.MATH([index]/PRODUCT([size of set 0]:[size of previous set]):
Partitions the "cartesian index" in chunks giving each chunk an "name".
The "cartesian index" is just a numbering from 1 to the number of elements in the Cartesian Product (given by the product of the sizes of each set).
The "name" is just an increasing-by-chunk enumeration of the "cartesian index".
To have the same "name" for all indexes belonging to each chunk, we divide the "cartesian index" by the number of partitions and "ceil" it (kind of round up) the result.
The amount of partitions is the total size of the last cycle, since, for each previous result one requires to repeat it for each of this set's elements.
It so happens that the size of the previous result is the product of all the previous sets sizes (including the size of a set before the first so we can generalize, which we will call the "set 0" and will have a constant size of 1).
With screenshots
Set sizes
Prepared set sizes including the "Set0" one and the size of the Cartesian Product.
Here, the sizes of sets are:
"Set0": 1 in cell B2
"Set1": 2 in cell C2
"Set2": 5 in cell D2
"Set3": 3 in cell E2
Thus the size of the Cartesian product is 30 (2*5*3) in cell A2.
Results
Table structure _tbl_CartesianProduct with the following columns and their formulas:
Results:
Cartesian Index: =IF(ROW()-ROW(_tbl_CartesianProduct[[#Headers];[Cartesian Index]])<=$A$2;ROW()-ROW(_tbl_CartesianProduct[[#Headers];[Cartesian Index]]);NA())
concatenation: =TEXTJOIN("-";TRUE;_tbl_CartesianProduct[#[Index S1]:[Index S3]])
Index S1: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:B$2))-1;C$2)+1
Index S2: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:C$2))-1;D$2)+1
Index S3: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:D$2))-1;E$2)+1
step "size of previous partition":
Size prev part S1: =PRODUCT($B$2:B$2)
Size prev part S2: =PRODUCT($B$2:C$2)
Size prev part S3: =PRODUCT($B$2:D$2)
step "Chunk name":
Chunk S1: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S1]])
Chunk S2: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S2]])
Chunk S3: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S3]])
final step "Cycle through the set":
Cycle chunk in S1: =MOD([#[Chunk S1]]-1;C$2)+1
Cycle chunk in S2: =MOD([#[Chunk S2]]-1;D$2)+1
Cycle chunk in S3: =MOD([#[Chunk S3]]-1;E$2)+1
*: for the actual job of producing the Cartesian enumerations
A little bit code in PowerQuery could solve the problem:
let
Quelle = Excel.CurrentWorkbook(){[Name="tbl_Data"]}[Content],
AddColDim2 = Table.AddColumn(Quelle, "Dim2", each Quelle[Second_col]),
ExpandDim2 = Table.ExpandListColumn(AddColDim2, "Dim2"),
AddColDim3 = Table.AddColumn(ExpandDim2, "Dim3", each Quelle[Third_col]),
ExpandDim3 = Table.ExpandListColumn(AddColDim3, "Dim3"),
RemoveColumns = Table.SelectColumns(ExpandDim3,{"Dim1", "Dim2", "Dim3"})
in RemoveColumns
Try using a DAX CROSS JOIN. Read more at MSDN
You can use the expression CROSSJOIN(table1, table2) to create a cartesian product.

how to find the max 'n' values for data based on SUM of values?

Below is the table
Amt | Val | Location
230 | a | DEL
450 | b | KOL
670 | c | BLR
890 | d | DEL
111 | e | KOL
133 | a | KOL
155 | b | DEL
177 | c | BLR
199 | a | DEL
221 | b | BLR
243 | c | BLR
265 | d | KOL
287 | a | KOL
309 | b | DEL
331 | c | DEL
353 | d | KOL
375 | e | BLR
397 | a | BLR
419 | b | DEL
441 | c | KOL
out of a,b,c,d,e values how to find the maximum 2 values for respective location based on the a's..b's..c's..d's..e's amount.
I am able to get the sum of values of top 2 val through Pivot table, for one location
Please tell how to get the top 2 val with their sum of amount for all location simultaneously through VBA,
I have Posted VBA code for the same, which gives result for only one location.
Sorry not able to upload the snapshot.
Say your data is in A1 thru C20. You have three unique locations: DEL, KOL, BLR.
In D1 enter:
=SUMPRODUCT(--(A$1:A$20)*(C$1:C$20=C1)) and copy down thru D3
In E1 enter:
=LARGE(D1:D3,1)
In E2 enter:
=LARGE(D1:D3,2)
Should look like:
EDIT:
based upon your comment, the highest two values for DEL would be:
=LARGE(IF(C1:C20="del",A1:A20),1)
and
=LARGE(IF(C1:C20="del",A1:A20),2)
These are array formulas that must be entered with CNTRL-SHFT-ENTER rather than just the ENTER key
DMAX function returns the largest number in a column in a list or database, based on a given criteria.
http://www.techonthenet.com/excel/formulas/dmax.php
1. Insert a Pivot Table.
2. Add Val in Row Labels.
3. Add Location in column Labels.
4. Add Amt in Values field(Sumof Amt).
Now In created Pivot Table,
1. In column labels filter for only one location(eg: Blr).
2. In Row Labels filter apply value filters and select Top 10..(last item).
3. In place of 10(by default) give 2.
4. Now the table consists of Top 2 val with their sum of Amount for BLR Location.
VBA Code for the Same:
Private Sub CommandButton1_Click()
Dim wkbk As Workbook
Set wkbk = ActiveWorkbook
With wkbk.Sheets(1)
LastRow = .Range("A1").End(xlDown).Row
LastCol = .Range("A1").End(xlToRight).Column
Set rngSource = .Range("A1", .Cells(LastRow, LastCol))
End With
With wkbk.Sheets(2)
Set dst = .Range("a1")
End With With wkbk
Sheets(1).PivotTableWizard _
SourceType:=xlDatabase, _
SourceData:=rngSource, _
TableDestination:=dst, _
TableName:="Pivotinfo"
End With
With wkbk.Sheets(2).PivotTables("Pivotinfo")
.PivotFields("Val").Orientation = xlRowField
.PivotFields("Location").Orientation = xlColumnField
With .PivotFields("Amt")
.Orientation = xlDataField
.Function = xlSum With wkbk.Sheets(2).PivotTables("Pivotinfo").PivotFields("Location")
.PivotItems("DEL").Visible = False
.PivotItems("KOL").Visible = False
End With
With wkbk.sheets(2).PivotTables("Pivotinfo").PivotFields("Val").AutoShow _
xlAutomatic, xlTop, 2, "Sum of Amt"
End With
End With
End With
End Sub