How to fix SSRS does NOT count NULL as value? - sql-server

I'm creating a report using SSRS, and I have a bunch of departments and I need to count the total of their running statuses. This is the result from my table
Please note, the UNKNOWN department shows NULL inside the table. i hard coded to 'UNKNOWN' --ISNULL(department,'UNKNOWN')
And i have tested the table has NULL record and I can count those NULL record COUNT(*)
However, it seems like SSRS does not count NULL values.
the SSRS expression i had its =COUNT(Fields!ID.Value)
I need UNKNOWN rows count just as other department
How do I fix this?

I think your problem comes from how the query was written. This is a guess (you didn't provide the query) but I expect you did something like this:
/* Start Demo Data */
DECLARE #Departments TABLE (DepartmentID INT IDENTITY, Name NVARCHAR(50));
INSERT INTO #Departments (Name) VALUES
('Architect'),('Business Intelligence Analyst'),('Data Analyst'),
('Database'),('Information Technology'),('Technical Analyst');
DECLARE #Tickets TABLE (TicketID INT IDENTITY, CreateDateUTC DATETIME DEFAULT GETUTCDATE(), DepartmentID INT, Status NVARCHAR(50));
INSERT INTO #Tickets (DepartmentID, Status) VALUES
(1, 'Completed'),
(2, 'Completed'),(2, 'Completed'),
(3, 'Completed'),(3, 'Completed'),(3, 'Completed'),(3, 'Completed'),(3, 'Completed'),
(3, 'Failure'),(3, 'Failure'),(3, 'Running'),(3, 'Running'),(3, 'Failure'),
(4, 'Completed'),
(5, 'Completed'),(5, 'Completed'),(5, 'Failure'),(5, 'Running'),(5, 'Completed'),
(6, 'Completed'),
(7, 'Failure'),(7, 'Completed');
/* End Demo Data */
SELECT COALESCE(d.Name,'Unknown') AS Department,
COUNT(CASE WHEN t.Status = 'Completed' THEN 1 END) AS Completed,
COUNT(CASE WHEN t.Status = 'Failure' THEN 1 END ) AS Failure,
COUNT(CASE WHEN t.Status = 'Running' THEN 1 END ) AS Running,
COUNT(t.Status) AS Total
FROM #Departments d
INNER JOIN #Tickets t
ON t.DepartmentID = d.DepartmentID
GROUP BY d.Name
ORDER BY Department
Department Completed Failure Running Total
-----------------------------------------------------------------
Architect 1 0 0 1
Business Intelligence Analyst 2 0 0 2
Data Analyst 5 3 2 10
Database 1 0 0 1
Information Technology 3 1 1 5
Technical Analyst 1 0 0 1
This will find all the tickets with a matching department ID in the tickets table, but it will not return any tickets which have a non-matching value in the departmentID column, a NULL for example.
If you change your approach to something like:
SELECT COALESCE(d.Name,'Unknown') AS Department,
COUNT(CASE WHEN t.Status = 'Completed' THEN 1 END) AS Completed,
COUNT(CASE WHEN t.Status = 'Failure' THEN 1 END ) AS Failure,
COUNT(CASE WHEN t.Status = 'Running' THEN 1 END ) AS Running,
COUNT(t.Status) AS Total
FROM #Tickets t
LEFT OUTER JOIN #Departments d
ON t.DepartmentID = d.DepartmentID
GROUP BY d.Name
ORDER BY Department
You're now asking for all the tickets, and joining that to the departments with a LEFT OUTER JOIN which allows non-matching rows from Tickets to be returned as well. When there is a non-matching (including NULL) value in the departmentID column, it's still part of the result set.
Department Completed Failure Running Total
-----------------------------------------------------------------
Architect 1 0 0 1
Business Intelligence Analyst 2 0 0 2
Data Analyst 5 3 2 10
Database 1 0 0 1
Information Technology 3 1 1 5
Technical Analyst 1 0 0 1
Unknown 1 1 0 2

Related

How to check values of different rows of a table

I have below sample input table. In real it has lots of records.
Input:
ID
Classification
123
1
123
2
123
3
123
4
657
1
657
3
657
4
For a 'ID', I want it's records should have 'Classification' column contains all the values 1, 2, 3 and 4. If any of these values are not present then that ID's records should be considered as an exception. The output should be as below.
ID
Classification
Flag
123
1
0
123
2
0
123
3
0
123
4
0
657
1
1
657
3
1
657
4
1
Can someone please help me with how can this can be achieved in sql server.
Thanks.
There are a couple of options here, which is more performant is up to you to test, not me (especially when I don't know what indexes you have). One uses conditional aggregation, to check that all the values are there, and the other uses a subquery and counts the DISTINCT values (as I don't know if there could be duplicate classifications):
SELECT *
INTO dbo.YourTable
FROM (VALUES(123,1),
(123,2),
(123,3),
(123,4),
(657,1),
(657,3),
(657,4))V(ID,Classification);
GO
CREATE CLUSTERED INDEX CI_YourIndex ON dbo.YourTable (ID,Classification);
GO
SELECT ID,
Classification,
CASE WHEN COUNT(CASE YT.Classification WHEN 1 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 2 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 3 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 4 THEN 1 END) OVER (PARTITION BY ID) > 0 THEN 1 ELSE 0
END AS Flag
FROM dbo.YourTable YT;
GO
SELECT ID,
Classification,
CASE (SELECT COUNT(DISTINCT sq.Classification)
FROM dbo.YourTable sq
WHERE sq.ID = YT.ID
AND sq.Classification IN (1,2,3,4)) WHEN 4 THEN 1 ELSE 0
END AS Flag
FROM dbo.YourTable YT;
GO
DROP TABLE dbo.YourTable;

Identify duplicates based on multiple columns and parent row

This is an example of table data that I am working on (the table contained a lot of columns, I am showing here only the relevant ones):
Id
job_number
status
parent_id
1
42FWD-42
0
0
2
42FWD-42
1
1
3
42FWD-42
5
1
Id is auto generated. parent_id links the job using the id.
When a new job is created via the app, a new row is created (with status "0"). The auto-generated Id is then used for subsequent rows of same job, and set as parent id.
Another record with status "1" (which is code for started) is also created just after parent record.
Explanation of the problem: due to a bug in the app, there are duplicate set of rows for the same job.
Example of problem
Id
job_number
status
parent_id
1
42FWD-42
0
0
2
42FWD-42
0
0
3
42FWD-42
1
1
4
42FWD-42
1
2
5
42FWD-42
5
1
As you can see from this example, due to the bug, there are 2 rows with "0" status for the same job, and 2 rows with "1" status.
This creates a lot of problems in operation in app where the job is updated using the job number.
The status number should not repeat for a specific job.
What I want to do is to find all duplicates like those in example. For example, I want a query where I can find all duplicates which have same job number, but different parent_id and NO "5" status.
Example result using the example table above, I need the query to return:
Id
job_number
status
parent_id
2
42FWD-42
0
0
4
42FWD-42
1
2
Explanation of this result:
Row with Id=1 is considered the correct record because it has an associated record with status "5"
Row with Id=2 is considered duplicate and its associated records are also considered duplicate
Another possible case: there are duplicate rows, but none have status=5. These rows can be discarded, ie need not be shown in results.
A brief explanation of how the query works will be appreciated.
EDIT:
I forgo to add an important information:
job_number is case sensitive.
ie: 42FWD-42 and 42fwd-42 are different and valid job number. They should not be considered duplicates, and are 2 separate jobs.
The reason for this is the actual job number is not small text as in my example. It is a long string like a guid.
First I must mention you should block identical rows by means of a unique constraint. I suggest that once you have eliminated all duplicates you put up a such a constraint to keep this from happening again.
Now for your question, you can do this by grouping on the duplicate columns, and have only those that count more than one.
Here is an example
declare #t table (id int, job_number varchar(10), status int, parent_id int)
insert into #t
values (1, '42FWD-42', 0, 0), (2, '42FWD-42', 0, 0), (3, '42FWD-42', 1, 1), (4, '42FWD-42', 1, 2), (5, '42FWD-42', 5, 1)
select max(t.id) as id, t.job_number, t.status
from #t t
group by t.job_number, t.status
having count(*) > 1
the result is
id job_number status
2 42FWD-42 0
4 42FWD-42 1
and to get also the parent_id you can add a self join
select max(t.id) as id,
t.job_number,
t.status,
(select t2.parent_id from #t t2 where t2.id = max(t.id)) as parent_id
from #t t
group by t.job_number, t.status
having count(*) > 1
this returns
id job_number status parent_id
2 42FWD-42 0 0
4 42FWD-42 1 2
EDIT
To solve the addional problem in the edit of your question, about the case sensitive, you can fix that by using a COLLATE in your field retrieval and your comparision
this should do it
declare #t table (id int, job_number varchar(10), status int, parent_id int)
insert into #t
values (1, '42FWD-42', 0, 0),
(2, '42FWD-42', 0, 0),
(3, '42FWD-42', 1, 1),
(4, '42fwd-42', 1, 2), -- LOWERCASE !!!
(5, '42FWD-42', 5, 1)
select max(t.id) as id,
t.job_number COLLATE Latin1_General_CS_AS,
t.status,
(select t2.parent_id from #t t2 where t2.id = max(t.id)) as parent_id
from #t t
group by t.job_number COLLATE Latin1_General_CS_AS, t.status
having count(*) > 1
and now the result will be
id job_number status parent_id
2 42FWD-42 0 0
Yet another edit
Now, suppose you need to use the result of these duplicate id's in another query, you could do something like this
select t.*
from #t t
where t.id in ( select max(t.id) as id
from #t t
group by t.job_number COLLATE Latin1_General_CS_AS, t.status
having count(*) > 1
)
What I am doing here is getting only the duplicate id's in a form that can be used to feed a where clause in another query.
This way you can use the result set in any way you wish.
Also note that for this we don't need the self join to retrieve the parent_id anymore.
One possible use of this could be to delete duplicate rows, you can write
delete from yourtable
where id in ( select max(t.id) as id
from #t t
group by t.job_number COLLATE Latin1_General_CS_AS, t.status
having count(*) > 1
)
you can try to use ROW_NUMBER window function to get duplicate row data and its id by job_number, then using cte recursive to find all error records by this id
Query 1:
;WITH CTE AS (
SELECT *,ROW_NUMBER() OVER (PARTITION BY job_number ORDER BY Id) rn
FROM T
WHERE status = 0
), CTE1 AS (
SELECT id,job_number,status,parent_id
FROM CTE
WHERE rn > 1
UNION ALL
SELECT t.id,t.job_number,t.status,t.parent_id
FROM CTE1 c INNER JOIN T t
ON c.id = t.parent_id
)
SELECT *
FROM CTE1
Results:
| id | job_number | status | parent_id |
|----|------------|--------|-----------|
| 2 | 42FWD-42 | 0 | 0 |
| 4 | 42FWD-42 | 1 | 2 |

Passing values into CASE statement

and thank you all in advance for your help.
I'm trying to take the results from two separate queries and include them in a third query that has a CASE statement. I've had some success but I'm not able to present the results of the third query in the proper order. The purpose of this is to show the employee count for each department under the different managers. So far I can only load separately the manager names and their departments and employee department count totals by department. What I can't figure out is how to get the manager names in and the employee department count in for each manager row. Below are the two source queries I've used so far and the query with the CASE statement. I've also looked at UNPIVOT function with no success yet.
a) This simple query lists each primary manager name. There are also sub managers that will be returned using a hierarchy query later.
select name from employees "Boss" where employeeid in
(‘1’,'5','25','84','85');
b) This query returns the department id count for each main manager (‘1’,'5','25','84','85') as well as all sub-managers.
select departmentid, count(departmentid) COUNT from employees
where departmentid = departmentid and level <= 3
connect by prior employeeid = bossid
start with employeeid = 5
group by departmentid
order by departmentid;
c) Here’s a CASE statement that outputs exactly as desired. The problem here is the select statement currently outputs only the manager names and the manager departments into the columns. What I need to do is output both the manager names and the manager's employee department counts into the individual manager row columns. I've tried to do a separate select of the manager names to get the ‘Boss’ column and another select to include the department counts. But that got messy. Also passing the counts in a second statement would create an additional unwanted column.
select e.name "Boss",
COUNT(CASE WHEN d.departmentid = '1' THEN 1 END) AS "Finance",
COUNT(CASE WHEN d.departmentid = '2' THEN 1 END) AS "HR",
COUNT(CASE WHEN d.departmentid = '3' THEN 1 END) AS "IT",
COUNT(CASE WHEN d.departmentid = '4' THEN 1 END) AS "Marketing",
COUNT(CASE WHEN d.departmentid = '5' THEN 1 END) AS "Sales"
from employees e, departments d
where e.employeeid in (select distinct e.bossid from employees e)
and e.departmentid = d.departmentid (+)
group by e.name
order by e.name;
Boss Finance HR IT Marketing Sales
-------------------- ---------- ---------- ---------- ---------- ----------
Baxter Carney 0 0 0 0 1
Blythe Pierce 0 0 0 0 1
Here's an altered CASE query that loads the employee department counts but unfortunately it loads by department and not by individual manager. That is the problem I'm stuck on right now. How to pass the counts to the right manager and into the right column.
select departmentid "DEPTNO",
COUNT(CASE WHEN departmentid = '1' THEN 1 END) AS "Finance",
COUNT(CASE WHEN departmentid = '2' THEN 1 END) AS "HR",
COUNT(CASE WHEN departmentid = '3' THEN 1 END) AS "IT",
COUNT(CASE WHEN departmentid = '4' THEN 1 END) AS "Marketing",
COUNT(CASE WHEN departmentid = '5' THEN 1 END) AS "Sales"
from employees
where departmentid = departmentid and level <= 3
connect by prior employeeid = bossid
start with employeeid = 5
group by departmentid
order by departmentid
/
DEPTNO Finance HR IT Marketing Sales
3 0 0 1 0 0
5 0 0 0 0 21
And here's for all managers. You can see that it just keeps increasing the individual department count.
DEPTNO Finance HR IT Marketing Sales
1 4 0 0 0 0
2 0 23 0 0 0
3 0 0 20 0 0
4 0 0 0 1 0
5 0 0 0 0 28

Count from a Count based on a condition in SQL server

I have 4 tables in my database.
Students (Idno, Name, CourseId)
Sample data:
Idno Name CourseId
------------------------
-101123456 Vijay 101
-101123457 John 102
-101123458 Sam 101
-101123459 Arvind 102
-101123460 Smith 101
Courses (CourseId, CourseNo, CourseName, StreamId)
Sample data:
CourseId CourseNo CourseName StreamId
------------------------------------------
-101 53245 C 1
-102 53245 C++ 2
Streams (StreamId, StreamName)
Sample data:
StreamId StreamName
---------------------------
-1 Engineering
-2 Medical
Booking (BId, Idno, BStatus)
Sample data:
Bid Idno BStatus
--------------------------------
-1110 101123456 Confirmed
-1111 101123456 Confirmed
-1112 101123457 Confirmed
-1113 101123458 Confirmed
-1114 101123459 Confirmed
-1115 101123460 Confirmed
-1116 101123456 Confirmed
-1117 101123457 Confirmed
-1118 101123458 Confirmed
-1119 101123459 Confirmed
-1119 101123460 Cancelled
I have a problem generating the following output
SNo Stream BookedOnce BookedTwice NonBooked
1 Engineering 2 3 0
2 Medical 3 1 1
Thanks
I think this requires a two step process. First, calculate the number of bookings by each student for a stream. Then per stream, count the number of students that have one, two or zero bookings.
Here's an example, with the first step in the inner query:
select StreamId
, StreamName
, sum(case when Bookings = 1 then 1 else 0 end) as BookedOnce
, sum(case when Bookings = 2 then 1 else 0 end) as BookedTwice
, sum(case when Bookings = 0 then 1 else 0 end) as NoneBooked
from (
select str.StreamId
, str.StreamName
, s.Idno
, count(b.BId) as Bookings
from Students s
left join
Booking b
on b.Idno = s.Idno
left join
Courses c
on c.CourseId = s.CourseId
left join
Streams str
on str.StreamId = c.StreamId
group by
str.StreamId
, str.StreamName
, s.Idno
) BookingsPerStudentPerStream
group by
StreamId
, StreamName

How SQL Server iterates over rows when computing several aggregate functions in one select query

I have a SQL Server 2012
And query
SELECT ManagerId,
SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as badSoldDays,
SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM SomeTable
GROUP BY ProductId
Some Table Definition
ManagerId | SoldInDay | Category
1 50 PC
1 20 Laptop
2 30 PC
3 40 Laptop
So, question is:
Does it mean that Sql will iterate over all rows twice? so, each aggregate function executes in separate cycle over all rows in table? or it's much smarter?
Doesn't matter what I want to get by this query, it's my dream.
(It appears that your question was addressed by a comment, but for completeness, I've provided an official answer here.)
First, your example SQL will not run. You are including ManagerId in the field list but not the GROUP BY. You will get an error akin to this:
Msg 8120, Level 16, State 1, Line 9 Column '#SomeTable.ManagerID' is
invalid in the select list because it is not contained in either an
aggregate function or the GROUP BY clause.
Assuming you meant "ManagerId" instead of "ProductId" in the field list, I reproduced your situation and reviewed the execution plans. It showed only one "Stream Aggregate" operator. You can force it to run over the table twice by separating the aggregations into two different common table expressions (CTEs) and JOINing them back together. In that case, you see two Stream Aggregate operators (one for each run through the table).
Here is the code to generate the execution plans:
DECLARE #SomeTable TABLE
(
ManagerId int,
SoldInDay int,
Category varchar(50)
);
INSERT INTO #SomeTable (ManagerId, SoldInDay, Category) VALUES (1, 50, 'PC');
INSERT INTO #SomeTable (ManagerId, SoldInDay, Category) VALUES (1, 20, 'Laptop');
INSERT INTO #SomeTable (ManagerId, SoldInDay, Category) VALUES (2, 30, 'PC');
INSERT INTO #SomeTable (ManagerId, SoldInDay, Category) VALUES (3, 40, 'Laptop');
/*
This produces an error:
Msg 8120, Level 16, State 1, Line 9
Column '#SomeTable.ManagerID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
SELECT
ManagerId,
SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as badSoldDays,
SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM #SomeTable
GROUP BY ProductId;
*/
SELECT
ManagerId,
SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as BadSoldDays,
SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM #SomeTable
GROUP BY ManagerId;
WITH DaysWithSoldPcTable AS
(
SELECT
ManagerId,
SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM #SomeTable
GROUP BY ManagerId
), BadSoldDaysTable AS
(
SELECT
ManagerId,
SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as BadSoldDays
FROM #SomeTable
GROUP BY ManagerId
)
SELECT
DaysWithSoldPcTable.ManagerId,
DaysWithSoldPcTable.DaysWithSoldPc,
BadSoldDaysTable.BadSoldDays
FROM DaysWithSoldPcTable
JOIN BadSoldDaysTable
ON DaysWithSoldPcTable.ManagerId = BadSoldDaysTable.ManagerId;

Resources