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 Framework.
- 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 Creation and Control of Data Connections to SQL Server and MSAccessThis page describes a "Working Path": documenting a PowerOpI logistics tool design that has been prototyped and used, is known to work, and provides a working instance to use or from which to adapt with incremental changes. It demonstrates many design techniques. As to incremental changes, this is Excel, and there are many paths that could work, and many more that lead to dead ends for a project as broad as this one. This is not tips and tricks, this is an architectural solution. As it is used, there will be many incremental changes possible. But you can start from an initial Working Path, using these examples. It will always be possible that Excel will change too. That is in fact the whole genesis of this page... the low-code designs shown for now on some other pages on this site, are now more limited by changes made to Excel in just the past year. Nevertheless, the premise of the designs shown here has proven to be very powerful for the Use Case documented here and for related projects, so this new PowerOpI Framework implementation has been developed to keep solutions viable for that Use Case. And this new methodology is more powerful than ever. Use Case Sets Operational ConstraintsA primary use case involves management of development and New Product Introduction (NPI) of fairly complex system elements and systems including silicon, board assemblies, subsystems, EE, ME; Firmware, Diagnostics, OS Driver, element and system management, client and server function such as transport, streaming, encoding, storage, rendering etc. Prototype and productized code and physical items are locally developed or come from a library or OEM, open-source, third-party or JDM with branch-driven DevOps. Organizational management entities include captive and contract developers, Manufacturing Supply Chain and Operations, with offshore JDM/CM. Product, organizational, and project operational structure are described quantitatively by tools and tables. A core set typically includes task-sequence GANTT/"schedule" including tasks/integrations/releases for all elements/assemblies for HW, FW, ME, SW, DevOps and from which operational dates can be derived; element allocation, configuration and use; element Bill-of-Materials (BOM) product hierarchical structure; Element task dates from GANTT; Element operational data (PN, Supplier/Distributor), technical data links/power/physical, buyer. A focused set of project leads manage Table content. A product is developed and validated within the effort which is obvious to all, but less obvious to many, concurrently developed and proven are sourcing, production, certification, compliance, and product and customer support. Project Leads include cross-functional Development (EE, ME, SW, SQA, DevOps et al), Compliance, Supply Chain, Operations, Support, Marketing and are physically distributed. Project Leads have some depth with Excel but no exposure to use of Database. Effort to be managed is complex enough that Excel standalone is inadequate: even well-developed local tools miss interactions, integrations, and project scope. Formal production tools focused on management of product development, NPI, finance and headcount are oriented toward revenue-production and minimally support product development. Project structure is ad-hoc meaning dependent on product structure and current organization status. Structure is temporary, used for the life of the development effort but handed off to production systems over the course of NPI. There is no IT support for management of product development so Program Manager and Leads develop their own structures and tools. The effort described here may look constructed as a worst-case management problem, but is common practice among well-known suppliers of electronic systems. Complexity varies with project scope. Much of this work has been offshored over time to CM, JDM, ODM and OEM partners. This has the effect of hiding the complexity from the company owning the effort, but the complexity exists nevertheless, managed by partner processes - the owner just takes the result without detailed knowledge or control of shortcuts taken or constraints built in to the product, or even of actual defects in product function, compliance, or environmental operation. Issues discovered during or after the development effort are routinely accepted as schedule issues or product defects to be corrected at some cost and delay and which may be difficult on units already in customer use; or they may even be ignored. This can be a cause of effort or even product failure. The same issues may exist even if development is local/captive within the owning company albeit with earlier and better visibility and perhaps mitigation. But the methodology and tools described on this site are aimed to avoid such issues in the first place, resulting in a better-managed effort, a more-robust product, and a more cost-effective product effort. Through experience applying tools to several (actually, many!) projects, I've come to the following principles:
Using VBA in Excel to Build and Manage ConnectionsThis site has for some time shown how to build and manage Excel Data Connections using the Excel User Interface. This is a low-code approach, available to a broad set of people and requiring only a low-level of training in VBA, Data Connections, or SQL. In this way, the approach has made Excel Structured tables used with SQL JOINs available in more organizations to solve hard problems with much greater power than otherwise available. However, some changes have been made to Excel that necessitate a new approach. This is a list of changes important to use of the Power OpI methodology and tools; it is not necessarily a complete list of changes; and there could be workarounds that haven't yet been determined. Nevertheless, a way of dealing with these problems is addressed below. As usual this description focuses on the PowerOpI tools; but the problems and solutions undoubtedly apply to any solution using Excel with a SQL database that works directly with the database using SQL.
However, Excel Data Connections can still be created and managed using VBA in Excel, and maintain the described Use Case.. So a tool is described on this website that provides automation for building, distributing and configuring connection-based database access from within Excel. Connection-based QUERY and JOIN operations on Excel Structured Tables from within Excel can be performed using SQL and configuration tools for Linked Servers, OPENROWSET etc. with:
A QUERY may bring JOINed Source Data into Excel, or into the database tool and then into Excel, from:
How is this different from PowerQUERY in Excel? This tooling provides automation to conveniently use Excel with SQL, creating QUERY and JOIN functionality, with configuration for Linked Server and OPENROWSET to bring in external data types. This tooling then supports use of Excel with configured SQL and M among multiple distributed users with automation to assist configuration of location-sensitive paths. The tool integrates seamlessly with PowerQUERY, providing that with enhanced support for use with SQL QUERY and JOIN. The functional result combines the power, connectivity and convenience of Excel with the power of SQL, together with the capabilities of PowerQUERY and M. Main Components of PowerOpI SQL Tooling FrameworkFirst, you should have SQL Server, SQL Server Management Studio (SSMS), MSOLEDBSQL, and Microsoft.ACE.OLEDB.XX.0 (ACE) installed and configured as described on page Excel-SQL Server on this site. Links are provided there for components to install. If possible, also set up Visual Studio and SQL Server Development Tools (VS-SSDT), also linked on that page. A single PowerOpI SQL Tooling Framework file contains several functionalities:
All components described here are contained within a single Excel Framework File. As noted above, functionality generally consists of Source Data, Reporting, and SQL Management; and these in practice are commonly split from the Framework into separate workbook files for the three separate purposes listed above.
The PowerOpI Framework is not distributed here, but the mission of this website is to show how to use a Database Tool with Excel to address hard configuration problems. Because of changes made to Excel this is no longer a Low-Code proposition... So, critical sections of the VBA code required are shown in the following links, to be described below.
Overall OperationNotice the command buttons at lower left of the Data Connection Configuration Table:
The top four buttons initiate Build or Update of connections to Source Data. Build means "create if no connection by that name exists", and set all parameters as described by the Table. Update also means "create if no connection exists by that name", but "Update" the existing database Connection String if one existed, and for new connections set the Connection String to the Template from the Table. All other parameters are set as described in the Table. DATE tables get their own Build/Update buttons. They function the same as for DATA tables but are generally updated separately so that you can work on dates without affecting logistics until you're ready to; and then you should update DATES before updating DATA, so that the DATA will pick up the newest set of dates. DATA tables are defined by LinkType "PivotSource" (for Data Connection) or "webQuery" (for PowerQUERY). DATE tables are defined by LinkType "mppImport" (for Data Connection) or "mppQuery" (for PowerQUERY). The same sequence applies to Build and Update, for DATA and for DATES; and also for both Connection and PowerQUERY QUERY definition records. The exact coding for QUERYs is a little different than for Connections but the overall sequence is the same:
Once all entries in tblConnectionConfig have been processed (that is, created and/or updated), all connections contained in the Workbook are examined. Only Connections/QUERYs that have a definition record in tblConnectionConfig are kept; any Connection/QUERY not defined in the Table is deleted. Un-used connections/QUERYs not actively kept up-to-date only cause problems over time when refreshed with parameters that no longer make sense, so this is garbage collection. The fifth command button initiates #Directive operation from tblSQL. Currently only Export is supported, and is described below. Let's jump right in to the core creation/update of Connections and QUERYs. Here is a skeleton of VBA to create a new Excel Data Connection, or update an existing one. Then we'll flesh that out and wrap that core into a full-function management of Data Connections using SQL, for SQL Server, MS Access, and PowerQuery. For this web page, all infrastructure and control logic implementing support functions, buttons, error handling, status updates etc. is NOT SHOWN. The full context for these constructions is contained in vbaConnect.txt which can, as a whole and as available, be placed into a functional Framework like the one described on this website. Please Contact for Details if interested. If Not boolExists(strConnectionName, thisWorkbook.Connections) Then ' If connection(strConnectionName) NOT PRE-EXISTING, ' create it with parameters required for creation Set thisConnection = thisWorkbook.Connections.Add2( _ strConnectionName, strDescription, _ strConnectionString, strCommandText, _ lCmdType) Else 'If connection(strConnectionName) IS PRE-EXISTING, update its params required ' for creation so that it's in state equivalent to a newly-created ' connection Set thisConnection = thisWorkbook.Connections(strConnectionName) If thisConnection.OLEDBConnection.Refreshing Then _ thisConnection.OLEDBConnection.CancelRefresh ' Turn off ongoing activity if any - it will refresh later thisConnection.Description = strDescription thisConnection.OLEDBConnection.Connection = strConnectionString thisConnection.OLEDBConnection.CommandText = strCommandText thisConnection.OLEDBConnection.CommandType = lCmdType End If Below we'll describe where the str and bool parameters come from; Connection String Templates; Error Handling; string replacement function strReplaceString2; further functionality to complete parameter updates, to refresh connections, and cleanup of un-used connections; and handling of SQL CREATE and DROP. Connection ParametersIn the Prototype Framework to be described on this site, Connections in an Excel workbook are described in the Excel UI within a Structured Table, one Connection described in each table record. The Connection parameter values are contained in fields (columns) in each record, each named for the parameter specified. The table currently lists 26 values for each connection. Fortunately, templates are provided in the table that describe many styles of connection (ACE, MSOLEDBSQL; Data Connection PowerQuery; embedded SQL, Stored Procedure call; etc.) each showing template parameter settings for its connection type. The table is named "tblConnectionConfig" and is visible to VBA as a ListObject by that name as is usual for a Structured Tables. (Clicking the image below will bring up an expanded image.) When Connections are built or updated, as commanded by a button object shown at bottom left in the preceding figure, the table is processed record-by-record. As each record is chosen, its parameter values are read from the table and set into VBA variables. The values are either string or boolean type, as determined by the use of the parameter in VBA procedure calls to build or configure each Connection, as shown in the VBA code above as needed for the Microsoft procedures called (e.g., "Add2" or the Workbook.Connections.Connection object itself.). Here's VBA code to take values from the table, or to set a default value if none is specified, and put the the value into a VBA variable. First, self-contained statements to take values directly from the table: an example of a parameter with a string value and another with a boolean value: Set connectionSheet = thisWorkbook.Worksheets("DataSources") Set tblConnectionConfig = connectionSheet.ListObjects("tblConnectionConfig") Set rngHeaderRow = tblConnectionConfig.HeaderRowRange If WorksheetFunction.Match("Alternate Path", rngHeaderRow, iMatchTypeExact) > 0 Then _ strAlternatePath = thisListRow.Range.Cells(WorksheetFunction.Match("Alternate Path", _ rngHeaderRow, iMatchTypeExact)) _ Else strAlternatePath = "" If WorksheetFunction.Match("BackgroundQuery", rngHeaderRow, iMatchTypeExact) > 0 Then _ boolBackgroundQuery = CBool( _ thisListRow.Range.Cells(WorksheetFunction.Match("BackgroundQuery", _ rngHeaderRow, iMatchTypeExact))) _ Else nameIndex = False ' ...and repeat one of these two statements for each of 26 parameters ' to be extracted from tblConnectionConfig as each Connection record is processed. Each of those is a mouthful that will be repeated 26 times. So they can be simplified a bit by first associating a table column number with each column name, instead of using a MATCH function twice to do that job in each case: ' First build a header collection associating each header name with a column number: ' Clear out collection of header names prior to populating For iCellIndex = cHeaderCollection.Count To 1 Step -1 cHeaderCollection.Remove iCellIndex Next iCellIndex ' Then populate the collection of header names For iCellIndex = 1 To tblConnectionConfig.HeaderRowRange.Count cHeaderCollection.Add Item:=iCellIndex, _ Key:=tblConnectionConfig.HeaderRowRange.Cells(iCellIndex) Next iCellIndex ' Then use that header collection to fetch each parameter value into a VBA variable: If boolExists("Alternate Path", cHeaderCollection) Then _ strAlternatePath = thisListRow.Range.Cells(cHeaderCollection.Item("Alternate Path")) _ Else strAlternatePath = "" If boolExists("BackgroundQuery", cHeaderCollection) Then _ boolBackgroundQuery = CBool(thisListRow.Range.Cells(cHeaderCollection.Item("BackgroundQuery"))) _ Else boolBackgroundQuery = False ' ...and repeat one of the last two statements for each of 26 parameters ' to be extracted from tblConnectionConfig as each Connection record is processed. Connection Parameter List, and Config VariablesFollowing table describes parameters set on each Connection. Some of the listed parameters control use of others.
Connection Parameter Completion and RefreshCreation of a new Connection listed in the Table sets a subset of parameters, and the Update operation sets the same subset in an exisitng Connection. Following Creation or Update of each listed Connection, the remaining parameters are set in the Connection and it is Refreshed by the following code: ' When Connection defined in tblConnectionConfig exists, either pre-existing or just created, ' update its params beyond creation-required params ' strRefreshEnable thisConnection.RefreshWithRefreshAll = boolRefreshAll If boolExists(thisConnection.Name, colPivotCache) Then ' Checks if connection is used to drive a pivot. If thisConnection.OLEDBConnection.Refreshing Then _ thisConnection.OLEDBConnection.CancelRefresh ' Turn off ongoing activity if any - it will refresh later If thisConnection.OLEDBConnection.EnableRefresh <> boolEnableRefresh Then _ thisConnection.OLEDBConnection.EnableRefresh = boolEnableRefresh ' May cause error, reason unknown thisConnection.OLEDBConnection.BackgroundQuery = boolBackgroundQuery thisConnection.OLEDBConnection.MaintainConnection = boolMaintainConnection thisConnection.OLEDBConnection.RefreshOnFileOpen = boolRefreshOnOpen thisConnection.OLEDBConnection.RefreshPeriod = intRefreshPeriod End If ' Connection Exists ' Refresh Connection after build / update. strSkipBuildUpdate does NOT affect Refresh. ' For button RebuildRefreshPivotSourceWebQuery or RefreshPivotSourceWebQuery, refresh PivotSource ' For button RebuildRefreshMPPImportMPPQuery, refresh mppImport If boolExists(strConnectionName, thisWorkbook.Connections) Then Set thisConnection = thisWorkbook.Connections(strConnectionName) If Not thisConnection.OLEDBConnection.Refreshing Then ' If it's already refreshing then skip it, else refresh depending on buttons thisConnection.OLEDBConnection.Refresh End If DoEvents End If ' connection exists Parameter Replacement in Connection String TemplatesThere are still some parameters remaining to configure Connections and PowerQuerys - those that must be included within the ConnectionString parameter. Those Connection Strings are defined by templates of the form:
PowerQuery uses the Power Query M formula language to describe connection and data import. Instead of fully parsing each string, we are going to find and replace sub-strings. For that, function strReplaceString2 (found in vbaConnect.txt) performs similarly to the VBA Replace function. The parameter values to be updated/replaced may occur within a sub-string terminated by a ";" character, or at the end of a string terminated by a Quote character in which case there is no terminating ";" character. So strReplaceString2 looks for termination by the earlier occurence of one of two possible termination characters. A collection of core patterns for Connection-Strings and M-Strings is provided at this link. Be aware that in any #Template, substitution will occur for the first match for a pattern subject to that #Template. This is great for Connection Strings, but a little more complicated for #Templates for M, including SQL inserted into an M string. Substitution does not parse syntax of Connections Strings, but will replace the first pattern match; so /* comments */ or //comments should occur AFTER any code in a SQL or M string that could be replaced. And again, for this web page, all infrastructure and control logic implementing support functions, buttons, error handling, status updates etc. is NOT SHOWN. The full context for these constructions is contained in vbaConnect.txt which can, as a whole and as available, be placed into a functional Framework like the one described on this website. Please Contact for Details if interested. Function calls to strReplaceString2 are chained to replace target sub-strings in Connections, like this: ' strConnectionString: Make replacements for Connection String strConnectionString = strReplaceString2("Data Source=", ";", "*", strConnectionString, _ strSourceTarget) ' Replace Data Source strConnectionString = strReplaceString2("Workstation ID=", ";", "*", strConnectionString, _ strWorkstation) ' Replace Workstation ID strConnectionString = strReplaceString2("Initial Catalog=", ";", "*", strConnectionString, _ strDatabase) ' Replace Initial Catalog strConnectionString = strReplaceString2("Mode=", ";", "*", strConnectionString, strMode) 'Replace ACE Read Write Mode ...and like this for PowerQuery: Select Case LCase(strProvider) Case "powerqueryoledb" strConnectionString = strReplaceString2("provider=", ";", """", strConnectionString, _ "MSOLEDBSQL") ' Replace strProvider strConnectionString = strReplaceString2("initial catalog=", ";", """", strConnectionString,_ strDatabase) ' Replace strDatabase strConnectionString = strReplaceString2("data source=", ";", """", strConnectionString, _ strServerName) ' Replace strServerName strConnectionString = strReplaceString2("Query=""", """", "*", strConnectionString, _ strCommandText) ' Replace strCommandText strConnectionString = updateQueryTableName(strConnectionString, "_Table =", strTableName) ' If there's a Table name within strConnectionString, replace it ' by the strTableName parameter. Case "powerqueryexcel" strConnectionString = strReplaceString2("File.Contents(""", """)", "*", _ strConnectionString, strSourceTarget) strConnectionString = updateQueryTableName(strConnectionString, "=Source{", strTableName) ' If there's a Table name within strConnectionString, replace it ' by the strTableName parameter. Case "powerquerysqlserver" strConnectionString = strReplaceString2("Sql.Database(""", """,", "*", strConnectionString,_ strServerName) ' Replace strServerName strConnectionString = strReplaceString2("Query=""", """", "*", strConnectionString, _ strCommandText) ' Replace strCommandText iCurLeft = InStr(1, strConnectionString, ",", vbTextCompare) ' Replacing the Database name is harder because it has no unique delimiter iCurLeft = InStr(iCurLeft, strConnectionString, """", vbTextCompare) + 1 iCurRight = InStr(iCurLeft, strConnectionString, """", vbTextCompare) - 1 strStringToReplace = Mid(strConnectionString, iCurLeft, iCurRight - iCurLeft + 1) ' Capture current-string to replaced within strConnectionString If InStr(1, strConnectionString, strStringToReplace, vbTextCompare) > 0 Then ' Check if we found the string to replace strConnectionString = Replace(strConnectionString, strStringToReplace, _ strDatabase, 1, -1, vbTextCompare) ' Match current-string within strConnectionString and replace by make-string End If Case "powerquerymsaccess" strConnectionString = strReplaceString2("File.Contents(""", """)", "*", _ strConnectionString, strSourceTarget) strConnectionString = updateQueryName(strConnectionString, "Item=""", """", strTableName) ' If there's a Table name within strConnectionString, ' replace it by the strTableName parameter. End Select PowerQuery Build, Update, Parameter Completion, and RefreshThe same sequence is applied to PowerQuery: Get Parameters and Pre-Process; Build or Update; Complete Param update; Refresh. Getting and pre-processing Parameters, and update of the Connection String is described above. The coding for Build/Upddate, Param update, and Refresh is a little different. A representation of all elements of the core sequence (Build/Update, Param Completion and Update, Refresh; and without displaying infrastructure) is shown. Note that when a PowerQUERY QUERY is created and assigned to object thisWBQuery, Excel automatically creates a Connection, named "Query - " & thisWBQuery.Name, and Parameter updates apply to that Connection. Also note, the function boolExists(ByVal thisname As String, ByVal thisCollection As Variant) As Boolean determines if an item named thisName exists in thisCollection, returning boolean TRUE or FALSE. PowerQuery QUERY Build/Update - Parameter Completion - Parameter Update - Refresh: If Not boolExists(strConnectionName, thisWorkbook.Queries) Then ' Here's the Build action Set thisWBQuery = thisWorkbook.Queries.Add(strConnectionName, strConnectionString, _ strDescription) ' If it pre-exists, update it Else ' Update pre-existing QUERY Connection String and Command Text Set thisWBQuery = thisWorkbook.Queries(strConnectionName) If strSkipBuildUpdate = "Update" Then strConnectionString = thisWBQuery.Formula ' If Update, start with pre-existing string in QUERY, else re-start from template End If ' Build the Connection String Select Case LCase(strProvider) Case "powerqueryoledb" strConnectionString = strReplaceString2("provider=", ";", """", strConnectionString, _ "MSOLEDBSQL") ' Replace strProvider strConnectionString = strReplaceString2("initial catalog=", ";", """", _ strConnectionString, strDatabase) ' Replace strDatabase strConnectionString = strReplaceString2("data source=", ";", """", _ strConnectionString, strServerName) ' Replace strServerName strConnectionString = strReplaceString2("Query=""", """", "*", strConnectionString, _ strCommandText) ' Replace strCommandText strConnectionString = updateQueryTableName(strConnectionString, "_Table =", _ strTableName) ' If there's a Table name within strConnectionString, ' replace it by the strTableName parameter. Case "powerqueryexcel" strConnectionString = strReplaceString2("File.Contents(""", """)", "*", _ strConnectionString, strSourceTarget) strConnectionString = updateQueryTableName(strConnectionString, "=Source{", _ strTableName) ' If there's a Table name within strConnectionString, ' replace it by the strTableName parameter. Case "powerquerysqlserver" strConnectionString = strReplaceString2("Sql.Database(""", """,", "*", _ strConnectionString, strServerName) ' Replace strServerName strConnectionString = strReplaceString2("Query=""", """", "*", strConnectionString, _ strCommandText) ' Replace strCommandText iCurLeft = InStr(1, strConnectionString, ",", vbTextCompare) ' Replacing the Database name is harder because it has no unique delimiter iCurLeft = InStr(iCurLeft, strConnectionString, """", vbTextCompare) + 1 iCurRight = InStr(iCurLeft, strConnectionString, """", vbTextCompare) - 1 strStringToReplace = Mid(strConnectionString, iCurLeft, iCurRight - iCurLeft + 1) ' Capture current-string to replaced within strConnectionString If InStr(1, strConnectionString, strStringToReplace, vbTextCompare) > 0 Then ' Check if we found the string to replace strConnectionString = Replace(strConnectionString, strStringToReplace, _ strDatabase, 1, -1, vbTextCompare) ' Match current-string within strConnectionString and replace by make-string End If Case "powerquerymsaccess" strConnectionString = strReplaceString2("File.Contents(""", """)", "*", _ strConnectionString, strSourceTarget) strConnectionString = updateQueryName(strConnectionString, "Item=""", """", _ strTableName) ' If there's a Table name within strConnectionString, ' replace it by the strTableName parameter. End Select thisWBQuery.Formula = strConnectionString thisWBQuery.Description = strDescription End If ' Update common parameters of QUERY Connection If boolExists("Query - " & thisWBQuery.Name, thisWorkbook.Connections) Then Set thisConnection = thisWorkbook.Connections("Query - " & thisWBQuery.Name) If thisConnection.OLEDBConnection.Refreshing Then _ thisConnection.OLEDBConnection.CancelRefresh ' Cancel refresh if active thisConnection.RefreshWithRefreshAll = boolRefreshAll If (thisConnection.Type = xlConnectionTypeOLEDB And _ strConnectionType = "xlConnectionTypeOLEDB") Then thisConnection.OLEDBConnection.EnableRefresh = boolEnableRefresh thisConnection.OLEDBConnection.BackgroundQuery = boolBackgroundQuery thisConnection.OLEDBConnection.MaintainConnection = boolMaintainConnection thisConnection.OLEDBConnection.RefreshOnFileOpen = boolRefreshOnOpen thisConnection.OLEDBConnection.RefreshPeriod = intRefreshPeriod End If End If If boolExists(strConnectionName, thisWorkbook.Queries) Then ' Check if QUERY exists - created or pre-existing Set thisWBQuery = thisWorkbook.Queries(strConnectionName) If boolExists("Query - " & thisWBQuery.Name, thisWorkbook.Connections) Then Set thisConnection = thisWorkbook.Connections("Query - " & thisWBQuery.Name) If Not thisConnection.OLEDBConnection.Refreshing Then ' If it's already refreshing let it go thisConnection.OLEDBConnection.BackgroundQuery = boolBackgroundQuery thisConnection.OLEDBConnection.Refresh End If DoEvents End If ' Refresh Operation End If ' REFRESH Button check End If ' QUERY existence check Cleanup of Un-Used Connections and PowerQuerysWhen all line items in tblConnectionConifg defining Connections have been processed, connections contained within the Workbook that ARE NOT defined in that table, are deleted to avoid accumulation of dysfunctional Connections. For PowerQuery QUERYs, both QUERY and its assigned Connection must be deleted. Presumably deleted Connections and QUERYs could be re-constructed by re-adding their definitions to the table, or changing their Skip parameter to Build. In the code below, colConnectionList and colWBQueryList were constructed, a collection of named Connectons and QUERYs intended to exist, as the Build/Update/Parameter/Refresh cycle progressed, in code not shown here. Connection and PowerQuery QUERY Cleanup: ' Once defined connections are handled, ' delete existing Connections not defined in tblConnectionConfig For Each thisConnection In thisWorkbook.Connections ' Connections defined in tblConnectionConfig were saved into colConnectionList ' Delete connection if it's not in colConnectionList, ' as long as its name doesn't begin with "Query -" If Not boolExists(thisConnection.Name, colConnectionList) Then If Not (Left(thisConnection.Name, 8) = "Query - ") Then If thisConnection.OLEDBConnection.Refreshing Then _ thisConnection.OLEDBConnection.CancelRefresh ' Stop refresh before deleting it thisConnection.Delete End If End If Next thisConnection ' Once defined QUERYs are handled, delete existing QUERYs not defined in tblConnectionConfig For Each thisWBQuery In thisWorkbook.Queries ' Queries defined in tblConnectionConfig were saved into colWBQueryList If Not boolExists(thisWBQuery.Name, colWBQueryList) Then ' Delete the Connection thisWorkbook.Connections("Query - " & thisWBQuery.Name).OLEDBConnection.CancelRefresh ' Stop any ongoing refresh before deleting thisWorkbook.Connections("Query - " & thisWBQuery.Name).Delete ' Then delete the associated QUERY If boolExists("Query - " & thisWBQuery.Name, thisWorkbook.Connections) Then thisWBQuery.Delete End If End If Next thisWBQuery Error Handling
Configured-SQL Editor, and Configuration VariablesMost Connections will have SQL embedded, so it's convenient to build SQL strings within Excel. Even more than convenient, in Excel you can use the Common Configuration Variables specifying file names and paths, localized as needed; you can reference other Stored Procedures and Linked Servers by name, whose SQL strings for CREATion are themselves created within Excel (referencing local variables etc.), to EXEC those SPs from within SQL strings created subsequently. In short, Excel provides a computational infrastructure useful especially for creating SQL strings that are related to each other and which share Source Data entities and paths to their locations. The Framework described on this website provides Common Configuration Variables that can provide some leverage, consistency, and robustness to solutions built on it. Those variables include Source Data filenames and paths; database location and instance names; Linked Server names Export location path; name of the Framework workbook, host processor executing the Framework file. Where useful, those variables provide a localized value which may be a function providing such information; and are provided as Named Ranges to be referenced by name. They could be overwritten as appropriate, although that complicates distribution of updates.
tblLocalizationConfig Common Configuration Variables are used with PowerQuery M primarily in setup of Connection Strings to data sources. For SQL with MS Access, SQL Server and PowerQuery, they are more extensively used, in Connection Strings and also to set up Linked Servers and OPENROWSET QUERYs. The Configured SQL Editor part of the Framework is conceptually straightforward. Each SQL string is represented in a column within Structured Table tblSQL. The name of the column is most useful if it indicates the functionality of the SQL contained in that column. (The section on DROP and CREATE just below describes how SQL strings can be exported .sql files named for their defining column, for use in SSMS and VS-SSDT.) Each cell in the Table takes normal Excel content which can include expressions of any kind, or calls to VBA etc., and which frequently are used to concatenate, parse or lookup other variables to create functional SQL. Common Configuration Variables are provided in templates including the PowerOpI Framework Environment, local Workstation and Workbook. Templates are provided for Data Sources as Servers, Files, and Linked Servers. A note on OPENROWSET: Although OPENROWSET pulls information from a File data source, it requires all the same information as is needed for a Linked Server even though it differs syntactically and logistically from a Linked Server. When configuring Common Configuration Variables for OPENROWSET, use the Linked Server template which besides the file name and path, will provide named Variables for the Excel Version, ACE Provider and Version for use in OPENROWSET QUERYs just as for Linked Server CREATE QUERYs. There are examples of this in the PowerOpI Template. When updates are made in tblConnectionConfig that change locations of files using SQL from tblSQL, then Stored Procedures in affected databases and Linked Servers, all of which are saved within SQL Server, must be updated by exporting and running configured QUERYs for SMS/VS-SSIS/VS Code; and connections into MS Access and MS Visio, stored in .accdb and .vsdm files for those application, must be updated as well from within those applications. A sequence for these updates is described below. Using Common Configuration Variables in tblSQL and tblConnection As shown in the image below, cells containing simple "simple text" are left un-formatted, and cells containing Excel expressions to concatenate etc. are formatted as bold-blue-italic. SQL Keywords are capitalized.
The Connection Configuration table tblConnectionConfig contains a field specifying SQLName, identifying the name of a column in tblSQL containing SQL to be embedded for each Connection. Using that, field CommandText fetches the SQL String which will be embedded into the Connection on a Build or Update command from one of the Buttons. Field CommandText uses Excel function XLOOKUP to locate the column in tblSQL named by field SQLName, and uses Excel function TextJoin to concatenate all non-blank cells in that column, delimiting each by a space and line-feed (ignored by SQL) to create a string containing the SQL to be embedded into each Connection or to be exported to a .sql file. Thus these SQL-Strings are invoked by name (at the top of each column in tblSQL), selected by a pull-down menu in tblConnectionConfig field named SQLName, for each Connection configured in tblConnectionConfig. The value of the SQL-String is then shown for each Connection, in tblConnectionConfig field CommandText. Configured-String Editor for Connection Strings and PowerQuery "M" StringsIt would be useful to have an editor to create, configure, and update Connection Strings, and PowerQuery "M" strings. The PowerOpI Framework workbook provides a Configured-String Editor for this. This Editor is fairly basic, but provides more functionality than would be provided by simply stating fixed string values as Connection-Templates and M-Templates. For M, the PowerQuery Editor in Excel is much more funtional. However, it produces values in its Queries and Connections that are read-only in the User Interface except within the PowerQuery Editor; and that is a manual proposition. The intent of the PowerOpI Framework is to provide a way to create and manage access and use of Excel with database environments, in a way that handles numerous connections with automation so that it can be provided to a distributed set of users. It is not fully developed and tested, but it is nevertheless intended that a user could go back and forth using the PowerQuery Editor, and the Configured-String Editor, together with the automation of the PowerOpI Framework - the PowerOpI Framework "Update" capability will ultimately provide this capability.
The Templates for both Connection-Strings and PowerQuery M-Strings discussed above, are provided via the Configured-String Editor. Connection strings could be added to the Template (ODBC comes to mind), and terms can be updated. Certainly M has more capability than the basic connectivity provided by the M-Template strings provided so there is lots of opportunity there. First are shown Configuration-Templates in the Configured-String Editor each named as its Provider; followed by M-Templates named to indicate Data Source type. As in the Configured-SQL Editor, each column in Configured-String Editor source tblConnection represents a String whose Name is the Title of that column. Within each column, each cell represents a term in the Connection- or M-String. Each cell can be created by simple text, or by concatenating values, such as references to Common Configuration Variables; or evaluating any other Excel expression that results in a string value; to specify files, paths, providers etc. Whole terms can be shown for inclusion or hidden for exclusion within the resulting string. Construction of a data-access clause in an M statement, fetching data for a QUERY from a file specified using a Common Configuration Variable: These Connection-Strings / M-Strings are invoked by name (at the top of each column in tblConnection), selected by a pull-down menu in the field named Provider, for each Connection configured in tblConnectionConfig. The value of the String selected is then shown for each Connection in tblConnectionConfig field ConnectionString. Connections specified in tblConnectionConfig specify their Provider connection-string by a UniqueName or a #TemplateName; Provider connection-strings themselves are detailed in tblConnection. Provider #Templates are used to modify a Provider connection-string to use values from tblConnectionConfig when a Connection is Built or Updated. The connection-string for a Provider identified by a UniqueName alone will be passed directly into the Connection as it is Built or Updated. A UniqueName may invoke Template field-substitution by post-pending a #TemplateName, as UniqueName#TemplateName. Connection-Strings that can be used as Templates are named with a "#" as the first character in the name, as #TemplateName. Each #TemplateName determines a set of fields within the connection string that will be updated and overridden when a connection based on it is Built or Updated. Templates exist for the following for Excel Data Connections: #Microsoft.ACE.OLEDB.12.0, #Microsoft.ACE.OLEDB.16.0, #MSOLEDBSQL, and #SQLOLEDB; and for PowerQuery M strings: #OleDb_DataSource, #Excel_Workbook, #Sql_Database, #Access_Database, and #Table_Join following the naming convention for the functions used in M for these operations: OleDb.DataSource, Excel.Workbook, Sql.Database, Access.Database, and Table.Join. In the figures above illustrating some of the templates, note the values highlighted in red in the Template strings. These values of these parameters will be overridden by values from fields in tblConnectionConfig at the time each Connection or M-String is built. The Connection builder uses the #TemplateName part of the Provider connection-string name, to determine to determine which parameters to update as determined by the Template. When Updated from pre-exisitng connection-strings, fields not found in an invoked #Templates would not be overidden, and would retain values placed into the M-String in the Configured-String Editor or by the PowerQuery Editor. The Configured-String Editor concatenates each non-blank cell in a column, delimiting each cell entry by a space and linefeed. The result is a readable string in SSMS VS-SSDT or WordPad etc. You must stay within rules of "M" regarding line breaks . Also, note that indentation of text in a cell is a visual effect only, not affecting the cell value: if a space is needed syntactically, an actual space character must be inserted properly in the string value. When updates are made in tblConnectionConfig that change locations of files using SQL from tblSQL, then Stored Procedures in affected databases and Linked Servers, all of which are saved within SQL Server, must be updated by exporting and running configured QUERYs for SMS/VS-SSIS/VS Code; and connections into MS Access and MS Visio, stored in .accdb and .vsdm files for those application, must be updated as well from within those applications. A sequence for these updates is described below. Again, the Configured-String Editor is pretty basic right now, but it provides infrastructure that may become more valuable as more functional use of M is explored with the PowerOpI Framework and its automation. SQL DROP and CREATE: SSMS and VS-SSDTOne of the recent changes to Excel eliminated capability to CREATE or DROP database entities such as Linked Servers, Stored Procedures, or Tables. That was useful for creating those entities for use with QUERYs interacting with Source Data and Reports in Excel, especially where those QUERYs are related to each other and can use Common Configuration Variables; obviously that function is needed somewhere. That function is available in SQL Server Management Studio (SSMS) and in Visual Studio (VS-SSDT). Both of those can execute SQL stored in .sql files, so functionality is included in the Framework described on this website, to export SQL created in its Configured SQL Editor to such .sql files. A Button is provided, Execute tblSQL #Directives, to do the export. The location to be exported to is defined in Common Configuration Variable ExportTo, and is commonly set to the directory from which SSMS and VS-SSDT can be directed to open .sql files in their Open File... dialogues. The Framework creates a directory structure, if needed, in the SSMS/VS-SSDT/VSCode file directory specified as the ExportTo Variable, and exports .sql files there. The default ExportTo value is C:\Users\WorkstationLocalLogin\Documents\SQL Server Management Studio[\ExportToSubstring]\WorkbookCurrentDirectory\WorkbookName (without extension)\Variables Tab Profile. (optional fields in brackets). The result is a path specification like C:\Users\Owner\Documents\SQL Server Management Studio\Merge\TestOLEDB\ProfileLocal. The default structure resembles the directory structure to the PowerOpI Framework file exporting the SQL, appending the Variable profile specifying file locations. The default can of course be overridden in Excel tblLocalizationConfig. The exported files can then be used in SSMS/VS-SSDT/VSCode to manage LSs, SPs, and Tables. This structure supports multiple Framework instances on a computer, and multiple databases and projects within a SQL Server instance. A separate .sql file is exported, overwritten if needed, to that directory, for each column in tblSQL containing a Directive string formatted as #VBTEXTJOIN: EXPORT; or #VBTEXTJOIN: EXPORTWITH domainName;. Note the hashtag and colon character , and closing semi-colon. It is recommended that a Directive String be placed within a SQL Comment so that SQL will ignore it, so usually a cell in the SQL string definition column contains /* #VBTEXTJOIN: EXPORT; */ or /* #VBTEXTJOIN: EXPORTWITH dbo; */ (or other domain name you specify) .
So files are exported that cannot be executed from Excel, and must be executed via SSMS or VS-SSDT - certainly DROP and CREATE. Those files when opened in SSMS/VS-SSDT must be linked to the intended database within the SQL Server instance. The SQL itself must conform to the GO batch construction of SSMS. The Framework shows an example in which a Stored Procedure configuring Microsoft.ACE.OLEDB.12.0 is EXECed prior to CREATEing a Linked Server; that EXEC must be separated within a batch step by GO. Note that a SQL string intended to be embedded into an Excel Connection rather than be executed by SSMS/VS-SSDT (e.g., to EXEC a Stored Procedure from Excel, that was created by SQL executed from a file exported for SSMS/VS-SSDT), would not contain the GO syntax. When updates are made in tblConnectionConfig that change locations of files using SQL from tblSQL, then Stored Procedures in affected databases and Linked Servers, all of which are saved within SQL Server, must be updated by exporting and running configured QUERYs for SMS/VS-SSIS/VS Code; and connections into MS Access and MS Visio, stored in .accdb and .vsdm files for those application, must be updated as well from within those applications. A sequence for these updates is described below. Organizing Storage and Permissions by Project and Domain
Naming, Saving, and Organization of Stored Procedures, Exported SQL, and Linked Servers
Using PowerQuery M Formula LanguageThe Power Query M Formula Language seems to be the real power behind Power Query. The Power Query UI basically creates M to perform tasks specified in the UI. Even more capabililty can be accessed by going to M directly, via the Advanced Editor button in the Power Query UI or by creating a Blank Query from the Excel UI or Power BI Desktop UI. Power Query M can be developed, with grammar coloring and Intellisense, in Excel and in PowerBI Desktop; and Extensions are available for both Microsoft Visual Studio Code (VSCode) and Microsoft Visual Studio. PowerQuery M is handled by the PowerOpI Framework, with M source code in the Configured-String Editor. M sourcecode in the Power OpI Framework in Excel can be created using the configuration variables available in the Framework, keeping things coordinated with other Queries, with SQL and embedded QUERYs, with Linked Servers, OPENROWSET, and source table names among many examples. M-Strings are chosen for use in Power Query Queries in the tblConnectionConfig Table, for LinkTypes webQuery and mppQuery, and select among Provider strings in the Configured-String Editor source table tblConnection. M-Strings are set into the WorkbookQuery.Formula property. In the Excel PowerQuery User Interface, these strings could be incorporated into QUERYs using the PowerQuery Advanced Editor. A Query created in the Power Query UI can be updated from the Power OpI Framework, and vice-versa. Such a Query can be set to "Skip" in the Power OpI tblConnectionConfig to avoid updates. Creating PowerQuery Query to JOIN using M Formula Language:
Reference and Training on M: Written documentation of M is comparatively scarce. Much available documentaion centers on the Power Query UI and DAX. There is quite a bit of material on YouTube though, although in usual fashion info on M is scattered across many of them.
M executes operations from the local computer Client, including Queries with JOINs. Query sourcecode is visible to the local Client computer which could be a disadvantage for sustaining and security as compared with Stored Procedures in other environments. Although the Query is initiated from the Client, Power Query delegates work to the server using a method called "Query Folding". Described in the Learning references above, the M Language constructs use a "LET ... IN..." structure. In each example below, a LET clause performs a sequence of table sourcing operations to get access to the five example Tables. Then a series of JOINs of two Tables at a time, followed by adding Columns to calculate aggregated Quantity and Cost. The IN clause states which of the Variables is output by the M-String. The Source methods differ among these examples, but all use the same methods for JOINs, Columns and Output. M-Strings are shown above in the Configured-String Editor section that push a SQL Query for execution by SQL Server or by MS Access, using either SQL embedded in the QUERY or invoking a Stored Procedure within the database environment itself, placed there by a CREATE operation as discussed just above. This is an easy introduction to using PowerQuery primarily as a source data access vehicle. M is much more powerful than just EXEC'ing a SQL SP, and can create, traverse, JOIN, and much more, over complex data structures including the Table and Record structures illustrated in the examples below that are useful for structured data problems similar to the Project Management Logistics problem described on this website. Chris Webb's video Power Query Beyond the Basics illustrates broader capabilities. JOIN Excel Tables from a separate Workbook, could of course be directed to Tables in the local Workbook. This M code replicates function the JOIN shown in several illustrations on this site, JOINing Tables within Excel using PowerQuery without use of a Database environment. Table_Join let Source=Excel.Workbook(File.Contents("C:\PowerOpI\Merge\ToolSource.xlsm"), null, true), xlAlloc=Source{[Item="tblAlloc",Kind="Table"]}[Data], xlBOM=Source{[Item="tblBOM",Kind="Table"]}[Data], xlDates=Source{[Item="tblDates",Kind="Table"]}[Data], xlBuilds=Source{[Item="tblBuilds",Kind="Table"]}[Data], xlElements=Source{[Item="tblElements",Kind="Table"]}[Data], AB=Table.Join(xlAlloc, {"akAllocRevKey"},xlBOM,{"bmBOMItemKeyIn"},JoinKind.LeftOuter), ABD=Table.Join(AB, {"bmBOMItemKeyOut"}, xlDates, {"daSchedItemRevKey"},JoinKind.LeftOuter), ABDB=Table.Join(ABD,{"daSchedItemRevKey"},xlBuilds,{"bdBuildRevKey"},JoinKind.LeftOuter), ABDBE1=Table.Join(ABDB,{"bmBOMItemKey"},xlElements,{"emElementItemKey"},JoinKind.LeftOuter), ABDBE2=Table.AddColumn(ABDBE1,"QtyExt", each [akQty]*[bmSubQty]), ABDBE=Table.AddColumn(ABDBE2,"CostExt", each [QtyExt]*[bmSubCost]) in ABDBE JOIN Tables from Excel, picked up from a Linked Server in SQL Server. Linked Servers are a SQL Server concept so SQL is used to navigate them from PowerQuery. The JOIN is executed in PowerQuery, not in SQL Server. For comparison, this example again replicates the Query commonly used as an example on this website. Make sure Excel doesn't auto-correct the "..." construct by a single ellipsis character! Also note, line breaks have been inserted here to make the display fit on the website pagewidth; in practice, linefeeds can occur within SQL statements and Connection String but not within the M content. PQJoin2#OleDb_DataSource let xlAlloc=OleDb.DataSource("Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", [Query="SELECT * FROM LS_Proto...rngAlloc"]), xlBOM=OleDb.DataSource("Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", [Query="SELECT * FROM LS_Proto...rngBOM"]), xlDates=OleDb.DataSource("Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", [Query="SELECT * FROM LS_Proto...rngDates"]), xlBuilds=OleDb.DataSource("Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", [Query="SELECT * FROM LS_Proto...rngBuilds"]), xlElements=OleDb.DataSource("Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", [Query="SELECT * FROM LS_Proto...rngElements"]), AB=Table.Join(xlAlloc, {"akAllocRevKey"},xlBOM,{"bmBOMItemKeyIn"},JoinKind.LeftOuter), ABD=Table.Join(AB, {"bmBOMItemKeyOut"}, xlDates, {"daSchedItemRevKey"},JoinKind.LeftOuter), ABDB=Table.Join(ABD,{"daSchedItemRevKey"},xlBuilds,{"bdBuildRevKey"},JoinKind.LeftOuter), ABDBE1=Table.Join(ABDB,{"bmBOMItemKey"},xlElements,{"emElementItemKey"},JoinKind.LeftOuter), ABDBE2=Table.AddColumn(ABDBE1,"QtyExt", each [akQty]*[bmSubQty]), ABDBE=Table.AddColumn(ABDBE2,"CostExt", each [QtyExt]*[bmSubCost]) in ABDBE JOIN Tables from Excel, picked up using OPENROWSET in SQL Server. Again, OPENROWSET is a SQL Server concept so SQL is used to navigate it from PowerQuery. This example again replicates the Query commonly used as an example on this website, for comparison. Again note, line breaks have been inserted here to make the display fit on the website pagewidth; in practice, linefeeds can occur within SQL statements and Connection String but not within the M content. PQJoinOPENROWSET let FileSource = "Provider=MSOLEDBSQL; initial catalog=ExcelProto; data source=TRILITHIUM\SQLEXPRESS", xlAlloc = OleDb.Query(FileSource,"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0; Database=C:\PowerOpI\Merge\ToolSource.xlsm;HDR=YES', rngAlloc)"), xlBOM = OleDb.Query(FileSource,"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0; Database=C:\PowerOpI\Merge\ToolSource.xlsm;HDR=YES', rngBOM)"), xlDates = OleDb.Query(FileSource,"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0; Database=C:\PowerOpI\Merge\ToolSource.xlsm;HDR=YES', rngDates)"), xlBuilds = OleDb.Query(FileSource,"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0; Database=C:\PowerOpI\Merge\ToolSource.xlsm;HDR=YES', rngBuilds)"), xlElements = OleDb.Query(FileSource,"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0; Database=C:\PowerOpI\Merge\ToolSource.xlsm;HDR=YES', rngElements)"), AB=Table.Join(xlAlloc, {"akAllocRevKey"},xlBOM,{"bmBOMItemKeyIn"},JoinKind.LeftOuter), ABD=Table.Join(AB, {"bmBOMItemKeyOut"}, xlDates, {"daSchedItemRevKey"},JoinKind.LeftOuter), ABDB=Table.Join(ABD,{"daSchedItemRevKey"},xlBuilds,{"bdBuildRevKey"},JoinKind.LeftOuter), ABDBE1=Table.Join(ABDB,{"bmBOMItemKey"},xlElements,{"emElementItemKey"},JoinKind.LeftOuter), ABDBE2=Table.AddColumn(ABDBE1,"QtyExt", each [akQty]*[bmSubQty]), ABDBE=Table.AddColumn(ABDBE2,"CostExt", each [QtyExt]*[bmSubCost]) in ABDBE Once a PowerQuery is created, embedding one of these M-Strings, it is assigned to drive a Report such as a PivotTable, using the method for PowerQuery in the following Section covering Driving Pivot Tables. Notice that each of these QUERYs shows Table/Named Range names, and field names linking them, thereby exposing database structure; and one of the QUERYs shows fully-qualified file paths. Setup of a Linked Server would also expose paths. The text for each of these QUERYs, since it is executed in M at each Client endpoint, resides within each Client and can be made visible there. For the distributed-user Use Case I generally work within, it is my preference to "hide" File Paths, Table/Range Names, and linking Field names as much as possible from Client visibility. That can be accomplished to varying degree by using Linked Servers within the shared database environment, or even more by putting QUERY logic within Shared Procedures in the database environment, and having each Client make EXEC calls to those rather than executing QUERY logic locally. This is especially true for use of OPENQUERY. This improves security and system integrity by hiding structure and physical locations, and simultaneously simplifies maintenance - an update to a Shared Procedure in a common database environment is available immediately to all Clients wherever they may be, without making changes to each Client instance. Recall my suggestion that QUERYs, excepting Client EXEC QUERYs driving Client endpoint reports, be authored and managed in a "Control" instance of the PowerOpI Framework, used with one or more Data Source instances available to Source Data table owners, and with multiple distributed Client instances. In my view, Client-local QUERY execution with full visibliity of QUERY structures within a Client is best-suited to individual-user/Data-Analyst cases, than to a Distributed multi-user Program Management or similar case. Individuals within the Multi-User case may be exceptions, for example Finance management or Dashboard upkeep may make use of specialized Client QUERY configurations. A Note on Names used in SQL and PowerQuery M QueriesYou may need to pull data from an Excel Workbook that you do not control, and that does not provide Named Ranges or Shadow Ranges to expose the data to MS Access or to SQL Server. However, the Database Environment can see each full Worksheet within the Workbook, which it can interpret to provide Tables.
Driving Pivot Tables Pivot Charts, Tables, Load To... from Connections and QUERYsWhen a Connection or Query is created by VBA using tblConnectionConfig, it initally isn't connected to drive a PivotTable, PivotChart or Structured Table. The trick is to get it assigned to such a report, and in such a way that it can be subsequently updated by a Build or Update button operation.
Templates for Project Source Data tables and Dates, SQL Structures, and ReportingThe PowerOpI Framework file contains numerous examples that can be used as Templates to be applied to specific projects.
Excel Data Sources: Shadow Ranges, Worksheets, ACE, Linked Servers, OPENROWSETTo pull data from Excel into a database environment, you will use a Linked Table from MS Access, or Microsoft.ACE.OLEDB.xx.0 with SQL Server via a Linked Server, or via OPENROWSET. The database environment will have to be able to "see" the data in Excel, and can see Worksheets, and Named Ranges. Unfortunately, Excel Structured Tables, extremely useful for these applications, are not automatically visible. So here's some VBA that will create a "Shadow" named range that tracks the table and stays up-to-date. When you click in a table, such as when you add/update/delete data in it, the range will update. The range will take the name of the table, and prepend "rng" to the name. If the table is called "tblTableName", the range will be named "rngTableName". If the table is called "TableName" the range will be named "rngTableName". From MS Access, you can select the Shadow range by name when creating a Linked Table, and then use that Linked table in QUERYs. From SQL Server via a Linked Server, reference the Shadow range by using a four-part name identifying the Linked Server and range such as in LS_Proto...rngAlloc AS xlAlloc as shown in examples on this site. Then QUERYs can reference fields, such as "Element", as xlAlloc.Element. From SQL Server via OPENROWSET, reference the Shadow Range as in OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0;Database=C:\Users\XXXXXX\ToolSource.xlsm;HDR=YES',rngAlloc) AS xlAlloc as shown in examples on this site. You can reference a full Worksheet in MS Access by referencing its name when creating a Linked Table. You can reference a Worksheet from SQL Server as OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0;Database=C:\Users\XXXXXX\ToolSource.xlsm;HDR=YES',[WorksheetName$]) AS xlAlloc. When you reference as a full Worksheet via either MS Access or SQL Server, you are relying on the capability of the database to discern the data; and whatever is on that Worksheet will be treated as a single Table. Here is VBA to create Shadow ranges in Excel that track Structured Tables. Place this code in Excel VBA Module1: Public Sub TableUpdate(ByVal Target As Range) 'Copyright (c) 2015 Richard M. Bixler, All Rights Reserved. Do not delete this copyright notice. 'Keeps a range object in sync with table definition. 'Range object is updated each time the table is changed/selected. Dim tblTarget As ListObject, thisListObject As ListObject Dim cellChanged As Range, rngTarget As Range Dim strTableName As String, strRangeName As String, strTblRangePrefix As String If Target Is Nothing Then Exit Sub Set cellChanged = Target strTblRangePrefix = "rng" For Each thisListObject In cellChanged.Worksheet.ListObjects If Not Intersect(cellChanged, thisListObject.Range) Is Nothing Then Set tblTarget = thisListObject strTableName = tblTarget.Name strRangeName = strTableName If Left(strRangeName, 3) = "tbl" Then _ strRangeName = Right(strRangeName, Len(strRangeName) - 3) strRangeName = strTblRangePrefix & strRangeName Set rngTarget = tblTarget.Range rngTarget.Name = strRangeName Exit For End If Next thisListObject End Sub Then place the following VBA in the code page behind each Worksheet that contains a Structured Table for which a tracking Shadow range is needed: Private Sub Worksheet_SelectionChange(ByVal Target As Range) Module1.TableUpdate Target:=Target End Sub When a click occurs within a Structured Table, the Selection_Change event catcher calls Module1.TableUpdate with the range of the cell clicked. Then Module1.TableUpdate determines which Table was clicked, and updates and names the Shadow Range to match the Table. Handling Dates: MS Project, mppImport, mppQuery, Date Build/Update/RefreshThe cleanest way to keep Dates and Logistics aligned, is to export dates from Microsoft Project, and to tie specific tasks and their dates, to Logistics data handled through PowerOpI database solutions and reports. In the Framework described on this website, a field is added to the MS Project Tasks page, called TaskMilestone containing a Key to uniquely identify tasks with their associated dates contained within a task record. The form of the TaskMilestone Key is ElementName:ElementRevision:Task - for example "Europa:P0-A:Fabout" (with the colon characters but without the quotes). Or you could create your own identifier scheme if you prefer. So the dates and other data in that particular task associated with this Key value can be identified. You must ensure that that Key value identifies a unique Task record. You would create a similar Key within Excel Source Data tables, so you can JOIN the task dates with material or expense logistics items. Code to export the date file from MS Project is in Code Samples 01. The file exported is in an .xlsx file named for the MS Project file exported from, and contains a worksheet referred to as [Task_Table1$] when it is imported into Excel via SQL Server. An example of such an import is included in the Framework Excel file. Connection Record templates are shown in the Framework file, for import via a Connection (LinkType "mppImport") and via PowerQuery (LinkType "mppQuery"). Build/Update and Refresh of Connection records of LinkTypes mppImport and mppQuery are initiated by buttons for Build+Update DATE Links and Refresh DATES. Function of those Buttons for DATES is identical to the function of the Build+Update DATA Links and Refresh DATA buttons. The Buttons for DATA and for DATES are separated so that Dates can be updated separately from Data. First, work on Dates is frequently handled separately from work on Materials and Expenses. They will come together ultimately, but you can work on the schedule and Dates while the logistics files are in use, and update the dates and add new materials when the schedule sequences, durations, dependencies and lead times are understood. Material updates usually occur much more frequently than date updates. Second, you need to refresh Dates before you refresh Data for materials et al, so that the materials logistics unambiguously are based on the dates you intend. For that reason, Dates and Data must be refreshed sequentially, not simultaneously. Dates are imported into Excel as a Table, to be used before JOIN operations. You import the mpp Worksheet as a whole, e.g. [Task_Table1$], so a Shadow Range for the mpp exported worksheet is not needed. Calculations using these dates may occur in other Source Data tables, such as to calculate PurchDate based on material lead time; PayDate based on purchase or delivery date, material considerations, and accounting rules; and dates are usually a factor in applying Capitalization accounting rules to BOM elements. Source data imported into Excel as a Structured Table, such as this date file, may have calculations added and they will dynamically expand or contract to fit to rows added or deleted on refresh if you place a "proto-record" row at the top of the table containing column formulas and blank or default data field values; of course provide a means to exclude that row from JOINs or Pivots - usually key field values calculated from blank data fields in this "proto-record" take care of this for you automatically. The PowerOpI Framework file shows an example. You will most likely use the Imported table as the basis for a tblDates to do date aliasing, and then use tblDates in JOINs. If you do wish to directly use the imported table in database JOINs, place the Table Update "SelectionChange" event-handler shown, onto the code page of the Worksheet Tab containing the import Table, and re-name the imported Table appropriately. A Shadow Range will be created for the imported Table and keep itself updated as the Table is refreshed to import the worksheet exported from MS Project, so the imported table will be visible to be used for JOINS in the database. As with all Excel Data Source tables, you use Microsoft.ACE.OLEDB.XX.0 with OPENROWSET or a Linked Server to access the table in SQL Server; and would create a Linked Table to use it in MS Access. Determining PurchDate, PayDate and Capital/Expense AccountingCorporate Finance is often not clear enough on accounting rules for project material, and the Program Manager needs to resolve several issues that depend on dates. There's often more clarity for production material than for development material with its more-situational dependencies. Resolution of these items will be handled in the tblDates and tblBOM tables discussed below.
Aliasing Dates for Dependent And Purchased ItemsWithout a doubt, the project GANTT plan in Microsoft Project is planning and tracking all the major project items - elements in development including design work, process steps, integration; and tasks among many groups - Engineering, OS, Management, Diags, Supply Chain, Operations, CM, Marketing, Support... But there are probably many, many more items needed by the project - purchased items. Where a development project may have a dozen or two key items that it is concentrating effort on, there are probably dozens if not hundreds of items needed for use with those items to build, test, integrate, certify, validate... This could include items like cables (!! - nemesis of many efforts), power supplies, racks, power strips, NICs, Storage disks and Flash, power cords, rack brackets... including some items that may be used internally but never become part of the released end-product. Need-by dates for these items are usually tied to start of tasks like "Build", "Integrate" or "Test" steps. Each item may have many part number variants (cables! length, connector, speed) or capacity, pins, frequency; and all have different lead times... It's inconvenient to put so many of these items into a GANTT, where they add little value. You're going to work backwards from Need Date to determine PurchDate so material availability won't become a gating item in the GANTT. The few items that are, you can put in the GANTT; most of them won't so work backward from Need Date: PurchDate is Need Date minus Lead Time; and plan them in Excel. Also, we want to limit editing of the GANTT to Program Management, but to allow Supply Chain personnel to update Excel Source Data tables to cover purchased material. So a mechanism is described here which allows purchased items to be scheduled in the Excel tool where logistics are managed and where those dates are really useful. We're already importing the MS Project date export, which provides the need-by task dates. Purchase lead time and payment terms can be captured for each item into the tblElement table containing such info for each item built or used in the project - quotes usually include purchase price, purchase lead time, and payment terms like net 30 ARO, or Payment at time of PO. With that information we can calculate item PurchDates and PayDates. These would be determined for purchased items, and may be applied similarly to key material for items planned in the GANTT (like new silicon parts on limited allocation; crystals, connectors), and also PayDates for both purchased and GANTT-planned items. The project GANTT plan is imported in the Framework example, into Structured Table tblP0001_Rollup, and includes tasks with their Start_Date and Finish_Dates. Tasks that are going to be JOINed with Logistics data in other tables, have had Keys assigned that are like Europa:P2-B:AtEng and those Keys are included in the imported tblP0001_Rollup. A new table, tblDates, is created to act as a "shell" to fetch dates from tblP0001_Rollup and to create dates in Excel that are not included in the GANTT. This allows us to create records to contain dates for tasks not in the GANTT, and for items not scheduled in the GANTT at all, tying those created dates to relevant dates that ARE included in the GANTT. This is called "aliasing". Here's an piece of a tblDates: Aliasing Dates in tblDates. The red boxes illustrate the fields used to create the Key values. The purple boxes illustrate how date aliasing is accomplished. Each record identifies an item daElement, its Revision daRev and a task daTask which together are associated with dates daStart_Date and daFinish_Date along with fields that can be calculated or looked up from them, determining Fiscal Year daFY, Fiscal Quarter daFQ, Lead Time daLeadTime and parameters determining Expense and Capitalization. From the fields identifying daElement, daRev and daTask, Key fields daSchedItemRevKey and daSchedItemRevTaskKey are created, to allow JOINing of these Date records with Logistics records from other tables tblAlloc, tblBOM, and tblElements. The Keys allow JOINing of all date records element-rev by element-rev, or retrieval of dates for individual tasks. The dates themselves are looked up from tblP0001_Rollup like this:
Notice that this expression uses fields daUseItem, daRev and daUseMilestone instead of daElement, daRev and daTask upon which the lookup Keys are based. This allows us to alias in dates from a different Element:Rev:Task than the Keys address, and in particular to substitute dates or to create dates for items or tasks that are not available in tblP0001_Rollup. (In this example we assume we are aliasing dates from the same Rev of the element; otherwise we could have created a field "daUseRev" to further specify a aliasing from a different Rev.) For items and tasks that are available in tblP0001_Rollup and that we want to use as scheduled in that table, the values of the "Use" fields are set to specify date fetch without aliasing:
But in records for which we want to return PayDate or PurchDate (or any other task we wish to alias, or to create a Task that isn't available in tblP0001_Rollup), the values of the "Use" fields are set to specify the Element, Rev and Task for which dates should be returned. This shown by the purple boxes in the image. The next image shows an example for which we created tasks and dates for a purchased item, Cable-QSFP28-3M, that is not scheduled in the GANTT at all. The boxes show that the logic is identical, but field values control where the dates are taken from. In this case, dates of "PurchDate" and "PayDate" are aliased to fields specifying Europa:P2-A:AtEng. The "daStart_Date" field also subtracts the value of field "daLeadTime", picked up from tblElements, to allow for material purchase Lead Time. Note that the value of "daStart_Date" is about a month earlier than the date for "AtEng", consistent with subtracting the 28-day "daLeadTime": Picking up StartDate, as Need Date for an item, from aliased task and, in red, subtracting LeadTime, from tblElements, to determine PurchDate:
where field daLeadTime is retrieved from tblElements as:
Tips and TroubleshootingShowstoppers are highlighted.
Paths that Work to Data Source Files on a Fileshare ServerA Path to a file used in a QUERY is a fully-qualified path, including the Server's fileshare name (like \\DBSOURCE\ or \\LOCALHOST\) if it's accessed over the local fileshare network, or the directory root (like C:\) if it's local to the server running the QUERY. Following the network root or directory root, the path through the filesystem, then followed by the data source filename. For example:
A file accessed over the network must be in a directory on the fileshare server, that has been shared such that the QUERY can access it - by user name if using Windows credentials, or by group membership, or by loging name and password. Log in to the source server if needed, before executing the QUERY. Client and server firewalls must enable Windows File Sharing (SMB). If SQL Server is in the path, firewalls must be configured to enable that: Configure the Windows Firewall to Allow SQL Server Access on docs.microsoft.com. The data source Server may have several directories shared. To further the above examples, directories Level1 and Level2 could have been shared. In that case, the target data source file could be accessed as
Whether one of those paths in the example will work, is up to SMB filesharing and might not work consistently. I suggest trying the target address in the Windows File Explorer to verify its functionality. Paste the target address into the address box near the top of the File Explorer pane, and Enter. If the path entered contains the path but not the target filename, the directory will open for view in the File Explorer pane if the path entered is functional. If the path includes the target filename, then that file will open if the path is functional. You may have to try several path constructions to determine which will work; then the Common Configuration Variables can be adjusted to use that path, the Connections can be re-built and you can try the connection. SQL Server From Remote ClientsNotes on setup of SQL Server configuration and security to allow QUERYs from a client located on a computer separate from the computer hosting the SQL Server instance can be found at SQL Server Database Security. Support Reference Links
|