MEET FEATURE REQUIREMENTS, SCHEDULE, and BUDGET! Consulting available to teach your organization to apply this methodology and tools for powerful quantitative management of your business processes.
- Instruction of your staff at your site, using courseware, application example, and a functional template.
- Mentoring your key staff on an ongoing or periodic basis, on these tools or on Quantitative Program Management.
- Contracting or Employment in your organization on specific topics.
Check out my YouTube Channel: Power Operational Intelligence
Now Live! Overview, Structure, Task Data, Table Design, SQL Server, and Re-Linking now showing.
Video courses covering material on this website, and more, are presented in the playlists.
Code snippet links at YouTube Code Snippets. Twitter at @poweroperation1, #poweropi, #poweroperationalintelligence.
Subscribe on YouTube, and click the "Notification" Bell icon to be notified as content is published.
VBA Access to Structured Tables Using Structured References and/or Excel Objects2019 July 31 - Updated to describe use of VBA WorksheetFunction, which may be the best option for Structured Reference in VBA using supported functions, which include key functions INDEX, MATCH, and SUMIF. Use of Structured References in the Excel User Interface to link Tables and to provide field calculations raises the level of design thinking up from cells and worksheets to Tables, Records and Fields. This design level in Excel uses data relationships that are independent of "fixed" locations of Cells and Worksheets. But what if you need to access Structured Table data from VBA? In the upcoming descriptions, we will use INDEX-MATCH, SUMIF, TEXTJOIN and FOREACH structures commonly used in Excel Structured Table references, to show how VBA semantics can directly incorporate Structured References by using the VBA Application.WorksheetFunction function, the VBA EVALUATE function, and also show VBA semantics using the Excel Object Model to perform these operations. When a new function is introduced to Excel, its VBA WORKSHEETFUNCTION equivalent function may not be available until a much later release, which is why we also show the EVALUATE and VBA syntax to accomplish several functions below. Syntax is shown for XLOOKUP and XMATCH as well, on the Programming Projects page on this site. Excel provides three ways to address Structured References from within VBA, with varying similarity to use of Structured References in the Excel User Interface:
The declarations shown at the top of the code figure below of course apply to all the following examples. Within the examples, Excel is Blue italic. The Structured Table referenced in the examples is shown at the bottom of this page. Dim wksThisWorksheet As Worksheet Dim rsltINDEXMATCH As Variant, varINDEXMATCHResult As Variant, varFUNCMATCHResult As Variant Dim rsltSUMIF As Double, rsltPriceSF As Double Dim intRowNum As Integer, intColNum As Integer Dim cellValue As String, strTargetValue As String, expr As String, strQuotTargetValue As String Dim strRsltColName As String, strSearchColName As String, strTableName As String Dim strVBCONCATResult As String, concatColName As String Dim objCellValue As Object, objRow As Object Dim loTableListObject As ListObject, tblMaterial As ListObject Dim thisRow As ListRow Dim thisCol As ListColumn Dim concatColRange As Range Set wksThisWorksheet = ActiveWorkbook.Worksheets("Material") 'Initialization INDEX-MATCH: VBA using Structured Reference In the Excel User Interface, you might have a formula =INDEX(tblMaterial, MATCH("Oslo Porcelain:Bronze:Gloss:Bronze", tblMaterial[MaterialKey],0), MATCH("Price$SF", tblMaterial[#Headers], 0)) Using WORKSHEETFUNCTION: Set up the objects as shown, and call the INDEX and MATCH WorksheetFunctions: strTableName = "tblMaterial" strSearchColName = "MaterialKey" strTargetValue = "Oslo Porcelain:Bronze:Gloss:Bronze" strRsltColName = "Price$SF" Set tblMaterial = wksThisWorksheet.ListObjects(strTableName) Set tblMaterialRange = tblMaterial.Range Set searchColumn = tblMaterial.ListColumns(strSearchColName).Range Set headerRow = tblMaterial.HeaderRowRange With Application.WorksheetFunction rsltINDEXMATCH = .Index(tblMaterialRange, .Match(strTargetValue, searchColumn, 0), _ .Match(strRsltColName, headerRow, 0)) End With The result closely emulates the structure of an Excel User Interface - Structured Reference expression, even if not its syntax. Using EVALUATE: Equivalent VBA encapsulates the structured reference within Quotes as the argument of an EVALUATE function: rsltINDEXMATCH = Evaluate("INDEX(tblMaterial, MATCH(""Oslo Porcelain:Bronze:Gloss:Bronze"", _ tblMaterial[MaterialKey],0), MATCH(""Price$SF"", tblMaterial[#Headers], 0))") You can see the argument to the EVALUATE function is almost identical to the Excel expression: INDEX(tblMaterial, MATCH("Oslo Porcelain:Bronze:Gloss:Bronze", tblMaterial[MaterialKey],0), MATCH("Price$SF", tblMaterial[#Headers], 0)). That expression is provided as a string, surrounded by QUOTE characters to indicate that to VBA. The exceptions deal with how the string value deals with quoted values within it. The Excel Structured Reference uses literal quoted values "Oslo Porcelain:Bronze:Gloss:Bronze" and "Price$SF" as arguments. So within the string argument to be passed to the EVALUATE function, double-quote escape-characters are required in order to force quote-symbols within the quoted string argument. The argument to the EVALUATE function is therefore "INDEX(tblMaterial, MATCH(""Oslo Porcelain:Bronze:Gloss:Bronze"", tblMaterial[MaterialKey],0), MATCH(""Price$SF"", tblMaterial[#Headers], 0))" including the surrounding quotes telling VBA that it is a string and the internal double-quotes indicating the single-quote characters to be included within the string value. That argument is evaluated by VBA before being passed into the EVALUATE, the evaluated value passed to the function being INDEX(tblMaterial, MATCH("Oslo Porcelain:Bronze:Gloss:Bronze", tblMaterial[MaterialKey],0), MATCH("Price$SF", tblMaterial[#Headers], 0)), a string value without surrounding quote characters, and evaluating the double-quote characters within as single-quote characters within the string value, so the value of the string exactly matches the intended Excel Structured Reference INDEX(tblMaterial, MATCH("Oslo Porcelain:Bronze:Gloss:Bronze", tblMaterial[MaterialKey],0), MATCH("Price$SF", tblMaterial[#Headers], 0)). SUMIF: VBA using Structured Reference In the Excel User Interface, you might have a formula =SUMIF(tblMaterial[MaterialKey], "Imperial Next:Bardiglio:Satin:Grey Marble", tblMaterial[Price$SF]) Using WORKSHEETFUNCTION: Again, set up the objects, and call the SUMIF WorksheetFunction. Set tblMaterial = wksThisWorksheet.ListObjects("tblMaterial") 'strSearchColName string is set above 'strRsltColName string is set above Set searchColumn = tblMaterial.ListColumns(strSearchColName).Range Set SumColumn = tblMaterial.ListColumns(strRsltColName).Range With Application.WorksheetFunction rsltSUMIF = .SumIf(searchColumn, strTargetValue, SumColumn) End With Again, the result closely emulates the structure of the Excel UI call, although not the syntax. Using EVALUATE: Similarly for SUMIF, you can use Structured Reference syntax, by using VBA EVALUATE function: Equivalent VBA using an EVALUATE function: rsltSUMIF = Evaluate("SUMIF(tblMaterial[MaterialKey], _ ""Imperial Next:Bardiglio:Satin:Grey Marble"", tblMaterial[Price$SF])") Once again, you can see the Excel Structured Reference, enclosed in quotes and with double-quote characters indicating quote characters to be included within the string to be passed as the argument to EVALUATE. Concatenating a VBA Variable into the Structured Reference argument of VBA EVALUATE The value of an argument may be set in a VBA variable by a statement outside the EVALUATE expression. That requires concatenating the variable into the quoted reference argument of EVALUATE. Excel: =SUMIF(tblMaterial[MaterialKey], strTargetValue, tblMaterial[Price$SF]) Equivalent VBA using an EVALUATE function, and concatenating in the variable "strTargetValue": 'Build the expression to EVALUATE, outside of the EVALUATE call strTargetValue = "Imperial Next:Bardiglio:Satin:Grey Marble" expr = "SUMIF(tblMaterial[MaterialKey], """ & strTargetValue & """, _ tblMaterial[Price$SF])" rsltSUMIF = Evaluate(expr) In the expr expression building up the Structured Reference, the variable strTargetValue will be evaluated to its value Imperial Next:Bardiglio:Satin:Grey Marble (text string that won't include quotes) so the initial part of the Structured Reference expression (the part preceding concatenation of the variable into the argument) must include a quote symbol, forced by the double-quote, and then that part of the Structured Reference is concluded by its quote symbol - resulting in the triple-quote """. That initial part of the Structured Reference is then concatenated with the evaluated value of variable strTargetValue using "&" operators. Finally, the part of the Structured Reference following the concatenation of the variable value must begin with a quote symbol to finish quoting the evaluated variable strTargetValue, so a double-quote symbol is placed inside the leading quote of the last part of the Structured Reference - resulting a leading """. The resulting string, is evaluated by VBA to pass the string value to EVALUATE, as Excel Structured Reference: SUMIF(tblMaterial[MaterialKey], "Imperial Next:Bardiglio:Satin:Grey Marble", tblMaterial[Price$SF]), identical to the preceding example. Alternately, you could build the EVALUATE argument within the call: strTargetValue = "Imperial Next:Bardiglio:Satin:Grey Marble" rsltSUMIF = Evaluate("SUMIF(tblMaterial[MaterialKey], """ & strTargetValue & """, _ tblMaterial[Price$SF])") ...or put the burden of quoting on the code that sets the value: 'Set strTargetValue, adding double-quote around the TargetValue strQuotTargetValue = """Imperial Next:Bardiglio:Satin:Grey Marble""" 'Concatenate strTargetValue into the SUMIF EVALUATE. rsltSUMIF = Evaluate("SUMIF(tblMaterial[MaterialKey], " & strQuotTargetValue & ", _ tblMaterial[Price$SF])") TEXTJOIN: VBA using Structured Reference You could use Structured Reference syntax via VBA EVALUATE for small or sparse tables. My own use has been to concatenate Table cells that collectively contain Excel connection descriptors and SQL directives, as an example of a small table for which this is useful. Excel formula: =TEXTJOIN(CHAR(10),True,tblMaterial[Item]) Note that the EVALUATE function argument is limited to 255 characters, otherwise a "Type mismatch" error is returned. Concatenating Table cells to create even a fairly short SQL expression can easily encounter this. You can avoid the problem by using WORKSHEETFUNCTION as shown just below, or by using the VBTEXTJOIN function farther down this page. USING WORKSHEETFUNCTION: concatColName = "Surface" Set concatColRange = tblMaterial.ListColumns(concatColName).Range Set concatColRange = Range(concatColRange(2), concatColRange(concatColRange.Count)) 'Drop the column name included in concatColRange. VBA picks up the column name ' in the ListColumn range, a difference from Excel. With Application.WorksheetFunction strVBCONCATResult = .TextJoin(Chr(10), True, concatColRange) 'Note use of CHR function in VBA vs CHAR function when TEXTJOIN is called ' from Excel User Interface, another difference from Excel. End With EQUIVALENT FUNCTION USING EVALUATE: strTargetValue = Evaluate("TEXTJOIN(CHAR(10),True,tblMaterial[Item])") The Excel Structured Reference, enclosed in quotes and with double-quote characters indicating quote characters is included within the string to be passed as the argument to EVALUATE. Note for general use: the argument to EVALUATE is limited to 255 characters, and the content of the Structured Reference argument to TEXTJOIN is evaluated prior to the call to TEXTJOIN (to provide the STRING argument type required by TEXTJOIN) and may exceed 255 characters unless the table column contains few characters, resulting in a "Type mismatch" error. For this reason, a VBA substitute for TEXTJOIN using the Object Model that can be called from within VBA is provided below. The VBA function "VBTEXTJOIN" is shown below. It can be called from an Excel expression including Structured Reference to a Structured Table column containing text values, and does not have the 255-character limit. Syntax for calling it from Excel is shown just below the VBA code defining the function. TEXTJOIN was introduced in Excel 2016 and the VBA function below may provide a substitute in earlier editions. FOR-EACH: VBA SYNTAX to Scan Down a Structured Table examining Columns Set wksThisWorksheet = Application.Worksheets("Material") Set loTableListObject = wksThisWorksheet.ListObjects("tblMaterial") intColNum = Application.WorksheetFunction.Match("MaterialKey", loTableListObject.HeaderRowRange, 0) For Each thisRow In loTableListObject.ListRows cellValue = thisRow.Range.Cells(intColNum) If cellValue = "Imperial Next:Bardiglio:Satin:Grey Marble" Then 'Act on targeted row rsltPriceSF = thisRow.Range.Cells(Application.WorksheetFunction.Match("Price$SF", _ loTableListObject.HeaderRowRange, 0)) End If Next thisRow FOR: VBA SYNTAX to Scan Down Structured Table Rows and Process each Row Set wksThisWorksheet = Application.Worksheets("Material") Set loTableListObject = wksThisWorksheet.ListObjects("tblMaterial") intColNum = Application.WorksheetFunction.Match("MaterialKey", loTableListObject.HeaderRowRange, 0) For intRowNum = 2 To loTableListObject.ListRows.Count 'Skip HeaderRowRange in Row 1 'Now process data in the row selected by intRowNum cellValue = Application.WorksheetFunction.Index(loTableListObject.Range, _ intRowNum, intColNum) Next intRowNum VBA USING EXCEL OBJECT MODEL Functions below are shown as "Public" so that they can also be called from Excel formula expressions. If the functions are only to be called from VBA, these functions can be "Private". INDEX-MATCH: VBA using Excel Object Model with WorksheetFunction varINDEXMATCHResult = INDEXMATCH("Material", "tblMaterial", _ "Imperial Next:Bardiglio:Satin:Grey Marble", _"MaterialKey", "Price$SF") Public Function INDEXMATCH(ByVal strWksName As String, ByVal strTableName As String, _ ByVal rowKeyVal As Variant, ByVal strRowKeyColName As String, _ ByVal strColKeyName As String) As Variant Dim wksThisWorksheet As Worksheet Dim loTableListObject As ListObject Dim intRowNum As Integer, intColNum As Integer Set wksThisWorksheet = Application.Worksheets(strWksName) Set loTableListObject = wksThisWorksheet.ListObjects(strTableName) intRowNum = Application.WorksheetFunction.Match(rowKeyVal, _ loTableListObject.ListColumns(strRowKeyColName).Range, 0) intColNum = Application.WorksheetFunction.Match(strColKeyName, _ loTableListObject.HeaderRowRange, 0) INDEXMATCH = Application.WorksheetFunction.Index(loTableListObject.Range, intRowNum, intColNum) End Function SUMIF, COUNTIF: VBA using Excel Object Model with Structured Reference and EVALUATE varFUNCMATCHResult = FUNCIF("SUMIF", "Material", "tblMaterial", "MaterialKey", _ "Marmi:Imperiali:Gloss:Grey-Tan", "Price$SF") Public Function FUNCIF(ByVal funcName As String, strWksName As String, _ ByVal strTableName As String, ByVal strCriteriaColName As String, _ ByVal strCriteriaValue As Variant, ByVal strFuncValueColName As Variant) Dim wksThisWorksheet As Worksheet Dim loTableListObject As ListObject Dim FuncIfStr As String Dim test As Variant Set wksThisWorksheet = Application.Worksheets(strWksName) Set loTableListObject = wksThisWorksheet.ListObjects(strTableName) If LCase(funcName) = "countif" Then FuncIfStr = funcName & "(" & strTableName & "[" & strCriteriaColName & "], """ & _ strCriteriaValue & """)" Else FuncIfStr = funcName & "(" & strTableName & "[" & strCriteriaColName & "], """ & _ strCriteriaValue & """, " & strTableName & "[" & strFuncValueColName & "])" End If FUNCIF = Evaluate(FuncIfStr) End Function TEXTJOIN: VBA using Excel Object Model 2020 May 1: Updated VBTEXTJOIN to eliminate strCRLFChar character at the end of the last line concatenated from the rngColConcat source text argument. Now the algorithm prepends strCRLFChar rather than postpending it. If you call the Excel TEXTJOIN function in VBA using EVAL, you may encounter the EVAL 255-character limit. You could avoid that by calling APPLICATION.WORKSHEETFUNCTION.TEXTJOIN. Or you could use the following VBTEXTJOIN which emulates TEXTJOIN, avoids the 255-character limit to the rngColConcat argument, and adds some features that are convenient for concatenating SQL strings. The strCRLFChar argument is a string, and can add more function than CRLF at the end of each line concatenated. VBTEXTJOIN also ignores blank lines in a Structured Table passed in argument rngColConcat preceding the first non-blank line, ignores blank lines following the last blank line, but maintains blank lines within the non-blank region to preserve spacing among non-blank lines. Note also that in the function call example (strVBCONCATResult line near the top of the code below), the strCRLFChar is set as a space concatenated with a vbCRLF character, to improve readability of strings to be passed to SQL by inserting both a space and a vbCRLF at end of line. This displays a space at the end of each line which improves readability of the concatenated SQL string result when it is viewed in a context that does not display line feeds. This does not affect SQL functionality. Including such a space character is optional of course, and its utility is entirely dependent on use of the strVBCONCATResult result from VBTEXTJOIN. The same can be done in Excel, using CHAR(32) & CHAR(10) in the function call from Excel. Set wksThisWorksheet = Application.Worksheets("Material") Set loTableListObject = wksThisWorksheet.ListObjects("tblMaterial") strVBCONCATResult = VBTEXTJOIN(chr(32) & Chr(10), True, _ loTableListObject.ListColumns("MaterialKey").Range, "VBA") Public Function VBTEXTJOIN(ByVal strCRLFChar As String, ByVal bIgnoreBlank As Boolean, _ ByVal rngColConcat As Range, ByVal calledFrom As String) As String 'Replicate Excel2016 TEXTJOIN function, in VB Dim thisString As String Dim objThisString As Object Dim bColumnLabel As Boolean Dim intColConcatIndex As Integer, intColConcatIndexLast As Integer Select Case LCase(calledFrom) Case "excel" bColumnLabel = False Case "vba" bColumnLabel = True Case Else bColumnLabel = False End Select 'Set bColumnLabel to ignore first element of rngColConcat if the call is from VBA. 'rngColConcat as tablename[columnname] from Excel does not include columnname ' as first element. 'rngColConcat as loTableListObject.ListColumns("MaterialKey"). ' Range includes columnname as first element. intColConcatIndexLast = rngColConcat.Count ' Find last non-empty cell For intColConcatIndex = rngColConcat.Count To 1 Step -1 thisString = rngColConcat(intColConcatIndex) If Not ((thisString = "" Or IsNull(thisString)) And bIgnoreBlank) Then intColConcatIndexLast = intColConcatIndex Exit For End If Next intColConcatIndex VBTEXTJOIN = "" For intColConcatIndex = 1 To intColConcatIndexLast If bColumnLabel Then bColumnLabel = False Else thisString = rngColConcat(intColConcatIndex) ' Ignore empty cells above first non-blank cell If VBTEXTJOIN = "" And Not ((thisString = "" Or IsNull(thisString)) _ And bIgnoreBlank) Then ' Add first non-blank cell to return value VBTEXTJOIN = thisString Else ' Add blank or non-blank cells after first non-blank cell, 'added value preceded by a CRLF character VBTEXTJOIN = VBTEXTJOIN & strCRLFChar & thisString End If End If Next intColConcatIndex End Function Note in the above code that the call to VBTEXTJOIN from within VBA includes the argument "calledFrom" with value "VBA". You probably don't want to include the column name cell in the string result when TEXTJOINing a column of values in a Structured Table. A call from VBA passes the rngColConcat argument as a ListObject.Column range which includes the column name, whereas an Excel call passing in a StructuredTable["columnname"] range does not include the column heading cell. The calledFrom argument controls whether the first element of the range will be ignored as would be typical in a call from VBA, or concatenated with the rest of the range elements as would be typical in a call from Excel. The VBTEXTJOIN function can be called from an Excel formula expression as
=VBTEXTJOIN(CHAR(10),True,tblMaterial[Item],"EXCEL") or
XLOOKUP: VBA to Return a Single Structured Table Column by Name The Excel XLOOKUP function is available AFTER Excel versions 2016 and 2019. So you or your users may not have the function available. Following is VBA emulation of a specific use: return of a column with a specified name, from a Structured Table. This function allows each destination Structured Table record (the one doing the lookup) to specify the name of a Column (probably in another Table), naming the Column via a field in the destination Table so that the Column name is not hard-coded into each reference. You could always use construct "tblTableName[ColumnName]" (ColumnName is hard-coded in the call), but that puts column name in the function call rather than in a data value in the Table. Function vbatableXLOOKUP makes it possible for each record to specify its needed column via drop-down, or calculation, etc. Public Function vbatableXLOOKUP(ByVal varLookupValue As Variant, ByVal rngTableLookupRange As Range,_ ByVal rngTableReturnArray As Range) As Range ' Emulation of XLOOKUP return of a column in a table, with a specified column heading. ' Call syntax in a Structured Table: vbatableXLOOKUP([@columnName], tableName[#Headers],tableName[#Data]) ' Call syntax for XLOOKUP almost identical: XLOOKUP([@columnName], tableName[#Headers],tableName[#Data]) Dim intMatchType As Integer Dim intMatchColNum As Integer intMatchType = 0 ' 0 for EQ; -1 for GE; 1 for LE intMatchColNum = WorksheetFunction.Match(varLookupValue, rngTableLookupRange, intMatchType) Set vbatableXLOOKUP = rngTableReturnArray.Columns(intMatchColNum) End Function Structured Table Referenced in Example VBAExample Structured Table VBA SourceSource for example code above: VBA Table Expressions. Code in the examples has been modified to fit this website pagewidth. |