{"id":93601,"date":"2022-03-22T22:23:45","date_gmt":"2022-03-22T22:23:45","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=93601"},"modified":"2026-04-15T19:06:48","modified_gmt":"2026-04-15T19:06:48","slug":"tips-and-tricks-with-dax-table-functions-in-paginated-reports","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/tips-and-tricks-with-dax-table-functions-in-paginated-reports\/","title":{"rendered":"DAX Table Functions in Power BI Paginated Reports: Advanced Patterns and Tips"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" id=\"h-executive-summary\">Executive Summary<\/h2>\n\n\n\n<p><strong>This final article in the DAX paginated reports series covers the advanced patterns and workarounds that arise in production report development. The topics are organised by problem type: imitating NOT IN (DAX has IN but no direct NOT IN &#8211; use EXCEPT or FILTER with negated conditions); removing filter columns from output (use SELECTCOLUMNS after filtering); complex OR filters across multiple parameter lists; handling multi-select SSRS parameters; filtering with comma-delimited string inputs; and managing large parameter lists and large dataset outputs. Each section provides a working DAX solution that can be adapted to your specific paginated report requirements.<\/strong><\/p>\n\n\n<p><strong>The series so far:<\/strong><\/p>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/introduction-to-dax-for-paginated-reports\/\">Introduction to DAX for paginated reports<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/how-to-filter-dax-for-paginated-reports\/\">How to filter DAX for paginated reports<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/dax-table-functions-for-paginated-reports-part-1\/\"> \u00a0DAX table functions for paginated reports: Part 1<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/dax-table-functions-for-paginated-reports-part-2\/\">DAX table functions for paginated reports: Part 2<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/tips-and-tricks-with-dax-table-functions-in-paginated-reports\/\">Tips and tricks with DAX table functions in paginated reports<\/a><\/li>\n<\/ol>\n\n\n\n\n<p>To conclude this short set of articles on using Power BI datasets as the source of data for paginated reports, I want to outline a few classic solutions to common challenges in paginated report development with DAX.<\/p>\n\n\n\n<p>Clearly, I cannot recount every paginated report challenge that I have ever met (or heard of) when creating reports from a Power BI dataset. However, as a report developer, it helps to be aware of some of the standard solutions to the challenges that many users encounter.<\/p>\n\n\n\n<p>This article uses the accompanying sample data (<a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/CarsDataWarehouse-1.pbix\">CarsDataWarehouse.pbix<\/a>) as the basis for the DAX that you will use to solve these problems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-imitating-not-in-when-filtering\">Imitating NOT IN when filtering<\/h2>\n\n\n\n<p>One initial frustration shared by SQL developers who move to DAX is that while there is an <code>IN<\/code> operator, there is no direct <code>NOT<\/code> <code>IN<\/code> equivalent.<\/p>\n\n\n\n<p>Fortunately, there are two solutions to this challenge that are simple and easy to apply. You can:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Filter using <code>NOT \u2026. IN<\/code>. As simple as this solution is, it has left many a report developer stumped initially.<\/li>\n\n\n\n<li>Use <code>EXCEPT<\/code><\/li>\n<\/ul>\n<\/div>\n\n\n<p>To begin with, suppose that you want to filter all colours but <em>Black<\/em>, <em>Blue<\/em>, and <em>Green<\/em>. This can be done using <code>NOT \u2026 IN<\/code>, as you can see in the following DAX snippet.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EVALUATE\n\nFILTER\n(\nSUMMARIZECOLUMNS\n(\n&nbsp;DimVehicle[Make]\n,DimVehicle[Model]\n,DimVehicle[Color]\n,\"Labour Cost\", SUM(FactSales[LaborCost])\n)\n,NOT DimVehicle[Color] IN {\"Black\", \"Blue\", \"Green\"}\n)<\/pre>\n\n\n\n<p>You can see a sample of the output from this query in the image below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"330\" height=\"295\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-20.png\" alt=\"\" class=\"wp-image-93602\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Alternatively, you can use <code>EXCEPT<\/code> to produce a similar output &#8211; with the added advantage that this approach avoids the filter column being included in the data returned to the report. What the following piece of DAX does is to create two table variables. The first contains the elements you wish to exclude, while the second contains a full dataset. The <code>DAX<\/code> then subtracts the second dataset from the first to achieve the effect of a SQL <code>NOT IN<\/code> clause.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR ExceptionList =\nSUMMARIZECOLUMNS\n(\t\n DimVehicle[Make]\n,DimVehicle[Model]\n,FILTER(VALUES(DimVehicle[Color]), DimVehicle[Color] = \"Black\" || DimVehicle[Color] = \"Blue\" || DimVehicle[Color] = \"Green\")\n)\nVAR FullList =\nSUMMARIZECOLUMNS\n(\n DimVehicle[Make]\n,DimVehicle[Model]\n)\nEVALUATE\nEXCEPT(FullList, ExceptionList)<\/pre>\n\n\n\n<p>The output from this query is shown below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"141\" height=\"313\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-21.png\" alt=\"\" class=\"wp-image-93603\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>When testing <code>EXCEPT<\/code> queries like this one, you can add the columns that you are filtering on (Make and Model in this example) in the output initially to test the results and ensure that the query is working as expected.<\/p>\n\n\n\n<p>Then, of course, you remove these test columns from the query.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-removing-filter-columns\">Removing filter columns<\/h2>\n\n\n\n<p>If you are using the <code>FILTER()<\/code> function, inevitably, the filtered column will be returned as part of the output. The initial solution is not to use any of the data columns used purely to filter the output in the paginated report. However, this will increase the quantity of data that is calculated and consequently increase the load on the Power BI Service.<\/p>\n\n\n\n<p>A more polished approach is to wrap the output in a <code>SUMMARIZE()<\/code> function to remove any extraneous data so that you are only returning data that is used in the paginated report. You can see this approach applied to the <code>NOT \u2026 IN<\/code> filter that you saw in the previous section in the following DAX snippet:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EVALUATE\n\nSUMMARIZE\n(\nFILTER\n(\nSUMMARIZECOLUMNS\n(\n&nbsp;DimVehicle[Make]\n,DimVehicle[Model]\n,DimVehicle[Color]\n)\n,NOT DimVehicle[Color] IN {\"Black\", \"Blue\", \"Green\"}\n)\n,DimVehicle[Make]\n,DimVehicle[Model]\n,\"Labour Cost\", SUM(FactSales[LaborCost])\n)<\/pre>\n\n\n\n<p>The output from this query is:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"224\" height=\"292\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-22.png\" alt=\"\" class=\"wp-image-93604\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>It is worth noting that you do not have to repeat any calculations (such as the Labour Cost in this example) inside the definition of the core data defined by the <code>SUMMARIZECOLUMNS()<\/code> function (although adding them here as well will not cause any effect on the output).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-custom-selections-in-a-multi-select-ssrs-parameter-list\">Custom selections in a multi-select SSRS parameter list<\/h2>\n\n\n\n<p>DAX can also be used to pre-select multiple elements in a multi-value parameter instead of entering them manually in Power BI Report Builder. This approach has the advantage of making the preselection code-based (and so easier and faster to update in the case of larger lists). You can see this in the following DAX snippet, which uses the <code>DATATABLE()<\/code> function. The entire piece of DAX is then used as the dataset that defines the selected values for the report parameter.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR OptionList = \nDATATABLE(\n\"Make\", STRING,\n   {\n    {\"Bentley\"},\n    {\"Aston Martin\"},\n    {\"Rolls Royce\"},\n    {\"Jaguar\"},\n    {\"Ferrari\"}\n   }\n)\nEVALUATE\nOptionList<\/pre>\n\n\n\n<p>The result of this simple query is a short list of chosen elements that you subsequently use in Power BI Report Builder as the data source for the pre-selected elements of the relevant parameter:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"94\" height=\"121\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-23.png\" alt=\"\" class=\"wp-image-93605\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-complex-or-filters\">Complex OR filters<\/h2>\n\n\n\n<p>The dashboard analytics paradigm championed by Power BI is based on finer and finer slicing of data \u2013 in other words, layering <code>AND<\/code> filters that delve deeper into the data. Paginated reports traditionally deliver lists in this way but also maintain a separate tradition of using input parameters to offer alternative selections. You could define this as the <code>OR<\/code> alternative.<\/p>\n\n\n\n<p>This is where some slightly inventive DAX can be required as, whereas it is easy to filter DAX using alternative values for a single column (or field if you prefer), it can be a little harder to deliver output from a Power BI dataset that amalgamates data where parameters can be from any of multiple fields without a cumulative filter effect. In other words, applying <code>OR<\/code> filters across several fields is a little tricker.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-multiple-or-parameters-where-all-are-compulsory\">Multiple OR parameters \u2013 where all are compulsory<\/h2>\n\n\n\n<p>As a first use-case, imagine a business requirement where one element must be selected from each parameter list, and if any elements from <strong>either<\/strong> list are found, then the data must be returned to the report. In this case, to simplify matters:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The parameter is not multi-select<\/li>\n\n\n\n<li>Nulls are not allowed<\/li>\n<\/ul>\n<\/div>\n\n\n<p>To make things clearer, as a practical example, you want the user to select:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>One Make (from a parameter named <em>Make<\/em>)<\/li>\n\n\n\n<li>One Country (from a parameter named <em>Country<\/em>)<\/li>\n<\/ul>\n<\/div>\n\n\n<p>And these will be used in the DAX filter to output <strong>all<\/strong> vehicles of the specific make <strong>or<\/strong> sold to the specified country.<\/p>\n\n\n\n<p>The DAX should be something like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR Multicriteria = \nCROSSJOIN\n(\n ALL(DimGeography[CountryName])\n,ALL(DimVehicle[Make])\n)\nVAR SelectedCriteria = \nFILTER(\n MultiCriteria\n,\nOR(\n DimGeography[CountryName] = @Country\n,DimVehicle[Make] = @Make\n)\n)\nVAR OrOutput = \nSUMMARIZECOLUMNS\n(\n DimVehicle[Make]\n,DimVehicle[Model]\n,DimGeography[CountryName]\n,SelectedCriteria\n,\"Total Sales\", SUM(FactSales[SalePrice])\n)\nEVALUATE\nOrOutput<\/pre>\n\n\n\n<p>I have left the filter columns in the output from this query so that the results make it clear that the <code>OR<\/code> filter is working as expected. You do not have to deliver these columns as part of the output when delivering final production-ready reports, of course. You can see a subset of the results (where the country variable is <em>France<\/em> and the make variable is <em>Ferrari<\/em>) below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"311\" height=\"274\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-24.png\" alt=\"\" class=\"wp-image-93606\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>A simple <code>OR<\/code> query works like this:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li><code>CROSSJOIN()<\/code> is used to produce the Cartesian join from the fields that are used as the <code>OR<\/code> parameters. These are defined as a table variable named Multicriteria.<\/li>\n\n\n\n<li>This table variable is filtered using an <code>OR()<\/code> function to filter on either of the two filter columns and the results are attributed to a new table variable named <code>SelectedCriteria<\/code>.<\/li>\n\n\n\n<li>The <code>SelectedCriteria<\/code> table variable is used as a filter table inside the <code>SUMMARIZECOLUMNS()<\/code> function that returns the required data.<\/li>\n<\/ol>\n<\/div>\n\n\n<p>There are a few points to note when using this approach:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>CROSSJOIN()<\/code> can handle more than two input tables. You can extend this filtering approach across multiple <code>OR<\/code> columns.<\/li>\n\n\n\n<li><code>CROSSJOIN()<\/code> can be replaced with <code>SUMMARIZECOLUMNS()<\/code> \u2013 and the latter is generally more efficient if you are creating the cartesian result of multiple columns or large datasets. Indeed, you could face a timeout if the output is of any size at all. So, I do not recommend using <code>CROSSJOIN()<\/code> with more than two tables unless each table has no more than a handful of rows. You can see an example of using <code>SUMMARIZECOLUMNS()<\/code> rather than<code> CROSSJOIN()<\/code> in the following snippet of DAX:<\/li>\n<\/ul>\n<\/div>\n\n\n<pre class=\"wp-block-preformatted\">VAR Multicriteria = \nSUMMARIZECOLUMNS\n(\n DimGeography[CountryName]\n,DimVehicle[Make]\n,\"Dummy\", COUNT(FactSales[SalePrice])\n)<\/pre>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Remember to add a calculated column to accelerate the output of the cartesian product when using <code>SUMMARIZECOLUMNS() <\/code>just as you would when sending output data to a paginated report.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-multiple-or-filters-for-single-elements-per-filtered-column\">Multiple OR filters \u2013 for single elements per filtered column<\/h2>\n\n\n\n<p>The simple <code>OR<\/code> approach that you have seen can easily be extended to handle multiple input parameters. Suppose, in this particular case, that you want to filter by any of:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>One Make<\/li>\n\n\n\n<li>Or One Country<\/li>\n\n\n\n<li>Or One Customer<\/li>\n<\/ul>\n<\/div>\n\n\n<p>This will require the following approach:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Define each of the parameters as shown above<\/li>\n\n\n\n<li>Extend the <code>CROSSJOIN()<\/code> function to encompass the Cartesian join on all the fields that will be used in the nested <code>OR()<\/code> functions.<\/li>\n\n\n\n<li>Extend the DAX from the previous example to using nested <code>OR()<\/code> functions to handle more than two parameters, like this:<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Specifically:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\n\nVAR Multicriteria =&nbsp;\n\nCROSSJOIN\n(\n&nbsp;ALL(DimGeography[CountryName])\n,ALL(DimVehicle[Make])\n,ALL(DimClient[ClientName])\n)\n\nVAR SelectedCriteria =&nbsp;\n\nFILTER(\n&nbsp;MultiCriteria\n,\nOR(\n&nbsp;&nbsp;&nbsp;OR(\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DimGeography[CountryName] = @Country\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;,DimVehicle[Make] = @Make\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)\n&nbsp;&nbsp;&nbsp;,DimClient[ClientName] = @Customer\n&nbsp;&nbsp;)\n)\n\n\nVAR OrOutput =&nbsp;\n\nSUMMARIZECOLUMNS\n(\n&nbsp;DimVehicle[Make]\n,DimVehicle[Model]\n,DimGeography[CountryName]\n\n,SelectedCriteria\n\n,\"Total Sales\", SUM(FactSales[SalePrice])\n)\n\nEVALUATE\nOrOutput<\/pre>\n\n\n\n<p>Using <em>France<\/em> as the <code>CountryName<\/code>, <em>Ferrari<\/em> as the <code>Make<\/code> and <em>WunderKar<\/em> as the <code>ClientName<\/code>, this DAX query produces the following output:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"492\" height=\"406\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-25.png\" alt=\"\" class=\"wp-image-93607\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-or-with-multiple-inputs-per-parameter\">OR \u2013 with multiple inputs per parameter<\/h2>\n\n\n\n<p>A different approach is necessary if you want to apply multiple alternative filter options, and allow multiple values for each parameter. In other words, if you have multiple multi-select input parameters, then you need to adopt a slightly different solution.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-defining-the-parameter\">Defining the parameter<\/h2>\n\n\n\n<p>The first thing to do is to handle the input from the multi-select parameter. Imagine using the same two parameters as in the previous example (<code>Country<\/code> &amp; <code>Make<\/code>) however both are multi-select parameters this time. This implies setting up the parameter input from the paginated report with a function that will concatenate the selected values, separated by the pipe character. This means creating a function in SSRS that looks like the following code snippet:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">=JOIN(Parameters!ParameterName.Value, \"|\")<\/pre>\n\n\n\n<p>Imagine, then, that this will result in the Country and Make variables having the following contents:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>@Country contains <em>Belgium|Switzerland|France<\/em><\/li>\n\n\n\n<li>@Make contains <em>Aston Martin|Ferrari<\/em><\/li>\n<\/ul>\n<\/div>\n\n\n<p>The DAX that handles these input parameters and returns data where any of the input values from either parameter are found is the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR InputCountry = @Country \nVAR InputMake  = @Make \nVAR Multicriteria = \nSUMMARIZECOLUMNS\n(\n DimGeography[CountryName]\n,DimVehicle[Make]\n,\"Dummy\", COUNT(FactSales[SalePrice])\n)\nVAR SelectedCriteria = \nFILTER(\n MultiCriteria\n,\nOR(\n PATHCONTAINS(InputCountry, DimGeography[CountryName])\n,PATHCONTAINS(InputMake, DimVehicle[Make])\n)\n)\nVAR OrOutput = \nSUMMARIZECOLUMNS\n(\n DimVehicle[Make]\n,DimVehicle[Model]\n,DimGeography[CountryName]\n,SelectedCriteria\n,\"Total Sales\", SUM(FactSales[SalePrice])\n)\nEVALUATE\nOrOutput<\/pre>\n\n\n\n<p>The output returned from this query is the following:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"292\" height=\"292\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-26.png\" alt=\"\" class=\"wp-image-93608\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The main difference between this approach and the previous technique is that <code>PATHCONTAINS()<\/code> is used instead of a simple equality operator. This allows the DAX to filter on the multiple elements contained in each input parameter. This code snippet also shows that you can pass the contents of a paginated report variable directly to a DAX variable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-multiple-or-parameters-where-none-are-compulsory\">Multiple OR parameters \u2013 where none are compulsory<\/h2>\n\n\n\n<p>As a second example of an <code>OR<\/code> filter challenge, imagine that you want to extend the previous <code>OR<\/code> query but that you do not want to make any of the parameters compulsory, and each parameter can nonetheless contain potentially multiple elements to filter on. Here the challenge arises because paginated reports do not allow for multi-select variables that can also allow <code>NULLs<\/code>.<\/p>\n\n\n\n<p>The classic solution to this challenge is to add a <em>&lt;No Selection&gt;<\/em> element to the list of parameters. In essence, you must add to each parameter list a value that is not actually present in the field that will be used as a filter. You then allow the possibility of passing this value back as a filter criterion. As you are applying an <code>OR<\/code> filter, the additional value that you are adding is not present in the data, and it will have no effect on the output. You can even set this value as the default for the relevant parameter.<\/p>\n\n\n\n<p>This is a two-stage process. Firstly, you need to add a <em>&lt;No Selection&gt;<\/em> (or any term that is not found in the data) element to the parameter list. Secondly, you need to create a multiple <code>OR<\/code> filter as you saw previously.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-adding-lt-no-selection-gt-to-the-parameter-list\">Adding &lt;No Selection&gt; to the parameter list<\/h2>\n\n\n\n<p>The following short piece of DAX is one way to add an added element to a parameter list. This is used to populate the multi-select parameter in <em>SSRSDEFINE<\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE \nVAR MainList = \nSUMMARIZECOLUMNS\n(\nDimGeography[CountryName]\n)\nVAR NoSelectElement = {\"&lt;No Selection&gt;\"}\nEVALUATE\nUNION(MainList, NoSelectElement)\nORDER BY DimGeography[CountryName]<\/pre>\n\n\n\n<p>The output (that you will see in a parameter list) is the following:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"115\" height=\"204\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-27.png\" alt=\"\" class=\"wp-image-93609\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can then use the DAX that you saw previously (using the <code>Selectedcriteria<\/code> and <code>MultiCriteria<\/code> table variables) to filter the output data. Even if <em>&lt;No Selection&gt;<\/em> is passed as an input parameter, as this is an <code>OR<\/code> filter, <em>&lt;No Selection&gt;<\/em> value that is passed in as a parameter element will have no effect on the output.<\/p>\n\n\n\n<p>A couple of points are worth noting here:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The variable <code>NoSelectElement<\/code> is a table containing a single row of data \u2013 all created using the DAX Row Constructor.<\/li>\n\n\n\n<li><code>ORDER BY<\/code> uses the data lineage of the first table in the <code>UNION()<\/code> function to specify the field to sort on.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-using-a-comma-delimited-list-of-input-values-to-filter-the-output\">Using a comma-delimited list of input values to filter the output<\/h2>\n\n\n\n<p>Although most users apply filters to narrow down the output data in a report, it is also possible to enter lists of data into a paginated report parameter to specify a precise set of output records. This approach has known limitations- most notably that there is a limit to the number of elements that can be entered as a delimited list in a report variable. Nonetheless, I have seen this kind of approach used on many occasions, so it is worth explaining here.<\/p>\n\n\n\n<p>As far as the DAX is concerned, the credit for the following code snippet goes to a superb piece of DAX by Chris Webb that I have very slightly tweaked to adapt to suit the requirements of Paginated Report input. You can find his original code <a href=\"https:\/\/blog.crossjoin.co.uk\/2018\/05\/16\/a-new-approach-to-handling-ssrs-multi-valued-parameters-in-dax-queries\/\">here<\/a> (where the concepts behind the core DAX are explained).<\/p>\n\n\n\n<p>What this DAX does is to take a comma-delimited string of text elements and:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Replaces the commas with vertical bars.<\/li>\n\n\n\n<li>Creates a table of the text elements (this is the clever bit).<\/li>\n\n\n\n<li>Uses this table as a filter inside a <code>CALCULATETABLE()<\/code> function.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Of course, the choice of the delimiter character is up to you. All you have to do is to enter the delimiter as the second element inside the <code>SUBSTITUTE()<\/code> function.<\/p>\n\n\n\n<p>The code snippet is the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR InputList = @InputListOfElements\nVAR NewInputList =\n    SUBSTITUTE(\n        InputList ,\n        \",\",\n        \"|\"\n    )\nVAR CaseCount =\n    PATHLENGTH (NewInputList)\nVAR NumberTable =\n    GENERATESERIES(1, CaseCount, 1)\nVAR CustomerTable =\n    GENERATE (\n        NumberTable,\n        VAR CurrentKey = [Value]\n        RETURN\n            ROW (\"Key\", PATHITEM (NewInputList, CurrentKey))\n    )\nVAR GetKeyColumn =\n    SELECTCOLUMNS (CustomerTable, \"Key\", [Key])\nVAR FilterTable =\n    TREATAS (GetKeyColumn, DimCLient[CustomerID])\nEVALUATE\nCALCULATETABLE(\nSUMMARIZECOLUMNS(\n DimCLient[ClientName]\n,DimVehicle[Make]\n,DimVehicle[Model]\n,\"Sales\", SUM(FactSales[SalePrice])\n)\n,FilterTable\n)<\/pre>\n\n\n\n<p>The major challenge here is the (undocumented) limitation on the number of characters that a paginated report variable will accept. I estimate this to be around 11,000 characters approximately. So, should you need to parse longer lists, the solution is to use several input parameters in SSRS. The multiple input parameters are then combined into one variable in the DAX that returns the final output. This can be done in DAX using a simple concatenation of DAX variables rather like the example below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">VAR INP01 = @Param01\nVAR INP02 = @Param02\nVAR INP03 = @Param03\n\nVAR InputList = INP01 &amp; \",\" &amp; INP02 &amp; \",\" &amp; INP03<\/pre>\n\n\n\n<p>Alternatively, you can combine the variables using SSRS code by setting the input variable for the dataset as a function using code like the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">=Parameters!Param01.Value &amp; IIF(IsNothing(Parameters!Param02.Value), \"\", \",\" &amp; Parameters!Param02.Value) &amp; IIF(IsNothing(Parameters!Param03.Value), \"\", \",\" &amp; Parameters!Param03.Value)<\/pre>\n\n\n\n<p>While going beyond the classic approaches to reporting, this simple evolution of paginated report parameter selection opens up a range of possibilities for report data selection.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-pass-rscustomdaxfilter-output-to-a-table-variable\">Pass RSCustomDaxFilter output to a table variable<\/h2>\n\n\n\n<p>One frustrating limitation to the <code>RSCustomDaxFilter<\/code> function is that it cannot be used twice with the same input variable inside the same DAX code block. Yet there will inevitably be times when you need to reuse the multiple filter elements to shape certain data outputs. One solution to this challenge is to use <code>RSCustomDaxFilter<\/code> to populate a table variable. This table variable can then be reused as a filter table inside a <code>CALCULATETABLE()<\/code> function. Indeed, you may prefer this technique as a coding approach for its elegance and versatility, even if you only need to use the elements in a multi-value parameter in DAX.<\/p>\n\n\n\n<p>You can see this approach applied in the following short piece of DAX:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR FilterTable = \nSUMMARIZECOLUMNS(\nRSCustomDaxFilter(@Make,EqualToCondition,[DimVehicle].[Make],String)\n)\nEVALUATE\nCALCULATETABLE(\nSUMMARIZECOLUMNS(\nDimCLient[ClientName]\n,DimGeography[CountryName]\n)\n,FilterTable\n)<\/pre>\n\n\n\n<p>You need to be aware that this code snippet will not run in DAX Studio as the <code>RSCustomDaxFilter()<\/code> function is specific to SSRS. So you will have to test it inside a real SSRS report created using Power BI Report Builder.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-alternative-input\">Alternative input<\/h2>\n\n\n\n<p>One paginated report UI requirement that surfaces fairly frequently is the capability to allow the user to select the actual parameter (and not the parameter contents) that can be used inside a query. In other words, you want to use a single query to produce different outputs depending on a user selecting one of several potential input parameters. I have seen this most frequently in cases where multiple date parameters are displayed in an SSRS report, and one date only must be specified to be used as the basis for a filter.<\/p>\n\n\n\n<p>In this example, however (given the simplicity of the sample dataset), assume that, instead of using dates, the colour and make parameters are passed to the DAX query along with a third parameter that indicates which of these two initial parameters should filter the output.<\/p>\n\n\n\n<p>To resume, the available parameters that are defined in the report are:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>InputSelector<\/code> \u2013 The choice of which parameter will be applied (colour or make)<\/li>\n\n\n\n<li><code>Colour<\/code> \u2013 the list of colours to filter on<\/li>\n\n\n\n<li><code>Make<\/code> \u2013 The list of makes to filter on<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The actual DAX is shown immediately below, with the explanation afterwards.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DEFINE\nVAR    InputSelector = @InputSelector\nVAR    ColourChoice = @Colour\nVAR    MakeChoice = @Make\nVAR CoreTable = \nSUMMARIZECOLUMNS\n(\n DimGeography[SalesRegion]\n,DimGeography[CountryName]\n,DimVehicle[Make]\n,DimVehicle[Color]\n,\"Total Sales\", SUM(FactSales[SalePrice])\n,\"No. Sales\", COUNT(FactSales[SalePrice])\n)\nVAR SelectedColour =\nADDCOLUMNS(\nFILTER(\nCoreTable\n,DimVehicle[Color] = ColourChoice\n)\n,\"TableSelector\", \"Colour\"\n)\nVAR SelectedMake =\nADDCOLUMNS(\nFILTER(\nCoreTable\n,DimVehicle[Make] = MakeChoice\n)\n,\"TableSelector\", \"Make\"\n)\nVAR OutputData = \nSUMMARIZE\n(\nFILTER\n(\nUNION(SelectedColour, SelectedMake)\n,[TableSelector] = InputSelector   \n)\n,DimGeography[SalesRegion]\n,DimGeography[CountryName]\n,DimVehicle[Color]\n,\"Total Sales\", SUM(FactSales[SalePrice])\n,\"No. Sales\", COUNT(FactSales[SalePrice])\n)\nEVALUATE\nOutputData<\/pre>\n\n\n\n<p>Running this code in DAX studio will display the Query Parameters dialog-where I have entered:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Colour as the parameter to use in the query<\/li>\n\n\n\n<li>Red as the colour (this will be used to filter the output)<\/li>\n\n\n\n<li>Ferrari as the make (this will not be used in to filter the output)<\/li>\n<\/ul>\n<\/div>\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"350\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-28.png\" alt=\"\" class=\"wp-image-93610\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Using these parameter settings only returns red cars-whether they are Ferraris or not, as you can see below.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"423\" height=\"294\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-29.png\" alt=\"\" class=\"wp-image-93611\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>How it works:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The SSRS variables are passed to DAX variables.<\/li>\n\n\n\n<li>A table variable is created containing the required (unfiltered) output data. This table contains the two columns that could be used to filter data.<\/li>\n\n\n\n<li>Two table variables (<code>SelectedColour<\/code> and <code>SelectedMake<\/code>) are created, each of which extends the initial table variable with ad added column containing the filter type (colour or make) that was used to filter the contents of the table variable.<\/li>\n\n\n\n<li>The two filtered table variables are <code>UNIONed<\/code> into a fourth table variable. This table is filtered (using the added column) only to display the output using the chosen parameter type.<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Notes:<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>When the <code>TableSelector<\/code> column (that is added to the table variables <code>SelectedMake<\/code> and <code>SelectedColour<\/code>) is used to filter the final <code>OutputData<\/code> table variable, the column name must be in square brackets inside the <code>FILTER()<\/code> function.<\/li>\n\n\n\n<li>The <code>SUMMARIZE()<\/code> function is only required to remove the filter columns. If you want to keep the filter columns in the output, then you can remove the <code>SUMMARIZE().<\/code><\/li>\n\n\n\n<li>Alternatively, it is possible to create multiple datasets instead (one for each filter type) and have multiple tables for the output in the report \u2013 and then use the selector variable to control the visibility of objects in the actual report. However, this approach entails data being sent to the report from two or more queries \u2013 and this can be voluminous. Moreover, this alternative approach rapidly makes for a complex report if there are multiple visuals that use the alternative output data.<\/li>\n\n\n\n<li>It is, of course, possible to have more than two alternative filters. All you have to do is to create multiple table variables (one for each of the alternative selections) then nest <code>UNION()<\/code> functions to aggregate all data into the <code>OutputData<\/code> table.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-final-comments\">Final comments<\/h2>\n\n\n\n<p>While it is clearly impossible to cover every DAX challenge that you will face when developing paginated reports based on a Power BI dataset, there are some limitations that you need to be aware of \u2013 particularly before promising your clients or bosses a failsafe reporting solution.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-large-dataset-output\">Large dataset output<\/h2>\n\n\n\n<p>It is fair to say, at risk of extreme generalisation, that Power BI and DAX are designed to aggregate and resume data rather than deliver large amounts of list-based data to users. Consequently, you need to be aware that very wide and deep paginated reports could simply overload the Power BI service and cause timeouts.<\/p>\n\n\n\n<p>Clearly, the definitions of what defines a \u201cwide and deep\u201d report are open to discussion. Equally, much will depend on the resource level that you have chosen and the stress placed on the Power BI Premium service by multiple simultaneous user requests (for dashboards and paginated reports), as well as any system configuration tweaks that you have carried out.<\/p>\n\n\n\n<p>Nonetheless, there are limits on the amount of data that can be returned using paginated reports in Power BI, and it is important not to attempt to use an SSRS-like approach to use the Power BI service as a data export and delivery vehicle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-large-parameter-list-input\">Large parameter list input<\/h2>\n\n\n\n<p>The paginated report interface does have its limits as far as the number of elements that can be selected from a multi-value parameter list. While no one seems to know the exact value, it\u2019s obvious when it has been reached because a report stops functioning correctly. This is something else that has to be lived within paginated reports. The only real solutions are to apply interface tweaks such as having a hierarchy of parameters where selecting one parameter restricts the selection in a second parameter \u2013 and so on. This equates to the Power BI dashboard technique of using hierarchical slicers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>In this article \u2013 along with the previous articles \u2013 you have learned the core approaches needed to use DAX effectively to query Power BI datasets for paginated reports. You can now deliver these reports either as stand-alone elements accessed from Power BI workspaces (or, better still, from the Power BI App) or embedded in Power BI dashboards using the paginated report visual.<\/p>\n\n\n\n<p>You now have, in a single platform, a wide-ranging series of options to deliver the data that your users require both as dashboards and as list-style reports. Moreover, all the varied output styles can be created from a single source of data \u2013 a Power BI data model.<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Tips and tricks with DAX table functions in paginated reports<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do I use NOT IN in DAX for paginated reports?<\/h3>\n            <div class=\"faq-answer\">\n                <p>DAX has the IN operator but no direct NOT IN equivalent. Simulate it using EXCEPT: EXCEPT(ALL(TableName[Column]), FilterTable) returns all rows in the column that are not in the filter table. Alternatively, use FILTER with a NOT condition: FILTER(ALL(TableName[Column]), NOT TableName[Column] IN {value1, value2}). For paginated reports with parameter inputs, build the exclusion filter from the parameter values and apply EXCEPT against the full column table. This approach is covered in the &#8216;Imitating NOT IN when filtering&#8217; section of this article.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do I build a complex OR filter in DAX for a paginated report?<\/h3>\n            <div class=\"faq-answer\">\n                <p>DAX&#8217;s FILTER function and CALCULATETABLE stack conditions with AND logic by default. To apply OR logic across different columns, use UNION to combine separate FILTER results: UNION(FILTER(Table, Table[Column1] = @Param1), FILTER(Table, Table[Column2] = @Param2)) returns rows matching either condition. For multi-select parameters where OR must apply across multiple values, this approach is extended using UNION across each parameter value. The CALCULATETABLE function can also be used with an OR condition in its filter argument for simpler OR cases.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do I handle a comma-delimited list of values as a DAX filter input?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Paginated reports can accept text input that contains a comma-delimited list (e.g., &#8216;Ferrari,Lamborghini,Bugatti&#8217;). To use this as a DAX filter, the RSCustomDaxFilter function (or equivalent PATHCONTAINS approach) processes the string input into a filter table. The approach: split the input string into individual values using a helper function, create a table of values, then use FILTER or CALCULATETABLE to match against the column. The &#8216;Using a comma-delimited list of input values to filter the output&#8217; section of this article provides a complete implementation.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I add a &#039;No Selection&#039; option to a multi-value parameter in a Power BI paginated report?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use a DAX expression that adds an additional row to the parameter dataset: UNION(DATATABLE(&#8216;ParameterField&#8217;, STRING, {{&#8216;&lt;No Selection&gt;&#8217;}}), SUMMARIZE(Table, Table[Column])). This adds &#8216;&lt;No Selection&gt;&#8217; as the first item in the parameter dropdown. In the DAX query for the report dataset, check for this value and return all rows if it is selected: IF(SELECTEDVALUE(ParameterTable[ParameterField]) = &#8216;&lt;No Selection&gt;&#8217;, Table, FILTER(Table, Table[Column] = SELECTEDVALUE(ParameterTable[ParameterField]))). The complete pattern is in the &#8216;Adding &lt;No Selection&gt; to the parameter list&#8217; section.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Advanced DAX patterns for Power BI paginated reports: simulate NOT IN, remove filter columns, build complex OR filters across multi-select parameters, handle comma-delimited list inputs, and manage large dataset output.&hellip;<\/p>\n","protected":false},"author":2181,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[143528,53],"tags":[5134],"coauthors":[12351],"class_list":["post-93601","post","type-post","status-publish","format-standard","hentry","category-bi-sql-server","category-featured","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93601","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/2181"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=93601"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93601\/revisions"}],"predecessor-version":[{"id":109855,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93601\/revisions\/109855"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=93601"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=93601"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=93601"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=93601"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}