Query for count and distinct - sql

I should make a report in T-SQL from several table.
I can join all the table needed but after I don't know excatly how to get my information.
Explanation :
I've got the following table :
Tbl_User (UserId, Username)
Tbl_Customer (CustomeriD, CustomerName)
Tbl_DocA (DocId, CustomerID, DateCreate, DateAdd, UseriD)
Tbl_DocB (DocId, CustomerID, DateCreate, DateAdd, UseriD)
Tbl_DocC (DocId, CustomerID, DateCreate, DateAdd, UseriD)
I am trying to get a report like this :
After I can get this, the idea is to have a filter with the date in SQL reporting.

You can union all the document tables together and join users and customers on it.
SELECT Customer.CustomerID
,Customer.CustomerName
,COUNT(CASE WHEN DocType = 'A' THEN 1 END) AS doc_a_total
,COUNT(CASE WHEN DocType = 'B' THEN 1 END) AS doc_b_total
,COUNT(CASE WHEN DocType = 'C' THEN 1 END) AS doc_c_total
,COUNT(CASE WHEN DocType = 'A' AND user.username ='azerty' THEN 1 END) AS doc_a_made_by_azerty
,COUNT(CASE WHEN DocType = 'B' AND user.username ='azerty' THEN 1 END) AS doc_b_made_by_azerty
,COUNT(CASE WHEN DocType = 'C' AND user.username ='azerty' THEN 1 END) AS doc_c_made_by_azerty
FROM (
(SELECT 'A' AS DocType, * FROM Tbl_DocA)
UNION ALL
(SELECT 'B' AS DocType, * FROM Tbl_DocB)
UNION ALL
(SELECT 'C' AS DocType, * FROM Tbl_DocC)
) AS docs
JOIN Tbl_User AS user ON user.UserId = docs.UseriD
JOIN Tbl_Customer AS Customer ON Customer.CustomeriD = docs.CustomeriD
GROUP BY Customer.CustomerID , Customer.CustomerName

You can use common table expressions to get the count for each report type per customer, with conditional aggregation for reports made by a specific user, and join them to the customers table.
Something like this should get you the desired results:
DECLARE #UserId int = 1; -- or whatever the id of the user you need
WITH CTEDocA AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocA
GROUP BY CustomerID
), CTEDocB AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocB
GROUP BY CustomerID
), CTEDocC AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocC
GROUP BY CustomerID
)
SELECT cust.CustomeriD
,cust.CustomerName
,ISNULL(a.NumberOfReports, 0) As NumberOfDocA
,ISNULL(a.NumberOfReportsByUserAzerty, 0) As NumberOfDocAByAzerty
,ISNULL(b.NumberOfReports, 0) As NumberOfDocB
,ISNULL(b.NumberOfReportsByUserAzerty, 0) As NumberOfDocBByAzerty
,ISNULL(c.NumberOfReports, 0) As NumberOfDocC
,ISNULL(c.NumberOfReportsByUserAzerty, 0) As NumberOfDocCByAzerty
FROM Tbl_Customer cust
LEFT JOIN CTEDocA As a
ON cust.CustomeriD = a.CustomerID
LEFT JOIN CTEDocA As b
ON cust.CustomeriD = b.CustomerID
LEFT JOIN CTEDocA As c
ON cust.CustomeriD = c.CustomerID
To filter by date you can add a where clause to each common table expresstion.
BTW, The fact that you have three identical tables for three document types suggest a bad database design.
If these tables are identical you should consider replacing them with a single table and add a column to that table describing the document type.

There are several ways to do this. One key feature needed is to count a particular user apart from the others. This is done with conditional aggregation. E.g.:
select
customerid,
count(*),
count(case when userid = <particular user ID here> then 1 end)
from tbl_doca
group by customerid;
Here is one possible query using a cross join to get the user in question once and cross apply to get the numbers.
select
c.customerid,
c.customername,
doca.total as doc_a_total,
doca.az as doc_a_by_azerty,
docb.total as doc_b_total,
docb.az as doc_b_by_azerty,
docc.total as doc_c_total,
docc.az as doc_c_by_azerty
from tbl_customer c
cross join
(
select userid from tbl_user where username = 'Azerty'
) azerty
cross apply
(
select
count(*) as total,
count(case when da.userid = azerty.userid then 1 end)n as az
from tbl_doca da
where da.customerid = c.customerid
) doca
cross apply
(
select
count(*) as total,
count(case when db.userid = azerty.userid then 1 end)n as az
from tbl_docb db
where db.customerid = c.customerid
) docb
cross apply
(
select
count(*) as total,
count(case when dc.userid = azerty.userid then 1 end)n as az
from tbl_docc dc
where dc.customerid = c.customerid
) docc
order by c.customerid;
Other options would be to replace the cross apply with left outer join and non-correlated subqueries or to put subqueries into the select clause.

Combining the totals for the documents is another method.
Then use conditional aggregation for the counts.
untested notepad scribble:
;WITH SPECIFICUSER AS
(
SELECT UseriD
FROM Tbl_User
WHERE UserName = 'azerty'
),
DOCTOTALS (
SELECT CustomeriD, UseriD, 'DocA' AS Src, COUNT(DocId) AS Total
FROM Tbl_DocA
GROUP BY CustomeriD, UseriD
UNION ALL
SELECT CustomeriD, UseriD, 'DocB', COUNT(DocId)
FROM Tbl_DocB
GROUP BY CustomeriD, UseriD
UNION ALL
SELECT CustomeriD, UseriD, 'DocC', COUNT(DocId)
FROM Tbl_DocC
GROUP BY CustomeriD, UseriD
)
SELECT
docs.CustomeriD,
cust.CustomerName,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocA' THEN docs.Total ELSE 0 END) AS Total_DocA_userX,
SUM(CASE WHEN Src = 'DocA' THEN docs.Total ELSE 0 END) AS Total_DocA,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocB' THEN docs.Total ELSE 0 END) AS Total_DocB_userX,
SUM(CASE WHEN Src = 'DocB' THEN docs.Total ELSE 0 END) AS Total_DocB,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocC' THEN docs.Total ELSE 0 END) AS Total_DocC_userX,
SUM(CASE WHEN Src = 'DocC' THEN docs.Total ELSE 0 END) AS Total_DocC
FROM DOCTOTALS docs
LEFT JOIN Tbl_Customer cust ON cust.CustomeriD = docs.CustomeriD
LEFT JOIN Tbl_User usr ON usr.UseriD = docs.UseriD
LEFT JOIN SPECIFICUSER usrX ON usrX.UseriD = docs.UseriD
GROUP BY docs.CustomeriD, cust.CustomerName
ORDER BY docs.CustomeriD
Those long column names could be set on the report side

Related

Re-writing EXISTS as JOIN or a subquery in Oracle

I have a query which is very costly and taking more than an hour to execute. I tried converting the EXISTS clause to join but I am stuck, can anyone help?
The purpose is to find duplicate product within a unique space id. FLAT_TABLE consists of 5 million records.
Query:
select
tbl1.product,
tbl1.status,
tbl1.reservation,
tbl1.unique_space_id
FROM
schema1.flat_table tbl1
WHERE
tbl1.status = 'Active'
AND tbl1.product = 'Cage'
AND EXISTS
(SELECT 1
FROM schema1.flat_table tbl2
WHERE tbl2.product = 'Cage'
AND tbl2.status = 'Active'
AND tbl2.reservation <> 'Space Reserved'
AND tbl1.unique_space_id = tbl2.unique_space_id
GROUP BY tbl2.unique_space_id
HAVING COUNT (1) > 1
);
You can use analytical function count as follows:
select * from
(select tbl1.product, tbl1.status, tbl1.reservation, tbl1.unique_space_id,
count(case when tbl1.reservation <> 'Space Reserved' then 1 end)
over(partition by tbl1.unique_space_id) as cnt
FROM schema1.flat_table tbl1
WHERE tbl1.status = 'Active' AND tbl1.product = 'Cage')
where cnt > 1
You could rewrite your query as an inner join to the current exists subquery. The join would have the effect of filtering in the same way the exists clause was behaving.
SELECT DISTINCT
tbl1.product,
tbl1.status,
tbl1.reservation,
tbl1.unique_space_id
FROM schema1.flat_table tbl1
INNER JOIN
(
SELECT unique_space_id
FROM schema1.flat_table
WHERE product = 'Cage' AND
status = 'Active' AND
reservation <> 'Space Reserved'
GROUP BY unique_space_id
HAVING COUNT(*) > 1
) tbl2
ON tbl2.unique_space_id = tbl1.unique_space_id
WHERE
tbl1.status = 'Active' AND
tbl1.product = 'Cage';
Here is a more concise version using COUNT as an analytic function, along with a QUALIFY clause;
SELECT DISTINCT product, status, reservation, unique_space_id
FROM schema1.flat_table
WHERE status = 'Active' AND product = 'Cage'
QUALIFY COUNT(CASE WHEN reservation <> 'Space Reserved' THEN 1 END)
OVER (PARTITION BY unique_space_id) > 1;

Combine 3 UNIONed queries into one

I have the following which I would like to do without UNIONs so that the string split is only happening once.
I would also like the results to be in one line per MemberId showing all 3 counts rather than 3 rows.
SELECT MemberKey, 'login' as countType, count(MemberKey) as total FROM [dbo].[CA_MembersAudit]
WHERE isSuccess = 1 and MemberKey IN (SELECT value FROM STRING_SPLIT( #userList, ','))
Group By MemberKey
UNION
SELECT MemberId as MemberKey, 'articles' as countType, count(MemberId) as total FROM [dbo].[CA_Activities]
WHERE StateId = 'Opened' and MemberId IN (SELECT value FROM STRING_SPLIT( #userList, ','))
Group By MemberId
UNION
SELECT MemberId as MemberKey,'assessments' as countType, count(MemberId) as total FROM [dbo].[CA_Activities]
WHERE PercentageComplete is not null AND MemberId IN (SELECT value FROM STRING_SPLIT( #userList, ','))
Group By MemberId
UNION
Can anyone suggest how I should amend the queries into one to be able to do this?
You could use a subquery for each total:
select m.MemberKey,
(select count(*) from CA_MembersAudit ma where m.MemberKey = ma.MemberKey and ma.isSuccess = 1) as 'login_total',
(select count(*) from CA_Activities a where m.MemberKey = a.MemberId and a.stateId = 'Opened') as 'articles_total',
(select count(*) from CA_Activities a where m.MemberKey = a.MemberId and a.PercentageComplete is not null) as 'assessments_total'
from (select value as MemberKey from STRING_SPLIT('1,2,3,4', ',')) m
If your tables have a primary key, you could also do something like this:
select m.MemberKey,
count(distinct ma.Id) 'login_total',
count(distinct a1.Id) 'articles_total',
count(distinct a2.Id) 'assessments_total'
from (select value as MemberKey from STRING_SPLIT('1,2,3,4', ',')) m
left outer join CA_MembersAudit ma on m.MemberKey = ma.MemberKey and ma.isSuccess = 1
left outer join CA_Activities a1 on m.MemberKey = a1.MemberId and a1.stateId = 'Opened'
left outer join CA_Activities a2 on m.MemberKey = a2.MemberId and a2.PercentageComplete is not null
group by m.MemberKey
I believe you can use a CTE and then JOIN to each of the UNION participants.
WITH MemberList AS (
SELECT value AS Member
FROM STRING_SPLIT(#userList, ',')
)
SELECT
MemberKey
,'login' AS countType
,count(MemberKey) AS total
FROM [dbo].[CA_MembersAudit]
JOIN MemberList
ON MemberList.Member = CA_MembersAudit.MemberKey
WHERE isSuccess = 1
GROUP BY MemberKey
UNION
SELECT
MemberId AS MemberKey
,'articles' AS countType
,count(MemberId) AS total
FROM [dbo].[CA_Activities]
JOIN MemberList
ON MemberList.Member = CA_Activities.MemberId
WHERE StateId = 'Opened'
GROUP BY MemberId
UNION
SELECT
MemberId AS MemberKey
,'assessments' AS countType
,count(MemberId) AS total
FROM [dbo].[CA_Activities]
JOIN MemberList
ON MemberList.Member = CA_Activities.MemberId
WHERE PercentageComplete IS NOT NULL
GROUP BY MemberId;
try this :
With MemberList as (
SELECT value as ID FROM STRING_SPLIT( #userList, ',')
),
Activities as (
select f1.MemberId, sum(case when f1.StateId = 'Opened' then 1 else 0 end) as TotalOpened,
sum(case when f1.PercentageComplete is not null then 1 else 0 end) as TotalPercentageComplete
FROM [dbo].[CA_Activities] f1 inner join MemberList f2 on f1.MemberId=f2.ID
where f1.StateId = 'Opened' or f1.PercentageComplete is not null
group by f1.MemberId
),
MemberAudit as (
SELECT f1.MemberKey, count(*) as TotalSuccess
FROM [dbo].[CA_MembersAudit] f1 inner join MemberList f2 on f1.MemberKey=f2.ID
WHERE f1.isSuccess = 1
Group By f1.MemberKey
)
select f1.*, isnull(f2.TotalOpened, 0) as TotalOpened, isnull(f2.TotalPercentageComplete, 0) as TotalPercentageComplete, isnull(f3.TotalSuccess, 0) as TotalSuccess
from MemberList f1
left outer join Activities f2 on f1.ID=f2.MemberId
left outer join MemberAudit f3 on f1.ID=f3.MemberKey
other solution :
SELECT f1.value as ID, isnull(f2.TotalOpened, 0) as TotalOpened, isnull(f2.TotalPercentageComplete, 0) as TotalPercentageComplete, isnull(f3.TotalSuccess, 0) as TotalSuccess
FROM STRING_SPLIT( #userList, ',') f1
outer apply
(
select sum(case when f1.StateId = 'Opened' then 1 else 0 end) as TotalOpened,
sum(case when f1.PercentageComplete is not null then 1 else 0 end) as TotalPercentageComplete
FROM [dbo].[CA_Activities] f1
where (f1.StateId = 'Opened' or f1.PercentageComplete is not null) and f1.MemberId=f1.value
) f2
outer apply
(
SELECT count(*) as TotalSuccess FROM [dbo].[CA_MembersAudit] f1 WHERE f1.isSuccess = 1 and f1.MemberKey=f1.value
) f3

Multiple subquery join in View with group by returns duplicate rows

I have created a view using subquery but I want this view to return few mendatory column which cant be added in group by subquery, so I have to create one more select statement and join with other group by subquery
I am come up with following query,
But problem I am facing is if group by seller has 28 rows it returns 28 duplicate rows, also I want whole query to order by TotalOrderItem.
Alter VIEW [dbo].[SellersPerformance] AS
Select
RequiredColumns.Id as Id,
aggrgateDT.SellerId as SellerId,
aggrgateDT.TenantId as TenantId,
aggrgateDT.Active as Active,
aggrgateDT.TotalOrderedItem as TotalOrderItem,
aggrgateDT.MoveToPurchase as MoveToPurchase,
aggrgateDT.GoodPurchase as GoodPurchase,
RequiredColumns.Created as Created,
RequiredColumns.Modified as Modified,
RequiredColumns.CreatorId as CreatorId,
RequiredColumns.ModifierId as ModifierId
From
(
(Select
sellerId, p.TenantId, p.Active, count(*) as TotalOrderedItem,
count(*) - count(o.Id) as MoveToPurchase,
count(o.Id) as GoodPurchase,
count(case when o.ApplicationStatus = 'Perfect' then 1 end) as Perfect,
count(case when o.ApplicationStatus = 'R-Perfect' then 1 end) as R_Perfect
FROM [dbo].[AmazonOrderPurchaseInfo] p
left join [dbo].[AmazonOrder] o
on p.AmazonOrderId = o.Id
AND p.Id = o.[AmazonOrderPurchaseInfoId]
group by SellerId, p.TenantId, p.Active
order by TotalOrderedItem offset 0 rows
) aggrgateDT
Left outer Join (
SELECT
NEWID() Id,
purchase.Created AS Created,
purchase.Modified AS Modified,
purchase.CreatorId AS CreatorId,
purchase.ModifierId AS ModifierId,
purchase.SellerId As SellerId
From dbo.AmazonOrderPurchaseInfo purchase
) RequiredColumns ON aggrgateDT.SellerId = RequiredColumns.SellerId
)
GO
You may try Group by for this.
Alter VIEW [dbo].[SellersPerformance] AS
select res.Id, res.SellerId, res.TenandId, res.Active, res.TotalOrderItem, res.MovetoPurchase, res.GoodPurchase, res.Created, res.Modified, res.CreatorId, res.ModifierId
from
(
Select
RequiredColumns.Id as Id,
aggrgateDT.SellerId as SellerId,
aggrgateDT.TenantId as TenantId,
aggrgateDT.Active as Active,
aggrgateDT.TotalOrderedItem as TotalOrderItem,
aggrgateDT.MoveToPurchase as MoveToPurchase,
aggrgateDT.GoodPurchase as GoodPurchase,
RequiredColumns.Created as Created,
RequiredColumns.Modified as Modified,
RequiredColumns.CreatorId as CreatorId,
RequiredColumns.ModifierId as ModifierId
From
(
(Select
sellerId, p.TenantId, p.Active, count(*) as TotalOrderedItem,
count(*) - count(o.Id) as MoveToPurchase,
count(o.Id) as GoodPurchase,
count(case when o.ApplicationStatus = 'Perfect' then 1 end) as Perfect,
count(case when o.ApplicationStatus = 'R-Perfect' then 1 end) as R_Perfect
FROM [dbo].[AmazonOrderPurchaseInfo] p
left join [dbo].[AmazonOrder] o
on p.AmazonOrderId = o.Id
AND p.Id = o.[AmazonOrderPurchaseInfoId]
group by SellerId, p.TenantId, p.Active
order by TotalOrderedItem offset 0 rows
) aggrgateDT
Left outer Join (
SELECT
NEWID() Id,
purchase.Created AS Created,
purchase.Modified AS Modified,
purchase.CreatorId AS CreatorId,
purchase.ModifierId AS ModifierId,
purchase.SellerId As SellerId
From dbo.AmazonOrderPurchaseInfo purchase
) RequiredColumns ON aggrgateDT.SellerId = RequiredColumns.SellerId
) as res
group by res.Id, res.SellerId, res.TenandId, res.Active, res.TotalOrderItem, res.MovetoPurchase, res.GoodPurchase, res.Created, res.Modified, res.CreatorId, res.ModifierId
)
GO
Here if Id, Created, Modified, CreatorId, ModifierId columns will have same id then you may get your expected result.

SQL Server query slow - how do optimize it?

I would like some help to optimize this query to be faster.
This query produces a view that latter will be showed in a table in a website.
This query is slow, and I am trying to make it faster.
The only thing I tried till now is to reduce the amount of columns I retrieve for the table.
This is the query:
SELECT TOP (100) PERCENT Id, MAX(BusinessTitle) AS BusinessTitle, MAX(ClientName) AS ClientName, MAX(ClientType) AS Type, MAX(CreatedWhen) AS CreatedWhen, MAX(CASE WHEN C.[CreatedBy] IS NULL
THEN 'Client' ELSE 'Admin' END) AS CreatedBy, CAST(MAX(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END) AS BIT) AS IsDisabled, MAX(ReferenceSource) AS ReferenceSource, MAX(OtherReferenceSource)
AS OtherReferenceSource, MAX(Address) AS Address,
(SELECT MAX(T.FirstName + ' ' + T.LastName) AS Expr1
FROM dbo.ApplicationUsers AS A INNER JOIN
dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS ClientAdmin,
(SELECT MAX(A.Email) AS Expr1
FROM dbo.ApplicationUsers AS A INNER JOIN
dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS Email,
(SELECT MAX(Name) AS Expr1
FROM dbo.Cities AS CY
WHERE (Id = MAX(C.CityId))) AS City,
(SELECT COUNT(*) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id)) AS TotalPatientCount,
(SELECT COUNT(*) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (IsDisabled = 0)) AS ActivePatientCount,
(SELECT MAX(CreatedWhen) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id)) AS LastPatientAddition,
(SELECT COUNT(*) AS Expr1
FROM dbo.Treatments AS T
WHERE (ClientId = C.Id)) AS TotalTreatmentCount,
(SELECT MAX(CreatedWhen) AS Expr1
FROM dbo.Treatments AS T
WHERE (ClientId = C.Id)) AS LastTreatmentAddition,
(SELECT COUNT(*) AS Expr1
FROM dbo.Therapists AS T
WHERE (ClientId = C.Id)) AS TotalTherapistCount,
(SELECT COUNT(*) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
WHERE (A.IsDeleted = 0) AND (A.IsDisabled = 0)) AS ActiveTherapistCount,
(SELECT MAX(A.CreatedWhen) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id) AS LastTherapistAddition,
(SELECT MAX(A.LastLoginDate) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
WHERE (A.LastLoginDate IS NOT NULL)) AS TherapistLastLoginDate, CAST((CASE WHEN
((SELECT COUNT(S.[Id])
FROM [dbo].[ClientSubscriptions] AS S
WHERE ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
(S.[SubscriptionEnd] > GETDATE())))) > 0) THEN 1 ELSE 0 END) AS BIT) AS HasActiveSubscription,
(SELECT MAX(SubscriptionEnd) AS Expr1
FROM dbo.ClientSubscriptions AS S
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
SubscriptionEnd > GETDATE())) AS LastValidSubscriptionEnd, CAST((CASE WHEN
((SELECT COUNT(S.[Id])
FROM [dbo].[ClientSubscriptions] AS S
WHERE ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
(S.[SubscriptionEnd] > GETDATE())) AND (S.[Id] <>
(SELECT MIN(S2.[Id])
FROM [dbo].[ClientSubscriptions] AS S2
WHERE ((S2.[ClientId] = C.[Id]) AND (S2.[IsDeleted] = 0)))))) > 0) THEN 1 ELSE 0 END) AS BIT) AS IsPayingCustomer, COALESCE
((SELECT MAX(MonthlyPrice) AS Expr1
FROM dbo.ClientSubscriptions AS S
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
SubscriptionEnd > GETDATE()) AND (MonthlyPrice > 0)), 0.00) AS ActiveSubscriptionMonthlyPrice, MAX(ClientStatus) AS Status, MAX(Phone1) AS Phone, MAX(Phone2) AS Phone2,
(SELECT Code
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponCode,
(SELECT IssuedTo
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponIssuedTo,
(SELECT ClientDiscount
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponClientDiscount, COALESCE
((SELECT COUNT(Id) AS Expr1
FROM dbo.ClientFiles AS F
WHERE (ClientId = C.Id)), 0) AS TotalFilesCount, COALESCE
((SELECT SUM(FileSize) AS Expr1
FROM dbo.ClientFiles AS F
WHERE (ClientId = C.Id)), 0) / 1048576.0 AS TotalFilesSize, CAST(MAX(CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded, MAX(CrmStatus) AS CrmStatus, MAX(CrmUnuseReason)
AS CrmUnuseReason,
(SELECT COUNT(1) AS Expr1
FROM dbo.Tipulog_Crm_Calls_new AS CC
WHERE (Cust_id = C.Id)) AS CrmCallCount
FROM dbo.Clients AS C
WHERE (IsDeleted = 0)
GROUP BY Id
I will add a second answer, which is the complete sql. This has of course not been tested as we have no access to your data, but I think you should be able to debug it yourself. There are many pointers in this code that should show you how to go.
The basic thing is to take out all of the correlated queries and put them as subqueries. The only reason to do this is all the Max/Min you use - I would look at those as if they are not necessary depending on your data then you should take them out and join to the tables directly. All the subqueries are left joins - again make them normal joins if you can depending on your data.
Also took out the outside group by Id, as 99% sure this is not necessary, as is the Top 100% bit.
SELECT BusinessTitle, ClientName, ClientType AS Type, CreatedWhen,
CASE WHEN C.[CreatedBy] IS NULL THEN 'Client' ELSE 'Admin' END) AS CreatedBy,
CAST(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END AS BIT) AS IsDisabled,
ReferenceSource, OtherReferenceSource, Address,
ApplicationByName.FullName AS ClientAdmin,
ApplicationByName.Email AS Email,
Cities.Name AS City,
Patients.TotalPatientCount,
Patients.ActivePatientCount,
Patients.LastPatientAddition,
Treatments.TotalTreatmentCount,
Treatments.LastTreatmentAddition,
Therapists.TotalTherapistCount,
Therapists.ActiveTherapistCount,
Therapists.LastTherapistAddition,
Therapists.TherapistLastLoginDate
CAST(CASE WHEN Subscriptions.SubscriptionCount>0 then 1 else 0 end as BIT) as HasActiveSubscription,
Subscriptions.LastValidSubscriptionEnd
CAST(Subscriptions.IsPayingCustomer AS BIT) AS IsPayingCustomer,
COALESCE(ActiveSubscriptionMonthlyPrice,0) as ActiveSubscriptionMonthlyPrice
ClientStatus AS Status, Phone1 AS Phone, Phone2 AS Phone2,
ClientPayments.DiscountCouponCode,
DiscountCoupons.IssuedTo AS DiscountCouponIssuedTo,
DiscountCoupons.ClientDiscount AS DiscountCouponClientDiscount,
COALESCE(ClientFiles.TotalFilesCount,0) AS TotalFilesCount,
COALESCE(ClientFiles.TotalFilesSize,0) AS TotalFilesSize,
CAST((CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded,
CrmStatus, CrmUnuseReason,
Crm_Calls.CrmCallCount
FROM dbo.Clients AS C
left join (
select A.UserName,
max(T.FirstName + ' ' + T.LastName) as FullName,
max(A.Email) as Email
from dbo.ApplicationUsers A
join dbo.Therapists T on T.ApplicationUserId=A.Id
group by A.Username
) ApplicationByName on ApplicationByName.UserName=C.ClientName
join dbo.Cities on Cities.ID=c.CityID
left join (
SELECT ClientId,
COUNT(*) AS TotalPatientCount,
sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
MAX(CreatedWhen) AS LastPatientAddition
FROM dbo.Patients
GROUP BY ClientId
) Patients on Patients.ClientId = C.Id
left join (
SELECT ClientId,
COUNT(*) AS TotalTreatmentCount,
MAX(CreatedWhen) AS LastTreatmentAddition
FROM dbo.Treatments
GROUP BY ClientId
) Treatments on Treatments.ClientID = C.Id
left join (
select T.ClientId,
count(distinct T.Id) as TotalTherapistCount,
sum(case when A.IsDeleted = 0 AND A.IsDisabled = 0 then 1 else 0 end) as ActiveTherapistCount,
max(A.CreatedWhen) as LastTherapistAddition,
max(A.LastLoginDate) as TherapistLastLoginDate
from Therapists T
left join dbo.ApplicationUsers A on A.Id=T.ApplicationUserId
group by T.ClientId
) Therapists on Therapists.ClientID = C.Id
left join (
SELECT S.ClientId,
count(*) as SubscriptionCount,
MAX(SubscriptionEnd) as LastValidSubscriptionEnd,
MAX(case when MinSub.Id!=S.ID then 1 else 0 end as IsPayingCustomer,
max(case when MonthlyPrice>0 then 0 end) as ActiveSubscriptionMonthlyPrice
FROM dbo.ClientSubscriptions S
join (
select ClientId, min(Id) as Id
from dbo.ClientSubscriptions
where IsDeleted=0
group by ClientId
) MinSub on MinSub.ClientId=ClientSubscriptions.ClientId
where IsDeleted=0 and (SubscriptionEnd is null or SubscriptionEnd>getdate())
group by ClientId
) Subscriptions on Subscriptions.ClientId=C.Id
left join (
select ClientId,
DiscountCouponCode,
row_number() over(partition by ClientId, order by Id) rn
from dbo.ClientPayments
) ClientPayments on ClientPayments.ClientId=C.ID and rn=1
left join dbo.DiscountCoupons on DiscountCoupons.Code=ClientPayments.DiscountCouponCode
left join (
select ClientId,
count(*) as TotalFilesCount,
sum(FileSize)/1048576.0 as TotalFilesSize
from dbo.ClientFiles
group by ClientId
) ClientFiles on ClientFiles.ClientId=Client.Id
left join (
SELECT Cust_id, COUNT(1) AS CrmCallCount
FROM dbo.Tipulog_Crm_Calls_new
group by Cust_id
) Crm_Calls on Crm_Calls.Cust_id=C.Id
WHERE C.IsDeleted = 0
This is a very partial answer, but you asked how to refer to a table once instead of multiple times in multiple subqueries.
This is an example of how you would replace all those subqueries to the Patients & Treatments tables, and also the cities table. You really need to learn about joins.
FROM dbo.Clients AS C
join dbo.Cities on Cities.ID=c.CityID
left join (
SELECT ClientId,
COUNT(*) AS TotalPatientCount,
sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
MAX(CreatedWhen) AS LastPatientAddition
FROM dbo.Patients
GROUP BY ClientId
) Patients on Patients.ClientId = C.Id
left join (
SELECT ClientId,
COUNT(*) AS TotalTreatmentCount,
MAX(CreatedWhen) AS LastTreatmentAddition
FROM dbo.Treatments
GROUP BY ClientId
) Treatments on Treatments.ClientID = C.Id
Then your column list replaces the subqueries to Patients and City like this:
Cities.Name AS City,
Patients.TotalPatientCount,
Patients.ActivePatientCount,
Patients.LastPatientAddition,
Treatments.TotalTreatmentCount,
Treatments.LastTreatmentAddition,
That should at least give you an idea.

How do I compare SUM and COUNT()s in SQL?

I am building a small query to find all CustomerNumbers where all of their policies are in a certain status (terminated).
Here is the query I am working on
select
a.cn
,p.pn
, tp = COUNT(p.pn)
, tp2 = SUM(case when p.status = 4 then 1 else 0 end)
from
(
select cn, cn2
from bc
union
select cn, cn2= fn
from ic
) as a
left join p as p
on a.cn = p.cn
group by
a.cn,
pn
My issue is when I add the clause:
WHERE cn = tp
It says the columns are invalid. Am I missing something incredibly obvious?
You can't use aliases at the same level of a query. The reason is that the where clause is logically evaluated before the select, so the aliases defined in the select are not available in the where.
A typical solution is to repeat the expression (other answers) or use a subquery or cte:
with cte as (
<your query here>
)
select cte.*
from cte
where TotalPolicies = TermedPolicies;
However, in your case, you have an easier solution, because you have an aggregation query. So just use:
having TotalPolicies = TermedPolicies
You cannot use the aliased aggregate column names in the where clause. You have to use the expression itself instead. Also you cannot use it as where cluase, but use it in the having clause
HAVING COUNT(p.PolicyNumber) = SUM(case when p.status = 4 then 1 else 0 end)
You can also make the whole query as a subquery then add your where statement:
select CustomerNumber
,PolicyNumber
,TotalPolicies
,TermedPolicies
from (
select
a.CustomerNumber
,p.PolicyNumber
, TotalPolicies = COUNT(p.PolicyNumber)
, TermedPolicies = SUM(case when p.status = 4 then 1 else 0 end)
from
(
select CustomerNumber, CompanyName
from BusinessClients
union
select CustomerNumber, CompanyName = FullName
from IndividualClients
) as a
left join Policies as p
on a.CustomerNumber = p.CustomerNumber
group by
a.CustomerNumber,
PolicyNumber
) tb
where TotalPolicies = TermedPolicies
select
a.CustomerNumber
,p.PolicyNumber
, COUNT(p.PolicyNumber) as TotalPolicies
, SUM(case when p.status = 4 then 1 else 0 end) as TermedPolicies
from
(
select CustomerNumber, CompanyName
from BusinessClients
union
select CustomerNumber, CompanyName = FullName
from IndividualClients
) as a
left join Policies as p
on a.CustomerNumber = p.CustomerNumber
WHERE COUNT(p.PolicyNumber)= SUM(case when p.status = 4 then 1 else 0 end)
group by
a.CustomerNumber,
PolicyNumber
This should work. But it is not tested.
In order to filter by an aggregate function, you must include it in the HAVING clause, rather than the WHERE clause.
select
a.CustomerNumber
,p.PolicyNumber
, TotalPolicies = COUNT(p.PolicyNumber)
, TermedPolicies = SUM(case when p.status = 4 then 1 else 0 end)
from
(
select CustomerNumber, CompanyName
from BusinessClients
union
select CustomerNumber, CompanyName = FullName
from IndividualClients
) as a
left join Policies as p
on a.CustomerNumber = p.CustomerNumber
having COUNT(p.PolicyNumber) = SUM(case when p.status = 4 then 1 else 0 end)
group by
a.CustomerNumber,
PolicyNumber
The reason for this has to do with the way SQL engines evaluate queries. The contents of the WHERE clause are used to filter out rows before the aggregate functions are applied. If you could reference aggregate functions there, the engine would have to have some way to determine which predicates to apply before aggregation and which to apply after. The HAVING clause allows the engine to have a clear demarcation between the two: WHERE applies before aggregation and HAVING applies after aggregation.
When dealing with aggregations in a query that has grouping, you will need to use HAVING. This should work:
select
a.CustomerNumber
,p.PolicyNumber
, TotalPolicies = COUNT(p.PolicyNumber)
, TermedPolicies = SUM(case when p.status = 4 then 1 else 0 end)
from
(
select CustomerNumber, CompanyName
from BusinessClients
union
select CustomerNumber, CompanyName = FullName
from IndividualClients
) as a
left join Policies as p
on a.CustomerNumber = p.CustomerNumber
group by
a.CustomerNumber,
PolicyNumber
HAVING TermedPolicies = SUM(case when p.status = 4 then 1 else 0 end)