560 lines
40 KiB
JSON
560 lines
40 KiB
JSON
[
|
|
{
|
|
"ID": "AVOID_FLOATING_POINT_DATA_TYPES",
|
|
"Name": "[Performance] Do not use floating point data types",
|
|
"Category": "Performance",
|
|
"Description": "The \"Double\" floating point data type should be avoided, as it can result in unpredictable roundoff errors and decreased performance in certain scenarios. Use \"Int64\" or \"Decimal\" where appropriate (but note that \"Decimal\" is limited to 4 digits after the decimal sign).",
|
|
"Severity": 2,
|
|
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
|
|
"Expression": "DataType = \"Double\"",
|
|
"FixExpression": "DataType = DataType.Decimal",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "ISAVAILABLEINMDX_FALSE_NONATTRIBUTE_COLUMNS",
|
|
"Name": "[Performance] Set IsAvailableInMdx to false on non-attribute columns",
|
|
"Category": "Performance",
|
|
"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()",
|
|
"FixExpression": "IsAvailableInMDX = false",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "AVOID_BI-DIRECTIONAL_RELATIONSHIPS_AGAINST_HIGH-CARDINALITY_COLUMNS",
|
|
"Name": "[Performance] Avoid bi-directional relationships against high-cardinality columns",
|
|
"Category": "Performance",
|
|
"Description": "For best performance, it is recommended to avoid using bi-directional relationships against high-cardinality columns. In order to run this rule, you must first run the script shown here: https://www.elegantbi.com/post/vertipaqintabulareditor",
|
|
"Severity": 2,
|
|
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
|
|
"Expression": "UsedInRelationships.Any(CrossFilteringBehavior == CrossFilteringBehavior.BothDirections)\n\nand\n\nConvert.ToInt32(GetAnnotation(\"Vertipaq_Cardinality\")) > 1000000",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REDUCE_USAGE_OF_LONG-LENGTH_COLUMNS_WITH_HIGH_CARDINALITY",
|
|
"Name": "[Performance] Reduce usage of long-length columns with high cardinality",
|
|
"Category": "Performance",
|
|
"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",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "SPLIT_DATE_AND_TIME",
|
|
"Name": "[Performance] Split date and time",
|
|
"Category": "Performance",
|
|
"Description": "This rule finds datetime columns that have values not at midnight. To maximize performance, the time element should be split from date element (or the time component should be rounded to midnight as this will reduce column cardinality).\r\nReference: 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": "[Performance] Large tables should be partitioned",
|
|
"Category": "Performance",
|
|
"Description": "Large tables should be partitioned in order to optimize processing. In order for this rule to run properly, you must run the script shown here: 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": "[Performance] Reduce usage of calculated columns that use the RELATED function",
|
|
"Category": "Performance",
|
|
"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\\(\")",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "SNOWFLAKE_SCHEMA_ARCHITECTURE",
|
|
"Name": "[Performance] Consider a star-schema instead of a snowflake architecture",
|
|
"Category": "Performance",
|
|
"Description": "Generally speaking, a star-schema is the optimal architecture for tabular models. That being the case, there are valid cases to use a snowflake approach. Please check your model and consider moving to a star-schema architecture.\r\nReference: 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": "[Performance] Model should have a date table",
|
|
"Category": "Performance",
|
|
"Description": "Generally speaking, models should generally have a date table. Models that do not have a date table generally are not taking advantage of features such as time intelligence or may not have a properly structured architecture.",
|
|
"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": "[Performance] Date/calendar tables should be marked as a date table",
|
|
"Category": "Performance",
|
|
"Description": "This rule looks for tables that contain the words 'date' or 'calendar' as they should likely be marked as a date table.\r\nReference: 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": "[Performance] Remove auto-date table",
|
|
"Category": "Performance",
|
|
"Description": "Avoid using auto-date tables. Make sure to turn off auto-date table in the settings in Power BI Desktop. This will save memory resources. \r\nReference: 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": "[Performance] Avoid excessive bi-directional or many-to-many relationships",
|
|
"Category": "Performance",
|
|
"Description": "Limit use of b-di and many-to-many relationships. This rule flags the model if more than 30% of relationships are bi-di or many-to-many.\r\nReference: 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": "[Performance] Limit row level security (RLS) logic",
|
|
"Category": "Performance",
|
|
"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\\(\"))",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "MODEL_USING_DIRECT_QUERY_AND_NO_AGGREGATIONS",
|
|
"Name": "[Performance] Consider using aggregations if using Direct Query in Power BI",
|
|
"Category": "Performance",
|
|
"Description": "If using Direct Query in Power BI Premium, you may want to consider using aggregations in order to boost performance.\r\nReference: 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": "[Performance] Minimize Power Query transformations",
|
|
"Category": "Performance",
|
|
"Description": "Minimize Power Query transformations in order to improve model processing performance. It is a best practice to offload these transformations to the data warehouse if possible. Also, please check whether query folding is occurring within your model. Please reference the article below for more information on query folding.\r\nReference: 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": "[Performance] Unpivot pivoted (month) data",
|
|
"Category": "Performance",
|
|
"Description": "Avoid using pivoted data in your tables. This rule checks specifically for pivoted data by month.\r\nReference: 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": "[Performance] Many-to-many relationships should be single-direction",
|
|
"Category": "Performance",
|
|
"Severity": 2,
|
|
"Scope": "Relationship",
|
|
"Expression": "FromCardinality == \"Many\"\nand\nToCardinality == \"Many\"\nand\nCrossFilteringBehavior == \"BothDirections\"",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REDUCE_USAGE_OF_CALCULATED_TABLES",
|
|
"Name": "[Performance] Reduce usage of calculated tables",
|
|
"Category": "Performance",
|
|
"Description": "Migrate calculated table logic to your data warehouse. Reliance on calculated tables will lead to technical debt and potential misalignments if you have multiple models on your platform.",
|
|
"Severity": 2,
|
|
"Scope": "CalculatedTable",
|
|
"Expression": "1=1",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REMOVE_REDUNDANT_COLUMNS_IN_RELATED_TABLES",
|
|
"Name": "[Performance] Remove redundant columns in related tables",
|
|
"Category": "Performance",
|
|
"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
|
|
},
|
|
{
|
|
"ID": "MEASURES_USING_TIME_INTELLIGENCE_AND_MODEL_IS_USING_DIRECT_QUERY",
|
|
"Name": "[Performance] Measures using time intelligence and model is using Direct Query",
|
|
"Category": "Performance",
|
|
"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)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REDUCE_NUMBER_OF_CALCULATED_COLUMNS",
|
|
"Name": "[Performance] Reduce number of calculated columns",
|
|
"Category": "Performance",
|
|
"Description": "Calculated columns do not compress as well as data columns so they take up more memory. They also slow down processing times for both the table as well as process recalc. Offload calculated column logic to your data warehouse and turn these calculated columns into data columns.\r\nReference: 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": "[Performance] Check if bi-directional and many-to-many relationships are valid",
|
|
"Category": "Performance",
|
|
"Description": "Bi-directional and many-to-many relationships may cause performance degradation or even have unintended consequences. Make sure to check these specific relationships to ensure they are working as designed and are actually necessary.\r\nReference: 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": "[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\\(\")",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "DAX_COLUMNS_FULLY_QUALIFIED",
|
|
"Name": "[DAX Expressions] Column references should be fully qualified",
|
|
"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",
|
|
"Expression": "DependsOn.Any(Key.ObjectType = \"Column\" and Value.Any(not FullyQualified))",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "DAX_MEASURES_UNQUALIFIED",
|
|
"Name": "[DAX Expressions] Measure references should be unqualified",
|
|
"Category": "DAX Expressions",
|
|
"Description": "Using unqualified measure references makes it easier to distinguish between column and measure references, and also helps avoid certain errors. When referencing a measure using DAX, do not specify the table name. Use only the measure name in square brackets.\r\nReference: 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": "[DAX Expressions] No two measures should have the same definition",
|
|
"Category": "DAX Expressions",
|
|
"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
|
|
},
|
|
{
|
|
"ID": "USE_THE_TREATAS_FUNCTION_INSTEAD_OF_INTERSECT",
|
|
"Name": "[DAX Expressions] Use the TREATAS function instead of INTERSECT for virtual relationships",
|
|
"Category": "DAX Expressions",
|
|
"Description": "The TREATAS function is more efficient and provides better performance than the INTERSECT function when used in virutal relationships.\r\nReference: https://www.sqlbi.com/articles/propagate-filters-using-treatas-in-dax/",
|
|
"Severity": 2,
|
|
"Scope": "Measure, CalculationItem",
|
|
"Expression": "RegEx.IsMatch(Expression.Replace(\" \",\"\"),\"\\W+(?i)INTERSECT\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"^(?i)INTERSECT\\(\")",
|
|
"CompatibilityLevel": 1400
|
|
},
|
|
{
|
|
"ID": "USE_THE_DIVIDE_FUNCTION_FOR_DIVISION",
|
|
"Name": "[DAX Expressions] Use the DIVIDE function for division",
|
|
"Category": "DAX Expressions",
|
|
"Description": "Use the DIVIDE function instead of using \"/\". The DIVIDE function resolves divide-by-zero cases. As such, it is recommended to use to avoid errors. \r\nReference: https://docs.microsoft.com/en-us/power-bi/guidance/dax-divide-function-operator",
|
|
"Severity": 2,
|
|
"Scope": "Measure, CalculatedColumn",
|
|
"Expression": "Expression.Replace(\" \",\"\").Contains(\"]/\")\n\r\nor\r\n\nExpression.Replace(\" \",\"\").Contains(\"])/\")\n",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "AVOID_USING_THE_IFERROR_FUNCTION",
|
|
"Name": "[DAX Expressions] Avoid using the IFERROR function",
|
|
"Category": "DAX Expressions",
|
|
"Description": "Avoid using the IFERROR function as it may cause performance degradation. If you are concerned about a divide-by-zero error, use the DIVIDE function as it naturally resolves such errors as blank (or you can customize what should be shown in case of such an error).\r\nReference: https://www.elegantbi.com/post/top10bestpractices",
|
|
"Severity": 2,
|
|
"Scope": "Measure, CalculatedColumn",
|
|
"Expression": "RegEx.IsMatch(Expression.Replace(\" \",\"\"),\"\\W+(?i)IFERROR\\(\")\r\nor\r\nRegEx.IsMatch(Expression.Replace(\" \",\"\"),\"^(?i)IFERROR\\(\")",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "MEASURES_SHOULD_NOT_BE_DIRECT_REFERENCES_OF_OTHER_MEASURES",
|
|
"Name": "[DAX Expressions] Measures should not be direct references of other measures",
|
|
"Category": "DAX Expressions",
|
|
"Description": "This rule identifies measures which are simply a reference to another measure. As an example, consider a model with two measures: [MeasureA] and [MeasureB]. This rule would be triggered for MeasureB if MeasureB's DAX was MeasureB:=[MeasureA]. Such duplicative measures should be removed.",
|
|
"Severity": 2,
|
|
"Scope": "Measure",
|
|
"Expression": "Model.AllMeasures.Any(DaxObjectName == current.Expression)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "DATA_COLUMNS_MUST_HAVE_A_SOURCE_COLUMN",
|
|
"Name": "[Error Prevention] Data columns must have a source column",
|
|
"Category": "Error Prevention",
|
|
"Description": "Data columns must have a source column. A data column without a source column will cause an error when processing the model.",
|
|
"Severity": 3,
|
|
"Scope": "DataColumn",
|
|
"Expression": "string.IsNullOrWhitespace(SourceColumn)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "CALCULATED_COLUMNS_MUST_HAVE_AN_EXPRESSION",
|
|
"Name": "[Error Prevention] Calculated columns must have an expression",
|
|
"Category": "Error Prevention",
|
|
"Description": "It is recommended not to use calculated columns. However, if you do, they must have a DAX expression.",
|
|
"Severity": 3,
|
|
"Scope": "CalculatedColumn",
|
|
"Expression": "string.IsNullOrWhiteSpace(Expression)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "UNNECESSARY_COLUMNS",
|
|
"Name": "[Maintenance] Remove unnecessary columns",
|
|
"Category": "Maintenance",
|
|
"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)))",
|
|
"FixExpression": "Delete()",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "UNNECESSARY_MEASURES",
|
|
"Name": "[Maintenance] Remove unnecessary measures",
|
|
"Category": "Maintenance",
|
|
"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",
|
|
"FixExpression": "Delete()",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REMOVE_DATA_SOURCES_NOT_REFERENCED_BY_ANY_PARTITIONS",
|
|
"Name": "[Maintenance] Remove data sources not referenced by any partitions",
|
|
"Category": "Maintenance",
|
|
"Description": "Data sources which are not referenced by any partitions may be removed.",
|
|
"Severity": 1,
|
|
"Scope": "ProviderDataSource, StructuredDataSource",
|
|
"Expression": "UsedByPartitions.Count() == 0",
|
|
"FixExpression": "Delete()",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "REMOVE_ROLES_WITH_NO_MEMBERS",
|
|
"Name": "[Maintenance] Remove roles with no members",
|
|
"Category": "Maintenance",
|
|
"Description": "May remove roles with no members.",
|
|
"Severity": 1,
|
|
"Scope": "ModelRole",
|
|
"Expression": "Members.Count() == 0",
|
|
"FixExpression": "Delete()",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "ENSURE_TABLES_HAVE_RELATIONSHIPS",
|
|
"Name": "[Maintenance] Ensure tables have relationships",
|
|
"Category": "Maintenance",
|
|
"Description": "This rule highlights tables which are not connected to any other table in the model with a relationship.",
|
|
"Severity": 1,
|
|
"Scope": "Table, CalculatedTable",
|
|
"Expression": "UsedInRelationships.Count() == 0",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "OBJECTS_WITH_NO_DESCRIPTION",
|
|
"Name": "[Maintenance] Objects with no description",
|
|
"Category": "Maintenance",
|
|
"Description": "Add descriptions to objects. These descriptions are shown on hover within the Field List in Power BI Desktop. Additionally, you can leverage these descriptions to create an automated data dictionary (see link below).\r\nReference: https://www.elegantbi.com/post/datadictionary",
|
|
"Severity": 1,
|
|
"Scope": "Table, Measure, DataColumn, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup, CalculationItem",
|
|
"Expression": "string.IsNullOrWhitespace(Description)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "PARTITION_NAME_SHOULD_MATCH_TABLE_NAME_FOR_SINGLE_PARTITION_TABLES",
|
|
"Name": "[Naming Conventions] Partition name should match table name for single partition tables",
|
|
"Category": "Naming Conventions",
|
|
"Description": "Tables with just one partition should match their table and partition names.Tables with more than one partition should have each partition name starting with the table name.",
|
|
"Severity": 1,
|
|
"Scope": "Table",
|
|
"Expression": "(Partitions.Count = 1 and Partitions[0].Name <> Name)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "SPECIAL_CHARS_IN_OBJECT_NAMES",
|
|
"Name": "[Naming Conventions] Object names must not contain special characters",
|
|
"Category": "Naming Conventions",
|
|
"Description": "Tabs, line breaks, etc.",
|
|
"Severity": 2,
|
|
"Scope": "Model, Table, Measure, Hierarchy, Perspective, Partition, DataColumn, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup, CalculationItem",
|
|
"Expression": "Name.IndexOf(char(9)) > -1 or\nName.IndexOf(char(10)) > -1 or\nName.IndexOf(char(13)) > -1",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "FORMAT_FLAG_COLUMNS_AS_YES/NO_VALUE_STRINGS",
|
|
"Name": "[Formatting] Format flag columns as Yes/No value strings",
|
|
"Category": "Formatting",
|
|
"Description": "Flags must be properly formatted as Yes/No as this is easier to read than using 0/1 integer values.",
|
|
"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": "[Formatting] Objects should not start or end with a space",
|
|
"Category": "Formatting",
|
|
"Description": "Objects should not start or end with a space",
|
|
"Severity": 3,
|
|
"Scope": "Model, Table, Measure, Hierarchy, Perspective, Partition, DataColumn, CalculatedColumn",
|
|
"Expression": "Name.StartsWith(\" \") or Name.EndsWith(\" \")",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "DATECOLUMN_FORMATSTRING",
|
|
"Name": "[Formatting] Provide format string for \"Date\" columns",
|
|
"Category": "Formatting",
|
|
"Description": "Columns of type \"DateTime\" that have \"Month\" in their names should be formatted as \"mm/dd/yyyy\".",
|
|
"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": "[Formatting] Provide format string for \"Month\" columns",
|
|
"Category": "Formatting",
|
|
"Description": "Columns of type \"DateTime\" that have \"Month\" in their names should be formatted as \"MMMM yyyy\".",
|
|
"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": "[Formatting] Provide format string for measures",
|
|
"Category": "Formatting",
|
|
"Description": "Visible measures should have their format string property assigned",
|
|
"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": "[Formatting] Do not summarize numeric columns",
|
|
"Category": "Formatting",
|
|
"Description": "Numeric columns (integer, decimal, double) should have their SummarizeBy property set to \"None\" to avoid accidental summation in Power BI (create measures instead).",
|
|
"Severity": 3,
|
|
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
|
|
"Expression": "(DataType = \"Int64\" or DataType=\"Decimal\" or DataType=\"Double\")\nand SummarizeBy <> \"None\"\nand not (IsHidden or Table.IsHidden)",
|
|
"FixExpression": "SummarizeBy = AggregateFunction.None",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "PERCENTAGE_FORMATTING",
|
|
"Name": "[Formatting] Percentages should be formatted with thousands separators and 1 decimal",
|
|
"Category": "Formatting",
|
|
"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
|
|
},
|
|
{
|
|
"ID": "INTEGER_FORMATTING",
|
|
"Name": "[Formatting] Whole numbers should be formatted with thousands separators and no decimals",
|
|
"Category": "Formatting",
|
|
"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
|
|
},
|
|
{
|
|
"ID": "RELATIONSHIP_COLUMNS_SHOULD_BE_OF_INTEGER_DATA_TYPE",
|
|
"Name": "[Formatting] Relationship columns should be of integer data type",
|
|
"Category": "Formatting",
|
|
"Description": "It is a best practice for relationship columns to be of integer data type. This applies not only to data warehousing but data modeling as well.",
|
|
"Severity": 1,
|
|
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
|
|
"Expression": "UsedInRelationships.Any()\n\nand \n\nDataType != DataType.Int64",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"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",
|
|
"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))",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "HIDE_FOREIGN_KEYS",
|
|
"Name": "[Formatting] Hide foreign keys",
|
|
"Category": "Formatting",
|
|
"Description": "Foreign keys should always be hidden.",
|
|
"Severity": 2,
|
|
"Scope": "DataColumn, CalculatedColumn, CalculatedTableColumn",
|
|
"Expression": "UsedInRelationships.Any(FromColumn.Name == current.Name and FromCardinality == \"Many\")\nand\r\n\nIsHidden == false",
|
|
"FixExpression": "IsHidden = true",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "MARK_PRIMARY_KEYS",
|
|
"Name": "[Formatting] Mark primary keys",
|
|
"Category": "Formatting",
|
|
"Description": "Set the 'Key' property to 'True' for primary key columns within the column properties.",
|
|
"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": "[Formatting] Hide fact table columns",
|
|
"Category": "Formatting",
|
|
"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\")",
|
|
"FixExpression": "IsHidden = true",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "FIRST_LETTER_OF_OBJECTS_MUST_BE_CAPITALIZED",
|
|
"Name": "[Formatting] First letter of objects must be capitalized",
|
|
"Category": "Formatting",
|
|
"Severity": 1,
|
|
"Scope": "Table, Measure, Hierarchy, CalculatedColumn, CalculatedTable, CalculatedTableColumn, CalculationGroup",
|
|
"Expression": "Name.Substring(0,1).ToUpper() != Name.Substring(0,1)",
|
|
"CompatibilityLevel": 1200
|
|
},
|
|
{
|
|
"ID": "MONTH_(AS_A_STRING)_MUST_BE_SORTED",
|
|
"Name": "[Formatting] Month (as a string) must be sorted",
|
|
"Category": "Formatting",
|
|
"Description": "This rule highlights month columns which are strings and are not sorted. If left unsorted, they will sort alphabetically (i.e. April, August...). Make sure to sort such columns so that they sort properly (January, February, March...).",
|
|
"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
|
|
}
|
|
] |