SSRS Matrix conditional formatting - reporting-services

I have spent some time but can't seem to be able to accomplish this. I have this very simple matrix report and would like to highlight the Amount column if it is different from previous date. Please see below for my report designer screen and my desired output screen with 04/15/2020 highlighted since the amount is different from 04/14/2020. (sorry I circled it instead highlighting it).
Thank you in advance for any suggestions.

You can do this directly in SSRS but its messy and will rarely give perfect results if you are using a dynamic number of columns which is almost always the case.
You end up having to use a bit of VBA code to track the last value but if you use that in a background color expression for example it will mess things up.
There are plenty of questions just like this and most either unanswered or rely on you knowing what the date values are in advance.
NOTE: I have used a windowed function here, I think this is available in SQL 2008 but cannot be certain.
So to start I created some test data
Then I summarize this into a temp table (assuming you need to do summarize by project and date?), its here I use the windowed function to get a row number. We need this in case there are gaps in the dates.
Finally I join the temp table back to itself offsetting by 1 Row (using the row number)
Here's the full code I used in my dataset.
-- create some sample data
DECLARE #t TABLE(dt date, project varchar(10), amount float)
INSERT INTO #t VALUES
('2020-04-01', 'A', 10),('2020-04-01', 'A', 10),('2020-04-01', 'B', 10),('2020-04-01', 'C', 10),('2020-04-01', 'C', 10),
('2020-04-02', 'A', 20),('2020-04-02', 'A', 20),('2020-04-02', 'B', 10),('2020-04-02', 'C', 20),('2020-04-02', 'C', 20),
('2020-04-04', 'A', 25),('2020-04-04', 'A', 15),('2020-04-04', 'B', 10),('2020-04-04', 'C', 25),('2020-04-04', 'C', 25)
-- summarise and add a row number
SELECT project, dt, SUM(amount) as amount , ROW_NUMBER() OVER(PARTITION BY project ORDER BY dt) as RowN
into #x
FROM #t
GROUP BY project, dt
-- join #x to itself offseting by 1 row and calc diff vs previous amount
SELECT
cur.*
, cur.amount - ISNULL(prv.amount, cur.amount) as diff -- if there is no previous amount compare to current amount to difference is zero
FROM #x cur
LEFT JOIN #x prv
ON cur.project = prv.project
and cur.RowN = prv.RowN + 1
This gives us the following results...
Now allwe have to do is use this in our matrix and set the BackgroundColor property of the textbox to something like
=IIF(Fields!diff.Value = 0, Nothing, "#ff8c8c")
This gives us this as the final output.

Related

Getting Scope error on SUM(IIF()) of rows from other table

Currently getting a scope error using the code below, what we're trying to do is count the number of rows that match the conditions we have:
=
SUM(
IIF(
Fields!Defect_Category.Value = "Packaging"
& Fields!Defect_Category.Value = "Major"
& Fields!WorkOrderDisplayID.Value = Fields!Work_Order_Id.Value,
1, 0),
"dsDefects"
)
Work_Order_Id is the "key" of the dsGeneral dataset which is the current scope/dataset of the tablix where we're trying to implement this. Any way we can fix this?
My understanding is that the Scope parameter of SUM is referring to the dataset we're trying to get the sum of (or count of, in this case). When I specify "dsDefects" as the scope of SUM, I get the following error:
The Value expression for the text box 'Textbox101' refers to the field
'Work_Order_Id'. Report item expressions can only refer to fields
within the current dataset scope or, if inside an aggregate, the
specified dataset scope. Letters in the names of fields must use the
correct case.
However, if I remove the scope parameter value, I'm getting the following error:
The Value expression for the text box 'Textbox101' refers to the field
'Defect_Category'. Report item expressions can only refer to fields
within the current dataset scope or, if inside an aggregate, the
specified dataset scope. Letters in the names of fields must use the
correct case.
I think you will need something like this...
=
IIF(
Fields!Defect_Category.Value = "Packaging"
& Fields!Defect_Category.Value = "Major",
LOOKUPSET(Fields!WorkOrderDisplayID.Value, Fields!WorkOrderDisplayID.Value, Fields!WorkOrderDisplayID.Value, "dsDefects").Length,
0)
I'll give a simple example of counting matches from another dataset and that might help you put the two together to get an solution.
If I create two datasets with the following queries, called dsEmp and dsDev respectively
dsEmp
DECLARE #e table (empid int, empname varchar(10))
insert into #e values
(1, 'Bob'), (2, 'Dave')
SELECT * FROM #e
dsDev
declare #d table(empid int, device varchar(10))
insert into #d VALUES
(1, 'Phone'),
(1, 'Laptop'),
(1, 'Desktop'),
(2, 'Phone'),
(3, 'Tablet')
SELECT * FROM #d
Then in my report I add a table bound to dsEmp showing the empID and empName and then in the final column I use the following expression
=LookupSet(
Fields!empid.Value,
Fields!empid.Value,
Fields!empid.Value, "dsDev"
).Length
I get this final output
As lookupset returns a collection, the collection's length is, in fact, the number of items contained in the collection.

How to SUM the values from group in SSRS which are distinct

I have a report where I want to calculate the Grand total but ignore the values of a group which have same value. Below are my tables and the data. The Package No column is grouped and will be unique always, but every unique Package may or may not have same dimensions. I want to SUM the dimension (Length, Width and Height) and it Package No has multiple items in it then have to consider only the first row for summing up the values as the dimensions will remain the same for all the items within the same Package. Can anyone please help me to achieve this result?
Assuming the data that gets returned from your dataset query looks like the sample you show in the image then you should be able to do something like...
=SUM(IIF(Fields!RowNum.Value = 1, Fields!Length.Value, 0))
EDIT after OP update
If the RowNum column is not in your dataset then you will need to add it.
If you dataset query is just a script then it should be as easy as something like this...
SELECT *
, RownN = ROW_NUMBER() OVER(PARTITION BY PackageNo ORDER BY Item)
FROM myTable
If the dataset is the result of a stored proc then you might have to change the dataset query to something like
CREATE TABLE #t (PackageNo varchar(50), Length int, Width int, Height int, Item varchar(50))
INSERT INTO #t
EXEC myStoredProc
SELECT *
, RownN = ROW_NUMBER() OVER(PARTITION BY PackageNo ORDER BY Item)
FROM #t
Once one of the above options are been taken then you should be able to simply apply the =SUM(IIF(Fields!RowN.Value = 1, Fields!Length.Value, 0))
expression in your report totals.

change colour of highest total column value in rdl report

In rdl report i am showing total column as sum aggregation, i want to highlighte highest of that total.
This is a generic answer that makes lots of assumptions. As you question does not contain enough info for a definitive answer but hopefully this will give you enough that you resolve your problem.
To recreate this do the following (and then adapt to suit your specific situation)
Create a new report.
Add a datset and use the following query as the dataset query
-- create some sample data
DECLARE #t TABLE(Customer varchar(10), Product varchar(10), Quantity int)
INSERT INTO #t VALUES
('Dave', 'Hammer', 6), ('Dave', 'Saw', 6),('Dave', 'Hammer', 1),
('George', 'Drill', 3),('George', 'Hammer', 6),('George', 'Saw', 6),('George', 'Hammer', 1),
('Mary', 'Drill', 3),('Mary', 'Hammer', 6),
('Jane', 'Saw', 6),('Jane', 'Hammer', 1),('Jane', 'Drill', 3)
-- return the sample data plus a total per customer
SELECT *
, CustomerTotal = SUM(Quantity) OVER(PARTITION BY Customer)
FROM #t
This give use the following results
Next add a Matrix control to the report.
Drag the fields to the matrix as shown here
Customer to "Rows"; Product to "Columns"; Quantity to "Data"
You should now have a matrix with some row and column groups shown underneath.
next we need to add a total column so right-click the "Product" column group select "Add Total => After"
Finally, we need to test if the value in the total column matches the largest CustomerTotal in our dataset. If it does match then change the textbox color property to "Red".
We can use this expression in the textbox color to do this..
=IIF(
Sum(Fields!Quantity.Value) = MAX(Fields!CustomerTotal.Value, "DataSet1"),
"Red",
"Black")
What this does is take the total quantity in the current scope (the entire row) and compare it to the highest CustomerTotal with the scope "Dataset1" which is the entire dataset. If the two match, set the vlue to "Red", else set it to "Black"
With a bit of a tidy-up the final output looks like this.

Running values for whole chart

I ran with a bit of a problem with SSRS and I would like to ask if you encountered this previously. The report requirement for the report is to show a graph that looks like this.
What I did was I made a Category group for the date value and Series Group for the data shown in Red and Blue. The issue I am having is representing the line pointed in the picture. When I tried to do it in SSRS, it generates N times based on how data the Series Group has. See screenshot below:
My question is would it be possible to ignore the groupings in the expression formula or somewhere so that the running value for both of the series groups in one line?
I was unable to do it on SSRS so I decided to do it in T-SQL instead. I added a column to be used as the value for the running table
WITH t as(
SELECT SUM(Value1) AS 'RunningValue',
[Date]
FROM Table1
WHERE [Group] IN ('Group1', 'Group2')
GROUP BY [Date]
)
SELECT
x.*, t.RunningValue
FROM(
SELECT
[Group],
[Date],
[Description] AS [Description],
SUM(Value1) AS Value1,
SUM(Value2) AS Value2,
SUM(Value3) AS Value3
FROM Table1
GROUP BY [Date], [Group], [Description]
) x
CROSS APPLY t WHERE t.[Date] = x.[Date]
The downside of using this is that it is that it becomes expensive when the table grows as i am basically querying twice.
I also used the link https://social.msdn.microsoft.com/Forums/sqlserver/en-US/74d9affc-ebf3-485c-988e-f28f7049b600/how-to-make-one-of-the-chart-ignore-series-grouping?forum=sqlreportingservices provided by #StrawberryShrub to hide the duplicate line in the graph. Thanks all!

SQL: Can I refer/access data the current row in a window function?

Here is an example. Suppose I have the following table:
id | list
--------+----------
10 |
| 10,20
20 | 10,20
For each id value, I'd like to calculate the number of rows having a list value which contains that id value. The result would be look like that:
id | count of lists
--------+----------
10 | 2
| 0
20 | 2
I suggest a window function should be used, but it seems that I can't access the id value from within such a function.
I totally agree that it is BAD design. This question is about the possibility.
Any MySQL/PostgreSQL solution is fine.
Assuming that you are using MySQL, and assuming that your table has the name test, and assuming that both columns are string types:
SELECT
t1.id, count(t2.list)
FROM
(test t1 LEFT JOIN test t2 ON
(t2.list LIKE CONCAT('%,', t1.id, ',%')) OR
(t2.list LIKE CONCAT('%,', t1.id)) OR
(t2.list LIKE CONCAT(t1.id, ',%')))
GROUP BY t1.id;
Please be aware that this solution might be very slow depending on the number of records you have and depending on the average length of the strings in the list field.
If you need something faster, I think it couldn't be a single query. Perhaps we would have to write a stored procedure or some application logic for that, or use additional tables or columns and a series of multiple SQL statements.
Before I start, as mentioned above, this is a poor design. However, this is how I would query it:
CREATE TABLE #Lists (id int, list varchar(500));
INSERT INTO #Lists (id, list) VALUES
(10, NULL), (NULL, '10,20'), (20, '10,20');
WITH cte AS (
SELECT LEFT(list, INSTR(list, '%,%')-1) AS value, SUBSTRING(list, INSTR(list, '%,%') + 1, 500) AS list FROM #Lists
UNION ALL
SELECT CASE WHEN list LIKE ('%,%') THEN LEFT(list, INSTR(list, '%,%')-1) ELSE list END AS value, CASE WHEN list LIKE ('%,%') THEN SUBSTRING(list, INSTR(list, '%,%') + 1, 500) END AS list FROM cte
WHERE CHAR_LENGTH(list) > 0
)
SELECT value, COUNT(*) FROM cte GROUP BY value;
DROP TABLE #Lists;
This solution allows for any number of values in the list string (like '10,20,30').
Ideally, the list values should be stored in a separate table so that each record has a single value, such as CREATE TABLE BetterDesign (id int, value int) INSERT INTO BetterDesign (id, value) VALUES (10, NULL), (NULL, 10), (NULL, 20), (20, 10), (20, 20). Along with a million other reasons, this is better for querying SELECT value, COUNT(*) FROM BetterDesign GROUP BY value. That being said, I understand the pains of legacy systems.