├── Images └── ExcelTemplate.png ├── LICENSE.txt ├── ModelAutoBuild ├── ModelAutoBuild.cs ├── ModelAutoBuild.xlsx └── ModelAutoBuild_Example.xlsx └── README.md /Images/ExcelTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-kovalsky/ModelAutoBuild/df177137e162276b58ac89959dedac7080b39c2f/Images/ExcelTemplate.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 m-kovalsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ModelAutoBuild/ModelAutoBuild.cs: -------------------------------------------------------------------------------- 1 | #r "System.IO" 2 | #r "Microsoft.Office.Interop.Excel" 3 | 4 | using System.IO; 5 | using Excel = Microsoft.Office.Interop.Excel; 6 | 7 | string fileName = @"C:\Desktop\ModelAutoBuild"; // Enter Model Auto Build Excel file 8 | string myWorkbook = fileName + ".xlsx"; 9 | var excelApp = new Excel.Application(); 10 | excelApp.Visible = false; 11 | excelApp.DisplayAlerts = false; 12 | Excel.Workbook wb = excelApp.Workbooks.Open(myWorkbook); 13 | 14 | string[] tabs = {"DataSources", "Tables", "MeasuresColumns", "Relationships", "Model", "Roles", "RLS", "Hierarchies"}; 15 | int tabCount = tabs.Count(); 16 | string[] tableTypes = {"FACT","DIM","BRIDGE","SEC","META"}; 17 | string[] objectTypes = {"column","measure"}; 18 | bool autoGenRel = false; 19 | 20 | for (int i=0; i a.Name == tableName).ToList()) 86 | { 87 | t.Delete(); 88 | } 89 | 90 | if (!Model.DataSources.Any(a => a.Name == dataSource)) 91 | { 92 | Error("Unable to create the '"+tableName+"' as it is assigned an invalid data source: '"+dataSource+"'."); 93 | return; 94 | } 95 | 96 | if (!tableTypes.Contains(tableType)) 97 | { 98 | Error("Unable to create the '"+tableName+"' as it is assigned an invalid table type: '"+tableType+"'."); 99 | return; 100 | } 101 | 102 | var obj = Model.AddTable(tableName); 103 | obj.Partitions[0].DataSource = Model.DataSources[dataSource]; 104 | obj.Partitions[0].Query = "SELECT * FROM [" + schema + "].[" + tableType + "_" + tableName + "]"; 105 | obj.Description = desc; 106 | 107 | if (dc == "date") 108 | { 109 | obj.DataCategory = "Time"; 110 | } 111 | 112 | if (m.StartsWith("direct") || m == "dq") 113 | { 114 | obj.Partitions[0].Mode = ModeType.DirectQuery; 115 | } 116 | } 117 | 118 | // Measures & columns 119 | else if (i==2) 120 | { 121 | string objectName = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 122 | string tableName = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString(); 123 | string objectType = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString().ToLower(); 124 | string sourceColumn = (string)(ws.Cells[r,4] as Excel.Range).Text.ToString(); 125 | string dt = (string)(ws.Cells[r,5] as Excel.Range).Text.ToString().ToLower(); 126 | string expr = (string)(ws.Cells[r,6] as Excel.Range).Text.ToString(); 127 | string hide = (string)(ws.Cells[r,7] as Excel.Range).Text.ToString().ToLower(); 128 | string fmt = (string)(ws.Cells[r,8] as Excel.Range).Text.ToString(); 129 | string key = (string)(ws.Cells[r,9] as Excel.Range).Text.ToString().ToLower(); 130 | string summ = (string)(ws.Cells[r,10] as Excel.Range).Text.ToString().ToLower(); 131 | string displayFolder = (string)(ws.Cells[r,11] as Excel.Range).Text.ToString(); 132 | string dataCategory = (string)(ws.Cells[r,12] as Excel.Range).Text.ToString(); 133 | string sortByCol = (string)(ws.Cells[r,13] as Excel.Range).Text.ToString(); 134 | string desc = (string)(ws.Cells[r,14] as Excel.Range).Text.ToString(); 135 | 136 | if (!Model.Tables.Any(a => a.Name == tableName)) 137 | { 138 | Error("'" + tableName + "' is not a valid table in this model. Attempt for '" + objectType + "' " + objectName); 139 | return; 140 | } 141 | 142 | if (!objectTypes.Contains(objectType)) 143 | { 144 | Error("Error generating a measure/column. Object type must be 'Column' or 'Measure'."); 145 | return; 146 | } 147 | 148 | // Add column properties 149 | if (objectType == "column") 150 | { 151 | if (String.IsNullOrEmpty(expr)) 152 | { 153 | var obj = Model.Tables[tableName].AddDataColumn(objectName); 154 | obj.SourceColumn = sourceColumn; 155 | } 156 | else 157 | { 158 | var obj = Model.Tables[tableName].AddCalculatedColumn(objectName); 159 | obj.Expression = expr; 160 | } 161 | 162 | var col = Model.Tables[tableName].Columns[objectName]; 163 | string colName = col.Name; 164 | 165 | if (dt.StartsWith("int")) 166 | { 167 | col.DataType = DataType.Int64; 168 | } 169 | else if (dt == "string") 170 | { 171 | col.DataType = DataType.String; 172 | } 173 | else if (dt == "datetime") 174 | { 175 | col.DataType = DataType.DateTime; 176 | } 177 | else if (dt == "double") 178 | { 179 | col.DataType = DataType.Double; 180 | } 181 | if (hide.StartsWith("y")) 182 | { 183 | col.IsHidden = true; 184 | } 185 | if (key.StartsWith("y")) 186 | { 187 | col.IsKey = true; 188 | } 189 | if (summ == "none") 190 | { 191 | col.SummarizeBy = AggregateFunction.None; 192 | } 193 | 194 | col.DisplayFolder = displayFolder; 195 | col.DataCategory = dataCategory; 196 | col.Description = desc; 197 | 198 | if (sortByCol != "") 199 | { 200 | if (!Model.Tables[tableName].Columns.Any(a => a.Name == sortByCol)) 201 | { 202 | Error("The column '"+colName+"' cannot be sorted by the column '"+sortByCol+"' as the sort-by column does not exist in the '"+tableName+"' table."); 203 | return; 204 | } 205 | col.SortByColumn = Model.Tables[tableName].Columns[sortByCol]; 206 | } 207 | if (fmt == "Whole Number") 208 | { 209 | col.FormatString = "#,0"; 210 | } 211 | else if (fmt == "Percentage") 212 | { 213 | col.FormatString = "#,0.0%;-#,0.0%;#,0.0%"; 214 | } 215 | else if (fmt == "Month Year") 216 | { 217 | col.FormatString = "mmmm YYYY"; 218 | } 219 | else if (fmt == "Currency") 220 | { 221 | col.FormatString = "$#,0;$#,0;$#,0"; 222 | } 223 | else if (fmt == "Decimal") 224 | { 225 | col.FormatString = "#,0.0"; 226 | } 227 | } 228 | 229 | // Add measure properties 230 | if (objectType == "measure") 231 | { 232 | var obj = Model.Tables[tableName].AddMeasure(objectName); 233 | obj.Expression = expr; 234 | obj.DisplayFolder = displayFolder; 235 | obj.Description = desc; 236 | 237 | if (hide.StartsWith("y")) 238 | { 239 | obj.IsHidden = true; 240 | } 241 | if (fmt == "Whole Number") 242 | { 243 | obj.FormatString = "#,0"; 244 | } 245 | else if (fmt == "Percentage") 246 | { 247 | obj.FormatString = "#,0.0%;-#,0.0%;#,0.0%"; 248 | } 249 | else if (fmt == "Currency") 250 | { 251 | obj.FormatString = "$#,0;$#,0;$#,0"; 252 | } 253 | else if (fmt == "Month Year") 254 | { 255 | obj.FormatString = "mmmm YYYY"; 256 | } 257 | } 258 | } 259 | 260 | // Relationships 261 | else if (i==3) 262 | { 263 | string fromTable = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 264 | string fromColumn = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString(); 265 | string toTable = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString(); 266 | string toColumn = (string)(ws.Cells[r,4] as Excel.Range).Text.ToString(); 267 | string act = (string)(ws.Cells[r,5] as Excel.Range).Text.ToString().ToLower(); 268 | string cfb = (string)(ws.Cells[r,6] as Excel.Range).Text.ToString().ToLower(); 269 | 270 | if (!Model.Tables.Any(a => a.Name == fromTable)) 271 | { 272 | Error("The relationship cannot be created because the table '"+fromTable+"' does not exist."); 273 | return; 274 | } 275 | 276 | if (!Model.Tables.Any(a => a.Name == toTable)) 277 | { 278 | Error("The relationship cannot be created because the table '"+toTable+"' does not exist."); 279 | return; 280 | } 281 | 282 | if (!Model.Tables[fromTable].Columns.Any(a => a.Name == fromColumn)) 283 | { 284 | Error("The relationship cannot be created because the column '"+fromColumn+"' does not exist in the "+fromTable+"."); 285 | return; 286 | } 287 | 288 | if (!Model.Tables[toTable].Columns.Any(a => a.Name == toColumn)) 289 | { 290 | Error("The relationship cannot be created because the column '"+toColumn+"' does not exist in the "+toTable+"."); 291 | return; 292 | } 293 | 294 | // This assumes that the model does not already contain a measure with the same name (if it does, the new measure will get a numeric suffix): 295 | var obj = Model.AddRelationship(); 296 | obj.FromColumn = Model.Tables[fromTable].Columns[fromColumn]; 297 | obj.ToColumn = Model.Tables[toTable].Columns[toColumn]; 298 | 299 | if (act == "no") 300 | { 301 | obj.IsActive = false; 302 | } 303 | 304 | if (cfb == "bi") 305 | { 306 | obj.CrossFilteringBehavior = CrossFilteringBehavior.BothDirections; 307 | } 308 | } 309 | 310 | else if (i==4) 311 | { 312 | string name = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 313 | string m = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString().ToLower(); 314 | string prem = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString().ToLower(); 315 | 316 | if (name == "") 317 | { 318 | Error("Model name must be provided on the 'Model' tab."); 319 | return; 320 | } 321 | else 322 | { 323 | Model.Database.Name = name; 324 | Model.Database.ID = name; 325 | } 326 | 327 | // Update model mode 328 | if (m.StartsWith("direct") || m == "dq") 329 | { 330 | Model.DefaultMode = ModeType.DirectQuery; 331 | } 332 | 333 | // This enables overwriting deployments to Power BI Premium 334 | if (prem.StartsWith("y")) 335 | { 336 | Model.DefaultPowerBIDataSourceVersion = PowerBIDataSourceVersion.PowerBI_V3; 337 | Model.Database.CompatibilityMode = CompatibilityMode.PowerBI; 338 | Model.Database.CompatibilityLevel = 1560; 339 | } 340 | } 341 | 342 | // Roles 343 | else if (i==5) 344 | { 345 | string ro = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 346 | string rm = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString(); 347 | string mp = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString().ToLower(); 348 | 349 | // Add Roles and do not duplicate 350 | if (!Model.Roles.ToList().Any(x => x.Name == ro)) 351 | { 352 | var obj = Model.AddRole(ro); 353 | obj.RoleMembers = rm; 354 | 355 | if (mp == "read") 356 | { 357 | obj.ModelPermission = ModelPermission.Read; 358 | } 359 | else if (mp == "admin") 360 | { 361 | obj.ModelPermission = ModelPermission.Administrator; 362 | } 363 | else if (mp == "refresh") 364 | { 365 | obj.ModelPermission = ModelPermission.Refresh; 366 | } 367 | else if (mp == "readrefresh") 368 | { 369 | obj.ModelPermission = ModelPermission.ReadRefresh; 370 | } 371 | else if (mp == "none") 372 | { 373 | obj.ModelPermission = ModelPermission.None; 374 | } 375 | } 376 | } 377 | 378 | // RLS 379 | else if (i==6) 380 | { 381 | string ro = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 382 | string tableName = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString(); 383 | string rls = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString(); 384 | int rlsLength = rls.Length; 385 | 386 | if (!Model.Tables.Any(a => a.Name == tableName)) 387 | { 388 | Error("Row level security for the '"+ro+"' role cannot be created since the '"+tableName+"' table does not exist."); 389 | return; 390 | } 391 | 392 | if (!Model.Roles.Any(a => a.Name == ro)) 393 | { 394 | Error("Row level security for the '"+ro+"' role cannot be created since the role does not exist."); 395 | return; 396 | } 397 | 398 | if (rls[0] == '"') 399 | { 400 | rls = rls.Substring(1,rlsLength - 2); 401 | } 402 | 403 | rls = rls.Replace("\"\"","\""); 404 | 405 | Model.Tables[tableName].RowLevelSecurity[ro] = rls; 406 | } 407 | 408 | // Hierarchies 409 | else if (i==7) 410 | { 411 | try 412 | { 413 | string hierarchyName = (string)(ws.Cells[r,1] as Excel.Range).Text.ToString(); 414 | string tableName = (string)(ws.Cells[r,2] as Excel.Range).Text.ToString(); 415 | string columnName = (string)(ws.Cells[r,3] as Excel.Range).Text.ToString(); 416 | 417 | if (!Model.Tables.Any(a => a.Name == tableName)) 418 | { 419 | Error("The hierarchy '"+hierarchyName+"' cannot be created because the table '"+tableName+"' does not exist."); 420 | return; 421 | } 422 | 423 | if (!Model.Tables[tableName].Columns.Any(a => a.Name == columnName)) 424 | { 425 | Error("The hierarchy '"+hierarchyName+"' cannot be created because the column '"+columnName+"' does not exist in the '"+tableName+"' table."); 426 | return; 427 | } 428 | 429 | if (!Model.AllHierarchies.ToList().Any(x => x.Name == hierarchyName)) 430 | { 431 | // Add the hierarchy 432 | var obj = Model.Tables[tableName].AddHierarchy(hierarchyName); 433 | } 434 | 435 | // Add each level of each hierarchy 436 | Model.Tables[tableName].Hierarchies[hierarchyName].AddLevel(columnName); 437 | } 438 | catch 439 | { 440 | } 441 | } 442 | } 443 | } 444 | 445 | // Remove quotes from Expression and FormatString (format all DAX measures) 446 | foreach(var o in Model.AllMeasures.ToList()) 447 | { 448 | string expr = o.Expression; 449 | int exprLength = expr.Length; 450 | 451 | // Remove quotes from Expressions 452 | if (expr[0] == '"') 453 | { 454 | o.Expression = expr.Substring(1,exprLength - 2); 455 | } 456 | 457 | o.Expression = o.Expression.Replace("\"\"","\""); 458 | 459 | // Remove quotes from Format Strings 460 | o.FormatString = o.FormatString.Trim('"'); 461 | 462 | // Uncomment the line below if you want the DAX to be formatted. 463 | //FormatDax(o); 464 | } 465 | 466 | // Remove quotes from Expression and FormatString (format all DAX calculated columns) 467 | foreach(var o in Model.AllColumns.Where(a => a.Type.ToString() == "Calculated").ToList()) 468 | { 469 | var obj = (Model.Tables[o.Table.Name].Columns[o.Name] as CalculatedColumn); 470 | string expr = obj.Expression; 471 | int exprLength = expr.Length; 472 | 473 | // Remove quotes from Format Strings 474 | o.FormatString = o.FormatString.Trim('"'); 475 | 476 | // Remove quotes from Expressions 477 | if (expr[0] == '"') 478 | { 479 | obj.Expression = expr.Substring(1,exprLength - 2); 480 | } 481 | 482 | obj.Expression = obj.Expression.Replace("\"\"","\""); 483 | 484 | // Uncomment the line below if you want the DAX to be formatted. 485 | //FormatDax(obj); 486 | } 487 | 488 | // Replaces \n with new line (measures) 489 | foreach(var o in Model.AllMeasures.ToList()) 490 | { 491 | // Replaces \n with new line 492 | o.Expression = o.Expression.Replace("\\n", "\r\n"); 493 | } 494 | 495 | // Replaces \n with new line (calculated columns) 496 | foreach(var o in Model.AllColumns.Where(a => a.Type.ToString() == "Calculated").ToList()) 497 | { 498 | var obj = (Model.Tables[o.Table.Name].Columns[o.Name] as CalculatedColumn); 499 | obj.Expression = obj.Expression.Replace("\\n", "\r\n"); 500 | } 501 | 502 | // Auto-generate relationships 503 | if (autoGenRel) 504 | { 505 | string keySuffix = "Id"; 506 | // Loop through all rows but skip the first one: 507 | foreach (var tbl in Model.Tables.Where(a => a.Partitions[0].Query.Contains("FACT_"))) 508 | { 509 | foreach (var factColumn in tbl.Columns.Where(a => a.Name.EndsWith(keySuffix))) 510 | { 511 | var dim = Model.Tables.FirstOrDefault(t => factColumn.Name == t.Name + keySuffix); 512 | 513 | if (dim != null) 514 | { 515 | var dimColumn = dim.Columns.FirstOrDefault(c => factColumn.Name == c.Name); 516 | if (dimColumn != null) 517 | { 518 | var rel = Model.AddRelationship(); 519 | rel.FromColumn = factColumn; 520 | rel.ToColumn = dimColumn; 521 | } 522 | } 523 | } 524 | } 525 | } 526 | 527 | wb.Close(); 528 | excelApp.Quit(); 529 | System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp); -------------------------------------------------------------------------------- /ModelAutoBuild/ModelAutoBuild.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-kovalsky/ModelAutoBuild/df177137e162276b58ac89959dedac7080b39c2f/ModelAutoBuild/ModelAutoBuild.xlsx -------------------------------------------------------------------------------- /ModelAutoBuild/ModelAutoBuild_Example.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-kovalsky/ModelAutoBuild/df177137e162276b58ac89959dedac7080b39c2f/ModelAutoBuild/ModelAutoBuild_Example.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Model Auto Build](https://www.elegantbi.com/post/modelautobuild "Model Auto Build") 2 | 3 | Model Auto Build is a framework that dynamically creates a tabular model based on an Excel template. This framework is compatible for all destinations of tabular models - [SQL Server Analysis Services](https://docs.microsoft.com/analysis-services/ssas-overview?view=asallproducts-allversions "SSAS"), [Azure Analysis Services](https://azure.microsoft.com/services/analysis-services/ "Azure AS") and [Power BI Premium](https://powerbi.microsoft.com/power-bi-premium/ "Power BI Premium") (using the [XMLA R/W endpoint](https://docs.microsoft.com/power-bi/admin/service-premium-connect-tools "XMLA R/W endpoint")). This framework is also viable for both Import and Direct Query models. 4 | 5 | ![](https://github.com/m-kovalsky/ModelAutoBuild/blob/master/Images/ExcelTemplate.png) 6 | 7 | ## Purpose 8 | 9 | To provide a framework for business stakeholders and developers when initially outlining a model. When completed, the Excel template serves as a blueprint for the tabular model - akin to a blueprint for designing a building. 10 | 11 | This framework speeds up the development time of the model once the blueprint has been laid out. Development time can be spent on more advanced tasks such as solving DAX challenges or complex business logic requirements. 12 | 13 | Lastly, many people who are new to Power BI are more familiar with Excel. Since the framework is based in Excel it provides a familiar environment for such folks. 14 | 15 | ## Instructions 16 | 17 | 1.) Download the following files from the ModelAutoBuild folder and save them to a single folder on your computer. 18 | 19 | ModelAutoBuild.xlsx 20 | ModelAutoBuild.cs 21 | ModelAutoBuild_Example.xlsx (this file shows an example of a properly completed ModelAutoBuild.xlsx file) 22 | 23 | 2.) Open the ModelAutoBuild.xlsx file. 24 | 25 | 3.) Populate the columns in each of the tabs, following the instructions within the notes shown on the header rows. Close the Excel file when finished. 26 | 27 | 4.) Open [Tabular Editor](https://tabulareditor.com/ "Tabular Editor") and create a new model (File -> New Model). 28 | 29 | 5.) Paste the ModelAutoBuild.cs into the [Advanced Scripting](https://docs.tabulareditor.com/Advanced-Scripting.html#working-with-the-model-object "Advanced Scripting") window within Tabular Editor. 30 | 31 | 6.) Update the fileName parameter (on the 7th line of code) to be the location and file name of your saved ModelAutoBuild.xlsx file (see the example below). 32 | 33 | ```C# 34 | string fileName = @"C:\Desktop\ModelAutoBuild"; 35 | ``` 36 | 37 | 7.) Click the 'Play' button (or press F5). 38 | 39 | After completing Step 7, your model has been created within Tabular Editor. 40 | 41 | If you want to deploy the model to [SQL Server Analysis Services](https://docs.microsoft.com/analysis-services/ssas-overview?view=asallproducts-allversions "SSAS") or [Azure Analysis Services](https://azure.microsoft.com/services/analysis-services/ "Azure AS"), view Tabular Editor's [Command Line Options](https://github.com/otykier/TabularEditor/wiki/Command-line-Options "Command Line Options"). 42 | 43 | If you want to deploy the model to [Power BI Premium](https://powerbi.microsoft.com/power-bi-premium/ "Power BI Premium"), view the instructions on this [post](https://github.com/TabularEditor/tabulareditor.github.io/blob/master/_posts/2020-06-02-PBI-SP-Access.md "post"). 44 | 45 | ## Additional Notes 46 | 47 | * It is not necessary to fill in all the details of the model. For example, the Expression (DAX) and other such elements may be created afterwards. The goal of this framework is not to create a completed model per say but to quickly and intelligently build the foundation. 48 | 49 | * If you want a column to be a calculated column, simply add in the DAX in the Expression column. Columns that have DAX expressions will automatically become calculated columns. If there is no expression they will default to a data column (where you must enter a source column). Note of caution: try your best to avoid calculated columns. If in doubt, view Best Practice #6 within this post: https://www.elegantbi.com/post/top10bestpractices. 50 | 51 | * The partition queries generated by this framework are in the following format (example below is of a fact table). This is a best practice and ensures no logic is housed within the partition query. 52 | 53 | ```SQL 54 | SELECT * FROM [SchemaName].[FACT_TableName] 55 | ``` 56 | 57 | ## Requirements 58 | 59 | * [Tabular Editor](https://tabulareditor.com/ "Tabular Editor") version 2.13.0 or higher 60 | 61 | 62 | ## Version History 63 | 64 | * 2021-05-27 [Version 1.4.2](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.4.2) released 65 | * 2021-04-30 [Version 1.4.1](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.4.1) released 66 | * 2021-04-14 [Version 1.4.0](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.4.0) released (complete code overhaul; simplified script to be executed in Tabular Editor and pull directly from Excel) 67 | * 2020-07-06 [Version 1.3.0](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.3.0) released (added support for Hierarchies) 68 | * 2020-06-24 [Version 1.2.0](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.2.0) released (added support for Calculated Columns) 69 | * 2020-06-16 [Version 1.1.0](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.1.0) released (added Roles and Row Level Security) 70 | * 2020-06-11 [Version 1.0.0](https://github.com/m-kovalsky/ModelAutoBuild/releases/tag/1.0.0) released on GitHub.com 71 | --------------------------------------------------------------------------------