David
David

Reputation: 593

SQL Server: Two COUNTs in one query multiplying with one another in output

I have a query is used to display information in a queue and part of that information is showing the amount of child entities (packages and labs) that belong to the parent entity (change). However instead of showing the individual counts of each type of child, they multiply with one another.

In the below case, there are supposed to be 3 labs and 18 packages, however the the multiply with one another and the output is 54 of each.

Below is the offending portion of the query.

SELECT cef.ChangeId, COUNT(pac.PackageId) AS 'Packages', COUNT(lab.LabRequestId) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN dbo.Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN dbo.Package pac 
ON (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId

I feel like this is obvious but it's not occurring to me how to fix it so the two counts are independent of one another like to me they should be. There doesn't seem to be a scenario like this in any of my research either. Can anyone guide me in the right direction?

Upvotes: 2

Views: 962

Answers (3)

Bhavin Gosai
Bhavin Gosai

Reputation: 211

From you question its difficult to derive what result do you expect from your query. So I presume you want following result:

+----------+----------+------+
| ChangeId | Packages | Labs |
+----------+----------+------+
|      255 |       18 |    3 |
+----------+----------+------+

Try below query if you are looking for above mentioned result.

SELECT cef.ChangeId, ISNULL(pac.PacCount, 0) AS 'Packages', ISNULL(Lab.LabCount, 0) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN (SELECT Lab.ChangeId, COUNT(*) LabCount FROM dbo.Lab GROUP BY) Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN (SELECT pac.ChangeId, COUNT(*) PacCount FROM dbo.Package pac WHERE pac.PackageStatus != 6 AND pac.PackageStatus !=7 GROUP BY pac.ChangeId) pac
ON cef.ChangeId = pac.ChangeId
WHERE cef.ChangeId = 255

Query Explanation:

  • In your query you didn't use group by, so it ended up giving you 54 as count which is Cartesian product.
  • In this query I tried to group by 'ChangeId' and find aggregate before joining tables. So 3 labs and 18 packages will be counted before join.
  • Your will also notice that I have moved PackageStatus filter before group by in pac table. So unwanted record won't mess with our count.

Upvotes: 1

EzLo
EzLo

Reputation: 14199

You start with a particular ChangeId from the dbo.ChangeEvaluationForm table (ChangeId = 255 from your example), then join to the dbo.Lab table. This join makes your result go from 1 row to 3, considering there are 3 Labs with ChangeId = 255. Your problem is on the next join, you are joining all 3 resulting rows from the previous join with the dbo.Package table, which has 18 rows for ChangeId = 255. The resulting count for columns pac.PackageId and lab.LabRequestId will then be 3 x 18 = 54.

To get what you want, there are 2 easy solutions:

  • Use COUNT DISTINCT instead of COUNT. This will just count the different values of pac.PackageId and lab.LabRequestId and not the repeated ones.
  • Split the joins into 2 subqueries and join their result (by ChangeId)

Upvotes: 0

IVNSTN
IVNSTN

Reputation: 9318

Because you do multiply source rows by each left join. So sometimes you have more likely cross join here.

SELECT cef.ChangeId, p.Packages, l.Labs
FROM dbo.ChangeEvaluationForm cef
OUTER APPLY(
  SELECT COUNT(*) as Labs
  FROM dbo.Lab
  WHERE cef.ChangeId = Lab.ChangeId
) l
OUTER APPLY(
  SELECT COUNT(*) AS Packages
  FROM dbo.Package pac 
  WHERE (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
) p
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId

perhaps GROUP BY is not needed now.

Upvotes: 3

Related Questions