This commit is contained in:
Christian Wade 2021-11-19 21:44:15 -08:00
commit 98b28eb315
5 changed files with 852 additions and 19 deletions

View File

@ -17,7 +17,7 @@
"Description": "To speed up processing time and conserve memory after processing, attribute hierarchies should not be built for columns that are never used for slicing by MDX clients. In other words, all hidden columns that are not used as a Sort By Column or referenced in user hierarchies should have their IsAvailableInMdx property set to false.\r\nReference: https://blog.crossjoin.co.uk/2018/07/02/isavailableinmdx-ssas-tabular/",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "IsAvailableInMDX\r\nand\r\n\n(IsHidden or Table.IsHidden)\r\nand\r\n\nnot UsedInSortBy.Any() \r\nand\r\n\nnot UsedInHierarchies.Any()",
"Expression": "IsAvailableInMDX\r\nand\r\n\n(IsHidden or Table.IsHidden)\r\nand\r\n\nnot UsedInSortBy.Any() \r\nand\r\n\nnot UsedInHierarchies.Any()\r\nand\r\nnot UsedInVariations.Any()",
"FixExpression": "IsAvailableInMDX = false",
"CompatibilityLevel": 1200
},
@ -38,7 +38,7 @@
"Description": "It is best to avoid lengthy text columns. This is especially true if the column has many unique values. These types of columns can cause longer processing times, bloated model sizes, as well as slower user queries. Long length is defined as more than 100 characters.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Convert.ToInt32(GetAnnotation(\"LongLengthRowCount\")) > 500000",
"Expression": "Convert.ToInt64(GetAnnotation(\"LongLengthRowCount\")) > 500000",
"CompatibilityLevel": 1200
},
{
@ -68,7 +68,7 @@
"Description": "Calculated columns do not compress as well as data columns and may cause longer processing times. As such, calculated columns should be avoided if possible. One scenario where they may be easier to avoid is if they use the RELATED function.\r\nReference: https://www.sqlbi.com/articles/storage-differences-between-calculated-columns-and-calculated-tables/",
"Severity": 2,
"Scope": "CalculatedColumn",
"Expression": "RegEx.IsMatch(Expression.Replace(\" \",\"\"),\"\\W+(?i)RELATED\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"^(?i)RELATED\\(\")",
"Expression": "RegEx.IsMatch(Expression,\"(?i)RELATED\\s*\\(\")",
"CompatibilityLevel": 1200
},
{
@ -128,7 +128,7 @@
"Description": "Try to simplify the DAX used for row level security. Usage of the functions within this rule can likely be offloaded to the upstream systems (data warehouse).",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "RowLevelSecurity.Any(\nRegEx.IsMatch(it.Replace(\" \",\"\"),\"\\W+(?i)RIGHT\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"^(?i)RIGHT\\(\"))\r\nor\r\nRowLevelSecurity.Any(\nRegEx.IsMatch(it.Replace(\" \",\"\"),\"\\W+(?i)LEFT\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"^(?i)LEFT\\(\"))\r\nor\r\nRowLevelSecurity.Any(\nRegEx.IsMatch(it.Replace(\" \",\"\"),\"\\W+(?i)UPPER\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"^(?i)UPPER\\(\"))\r\nor\r\nRowLevelSecurity.Any(\nRegEx.IsMatch(it.Replace(\" \",\"\"),\"\\W+(?i)LOWER\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"^(?i)LOWER\\(\"))\r\nor\r\nRowLevelSecurity.Any(\nRegEx.IsMatch(it.Replace(\" \",\"\"),\"\\W+(?i)FIND\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"^(?i)FIND\\(\"))",
"Expression": "RowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)RIGHT\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)LEFT\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)UPPER\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)LOWER\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)FIND\\s*\\(\"))\r\n",
"CompatibilityLevel": 1200
},
{
@ -196,7 +196,7 @@
"Description": "At present, time intelligence functions are known to not perform as well when using Direct Query. If you are having performance issues, you may want to try alternative solutions such as adding columns in the fact table that show previous year or previous month data.",
"Severity": 2,
"Scope": "Measure, CalculationItem",
"Expression": "Model.Tables.Any(ObjectTypeName == \"Table (DirectQuery)\")\r\nand\r\n(\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"CLOSINGBALANCEMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"CLOSINGBALANCEQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"CLOSINGBALANCEYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATEADD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATESBETWEEN(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATESINPERIOD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATESMTD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATESQTD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"DATESYTD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"ENDOFMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"ENDOFQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"ENDOFYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"FIRSTDATE(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"FIRSTNONBLANK(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"FIRSTNONBLANKVALUE(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"LASTDATE(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"LASTNONBLANK(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"LASTNONBLANKVALUE(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"NEXTDAY(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"NEXTMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"NEXTQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"NEXTYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"OPENINGBALANCEMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"OPENINGBALANCEQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"OPENINGBALANCEYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"PARALLELPERIOD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"PREVIOUSDAY(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"PREVIOUSMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"PREVIOUSQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"PREVIOUSYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"SAMEPERIODLASTYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"STARTOFMONTH(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"STARTOFQUARTER(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"STARTOFYEAR(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"TOTALMTD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"TOTALQTD(\")\r\nor\r\nExpression.ToUpper().Replace(\" \",\"\").Contains(\"TOTALYTD(\")\r\n)",
"Expression": "Model.Tables.Any(ObjectTypeName == \"Table (DirectQuery)\")\r\nand\r\n(\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATEADD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESBETWEEN\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESINPERIOD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESMTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESQTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESYTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTDATE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTNONBLANK\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTNONBLANKVALUE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTDATE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTNONBLANK\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTNONBLANKVALUE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTDAY\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PARALLELPERIOD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSDAY\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"SAMEPERIODLASTYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALMTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALQTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALYTD\\s*\\(\")\r\n)",
"CompatibilityLevel": 1200
},
{
@ -220,13 +220,13 @@
"CompatibilityLevel": 1200
},
{
"ID": "CHECK_IF_DYNAMIC_ROW_LEVEL_SECURITY_(RLS)__IS_NECESSARY",
"Name": "[Performance] Check if dynamic row level security (RLS) is necessary",
"ID": "CHECK_IF_DYNAMIC_ROW_LEVEL_SECURITY_(RLS)_IS_NECESSARY",
"Name": "[Performance] Check if dynamic row level security (RLS) is necessary",
"Category": "Performance",
"Description": "Usage of dynamic row level security (RLS) can add memory and performance overhead. Please research the pros/cons of using it.\r\nReference: https://docs.microsoft.com/en-us/power-bi/admin/service-admin-rls",
"Severity": 1,
"Scope": "Table, CalculatedTable",
"Expression": "RegEx.IsMatch(Expression.Replace(\" \",\"\"),\"\\W+(?i)USERNAME\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"^(?i)USERNAME\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"\\W+(?i)USERPRINCIPALNAME\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"^(?i)USERPRINCIPALNAME\\(\")",
"Expression": "RegEx.IsMatch(Expression,\"(?i)USERNAME\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"(?i)USERPRINCIPALNAME\\(\")",
"CompatibilityLevel": 1200
},
{
@ -235,7 +235,7 @@
"Category": "DAX Expressions",
"Description": "Using fully qualified column references makes it easier to distinguish between column and measure references, and also helps avoid certain errors. When referencing a column in DAX, first specify the table name, then specify the column name in square brackets.\r\nReference: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 3,
"Scope": "Measure, KPI, CalculationItem",
"Scope": "Measure, KPI, TablePermission, CalculationItem",
"Expression": "DependsOn.Any(Key.ObjectType = \"Column\" and Value.Any(not FullyQualified))",
"CompatibilityLevel": 1200
},
@ -348,6 +348,16 @@
"Expression": "string.IsNullOrWhiteSpace(Expression)",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_STRUCTURED_DATA_SOURCES_WITH_PROVIDER_PARTITIONS",
"Name": "[Error Prevention] Avoid structured data sources with provider partitions",
"Category": "Error Prevention",
"Description": "Power BI does not support provider (a.k.a. 'legacy') partitions which reference structured data sources. Partitions which reference structured data sources must use the M-language. Otherwise, 'provider' partitions must reference a 'provider' data source. This can be resolved by converting the structured data source into a provider data source (see 2nd reference link below).\r\n\r\nReference: https://docs.microsoft.com/power-bi/admin/service-premium-connect-tools#data-source-declaration\r\nReference: https://www.elegantbi.com/post/convertdatasources",
"Severity": 2,
"Scope": "Partition",
"Expression": "SourceType == \"Query\"\r\nand\r\nDataSource.Type == \"Structured\"",
"CompatibilityLevel": 1200
},
{
"ID": "UNNECESSARY_COLUMNS",
"Name": "[Maintenance] Remove unnecessary columns",
@ -355,7 +365,7 @@
"Description": "Hidden columns that are not referenced by any DAX expressions, relationships, hierarchy levels or Sort By-properties should be removed.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(IsHidden or Table.IsHidden)\n\nand ReferencedBy.Count = 0 \n\nand (not UsedInRelationships.Any())\n\nand (not UsedInSortBy.Any())\n\nand (not UsedInHierarchies.Any())\n\nand (not Table.RowLevelSecurity.Any(\n it <> null and \n it.IndexOf(\"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n))\n\nand (not Model.Roles.Any(RowLevelSecurity.Any(\n it <> null and \n (\n it.IndexOf(current.Table.Name + \"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0 or\n it.IndexOf(\"'\" + current.Table.Name + \"'[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n )\n)))",
"Expression": "(IsHidden or Table.IsHidden)\n\n\r\nand ReferencedBy.Count = 0\r\n\n\nand (not UsedInRelationships.Any())\n\n\r\nand (not UsedInSortBy.Any())\n\n\r\nand (not UsedInHierarchies.Any())\n\n\r\nand (not Table.RowLevelSecurity.Any(\nit <> null and it.IndexOf(\"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n))\n\n and (not Model.Roles.Any(RowLevelSecurity.Any(\nit <> null and \n(\nit.IndexOf(current.Table.Name + \"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0 or\n it.IndexOf(\"'\" + current.Table.Name + \"'[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n )\n)))",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
@ -366,10 +376,20 @@
"Description": "Hidden measures that are not referenced by any DAX expressions should be removed for maintainability",
"Severity": 2,
"Scope": "Measure",
"Expression": "(Table.IsHidden or IsHidden) and ReferencedBy.Count = 0",
"Expression": "(Table.IsHidden or IsHidden) \r\nand ReferencedBy.Count = 0",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "FIX_REFERENTIAL_INTEGRITY_VIOLATIONS",
"Name": "[Maintenance] Fix referential integrity violations",
"Category": "Maintenance",
"Description": "This rule highlights relationships which have referential integrity violations. This indicates that there are values in the table on the 'from' side of the relationship which do not exist in the table on the 'to' side of the relationship. Referential integrity violations will also produce the 'blank' member value in slicers. It is recommended to fix these issues by ensuring that the 'to' table's primary key column has all the values in the 'from' table's foreign key column.\r\n\r\nReference: https://blog.enterprisedna.co/vertipaq-analyzer-tutorial-relationships-referential-integrity/",
"Severity": 2,
"Scope": "Relationship",
"Expression": "Convert.ToInt64(GetAnnotation(\"Vertipaq_RIViolationInvalidRows\")) > 0",
"CompatibilityLevel": 1200
},
{
"ID": "REMOVE_DATA_SOURCES_NOT_REFERENCED_BY_ANY_PARTITIONS",
"Name": "[Maintenance] Remove data sources not referenced by any partitions",
@ -551,10 +571,10 @@
"ID": "ADD_DATA_CATEGORY_FOR_COLUMNS",
"Name": "[Formatting] Add data category for columns",
"Category": "Formatting",
"Description": "Add Data Category property for appropriate columns. \r\nReference: https://docs.microsoft.com/en-us/power-bi/transform-model/desktop-data-categorization",
"Description": "Add Data Category property for appropriate columns.\r\n\r\nReference: https://docs.microsoft.com/en-us/power-bi/transform-model/desktop-data-categorization",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Name == \"Country\" \r\nor \r\n\nName == \"Continent\"\n \r\nor ((\nName == \"Latitude\" \n or \nName == \"Longitude\") and (DataType == DataType.Decimal or DataType == DataType.Double))",
"Expression": "string.IsNullOrWhitespace(DataCategory)\r\nand\r\n(\r\n(\r\nName.ToLower().Contains(\"country\")\r\nor \r\n\nName.ToLower().Contains(\"continent\"\n)\r\nor\r\nName.ToLower().Contains(\"city\")\r\n)\r\nand DataType == \"String\"\r\n)\r\nor \r\n(\r\n(\nName.ToLower() == \"latitude\" \n or \nName.ToLower() == \"longitude\")\r\nand (DataType == DataType.Decimal or DataType == DataType.Double)\r\n)",
"CompatibilityLevel": 1200
},
{
@ -586,7 +606,7 @@
"Description": "It is a best practice to hide fact table columns that are used for aggregation in measures.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(ReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"COUNT(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"COUNTBLANK(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"SUM(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"AVERAGE(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"VALUES(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"DISTINCT(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"DISTINCTCOUNT(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"MIN(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"MAX(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"COUNTA(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"AVERAGEA(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"MAXA(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\nor\nReferencedBy.AllMeasures.Any(Expression.Replace(\" \",\"\").ToUpper().Contains(\"MINA(\"+current.DaxObjectFullName.Replace(\" \",\"\").ToUpper()))\n)\nand IsHidden == false\nand (DataType == \"Int64\" || DataType == \"Decimal\" || DataType == \"Double\")",
"Expression": "(\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNTBLANK\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)SUM\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)AVERAGE\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)VALUES\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)DISTINCT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)DISTINCTCOUNT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\n\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MIN\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\n\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MAX\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNTA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\n\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)AVERAGEA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MAXA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MINA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n)\r\n\nand IsHidden == false\r\n\nand (DataType == \"Int64\" || DataType == \"Decimal\" || DataType == \"Double\")",
"FixExpression": "IsHidden = true",
"CompatibilityLevel": 1200
},

View File

@ -0,0 +1,638 @@
[
{
"ID": "AVOID_FLOATING_POINT_DATA_TYPES",
"Name": "[Prestazioni] Non utilizzare tipi di dati in virgola mobile (flotating)",
"Category": "Prestazioni",
"Description": "Il tipo di dati a virgola mobile \"Double\" dovrebbe essere evitato, poiché può causare errori di arrotondamento imprevedibili e prestazioni ridotte in determinati scenari. Usare \"Int64\" o \"Decimale\" dove più appropriato (ma tieni presente che \"Decimale\" è limitato a 4 cifre dopo il segno decimale).",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "DataType = \"Double\"",
"FixExpression": "DataType = DataType.Decimal",
"CompatibilityLevel": 1200
},
{
"ID": "ISAVAILABLEINMDX_FALSE_NONATTRIBUTE_COLUMNS",
"Name": "[Prestazioni] Imposta IsAvailableInMdx a false su colonne che non sono attributi",
"Category": "Prestazioni",
"Description": "Per accelerare il tempo di elaborazione e conservare la memoria dopo l'elaborazione, le gerarchie di attributi non devono essere create per le colonne che non vengono mai utilizzate per lo slicing dai client MDX. In altre parole, tutte le colonne nascoste che non vengono utilizzate come Ordina per colonna o a cui viene fatto riferimento nelle gerarchie utente devono avere la proprietà IsAvailableInMdx impostata su false. Riferimento: https://blog.crossjoin.co.uk/2018/07/02/isavailableinmdx-ssas-tabular/",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "IsAvailableInMDX\r\nand\r\n\n(IsHidden or Table.IsHidden)\r\nand\r\n\nnot UsedInSortBy.Any() \r\nand\r\n\nnot UsedInHierarchies.Any()\r\nand\r\nnot UsedInVariations.Any()",
"FixExpression": "IsAvailableInMDX = false",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_BI-DIRECTIONAL_RELATIONSHIPS_AGAINST_HIGH-CARDINALITY_COLUMNS",
"Name": "[Prestazioni] Evitare relazioni bidirezionali utilizzando colonne ad alta cardinalità",
"Category": "Prestazioni",
"Description": "Per prestazioni ottimali, si consiglia di evitare l'uso di relazioni bidirezionali con colonne ad alta cardinalità. Per eseguire questa regola, devi prima eseguire lo script mostrato qui: https://www.elegantbi.com/post/vertipaqintabulareditor",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "UsedInRelationships.Any(CrossFilteringBehavior == CrossFilteringBehavior.BothDirections)\n\nand\n\nConvert.ToInt64(GetAnnotation(\"Vertipaq_Cardinality\")) > 100000",
"CompatibilityLevel": 1200
},
{
"ID": "REDUCE_USAGE_OF_LONG-LENGTH_COLUMNS_WITH_HIGH_CARDINALITY",
"Name": "[Prestazioni] Ridurre l'uso di colonne con stringhe lunghe e con elevata cardinalità",
"Category": "Prestazioni",
"Description": "È meglio evitare lunghe colonne di testo. Ciò è particolarmente vero se la colonna ha molti valori univoci. Questi tipi di colonne possono causare tempi di elaborazione più lunghi, dimensioni del modello eccessive e query utente più lente. La lunghezza lunga è definita come più di 100 caratteri.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Convert.ToInt64(GetAnnotation(\"LongLengthRowCount\")) > 500000",
"CompatibilityLevel": 1200
},
{
"ID": "SPLIT_DATE_AND_TIME",
"Name": "[Prestazioni] Dividi data e ora in colonne separate",
"Category": "Prestazioni",
"Description": "Questa regola trova le colonne datetime che hanno valori non a mezzanotte (00:00:00). Per massimizzare le prestazioni, l'elemento ora dovrebbe essere diviso dall'elemento data (oppure il componente ora dovrebbe essere arrotondato a mezzanotte in quanto ciò ridurrà la cardinalità della colonna). Riferimento: https://www.sqlbi.com/articles/separate-date-and-time-in-powerpivot-and-bism-tabular/",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Convert.ToInt32(GetAnnotation(\"DateTimeWithHourMinSec\")) > 0",
"CompatibilityLevel": 1200
},
{
"ID": "LARGE_TABLES_SHOULD_BE_PARTITIONED",
"Name": "[Prestazioni] Tabelle grandi dovrebbero partizionate",
"Category": "Prestazioni",
"Description": "Le tabelle di grandi dimensioni devono essere partizionate per ottimizzare l'elaborazione. Affinché questa regola venga eseguita correttamente, è necessario eseguire lo script mostrato qui: https://www.elegantbi.com/post/vertipaqintabulareditor",
"Severity": 2,
"Scope": "Table",
"Expression": "Convert.ToInt64(GetAnnotation(\"Vertipaq_RowCount\")) > 25000000\r\nand\r\nPartitions.Count = 1",
"CompatibilityLevel": 1200
},
{
"ID": "REDUCE_USAGE_OF_CALCULATED_COLUMNS_THAT_USE_THE_RELATED_FUNCTION",
"Name": "[Prestazioni] Ridurre l'utilizzo di colonne calcolate che usano la funzione RELATED",
"Category": "Prestazioni",
"Description": "Le colonne calcolate non si comprimono così come le colonne di dati native e possono causare tempi di elaborazione più lunghi. Pertanto, le colonne calcolate dovrebbero essere evitate se possibile. Uno scenario in cui potrebbero essere più facili da evitare è se utilizzano la funzione RELATED. Riferimento: https://www.sqlbi.com/articles/storage-differences-between-calculated-columns-and-calculated-tables/",
"Severity": 2,
"Scope": "CalculatedColumn",
"Expression": "RegEx.IsMatch(Expression,\"(?i)RELATED\\s*\\(\")",
"CompatibilityLevel": 1200
},
{
"ID": "SNOWFLAKE_SCHEMA_ARCHITECTURE",
"Name": "[Prestazioni] Considerare prima uno Star-Schema invece di un'architettura Snowflake",
"Category": "Prestazioni",
"Description": "In generale, uno schema a stella è l'architettura ottimale per i modelli tabulari. Stando così le cose, ci sono casi validi per utilizzare un approccio a fiocco di neve. Controlla il tuo modello e considera il passaggio a un'architettura con schema a stella. Riferimento: https://docs.microsoft.com/en-us/power-bi/guidance/star-schema",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "UsedInRelationships.Any(current.Name == FromTable.Name)\r\nand\r\nUsedInRelationships.Any(current.Name == ToTable.Name)",
"CompatibilityLevel": 1200
},
{
"ID": "MODEL_SHOULD_HAVE_A_DATE_TABLE",
"Name": "[Prestazioni] Il modello dovrebbe avere una tabella contrassegnate con la proprietà Date Table",
"Category": "Prestazioni",
"Description": "In generale, i modelli dovrebbero generalmente avere una tabella delle date. I modelli che non dispongono di una tabella delle date in genere non sfruttano funzionalità come l'intelligenza temporale o potrebbero non disporre di un'architettura adeguatamente strutturata.",
"Severity": 2,
"Scope": "Model",
"Expression": "Tables.Any(DataCategory == \"Time\" && Columns.Any(IsKey == true && DataType == \"DateTime\")) == false",
"CompatibilityLevel": 1200
},
{
"ID": "DATE/CALENDAR_TABLES_SHOULD_BE_MARKED_AS_A_DATE_TABLE",
"Name": "[Prestazioni] Le tabelle di tipo data/calendario dovrebbero essere contrassegnate come Date Table",
"Category": "Prestazioni",
"Description": "Questa regola cerca le tabelle che contengono le parole \"data\" o \"calendario\" poiché probabilmente dovrebbero essere contrassegnate come tabella di date. Riferimento: https://docs.microsoft.com/en-us/power-bi/transform-model/desktop-date-tables",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "(Name.ToUpper().Contains(\"DATE\") or Name.ToUpper().Contains(\"CALENDAR\"))\n\nand\n\n(\nDataCategory <> \"Time\"\n\nor\n\nColumns.Any(IsKey == true && DataType == \"DateTime\") == false\n)",
"CompatibilityLevel": 1200
},
{
"ID": "REMOVE_AUTO-DATE_TABLE",
"Name": "[Prestazioni] Rimuovi le tabelle automatiche di data\\calendario (auto-date table)",
"Category": "Prestazioni",
"Description": "Evita di utilizzare tabelle di datazione automatica (Auto data-tables). Assicurati di disattivare la tabella di datazione automatica nelle impostazioni in Power BI Desktop. Ciò farà risparmiare risorse di memoria. Riferimento: https://www.youtube.com/watch?v=xu3uDEHtCrg",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "ObjectTypeName == \"Calculated Table\"\n\r\nand\r\n\n(\nName.StartsWith(\"DateTableTemplate_\") \n\nor \n\nName.StartsWith(\"LocalDateTable_\")\n)",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_EXCESSIVE_BI-DIRECTIONAL_OR_MANY-TO-MANY_RELATIONSHIPS",
"Name": "[Prestazioni] Evitare l'uso eccessivo di relazioni bidirezionali o molti-a-molti",
"Category": "Prestazioni",
"Description": "Limita l'uso delle relazioni b-direzionali e molti-a-molti. Questa regola contrassegna il modello se più del 30% delle relazioni è bi-direzionale o molti a molti. Riferimento: https://www.sqlbi.com/articles/bidirectional-relationships-and-ambiguity-in-dax/",
"Severity": 2,
"Scope": "Model",
"Expression": "(\r\n\nRelationships.Where(CrossFilteringBehavior == CrossFilteringBehavior.BothDirections).Count()\r\n\n+\r\n\nRelationships.Where(FromCardinality.ToString() == \"Many\" && ToCardinality.ToString() == \"Many\").Count()\r\n\n)\r\n\n\n/\r\n\n\nMath.Max(Convert.ToDecimal(Relationships.Count)\n\n,1)> 0.3",
"CompatibilityLevel": 1200
},
{
"ID": "LIMIT_ROW_LEVEL_SECURITY_(RLS)_LOGIC",
"Name": "[Prestazioni] Limita le logiche di sicurezza a livello di riga (RLS)",
"Category": "Prestazioni",
"Description": "Prova a semplificare il DAX utilizzato per la sicurezza a livello di riga. L'utilizzo delle funzioni all'interno di questa regola può essere probabilmente scaricato sui sistemi upstream (data warehouse).",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "RowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)RIGHT\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)LEFT\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)UPPER\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)LOWER\\s*\\(\"))\r\nor\r\nRowLevelSecurity.Any(RegEx.IsMatch(it.Replace(\" \",\"\"),\"(?i)FIND\\s*\\(\"))\r\n",
"CompatibilityLevel": 1200
},
{
"ID": "MODEL_USING_DIRECT_QUERY_AND_NO_AGGREGATIONS",
"Name": "[Prestazioni] Considerare l'utilizzo di aggregazioni se si utilizza Direct Query in Power BI",
"Category": "Prestazioni",
"Description": "Se usi Direct Query in Power BI Premium, potresti prendere in considerazione l'uso di aggregazioni per migliorare le prestazioni. Riferimento: https://docs.microsoft.com/en-us/power-bi/transform-model/desktop-aggregations",
"Severity": 1,
"Scope": "Model",
"Expression": "Tables.Any(ObjectTypeName == \"Table (DirectQuery)\")\r\nand\r\n\n\nAllColumns.Any(AlternateOf != null) == false\r\nand \r\nDefaultPowerBIDataSourceVersion.ToString() == \"PowerBI_V3\"",
"CompatibilityLevel": 1200
},
{
"ID": "MINIMIZE_POWER_QUERY_TRANSFORMATIONS",
"Name": "[Prestazioni] Minimizza l'utilizzo delle trasformazioni di Power Query",
"Category": "Prestazioni",
"Description": "Riduci al minimo le trasformazioni di Power Query per migliorare le prestazioni di elaborazione del modello. Se possibile, è consigliabile scaricare queste trasformazioni nel data warehouse. Inoltre, controlla se il raggruppamento delle query (Query Folding) si verifica all'interno del tuo modello. Fare riferimento all'articolo di seguito per ulteriori informazioni sul raggruppamento delle query. Riferimento: https://docs.microsoft.com/en-us/power-query/power-query-folding",
"Severity": 2,
"Scope": "Partition",
"Expression": "\nSourceType.ToString() = \"M\"\r\nand\r\n(\r\nQuery.Contains(\"Table.Combine(\")\r\nor\r\n\nQuery.Contains(\"Table.Join(\")\r\nor\r\n\nQuery.Contains(\"Table.NestedJoin(\")\r\nor\r\nQuery.Contains(\"Table.AddColumn(\")\r\nor\r\nQuery.Contains(\"Table.Group(\")\r\nor\r\nQuery.Contains(\"Table.Sort(\")\r\nor\r\nQuery.Contains(\"Table.Pivot(\")\r\nor\r\nQuery.Contains(\"Table.Unpivot(\")\r\nor\r\nQuery.Contains(\"Table.UnpivotOtherColumns(\")\r\nor\r\nQuery.Contains(\"Table.Distinct(\")\r\nor\r\nQuery.Contains(\"[Query=\"\"SELECT\")\r\nor\r\nQuery.Contains(\"Value.NativeQuery\")\r\nor\r\nQuery.Contains(\"OleDb.Query\")\r\nor\r\nQuery.Contains(\"Odbc.Query\")\r\n)",
"CompatibilityLevel": 1200
},
{
"ID": "UNPIVOT_PIVOTED_(MONTH)_DATA",
"Name": "[Prestazioni] Evitare di utilizzare i dati \"unpivottati\" nelle tabelle.",
"Category": "Prestazioni",
"Description": "Evita di utilizzare dati pivot nelle tabelle. Questa regola controlla specificamente i dati pivot per mese. Riferimento: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 2,
"Scope": "Table, CalculatedTable",
"Expression": "Columns.Any(Name.ToUpper().Contains(\"JAN\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))\nand\nColumns.Any(Name.ToUpper().Contains(\"FEB\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))\nand\nColumns.Any(Name.ToUpper().Contains(\"MAR\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))\nand\nColumns.Any(Name.ToUpper().Contains(\"APR\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))\nand\nColumns.Any(Name.ToUpper().Contains(\"MAY\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))\nand\nColumns.Any(Name.ToUpper().Contains(\"JUN\") && (DataType == DataType.Int64 || DataType == DataType.Decimal || DataType == DataType.Double))",
"CompatibilityLevel": 1200
},
{
"ID": "MANY-TO-MANY_RELATIONSHIPS_SHOULD_BE_SINGLE-DIRECTION",
"Name": "[Prestazioni] Le relazioni molti a molti dovrebbero essere monodirezionali",
"Category": "Prestazioni",
"Severity": 2,
"Scope": "Relationship",
"Expression": "FromCardinality == \"Many\"\nand\nToCardinality == \"Many\"\nand\nCrossFilteringBehavior == \"BothDirections\"",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "REDUCE_USAGE_OF_CALCULATED_TABLES",
"Name": "[Prestazioni] Ridurre l'utilizzo delle tabelle calcolate",
"Category": "Prestazioni",
"Description": "Migra la logica della tabella calcolata nel tuo data warehouse. Fare affidamento su tabelle calcolate porterà a debiti tecnici e potenziali disallineamenti se si dispone di più modelli sulla piattaforma.",
"Severity": 2,
"Scope": "CalculatedTable",
"Expression": "1=1",
"CompatibilityLevel": 1200
},
{
"ID": "REMOVE_REDUNDANT_COLUMNS_IN_RELATED_TABLES",
"Name": "[Prestazioni] Rimuovi le colonne ridondanti nelle tabelle relazionate",
"Category": "Prestazioni",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "UsedInRelationships.Any() == false \r\nand\r\nModel.AllColumns.Any(Name == current.Name and Table.Name != current.Table.Name and Table.UsedInRelationships.Any(FromTable.Name == current.Table.Name))",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "MEASURES_USING_TIME_INTELLIGENCE_AND_MODEL_IS_USING_DIRECT_QUERY",
"Name": "[Prestazioni] Misura che usano la Time Intelligence non sono performanti in modelli Direct Query",
"Category": "Prestazioni",
"Description": "Al momento, è noto che le funzioni di Time Intelligence non funzionano altrettanto bene quando si utilizza Direct Query. Se riscontri problemi di prestazioni, potresti provare soluzioni alternative come l'aggiunta di colonne nella tabella dei fatti che mostrano i dati dell'anno precedente o del mese precedente.",
"Severity": 2,
"Scope": "Measure, CalculationItem",
"Expression": "Model.Tables.Any(ObjectTypeName == \"Table (DirectQuery)\")\r\nand\r\n(\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"CLOSINGBALANCEYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATEADD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESBETWEEN\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESINPERIOD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESMTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESQTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"DATESYTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"ENDOFYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTDATE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTNONBLANK\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"FIRSTNONBLANKVALUE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTDATE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTNONBLANK\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"LASTNONBLANKVALUE\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTDAY\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"NEXTYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"OPENINGBALANCEYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PARALLELPERIOD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSDAY\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"PREVIOUSYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"SAMEPERIODLASTYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFMONTH\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFQUARTER\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"STARTOFYEAR\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALMTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALQTD\\s*\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"TOTALYTD\\s*\\(\")\r\n)",
"CompatibilityLevel": 1200
},
{
"ID": "REDUCE_NUMBER_OF_CALCULATED_COLUMNS",
"Name": "[Prestazioni] Ridurre il numero di colonne calcolate",
"Category": "Prestazioni",
"Description": "Le colonne calcolate non si comprimono bene come le colonne di dati, quindi occupano più memoria. Inoltre, rallentano i tempi di elaborazione sia per la tabella che per il ricalcolo del processo. Delega la logica delle colonne calcolate nel tuo data warehouse e trasforma queste colonne calcolate in colonne di dati. Riferimento: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 2,
"Scope": "Model",
"Expression": "AllColumns.Where(Type.ToString() == \"Calculated\").Count() > 5",
"CompatibilityLevel": 1200
},
{
"ID": "CHECK_IF_BI-DIRECTIONAL_AND_MANY-TO-MANY_RELATIONSHIPS_ARE_VALID",
"Name": "[Prestazioni] Verifica se le relazioni bi-direzionali e molti-a-molti sono valide",
"Category": "Prestazioni",
"Description": "Le relazioni bidirezionali e molti-a-molti possono causare un degrado delle prestazioni o persino avere conseguenze indesiderate. Assicurati di controllare queste relazioni specifiche per assicurarti che funzionino come previsto e che siano effettivamente necessarie. Riferimento: https://www.sqlbi.com/articles/bidirectional-relationships-and-ambiguity-in-dax/",
"Severity": 1,
"Scope": "Relationship",
"Expression": "FromCardinality.ToString() = \"Many\" and ToCardinality.ToString() = \"Many\"\r\nor\r\nCrossFilteringBehavior == CrossFilteringBehavior.BothDirections",
"CompatibilityLevel": 1200
},
{
"ID": "CHECK_IF_DYNAMIC_ROW_LEVEL_SECURITY_(RLS)_IS_NECESSARY",
"Name": "[Prestazioni] Verifica se è necessaria la sicurezza dinamica sulle riga (Dynamic RLS)",
"Category": "Prestazioni",
"Description": "L'utilizzo della sicurezza dinamica a livello di riga (RLS) può aggiungere memoria e sovraccarico delle prestazioni. Si prega di ricercare i pro/contro del suo utilizzo. Riferimento: https://docs.microsoft.com/en-us/power-bi/admin/service-admin-rls",
"Severity": 1,
"Scope": "Table, CalculatedTable",
"Expression": "RegEx.IsMatch(Expression,\"(?i)USERNAME\\(\")\r\nor\r\nRegEx.IsMatch(Expression,\"(?i)USERPRINCIPALNAME\\(\")",
"CompatibilityLevel": 1200
},
{
"ID": "DAX_COLUMNS_FULLY_QUALIFIED",
"Name": "[Espressioni DAX] I riferimenti in colonna devono essere pienamente qualificati. Usare NomeTabella[NomeCampo]",
"Category": "Espressioni DAX",
"Description": "L'utilizzo di riferimenti di colonna completi semplifica la distinzione tra i riferimenti di colonna e di misura e consente inoltre di evitare determinati errori. Quando si fa riferimento a una colonna in DAX, specificare prima il nome della tabella, quindi specificare il nome della colonna tra parentesi quadre. Riferimento: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 3,
"Scope": "Measure, KPI, TablePermission, CalculationItem",
"Expression": "DependsOn.Any(Key.ObjectType = \"Column\" and Value.Any(not FullyQualified))",
"CompatibilityLevel": 1200
},
{
"ID": "DAX_MEASURES_UNQUALIFIED",
"Name": "[Espressioni DAX] I riferimenti alle misure non dovrebbero essere qualificati. Usare solo il Nome Misura",
"Category": "Espressioni DAX",
"Description": "L'utilizzo di riferimenti a misure non qualificati semplifica la distinzione tra riferimenti di colonna e di misura e consente inoltre di evitare determinati errori. Quando si fa riferimento a una misura utilizzando DAX, non specificare il nome della tabella. Utilizzare solo il nome della misura tra parentesi quadre. Riferimento: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 3,
"Scope": "Measure, CalculatedColumn, CalculatedTable, KPI, CalculationItem",
"Expression": "DependsOn.Any(Key.ObjectType = \"Measure\" and Value.Any(FullyQualified))",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_DUPLICATE_MEASURES",
"Name": "[Espressioni DAX] Due misure non dovrebbero avere la stessa definizione",
"Category": "Espressioni DAX",
"Severity": 2,
"Scope": "Measure",
"Expression": "Model.AllMeasures.Any(Expression.Replace(\" \",\"\").Replace(\"\\n\",\"\").Replace(\"\\r\",\"\").Replace(\"\\t\",\"\") = outerIt.Expression.Replace(\" \",\"\").Replace(\"\\n\",\"\").Replace(\"\\r\",\"\").Replace(\"\\t\",\"\") and it <> outerIt)",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "USE_THE_TREATAS_FUNCTION_INSTEAD_OF_INTERSECT",
"Name": "[Espressioni DAX] Usa la funzione TREATAS invece di INTERSECT per le relazioni virtuali",
"Category": "Espressioni DAX",
"Description": "La funzione TREATAS è più efficiente e fornisce prestazioni migliori rispetto alla funzione INTERSECT quando viene utilizzata nelle relazioni virtuali. Riferimento: https://www.sqlbi.com/articles/propagate-filters-using-treatas-in-dax/",
"Severity": 2,
"Scope": "Measure, CalculationItem",
"Expression": "RegEx.IsMatch(Expression,\"(?i)INTERSECT\\s*\\(\")",
"CompatibilityLevel": 1400
},
{
"ID": "USE_THE_DIVIDE_FUNCTION_FOR_DIVISION",
"Name": "[Espressioni DAX] Usa la funzione DIVIDE per la divisione",
"Category": "Espressioni DAX",
"Description": "Utilizzare la funzione DIVIDE invece di utilizzare \"/\". La funzione DIVIDE risolve i casi di divisione per zero. Pertanto, si consiglia di utilizzare per evitare errori. Riferimento: https://docs.microsoft.com/en-us/power-bi/guidance/dax-divide-function-operator",
"Severity": 2,
"Scope": "Measure, CalculatedColumn, CalculationItem",
"Expression": "RegEx.IsMatch(Expression,\"\\]\\s*\\/(?!\\/)(?!\\*)\")\r\nor\r\nRegEx.IsMatch(Expression,\"\\)\\s*\\/(?!\\/)(?!\\*)\")",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_USING_THE_IFERROR_FUNCTION",
"Name": "[Espressioni DAX] Evitare di usare la funzione IFERROR",
"Category": "Espressioni DAX",
"Description": "Evitare di utilizzare la funzione IFERROR poiché potrebbe causare un degrado delle prestazioni. Se sei preoccupato per un errore di divisione per zero, usa la funzione DIVIDE poiché risolve naturalmente tali errori come vuoti (oppure puoi personalizzare ciò che dovrebbe essere mostrato in caso di tale errore). Riferimento: https://www.elegantbi.com/post/top10bestpractices",
"Severity": 2,
"Scope": "Measure, CalculatedColumn",
"Expression": "RegEx.IsMatch(Expression,\"(?i)IFERROR\\s*\\(\")",
"CompatibilityLevel": 1200
},
{
"ID": "MEASURES_SHOULD_NOT_BE_DIRECT_REFERENCES_OF_OTHER_MEASURES",
"Name": "[Espressioni DAX] Le misure non dovrebbero essere riferimenti diretti ad altre misure",
"Category": "Espressioni DAX",
"Description": "Questa regola identifica le misure che sono semplicemente un riferimento ad un'altra misura. Ad esempio, si consideri un modello con due misure: [MisuraA] e [MisuraB]. Questa regola verrebbe attivata per MisuraB se il DAX di MisuraB fosse MisuraB:=[MisuraA]. Tali misure duplicate dovrebbero essere rimosse.",
"Severity": 2,
"Scope": "Measure",
"Expression": "Model.AllMeasures.Any(DaxObjectName == current.Expression)",
"CompatibilityLevel": 1200
},
{
"ID": "FILTER_COLUMN_VALUES",
"Name": "[Espressioni DAX] Filtra i valori delle colonne con la sintassi corretta",
"Category": "Espressioni DAX",
"Description": "Invece di utilizzare questa sintassi FILTER('Table','Table'[Column]=\"Value\") per i parametri di filtro di una funzione CALCULATE o CALCULATETABLE, utilizzare una delle opzioni seguenti. Per quanto riguarda l'utilizzo della funzione KEEPFILTERS, vedere il secondo collegamento di riferimento di seguito. Opzione 1: KEEPFILTERS('Table'[Column]=\"Value\") Opzione 2: 'Table'[Column]=\"Value\" Riferimento: https://docs.microsoft.com/power-bi/guidance/dax-avoid-avoid-filter-as-filter-argument Reference: https://www.sqlbi.com/articles/using-keepfilters-in-dax/",
"Severity": 2,
"Scope": "Measure, CalculatedColumn, CalculationItem",
"Expression": "RegEx.IsMatch(Expression,\"(?i)CALCULATE\\s*\\(\\s*[^,]+,\\s*(?i)FILTER\\s*\\(\\s*\\'*[A-Za-z0-9 _]+'*\\s*,\\s*\\'*[A-Za-z0-9 _]+\\'*\\[[A-Za-z0-9 _]+\\]\")\r\nor\r\nRegEx.IsMatch(Expression,\"(?i)CALCULATETABLE\\s*\\([^,]*,\\s*(?i)FILTER\\s*\\(\\s*\\'*[A-Za-z0-9 _]+\\'*,\\s*\\'*[A-Za-z0-9 _]+\\'*\\[[A-Za-z0-9 _]+\\]\")",
"CompatibilityLevel": 1200
},
{
"ID": "FILTER_MEASURE_VALUES_BY_COLUMNS",
"Name": "[Espressioni DAX] Quando fai una misura prediligi il filtro per colonne, non per tabelle",
"Category": "Espressioni DAX",
"Description": "Invece di utilizzare questo modello FILTER('Table',[Measure]>Value) per i parametri di filtro di una funzione CALCULATE o CALCULATETABLE, utilizzare una delle opzioni seguenti (se possibile). Il filtraggio su una colonna specifica produrrà una tabella più piccola da elaborare per il motore, consentendo così prestazioni più veloci. L'utilizzo della funzione VALUES o della funzione ALL dipende dal risultato della misura desiderato. Opzione 1: FILTER(VALUES('Table'[Column]),[Measure] > Value) Opzione 2: FILTER(ALL('Table'[Column]),[Measure] > Value) Riferimento: https://docs. microsoft.com/power-bi/guidance/dax-avoid-avoid-filter-as-filter-argument",
"Severity": 2,
"Scope": "Measure, CalculatedColumn, CalculationItem",
"Expression": "RegEx.IsMatch(Expression,\"(?i)CALCULATE\\s*\\(\\s*[^,]+,\\s*(?i)FILTER\\s*\\(\\s*\\'*[A-Za-z0-9 _]+\\'*\\s*,\\s*\\[[^\\]]+\\]\")\r\nor\r\nRegEx.IsMatch(Expression,\"(?i)CALCULATETABLE\\s*\\([^,]*,\\s*(?i)FILTER\\s*\\(\\s*\\'*[A-Za-z0-9 _]+\\'*,\\s*\\[\")",
"CompatibilityLevel": 1200
},
{
"ID": "INACTIVE_RELATIONSHIPS_THAT_ARE_NEVER_ACTIVATED",
"Name": "[Espressioni DAX] Relazioni inattive che non vengono mai attivate",
"Category": "Espressioni DAX",
"Description": "Le relazioni inattive vengono attivate utilizzando la funzione USERELATIONSHIP. Se una relazione inattiva non viene referenziata in alcun modo tramite questa funzione, la relazione non verrà utilizzata. Dovrebbe essere determinato se la relazione non è necessaria o attivare la relazione tramite questo metodo. Riferimento: https://docs.microsoft.com/power-bi/guidance/relationships-active-inactive Reference: https://dax.guide/userelationship/",
"Severity": 2,
"Scope": "Relationship",
"Expression": "IsActive == false\r\nand not\r\n(\r\nModel.AllMeasures.Any(RegEx.IsMatch(Expression,\r\n\"(?i)USERELATIONSHIP\\s*\\(\\s*\\'*\" +\r\ncurrent.FromTable.Name + \"\\'*\\[\" + \r\ncurrent.FromColumn.Name + \"\\]\\s*,\\s*\\'*\" +\r\ncurrent.ToTable.Name + \"\\'*\\[\" +\r\ncurrent.ToColumn.Name + \"\\]\"))\r\nor\r\nModel.AllCalculationItems.Any(RegEx.IsMatch(Expression,\r\n\"(?i)USERELATIONSHIP\\s*\\(\\s*\\'*\" +\r\ncurrent.FromTable.Name + \"\\'*\\[\" + \r\ncurrent.FromColumn.Name + \"\\]\\s*,\\s*\\'*\" +\r\ncurrent.ToTable.Name + \"\\'*\\[\" +\r\ncurrent.ToColumn.Name + \"\\]\"))\r\n)",
"CompatibilityLevel": 1200
},
{
"ID": "DATA_COLUMNS_MUST_HAVE_A_SOURCE_COLUMN",
"Name": "[Prevenzione Errori] Le colonne di dati devono avere una colonna di origine",
"Category": "[Prevenzione errori]",
"Description": "Le colonne di dati devono avere una colonna di origine. Una colonna di dati senza una colonna di origine causerà un errore durante l'elaborazione del modello.",
"Severity": 3,
"Scope": "DataColumn",
"Expression": "string.IsNullOrWhitespace(SourceColumn)",
"CompatibilityLevel": 1200
},
{
"ID": "EXPRESSION_RELIANT_OBJECTS_MUST_HAVE_AN_EXPRESSION",
"Name": "[Prevenzione errori] Gli oggetti dipendenti dalle espressioni devono avere un'espressione",
"Category": "[Prevenzione errori]",
"Description": "Le colonne calcolate, gli elementi di calcolo e le misure devono avere un'espressione. Senza un'espressione, questi oggetti non mostreranno alcun valore.",
"Severity": 3,
"Scope": "Measure, CalculatedColumn, CalculationItem",
"Expression": "string.IsNullOrWhiteSpace(Expression)",
"CompatibilityLevel": 1200
},
{
"ID": "AVOID_STRUCTURED_DATA_SOURCES_WITH_PROVIDER_PARTITIONS",
"Name": "[Prevenzione errori] Evita origini dati strutturate con partizioni del provider",
"Category": "[Prevenzione errori]",
"Description": "Power BI non supporta le partizioni del provider (dette anche \"legacy\") che fanno riferimento a origini dati strutturate. Le partizioni che fanno riferimento a origini dati strutturate devono utilizzare il linguaggio M. In caso contrario, le partizioni \"provider\" devono fare riferimento a un'origine dati \"provider\". Questo può essere risolto convertendo l'origine dati strutturata in un'origine dati del fornitore (vedere il secondo collegamento di riferimento di seguito). Riferimento: https://docs.microsoft.com/power-bi/admin/service-premium-connect-tools#data-source-declaration Riferimento: https://www.elegantbi.com/post/convertdatasources",
"Severity": 2,
"Scope": "Partition",
"Expression": "SourceType == \"Query\"\r\nand\r\nDataSource.Type == \"Structured\"",
"CompatibilityLevel": 1200
},
{
"ID": "UNNECESSARY_COLUMNS",
"Name": "[Manutenzione] Rimuovi colonne non necessarie",
"Category": "[Manutenzione]",
"Description": "Le colonne nascoste a cui non fanno riferimento espressioni DAX, relazioni, livelli gerarchici o proprietà \"Ordina per\" devono essere rimosse.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(IsHidden or Table.IsHidden)\n\n\r\nand ReferencedBy.Count = 0\r\n\n\nand (not UsedInRelationships.Any())\n\n\r\nand (not UsedInSortBy.Any())\n\n\r\nand (not UsedInHierarchies.Any())\n\n\r\nand (not Table.RowLevelSecurity.Any(\nit <> null and it.IndexOf(\"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n))\n\n and (not Model.Roles.Any(RowLevelSecurity.Any(\nit <> null and \n(\nit.IndexOf(current.Table.Name + \"[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0 or\n it.IndexOf(\"'\" + current.Table.Name + \"'[\" + current.Name + \"]\", \"OrdinalIgnoreCase\") >= 0\n )\n)))",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "UNNECESSARY_MEASURES",
"Name": "[Manutenzione] Rimuovere le misure non necessarie",
"Category": "[Manutenzione]",
"Description": "Le misure nascoste a cui non fa riferimento alcuna espressione DAX dovrebbero essere rimosse per la manutenibilità",
"Severity": 2,
"Scope": "Measure",
"Expression": "(Table.IsHidden or IsHidden) \r\nand ReferencedBy.Count = 0",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "FIX_REFERENTIAL_INTEGRITY_VIOLATIONS",
"Name": "[Manutenzione] Correggi le violazioni dell'integrità referenziale",
"Category": "[Manutenzione]",
"Description": "Questa regola evidenzia le relazioni che presentano violazioni dell'integrità referenziale. Ciò indica che nella tabella sul lato \"da\" della relazione sono presenti valori che non esistono nella tabella sul lato \"a\" della relazione. Le violazioni dell'integrità referenziale produrranno anche il valore del membro \"vuoto\" nei filtri dei dati. Si consiglia di risolvere questi problemi assicurandosi che la colonna della chiave primaria della tabella \"a\" abbia tutti i valori nella colonna della chiave esterna della tabella \"da\". Riferimento: https://blog.enterprisedna.co/vertipaq-analyzer-tutorial-relationships-referential-integrity/",
"Severity": 2,
"Scope": "Relationship",
"Expression": "Convert.ToInt64(GetAnnotation(\"Vertipaq_RIViolationInvalidRows\")) > 0",
"CompatibilityLevel": 1200
},
{
"ID": "REMOVE_DATA_SOURCES_NOT_REFERENCED_BY_ANY_PARTITIONS",
"Name": "[Manutenzione] Rimuovere le origini dati non referenziate da alcuna partizione",
"Category": "[Manutenzione]",
"Description": "Le origini dati a cui non fa riferimento alcuna partizione possono essere rimosse.",
"Severity": 1,
"Scope": "ProviderDataSource, StructuredDataSource",
"Expression": "UsedByPartitions.Count() == 0",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "REMOVE_ROLES_WITH_NO_MEMBERS",
"Name": "[Manutenzione] Rimuovi i ruoli senza membri",
"Category": "[Manutenzione]",
"Description": "Puoi rimuovere ruoli senza membri.",
"Severity": 1,
"Scope": "ModelRole",
"Expression": "Members.Count() == 0",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "ENSURE_TABLES_HAVE_RELATIONSHIPS",
"Name": "[Manutenzione] Assicurati che le tabelle abbiano relazioni",
"Category": "[Manutenzione]",
"Description": "Questa regola evidenzia le tabelle che non sono collegate a nessun'altra tabella nel modello con una relazione.",
"Severity": 1,
"Scope": "Table, CalculatedTable",
"Expression": "UsedInRelationships.Count() == 0",
"CompatibilityLevel": 1200
},
{
"ID": "OBJECTS_WITH_NO_DESCRIPTION",
"Name": "[Manutenzione] Oggetti visibili senza descrizione",
"Category": "[Manutenzione]",
"Description": "Aggiungi descrizioni agli oggetti. Queste descrizioni vengono visualizzate al passaggio del mouse nell'elenco dei campi in Power BI Desktop. Inoltre, puoi sfruttare queste descrizioni per creare un dizionario dati automatizzato (vedi link sotto). Riferimento: https://www.elegantbi.com/post/datadictionary",
"Severity": 1,
"Scope": "Table, Measure, DataColumn, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup",
"Expression": "string.IsNullOrWhitespace(Description)\r\nand\r\nIsHidden == false",
"CompatibilityLevel": 1200
},
{
"ID": "PERSPECTIVES_WITH_NO_OBJECTS",
"Name": "[Manutenzione] Prospettive senza oggetti",
"Category": "[Manutenzione]",
"Description": "Le prospettive che non contengono oggetti (tabelle) molto probabilmente non sono necessarie. In questa regola, è solo necessario controllare le tabelle poiché l'aggiunta di una colonna/misura/gerarchia a una prospettiva aggiunge anche la tabella alla prospettiva. Inoltre, le tabelle in generale coprono anche le tabelle calcolate e i gruppi di calcolo.",
"Severity": 1,
"Scope": "Perspective",
"Expression": "Model.Tables.Any(InPerspective[current.Name]) == false",
"FixExpression": "Delete()",
"CompatibilityLevel": 1200
},
{
"ID": "CALCULATION_GROUPS_WITH_NO_CALCULATION_ITEMS",
"Name": "[Manutenzione] Gruppi di calcolo senza elementi di calcolo",
"Category": "[Manutenzione]",
"Description": "I gruppi di calcolo non hanno alcuna funzione a meno che non dispongano di elementi di calcolo.",
"Severity": 2,
"Scope": "CalculationGroup",
"Expression": "CalculationItems.Count == 0",
"CompatibilityLevel": 1200
},
{
"ID": "PARTITION_NAME_SHOULD_MATCH_TABLE_NAME_FOR_SINGLE_PARTITION_TABLES",
"Name": "[Naming Conventions] Il nome della partizione deve corrispondere al nome della tabella per le singole tabelle delle partizioni",
"Category": "Naming Conventions",
"Description": "Le tabelle con una sola partizione dovrebbero corrispondere ai nomi delle tabelle e delle partizioni. Le tabelle con più di una partizione dovrebbero avere ogni nome di partizione che inizia con il nome della tabella.",
"Severity": 1,
"Scope": "Table",
"Expression": "(Partitions.Count = 1 and Partitions[0].Name <> Name)",
"FixExpression": "Partitions[0].Name = it.Name",
"CompatibilityLevel": 1200
},
{
"ID": "SPECIAL_CHARS_IN_OBJECT_NAMES",
"Name": "[Naming Conventions] I nomi degli oggetti non devono contenere caratteri speciali",
"Category": "Naming Conventions",
"Description": "Tabs, A capo, etc.",
"Severity": 2,
"Scope": "Model, Table, Measure, Hierarchy, Perspective, Partition, DataColumn, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup, CalculationItem",
"Expression": "Name.IndexOf(char(9)) > -1\r\nor\r\n\nName.IndexOf(char(10)) > -1 \r\nor\r\n\nName.IndexOf(char(13)) > -1",
"CompatibilityLevel": 1200
},
{
"ID": "FORMAT_FLAG_COLUMNS_AS_YES/NO_VALUE_STRINGS",
"Name": "[Formattazione] Formatta le colonne flag come stringhe di valori Sì/No",
"Category": "Formattazione",
"Description": "I flag devono essere formattati correttamente come Sì/No poiché è più facile da leggere rispetto all'utilizzo di valori interi 0/1.",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(\nName.StartsWith(\"Is\") and \nDataType = \"Int64\" and \nnot (IsHidden or Table.IsHidden)\n) \r\nor\r\n\n(\nName.EndsWith(\" Flag\") and \nDataType <> \"String\" and \nnot (IsHidden or Table.IsHidden)\n)",
"CompatibilityLevel": 1200
},
{
"ID": "OBJECTS_SHOULD_NOT_START_OR_END_WITH_A_SPACE",
"Name": "[Formattazione] Gli oggetti non devono iniziare o terminare con uno spazio",
"Category": "Formattazione",
"Description": "Gli oggetti non dovrebbero iniziare o finire con uno spazio",
"Severity": 3,
"Scope": "Model, Table, Measure, Hierarchy, Perspective, Partition, DataColumn, CalculatedColumn",
"Expression": "Name.StartsWith(\" \") or Name.EndsWith(\" \")",
"CompatibilityLevel": 1200
},
{
"ID": "DATECOLUMN_FORMATSTRING",
"Name": "[Formattazione] Fornisci la stringa di formato per le colonne \"Data\"",
"Category": "Formattazione",
"Description": "Le colonne di tipo \"DateTime\" che hanno \"Mese\" nel nome devono essere formattate come \"mm/gg/aaaa\".",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Name.IndexOf(\"Date\", \"OrdinalIgnoreCase\") >= 0 and DataType = \"DateTime\" and FormatString <> \"mm/dd/yyyy\"",
"FixExpression": "FormatString = \"mm/dd/yyyy\"",
"CompatibilityLevel": 1200
},
{
"ID": "MONTHCOLUMN_FORMATSTRING",
"Name": "[Formattazione] Fornisci la stringa di formato per le colonne \"Mese\"",
"Category": "Formattazione",
"Description": "Le colonne di tipo \"DateTime\" che hanno \"Mese\" nel nome devono essere formattate come \"MMMM aaaa\".",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Name.IndexOf(\"Month\", \"OrdinalIgnoreCase\") >= 0 and DataType = \"DateTime\" and FormatString <> \"MMMM yyyy\"",
"FixExpression": "FormatString = \"MMMM yyyy\"",
"CompatibilityLevel": 1200
},
{
"ID": "PROVIDE_FORMAT_STRING_FOR_MEASURES",
"Name": "[Formattazione] Fornire una stringa di formato per le misure",
"Category": "Formattazione",
"Description": "Le misure visibili dovrebbero avere la proprietà formato assegnata",
"Severity": 3,
"Scope": "Measure",
"Expression": "not IsHidden \r\nand not Table.IsHidden \r\nand string.IsNullOrWhitespace(FormatString)",
"CompatibilityLevel": 1200
},
{
"ID": "NUMERIC_COLUMN_SUMMARIZE_BY",
"Name": "[Formattazione] Non riepilogare colonne di tipo numerico",
"Category": "Formattazione",
"Description": "Le colonne numeriche (integer, decimal, double) devono avere la proprietà SummarizeBy impostata su \"Nessuno\" per evitare la somma accidentale in Power BI (creare invece misure).",
"Severity": 3,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(\r\nDataType = \"Int64\"\r\nor \r\nDataType=\"Decimal\" \r\nor \r\nDataType=\"Double\"\r\n)\n\r\nand \r\nSummarizeBy <> \"None\"\r\n\nand not (IsHidden or Table.IsHidden)",
"FixExpression": "SummarizeBy = AggregateFunction.None",
"CompatibilityLevel": 1200
},
{
"ID": "PERCENTAGE_FORMATTING",
"Name": "[Formattazione] Le percentuali dovrebbero essere formattate con separatori delle migliaia e 1 decimale",
"Category": "Formattazione",
"Severity": 2,
"Scope": "Measure",
"Expression": "FormatString.Contains(\"%\") and FormatString <> \"#,0.0%;-#,0.0%;#,0.0%\"",
"FixExpression": "FormatString = \"#,0.0%;-#,0.0%;#,0.0%\"",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "INTEGER_FORMATTING",
"Name": "[Formattazione] I numeri interi devono essere formattati con separatori delle migliaia e senza decimali",
"Category": "Formattazione",
"Severity": 2,
"Scope": "Measure",
"Expression": "not FormatString.Contains(\"$\") and not FormatString.Contains(\"%\") and not (FormatString = \"#,0\" or FormatString = \"#,0.0\")",
"FixExpression": "FormatString = \"#,0\"",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "RELATIONSHIP_COLUMNS_SHOULD_BE_OF_INTEGER_DATA_TYPE",
"Name": "[Formattazione] Le colonne delle relazioni devono essere di tipo intero",
"Category": "Formattazione",
"Description": "È consigliabile che le colonne di relazione siano di tipo intero. Questo vale non solo per il data warehousing, ma anche per la modellazione dei dati.",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "UsedInRelationships.Any()\n\nand \n\nDataType != DataType.Int64",
"CompatibilityLevel": 1200
},
{
"ID": "ADD_DATA_CATEGORY_FOR_COLUMNS",
"Name": "[Formattazione] Aggiungi categoria di dati per colonne",
"Category": "Formattazione",
"Description": "Aggiungi la proprietà Categoria dati per le colonne appropriate. Riferimento: https://docs.microsoft.com/en-us/power-bi/transform-model/desktop-data-categorization",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "string.IsNullOrWhitespace(DataCategory)\r\nand\r\n(\r\n(\r\nName.ToLower().Contains(\"country\")\r\nor \r\n\nName.ToLower().Contains(\"continent\"\n)\r\nor\r\nName.ToLower().Contains(\"city\")\r\n)\r\nand DataType == \"String\"\r\n)\r\nor \r\n(\r\n(\nName.ToLower() == \"latitude\" \n or \nName.ToLower() == \"longitude\")\r\nand (DataType == DataType.Decimal or DataType == DataType.Double)\r\n)",
"CompatibilityLevel": 1200
},
{
"ID": "HIDE_FOREIGN_KEYS",
"Name": "[Formattazione] Nascondi chiavi esterne",
"Category": "Formattazione",
"Description": "Le chiavi esterne dovrebbero essere sempre nascoste.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "UsedInRelationships.Any(FromColumn.Name == current.Name and FromCardinality == \"Many\")\n\r\nand\r\n\nIsHidden == false",
"FixExpression": "IsHidden = true",
"CompatibilityLevel": 1200
},
{
"ID": "MARK_PRIMARY_KEYS",
"Name": "[Formattazione] Contrassegna le chiavi primarie",
"Category": "Formattazione",
"Description": "Imposta la proprietà \"Key\" su \"Vero\" per le colonne della chiave primaria all'interno delle proprietà della colonna.",
"Severity": 1,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "UsedInRelationships.Any(ToTable.Name == current.Table.Name and ToColumn.Name == current.Name and ToCardinality == \"One\")\r\n\nand\r\n\nIsKey == false\r\nand\r\ncurrent.Table.DataCategory != \"Time\"",
"FixExpression": "IsKey = true",
"CompatibilityLevel": 1200
},
{
"ID": "HIDE_FACT_TABLE_COLUMNS",
"Name": "[Formattazione] Nascondi colonne della tabella dei fatti",
"Category": "Formattazione",
"Description": "È consigliabile nascondere le colonne della tabella dei fatti utilizzate per l'aggregazione nelle misure.",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "(\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNTBLANK\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)SUM\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)AVERAGE\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)VALUES\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)DISTINCT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)DISTINCTCOUNT\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\n\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MIN\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\n\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MAX\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)COUNTA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\n\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)AVERAGEA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MAXA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n\nor\r\nReferencedBy.AllMeasures.Any(RegEx.IsMatch(Expression,\"(?i)MINA\\s*\\(\\s*\\'*\" + outerit.Table.Name + \"\\'*\\[\" + outerit.Name + \"\\]\\s*\\)\"))\r\n)\r\n\nand IsHidden == false\r\n\nand (DataType == \"Int64\" || DataType == \"Decimal\" || DataType == \"Double\")",
"FixExpression": "IsHidden = true",
"CompatibilityLevel": 1200
},
{
"ID": "FIRST_LETTER_OF_OBJECTS_MUST_BE_CAPITALIZED",
"Name": "[Formattazione] La prima lettera degli oggetti deve essere in maiuscolo",
"Category": "Formattazione",
"Severity": 1,
"Scope": "Table, Measure, Hierarchy, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup",
"Expression": "Name.Substring(0,1).ToUpper() != Name.Substring(0,1)",
"CompatibilityLevel": 1200,
"Description": ""
},
{
"ID": "MONTH_(AS_A_STRING)_MUST_BE_SORTED",
"Name": "[Formattazione] Il mese (come stringa) deve essere ordinato",
"Category": "Formattazione",
"Description": "Questa regola evidenzia le colonne del mese che sono stringhe e non sono ordinate. Se non vengono ordinati, verranno ordinati in ordine alfabetico (ad es. aprile, agosto...). Assicurati di ordinare tali colonne in modo che vengano ordinate correttamente (gennaio, febbraio, marzo...).",
"Severity": 2,
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
"Expression": "Name.ToUpper().Contains(\"MONTH\")\r\nand\r\n! Name.ToUpper().Contains(\"MONTHS\") \r\nand \r\n\n\nDataType == DataType.String \r\nand \r\nSortByColumn == null",
"CompatibilityLevel": 1200
}
]

View File

@ -2,11 +2,21 @@
Make sure to also check out the [PowerBI.com blog post](https://powerbi.microsoft.com/en-us/blog/best-practice-rules-to-improve-your-models-performance/ "PowerBI.com blog post") on this topic!
And, check out the new [PowerBI.com blog post on v1.1](https://powerbi.microsoft.com/en-us/blog/best-practice-rules-to-improve-your-models-performance-and-design-v1-1/, "PowerBI.com blog post").
Check out the [PowerBI.com blog post on v1.1](https://powerbi.microsoft.com/en-us/blog/best-practice-rules-to-improve-your-models-performance-and-design-v1-1/, "PowerBI.com blog post").
Also, check out this [post](https://www.elegantbi.com/post/bestpracticerulesavings "Best Practice Rule Savings") for quantifying the savings of following specific Best Practice Rules.
Check out this blog post to learn how to quantify the savings of following specific Best Practice Rules.
Lastly, check out this [video](https://www.youtube.com/watch?v=5pu9FoTUpys) for an in-depth walkthrough of the Best Practice Rules and [Tabular Editor](https://tabulareditor.com/ "Tabular Editor")'s [Best Practice Analyzer](https://docs.tabulareditor.com/Best-Practice-Analyzer.html "Best Practice Analyzer").
[![image](https://user-images.githubusercontent.com/29556918/131373174-19f31ecb-67b3-4515-83be-f6687d442053.jpg)](https://www.elegantbi.com/post/bestpracticerulesavings)
Check out this blog post to learn how to export the Best Practice Analyzer results into Excel.
[![image](https://user-images.githubusercontent.com/29556918/136688637-f638405e-0bda-462d-8d95-00a0037000c6.jpg)](https://www.elegantbi.com/post/exportbparesults)
Lastly, check out the video below for an in-depth walkthrough of the Best Practice Rules and [Tabular Editor](https://tabulareditor.com/ "Tabular Editor")'s [Best Practice Analyzer](https://docs.tabulareditor.com/Best-Practice-Analyzer.html "Best Practice Analyzer").
<a href="https://www.youtube.com/watch?v=5pu9FoTUpys
" target="_blank"><img src="http://i3.ytimg.com/vi/5pu9FoTUpys/hqdefault.jpg"
alt="IMAGE ALT TEXT HERE" width="240" height="180" border="10" /></a>
## Purpose
@ -18,9 +28,15 @@ We would love to hear feedback on how this tool has helped your organization. Pl
If you find any issues or have any requests for new rules, please [submit an issue](https://github.com/microsoft/Analysis-Services/issues "submit an issue") within this repository. Just prefix the issue with "BPARules" to make it easier to track.
## Rule Details
The rules are divided into categories (i.e. Performance, DAX Expressions, Error Prevention, Formatting, Maintenance etc.) for easier viewing. Additionally, each rule has a description and many of the rules also have a reference article/video. Reading the rule description and article will provide context as to why the rule is important and why one should follow it. The rule descriptions are accessible by navigating to 'Tools' -> 'Manage BPA Rules...' -> 'Rules for the local user' -> Click on a rule -> Click 'Edit rule...'.
<img width="400" alt="RuleDescription35" src="https://user-images.githubusercontent.com/29556918/132206247-b2f73948-b976-4351-9830-a62f4145f263.png">
## Setup (automated)
Following these steps will automatically load the Best Practice Rules into your local Tabular Editor. Note that this will overwrite the existing BPARules.json file (if you are already have one).
Following these steps will automatically load the Best Practice Rules into your local Tabular Editor. Note that this will overwrite the existing BPARules.json file (if you are already have one) so be sure to back up your existing rules file.
1. Open [Tabular Editor](https://tabulareditor.com/ "Tabular Editor").
2. Connect to a model.
@ -42,6 +58,11 @@ if (version == "3")
w.DownloadFile(url, downloadLoc);
```
*Note: If you want to load the rules in [Italian](https://github.com/microsoft/Analysis-Services/tree/master/BestPracticeRules/Italian), replace the url parameter in the code above with the code below.*
```C#
string url = "https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/Italian/BPARules.json";
```
4. Close and reopen [Tabular Editor](https://tabulareditor.com/ "Tabular Editor").
5. Connect to a model.
6. Select 'Tools' from the File menu and select 'Best Practice Analyzer'.
@ -65,6 +86,7 @@ w.DownloadFile(url, downloadLoc);
* Large tables should be partitioned *
* Reduce usage of long-length columns with high cardinality *^
* Split date and time ***
* Fix referential integrity violations *
*These rules use [Vertipaq Analyzer](https://www.sqlbi.com/tools/vertipaq-analyzer/) data. There are 2 methods to load this data into Tabular Editor:
@ -80,8 +102,41 @@ w.DownloadFile(url, downloadLoc);
[Tabular Editor](https://tabulareditor.com/ "Tabular Editor") version 2.16.1 or higher.
## Languages
* English
* [Italian](https://github.com/microsoft/Analysis-Services/tree/master/BestPracticeRules/Italian)
*Note: If you would like to volunteer to translate the Best Practice Rules into another language, please contact us at pbibestpractice@microsoft.com.*
## Version History
* 2021-10-21 The Best Practice Rules are now available in [Italian](https://github.com/microsoft/Analysis-Services/tree/master/BestPracticeRules/Italian)!
* 2021-08-18 Version 1.2.1
* New Rules
* [Maintenance] Fix referential integrity violations ([blog post](https://www.elegantbi.com/post/findblankrows))
* 2021-07-26 Version 1.2
* New Rules
* [Error Prevention] Avoid structured data sources with provider partitions ([blog post](https://www.elegantbi.com/post/convertdatasources))
* Modified Rules
* [DAX Expressions] Column references should be fully qualified
* Scope: Added 'Table Permissions'
* [Formatting] Hide fact table columns
* Simplified rule logic
* [Performance] Check if dynamic row level security (RLS) is necessary
* Simplified rule logic
* [Performance] Limit row level security (RLS) logic
* Simplified rule logic
* [Formatting] Add data category for columns
* Fixed rule logic
* [Performance] Measures using time intelligence and model is using Direct Query
* Simplified rule logic
* [Performance] Reduce usage of calculated columns that use the RELATED function
* Simplified rule logic
* [Performance] Reduce usage of long-length columns with high cardinality
* Updated rule logic to use Int64
* 2021-10-21 Best Practice Rules now available in [Italian](https://github.com/microsoft/Analysis-Services/tree/master/BestPracticeRules/Italian)!
* 2021-06-13 Version 1.1.2
* Modified Rules
* [DAX Expressions] Use the DIVIDE function for division

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Script.Serialization;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
namespace ConsoleApp1
{
public class QueryRequest
{
public class Query
{
public string query { get; set; }
}
public List<Query> queries { get; set; }
public QueryRequest()
{
}
public QueryRequest(string daxQuery)
{
queries = new List<Query> { new Query { query = daxQuery } };
}
}
public class ParsedResponse
{
public class Result
{
public class Table
{
public List<Dictionary<string, object>> rows { get; set; }
}
public List<Table> tables { get; set; }
}
public List<Result> results { get; set; }
}
class Program
{
/// <summary>
/// Please fill in the application parameters.
/// </summary>
private static string clientId = "<Provide your app's client Id>";
private static string tenantID = "<Provide your Azure tenant Id>";
private static string replyUrl = "<Provide your app's reply Url>";
private static string resourceID = "https://analysis.windows.net/powerbi/api";
/// <summary>
/// Please provide the DAX Rest API parameters.
/// </summary>
private static Guid datasetId = new Guid("<Provide a dataset Id>");
private static string daxQuery = @"<Provide a DAX query>";
static void Main(string[] args)
{
string authToken = GetToken().Result;
string jsonResponse;
if (QueryDataset(datasetId, daxQuery, authToken, out jsonResponse))
{
var queryResponse = new JavaScriptSerializer().Deserialize<ParsedResponse>(jsonResponse);
foreach (var row in queryResponse.results[0].tables[0].rows)
{
foreach (var keyValuePair in row)
{
Console.WriteLine($"{keyValuePair.Key}:{keyValuePair.Value}");
}
Console.WriteLine();
}
}
else
{
Console.WriteLine("The Web request did not succeed.");
}
Console.WriteLine("Press [Enter] to exit the console app...");
Console.ReadLine();
}
private static async Task<string> GetToken()
{
IPublicClientApplication PublicClientApp = PublicClientApplicationBuilder.Create(clientId)
.WithRedirectUri(replyUrl)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantID)
.Build();
AuthenticationResult authResult = await PublicClientApp.AcquireTokenInteractive(scopes: new[] { resourceID + "/Dataset.Read.All" })
.ExecuteAsync();
return authResult.AccessToken;
}
static bool QueryDataset(Guid datasetId, string daxQuery, string authToken, out string jsonResponse)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", authToken);
string url = $"https://api.powerbi.com/v1.0/myorg/datasets/{datasetId}/executeQueries";
var requestBody = new JavaScriptSerializer().Serialize(
new QueryRequest(daxQuery)
);
var response = client.PostAsync(url, new StringContent(requestBody, UnicodeEncoding.UTF8, "application/json")).Result;
jsonResponse = response.Content.ReadAsStringAsync().Result;
return response.IsSuccessStatusCode;
}
}
}

View File

@ -26,7 +26,7 @@ Sample U-SQL scripts that demonstrate how to process a TPC-DS data set in Azure
Python script to reassemble job graph events from Analysis Services.
## [Best Practice Rules](https://github.com/microsoft/Analysis-Services/tree/master/BestPracticeRules)
A curated set of rules covering best practices for tabular model performance and design which can be run from [Tabular Editor's](https://tabulareditor.com/ "Tabular Editor") [Best Practice Analyzer](https://docs.tabulareditor.com/Best-Practice-Analyzer.html "Best Practice Analyzer").
A curated set of rules covering best practices for tabular model performance and design which can be run from [Tabular Editor](https://tabulareditor.com/ "Tabular Editor")'s [Best Practice Analyzer](https://docs.tabulareditor.com/Best-Practice-Analyzer.html "Best Practice Analyzer").
## [Metadata Translator](https://github.com/microsoft/Analysis-Services/tree/master/MetadataTranslator)
Metadata Translator can translate the names, descriptions, and display folders of the metadata objects in a semantic model by using Azure Cognitive Services.