Tableau's functions - how to find an equivalent to IF EXISTS - function

I'm creating a Tableau Dashboard with 'buttons' which are coloured red or green based on certain criteria and what is selected in the filters. The filters are just a way to select different offices in different regions and when selecting an office the buttons should change colour depending on whether the targets for the different metrics have been hit for that office or not.
The navigation buttons on Tableau won't accommodate this so I've made a work around. For each 'button' I've created a worksheet with just the text of the metric name on the Label mark and a calculated field on the colour mark. I've then added the worksheet to the Dashboard and added an action to go to the corresponding metric dashboard when the 'button' is clicked on.
The issue I'm having is the conditional colouring of one of these metrics. This metric is based on stock levels. For each office there are multiple categories of stock types, each with a corresponding target, with multiple 'bins' in each category. I want the button to turn red if ANY of the combined total of stock in the bins for one category is over the target for that category for that office.
To try and type it logically-
For the currently filtered data: IF EXISTS(FOR EACH OFFICE( FOR EACH CATEGORY: [SUM(BinValue)< CategoryTarget])) THEN 'Green' ELSE 'Red'
I've tried to translate that logic into Tableau's functions in a calculated field and have the following:
SUM(INT({INCLUDE [Category]:Min([CategoryTarget])} > {INCLUDE [Category]:SUM(BinValue)}))
This colouring is correct when I add the Office Name and Category pills to the worksheet to test my logic however when I remove the pills the colouring isn't correct. Something seems to be going wrong when I try to sum the number of categories that are within target levels over all offices and targets.
I've tried so many iterations of the following functions and have been going around in circles for days now:
INCLUDE, EXCLUDE, FIXED, IF, SUM, INT
If anyone knows how to do this properly or even just a different way of being able to conditionally colour buttons on a dashboard I would be incredibly grateful.
The structure of my data is as follows with some dummy data as an example:
Region
SubRegion
Office
Category
Bin
BinValue
CategoryTarget
North
NorthWest
Manchester
Toys
B123
30
50
North
NorthWest
Manchester
Toys
B456
40
50
So for a Stock Level metric selecting any of ALL/North/NorthWest/Manchester filter options should flag as red due to the total of the bins in one category in an office being higher than the target amount for that category for that office.
I've updated my calculated field however I'm still having issues with the grouping showing as true/false correctly.
This is what it is now:
MAX( {INCLUDE Category, Office:Sum(BinValue)} > {INCLUDE Category, Office:MIN(CategoryTarget)} )
With True showing as Red and False Green (we want to be below target hence the green).

When working on the example to showcase the issue I managed to get it working.
I ended up using the following logic:
max({EXCLUDE [Bin]:SUM([Bin Value])} > [Category Target])
This meant that even if most of the Offices in the filter were within their stock level targets, if there was one with stock levels over target the 'button' showed as red.
I published the example I've used anyway in case it helps others in the future.
Link to the Tableau Public dashboard:
https://public.tableau.com/views/ConditionalColouring/Dashboard1?:language=en-GB&:useGuest=true&:display_count=y&:origin=viz_share_link
Thank you very much for the help!

To work with logical conditions, such as testing whether a condition holds for any (or every) record in a group of data rows, it helps to understand that Tableau treats the boolean value "True" as greater than the boolean value "False".
Once you get comfortable with that idea, you can use the functions MAX() (or MIN()) to test whether a condition holds for any record (or for every record, respectively). So MAX(False, False, True, False) is True.
So to tell if any records have an actual value below their target, test MAX([Actual Value] < [Target Value])
You can then combine this idea with dimensions on the viz (or LOD calcs if necessary) to group the data records appropriately before testing your conditions. If you work with the same conditions repeatedly, this type of calculation can be very useful for defining sets that get used in multiple places.
One technical caveat, if your condition test ever evaluates to NULL, then those null values are ignored by MIN() and MAX() - just like other aggregation functions do. So for example, you could test whether every record satisfies a condition using MIN() and get a possibly misleading result if all the non-null values are True (so MIN() reports True). MIN(TRUE, TRUE, NULL, TRUE) = TRUE. If your condition can evaluate to NULL, and you don't want to ignore nulls, but instead treat it as, say, the same as False, then you can use the IFNULL() function to provide a default value for your condition.
As an example, MIN(IFNULL([Actual Value] > [Target Value], FALSE)) returns True only if every record has a value above its target, treating any records with missing values or targets as failing the condition - i.e. not exceeding the target. The choice of whether to have a default value for a condition, and what it should be, are problem dependent of course. If your data does not have null values, you don't have this complication to consider.

Though the data you have given is very less, yet I think this calculation field you require
IF { FIXED [Region], [Sub-Region], [Office], [Category] : SUM([Bin Value])}
> {FIXED [Region], [Sub-Region], [Office], [Category] : MIN([Category Target])} THEN 'RED' ELSE 'GREEN' END
This is based on assumption that for every group of region/sub-region/office/category target value will be same in each row within the group. Therefore MAX/AVG etc. will all work in place of MIN used in the calculation.
See I added two rows in your data
and result

Related

MS Access Sum and IIF expression

I'm with a non-profit. We divide the state up into 16 regions for administrative purposes. I'm trying to find out how many members live in each region. We have 4 different types of members too. There's a table that lists all the members. There is also a table in which we have listed all the towns in the state and put in which of our regions the town is in. For the first member type the record has the value "1" in the TYPE field. I'm trying to write an expression that basically says this. If the TYPE field has a "1" in it, then the condition is true, return 1. Else return 0. Then sum up all the ones.
I didn't build the database or the query. This is the expression that was already there: INDIVIDUAL: Sum(IIf([Membership.TYPE]=1,1,0)) The sum that it comes up with just doesn't make sense.
Edit: I hope I did this right. Here is a table showing what information the query returns except for the 502 and the 96 which I put there as a check. The values in that one column add up to 502 but they should add up to 674. The 96 should be 113.
enter image description here

Searching ALL ROWS in a Group using IIF Expression

I am working on a report that displays patient names (as groups with drilldowns) and several fields related to their visits. I have created a column in the report to display whether or not a specific value appears in the 'LocationID' column. The expression I used is
=IIF(Fields!LocationID.Value="WELL","Y","N")
I thought this was working great, it displays Y or N next to each name to let me know if 'WELL' was in their 'LocationID'. I checked several to ensure that this was going to work and discovered that there was a LocationID code of 'WHS' and since I have the rows ordered by Name and LocationID if there was a WHS visit it shows up at the top of the group and my expression is only seeing this top item. How can this expression be written differently so that it searches the entire result of each group? Depending on the date range a patient may have one visit or they may have ten. I need to check all visits that are returned. Perhaps there is a better method. Thanks in advance.
I agree with jimmy8ball that the easiest way to solve most issues like this is to push some logic back into the SQL layer.
However, if you really want to do this via SSRS functionality, then you could implement a substring search against a lookupset. Assuming you have a patient id in your dataset that is unique for each patient (I hope your group isn't on the name) then...
=Iif(InStr(Join(Lookupset(Fields!patientid.Value, Fields!patientid.Value, Fields!LocationsID.Value, "dataset"), ","), "WELL") > 0, "Y", "N")
Which says, "Search through the dataset for all rows related to my patientid, join every location into a comma deliminated string, search the string for the text "WELL" and return "Y" if it's found.
Obviously if you have locations in your dataset like "WELLY", these will become false positives and you'll have to implement some more nested logic. Try appending a value (perhaps !) to the lookupset return field so that you can search for "WELL!" or some other terminator character.

How to Sum the aggregates of a child group

This should be easy, but I am stuck.
I have a table listing some figures about Qualifications - to achieve which a dataset that is essentially a row per Student is being grouped on Qualification with a Parent Grouping on "Measure" (which is just a bucket of qualifications).
One of the columns is trying to work out the number of students (well, more properly the number of students with a value in a particular field, weighted by another field) in each Measure/Qualification. In the screenshot below, it's the "Pred. Avg" column on the right hand side.
So for the Qualification Row Grouping, that column is calculated by:
=CountDistinct(Iif(IsNothing(Fields!AVG_PTS.Value) = False, Fields!Learner_ID.Value, Nothing), "Qual") * Lookup(Fields!Qual_Code.Value, Fields!Qual_Code.Value, Fields!size.Value, "DS_KS5Nationals_LKP")
This works fine - the values of 35 and 11.5 in that rightmost column are correct for those rows. What the top row should be doing is simply adding up the values in the other rows to give me the number of students in this Measure, in this case to give 46.5. To do that the expression I am using is:
=Sum(CountDistinct(Iif(IsNothing(Fields!AVG_PTS.Value) = False, Fields!Learner_ID.Value, Nothing), "Qual") * Lookup(Fields!Qual_Code.Value, Fields!Qual_Code.Value, Fields!size.Value, "DS_KS5Nationals_LKP"), "Measure")
However as you can see in the screenshot, this returns 2917 instead.
So my question is; Why doesn't that work, and given that it doesn't work how can I, within a parent group, aggregate the results of aggregates inside a child group?
EDIT:
OK so, I have determined that the following works correctly:
=Sum(CountDistinct(Iif(IsNothing(Fields!AVG_PTS.Value) = False, Fields!Learner_ID.Value, Nothing), "Qual"), "Measure")
The problem there is that the Qual row that returns 11.5 is weighted to 0.5. I.E. it actually returns 23, and the Lookup(Fields!Qual_Code.Value, Fields!Qual_Code.Value, Fields!size.Value, "DS_KS5Nationals_LKP") is for that row returning 0.5 and altering it to 11.5...so the question becomes; "how do I force that ...*Lookup(Fields!Qual_Code.Value, Fields!Qual_Code.Value, Fields!size.Value, "DS_KS5Nationals_LKP") into the "Qual" scope, like the CountDistinct() is already in?
The issue here is that you're trying to aggregate values using that Lookup function which only returns one value. There are a couple ways you could go about doing this. One option would be to use the LookupSet function to get the applicable weightings. An even better option is to combine the data in your dataset so that the weighting is available without using a lookup. That way the function can recalculate an any grouping level without you having to force a scope on it. Also, CountDistinct ignores "Nothing" so you can do without the extra IIf statement. Hope that helps.

In SSRS is there a way to make a Row Group expand based on parameter value?

I've got a table with 5 columns, the first 3 of which allow the user to drill down through the levels of detail. Each of these columns (Region, Country & Office) has an associated Parameter so the user can select the geographic region for their report. Each parameter allows the selection of multiple values.
If the user selects 1 Region, 1 Country and 1 Office it's not exactly ideal for them to then have to expand each selection. Is there an expression I can enter somewhere to state that if only 1 value is entered in a parameter then that data set will automatically show as expanded?
This is in SSRS 2008 R2 if that makes any difference.
In the Group Properties for the detail group you can enter an expression for the initial visibility. Right now you probably have that set to "Hide." The expression needs to return a Boolean and could be something like:
=Parameters!Country.Count <> 1
This will have SSRS hide those rows if more (or less) than one value are selected in the parameter Country
But I have seen some unexpected results with this: test thoroughly. In my experience, BIDS handles these better than SSRS itself, so just when you think you've got it all working, it fails miserably once deployed. (Reason number 14 to have a test folder on production SSRS.)

How do i represent an unknown number of columns in SSRS?

I'm working on a rather complex report in Sql Server Reporting Services. My SP returns a dynamic number of columns each of which are dynamically named.
Basically think of a time keeping application. Each column that is dynamic represents a time bucket that time was charged to for that team. If no time was charged to that bucket for the period of time the report covers it doesn't show. Each bucket has its own identifier which i need to be the column headers.
I have an SP that returns this all. It does it by doing a bit of dynamic SQL with an exec statement (ugly i know but I'm on SQL 2000 so a PIVOT option wouldn't work)
I can have an indefinite number of buckets and any or all might show.
I found this - http://www.codeproject.com/KB/reporting-services/DynamicReport.aspx - which is helpful but in the example he has a finite number of columns and he just hides or shows them according to which ones have values. In my case i have a variable number of columns so somehow i need the report to add columns.
Any thoughts?
As long as you know a maximum number of columns, it's possible to do this after a fashion.
First, name the columns with a result from your query, so you can either pass it in to the query or derive it there. Second, just build out the report as if it had the maximum number of columns, and hide them if they are empty.
For example, I had to build a report that would report monthly sales numbers for up to a year, but the months weren't necessarily starting in January. I passed back the month name in one column, followed by the numbers for my report. On the .rdl, I built out 12 sets of columns, one for each possible month, and just used an expression to hide the column if it were empty. The result is the report appears to expand out to the number of columns needed.
Of course, it's not really dynamic in the sense that it can expand out as far as you need without knowing the upper bound.
This can be done. I did this and it works fine.
You don't have to know the maximum number of columns or show and hide columns in my approach. Use a matrix and modify your sp to return dynamic data to the structure mentioned in this blog post http://sonalimendis.blogspot.com/2011/07/dynamic-column-rdls.html
Build 2 related Datasets, first one for the report content, and the second one for the list of its column labels.
The Dataset of the report content must have a fixed number of columns and name. You can allocate some maximum number of columns.
In this example I have the first 2 columns as fixed, or always visible, and a maximum of 4 columns to be displayed by choice through a multivalued parameter, or depends on the query conditions. And as usual, we may have a total as well. So, it may look like this:
Fixed01, Fixed02, Dyna01, Dyna02, Dyna03, Dyna04, Total
The second Dataset with its values will look like this:
Name Label
---- -----
Dyna01 Label01
Dyna02 Label02
Dyna03 Label03
I have omitted the 4th Label to demonstrate that not all columns are being used by a certain query condition. Remember that both Datasets are meant to be related to the same query.
Now create a parameter named, say, #columns; populate its Available Values and Default Values with the second Dataset.
For each of those 4 dynamic columns, set the column visibility with the following expression:
=IIf(InStr(join(Parameters!columns.Value,","),"Dyna01"),false,true)
And for each of their column header Text Boxes, use the following expression:
=Lookup("Dyna01", Fields!Name.Value, Fields!Label.Value, "dsColumns")
As for the Total, here is the expression for its visibility:
= IIf(InStr(join(Parameters!columns.Value, ","), "Dyna01"), false, true)
AndAlso IIf(InStr(join(Parameters!columns.Value, ","), "Dyna02"), false, true)
AndAlso IIf(InStr(join(Parameters!columns.Value, ","), "Dyna03"), false, true)
AndAlso IIf(InStr(join(Parameters!columns.Value, ","), "Dyna04"), false, true)
And here is for its values:
= IIf(InStr(join(Parameters!columns.Value, ","), "Dyna01"), Fields!C01.Value, 0)
+ IIf(InStr(join(Parameters!columns.Value, ","), "Dyna02"), Fields!C02.Value, 0)
+ IIf(InStr(join(Parameters!columns.Value, ","), "Dyna03"), Fields!C03.Value, 0)
+ IIf(InStr(join(Parameters!columns.Value, ","), "Dyna04"), Fields!C04.Value, 0)
That's all, hope it helps.
Bonus, that second Dataset, dsColumns, can also hold other column attributes, such as: color, width, fonts, etc.
I think the best way to do it is add all the columns in your table and edit the visibility property of it with the help of arguments that you get from your SP..this will solve the purpose of dynamic column but when viewing the report you will get a lot of white-space which you can solve with SSRS - Keep a table the same width when hiding columns dynamically? and your report will be ready
I've had the need to do this in the past and the conclusion I came to is "you can't", however I'm not positive about that. If you find a solution, I'd love to hear about it.
An issue that comes to mind is that you need to define the report using the names of the columns that you're going to get back from the stored proc, and if you don't know those names or how many there are, how can you define the report?
The only idea I had on how to do this is to dynamically create the report definition (.rdl file) via C#, but at the time, I wasn't able to find an MS API for doing so, and I doubt one exists now. I found an open source one, but I didn't pursue that route.