I need to know how to create a crosstab query - sql

I need help creating the below results. I thought of a sql pivot but I don't know how to use it. Looked at a few examples and cannot come up with a solution. Any other ideas on how to accomplish this is also welcome. Status columns must be dynamically generated.
Have three tables, assets, assettypes, assetstatus
Table: assets
assetid int
assettag varchar(25)
assettype int
assetstatus int
Table: assettypes
id int
typename varchar(20) (ex: Desktop, Laptop, Server, etc.)
Table: assetstatus
id int
statusname varchar(20) (ex: Deployed, Inventory, Shipped, etc.)
Desired results:
AssetType Total Deployed Inventory Shipped ...
-----------------------------------------------------------
Desktop 100 75 20 5 ...
Laptop 75 56 19 1 ...
Server 60 50 10 0 ...
Some Data:
assets table:
1,hol1234,1,1
2,hol1233,1,2
3,hol3421,2,3
4,svr1234,3,1
assettypes table:
1,Desktop
2,Laptop
3,Server
assetstatus table:
1,Deployed
2,Inventory
3,Shipped

This type of transformation is called a pivot. You did not specify what database you are using so I will provide a answers for SQL Server and MySQL.
SQL Server: If you are using SQL Server 2005+ you can implement the PIVOT function.
If you have a known number of values that you want to convert to columns then you can hard-code the query.
select typename, total, Deployed, Inventory, shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
pivot
(
count(statusname)
for statusname in (Deployed, Inventory, shipped)
) piv;
See SQL Fiddle with Demo.
But if you have an unknown number of status values, then you will need to use dynamic sql to generate the list of columns at run-time.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(statusname)
from assetstatus
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT typename, total,' + #cols + ' from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) x
pivot
(
count(statusname)
for statusname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
This can also be written using an aggregate function with a case expression:
select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total
See SQL Fiddle with Demo
MySQL: This database does not have a pivot function so you will have to use the aggregate function and a CASE expression. It also does not have windowing functions, so you will have to alter the query slightly to the following:
select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total;
See SQL Fiddle with Demo
Then if you need a dynamic solution in MySQL, you will have to use a prepared statement to generate the sql string to execute:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN statusname = ''',
statusname,
''' THEN 1 else 0 END) AS `',
statusname, '`'
)
) INTO #sql
FROM assetstatus;
SET #sql
= CONCAT('SELECT typename,
total, ', #sql, '
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
The result is the same for all queries in both databases:
| TYPENAME | TOTAL | DEPLOYED | INVENTORY | SHIPPED |
-----------------------------------------------------
| Desktop | 2 | 1 | 1 | 0 |
| Laptop | 1 | 0 | 0 | 1 |
| Server | 1 | 1 | 0 | 0 |

Using a non pivot compliant DBMS (Absolute Database) I was more successful using this SQL cross-tab equivalent statement:
SELECT
sub.TypeName
, SUM(sub.[Count]) AS "Total"
, SUM(CASE WHEN AssetStatus='1' THEN sub.[Count] ELSE 0 END) AS "Deployed"
, SUM(CASE WHEN AssetStatus='2' THEN sub.[Count] ELSE 0 END) AS "Inventory"
, SUM(CASE WHEN AssetStatus='3' THEN sub.[Count] ELSE 0 END) AS "Shipped"
FROM
(
SELECT
t.TypeName
, AssetStatus
, COUNT(AssetID) AS "Count"
FROM
Assets
JOIN AssetTypes t ON t.ID = AssetType
JOIN AssetStatus s ON s.ID = AssetStatus
GROUP BY t.TypeName, AssetStatus, s.StatusName
) sub
GROUP BY sub.TypeName
;
As I realized this code (above) didn't work with MySQL I adapted my code as below executing equally well in MySQL as in my current Absolute Database. The reason is the specific NULL handling avoiding the pitfall of dBase, Paradox as well as Absolute Database generously accepting COUNT(NULL) = 0 not accepted in mainstream databases.
So believing this will execute well in most databases (handling CASE ..) this is my adapted code:
SELECT
sub.TypeName
, SUM(sub.AssetCase) AS "Total"
, SUM(CASE WHEN sub.StatusName = 'Deployed' THEN sub.AssetCase ELSE 0 END) AS "Deployed"
, SUM(CASE WHEN sub.StatusName = 'Inventory' THEN sub.AssetCase ELSE 0 END) AS "Inventory"
, SUM(CASE WHEN sub.StatusName = 'Shipped' THEN sub.AssetCase ELSE 0 END) AS "Shipped"
FROM
(
SELECT
c.TypeName
, c.StatusName
, CASE WHEN a.AssetID IS NULL THEN 0 ELSE 1 END AS "AssetCase"
FROM
(
SELECT
t.ID AS tID
, t.TypeName
, s.ID AS sID
, s.StatusName
FROM
AssetTypes t, AssetStatus s
) c
LEFT JOIN Assets a
ON a.AssetType = c.tID AND a.AssetStatus = c.sID
) sub
GROUP BY
sub.TypeName
;
Best Regards
Niels Knabe

Related

Query for count and distinct

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

Subquery returned more than 1 value. in SQL Server 2005

When I query data in my application, I get the following error
Subquery returned more than 1 value.This is not permitted when the subquery follows =,!=,<,<=,>,>= or when the subquery is use as an expression.
The statement has been terminated.
Below is my stored procedure. anyone help me please?
ALTER proc [dbo].[SP_RC_DS_2]
#CustomerID varchar(10),
#ExfacDateFrom Datetime,
#ExfacDateTo DateTime
SELECT
SOD_SOLineNbr as Line_No
,IMA_LeadTimeCode as Manu_Pur
,IMA_Classification as Category
,case when IMA_ItemName is null then SOI_MiscLineDescription else IMA_ItemName end as ItemName
--,SOI_MiscLineDescription as MiscLine
,IAD_AliasName as AliasName
--,SOD_UserDef1 as PrivateRemark
,PrivateRemark =
(Case CUS_CustomerID
When 'C1175' Then isnull(convert(nvarchar(200),SOM_SpecialInst),'') + ' / ' + isnull(SOD_UserDef1,'')
When 'C1209' Then isnull(convert(nvarchar(200),SOM_SpecialInst),'') + ' / ' + isnull(SOD_UserDef1,'')
Else SOD_UserDef1
End)
,SOD_UserDef2 as RemarkLine--
,CUS_CustomerID as CustomerID
,CSA_Name as CustomerName
,SOM_Destination as Destination
,SOM_ContainerSize as ContainerSize
,CSA_RecordID as CSA_RecordID
,SOM_SalesOrderID as SalesOrderID
,SOM_CustomerPOID as CustomerPO
,SOD_RequiredDate as ExFacDate
,SOM_DefaultDockDate as ETD
,SOM_ETA as ETA
,SOD_PromiseDate as PromiseDate
,SOD_RequiredQty as Qty
,SOI_SalesConvUnitMeasure as UnitMeasure
,SOD_UnitPrice as UnitPrice
,isnull(IMA_Weight,0) as NetWeight
,isnull(IMA_GrossWeight,0)as GrossWeight
,(Case When IMA_ProdFam is null then 'Others' else IMA_ProdFam end) as ProdFamilyItem
,(Case When IMA_ProdFam is null then 'Others' else (Select PFM_PGI From PFM Where PFM_ProdFam = IMA_ProdFam) end) as ProdFamInvGroup
,UnitVol =
(Case When(Select Count(*) From RC_Special_UnitVol Where SU_CSA_RecordID =CSA_RecordID and SU_IMA_ItemID =IMA_ItemID) =1
Then (Select SU_CubicVolume From RC_Special_UnitVol Where SU_CSA_RecordID =CSA_RecordID and SU_IMA_ItemID =IMA_ItemID)
Else isnull(IMA_CubicVolume,0) End)
,IMA_CubicVolUnitMeasure as VolUnitMeasure
,isnull((SOD_RequiredQty *
(Case When(Select distinct Count(*) From RC_Special_UnitVol Where SU_CSA_RecordID =CSA_RecordID and SU_IMA_ItemID =IMA_ItemID) =1
Then (Select SU_CubicVolume From RC_Special_UnitVol Where SU_CSA_RecordID =CSA_RecordID and SU_IMA_ItemID =IMA_ItemID)
Else isnull(IMA_CubicVolume,0) End)),0) as TotalUnitVol
,SOM_DefaultVATCodeID as Vat
,SOM_SpecialInst as RemarkPO
,SOM_Buyer as OrderNumber
,isnull(EMP_MidName,'-')+' '+ isnull(EMP_LastName,'-')+' ('+ isnull(EMP_FirstName,'-')+')' as LastUpdateBy
,SOI_LineNbrTypeCode as LineTypeCode
,isnull(IMA_Kit,'') as Kit
,IMA_ItemID as ItemID--> Add
,day(SOM_DefaultRequiredDate) as [Day]
,month(SOM_DefaultRequiredDate) as [Month]
,year(SOM_DefaultRequiredDate) as [Year]
Into #OPS
FROM SalesOrder
Inner Join SalesOrderLine on SOM_RecordID = SOI_SOM_RecordID
left Join ITEM On IMA_RecordID = SOI_IMA_RecordID
left join ItemAliasDetail on IAD_RecordID = SOI_IAD_ID
inner Join SalesOrderDelivery On SOD_SOI_RecordID = SOI_RecordID
inner Join EMP On SOM_EMP_RecordID = EMP_RecordID
inner Join Customer On CUS_RecordId = SOM_CUS_RecordId
inner join CustomerShipTo on CSA_RecordID = SOM_DefaultCSA_RecordID
Where SOD_RequiredDate Between dbo.Fn_Rc_DateWithTime(#ExfacDateFrom,0) and dbo.Fn_Rc_DateWithTime(#ExfacDateTo,1)
And CUS_CustomerID = #CustomerID
And SOI_LineNbrTypeCode = 'Item'
order by SOM_SalesOrderID,SOD_SOLineNbr
--Select * From #OPS
Select
CustomerID,
CustomerName,
ItemID,
AliasName,
PartName = (Select IMA_ItemName From Item Where IMA_ItemID = ItemID ),
PrivateRemark,
Qty,
ExFacDate,
SalesOrderID,
CustomerPO,
OrderNumber,
where IMA_ItemID = ItemID)
UserDef1 = (Select WH_LOC from VW_ItemID_WH_Loc where IMA_ItemID = ItemID)
Into #TempFinish
From #OPS
order by ExFacDate
Select * From #TempFinish
Drop table #OPS
Drop table #TempFinish
This error shows when you use Sub query and =,>,< etc operators, and the sub query returning more than one record.
Try changing this:
PartName = (Select IMA_ItemName From Item Where IMA_ItemID = ItemID )
to this:
PartName = (Select Top 1 IMA_ItemName From Item Where IMA_ItemID = ItemID
This is applicable to all the places in your query where such same scenario is there

SQL combine relationship table as separate columns

I have this 2 tables
Items
ID Type ClientID
========================
1 0 123
2 0 123
Texts
ItemID Language Text
================================
1 Eng Hi there!
1 Spa Holla!
2 Eng bye!
2 Spa bye in Spanish!
In my final result I wat the SQL to return me this table
ID Type Eng Spa
================================
1 0 Hi there! Holla!
2 0 Bye! bye in Spanish!
I tried to create this statement:
SELECT DISTINCT I.ID ,I.Type,
(SELECT T.Text WHERE D.Language='Eng') AS 'Eng',
(SELECT T.Text WHERE D.Language='Spa') AS 'Spa'
FROM Items I
INNER JOIN Texts T ON I.ID=T.ItemID
but i get this result:
ID Type Eng Spa
================================
1 0 Hi there! NULL
1 0 NULL Holla!
2 0 Bye! NULL
2 0 NULL bye in Spanish!
I don't see why a join is necessary. You can just use conditional aggregation:
select t.itemid,
max(case when t.language = 'Eng' then t.text end) as Eng,
max(case when t.language = 'Spa' then t.text end) as Spa
from texts t
group by t.itemid;
Use nesting to make two joins on same table with a filter (where clause) .
Below i have tested on MySQL
SELECT
i.id, eng_table.text AS eng, spa_table.text AS spa
FROM
i
LEFT OUTER JOIN
(SELECT
ItemID AS ID, Text
FROM
t
WHERE
Language = 'ENG') AS eng_table ON i.id = eng_table.id
LEFT OUTER JOIN
(SELECT
ItemID AS ID, Text
FROM
t
WHERE
Language = 'SPA') AS spa_table ON i.id = spa_table.id
Regards,
Bikxs
A solution using joins would look something like....
Select i.ID
,i.[Type]
,t1.[Text] AS [Eng]
,t2.[Text] AS [Spa]
FROM Items i
INNER JOIN Texts t1 ON i.ID = t1.ItemID AND t1.[Language] = 'Eng'
INNER JOIN Texts t2 ON i.ID = t2.ItemID AND t2.[Language] = 'Spa'
WITH Eng
AS
(
SELECT * FROM Texts t WHERE t.Language = 'Eng'
),
Spa
AS
(
SELECT * FROM Texts t WHERE t.Language = 'Spa'
)
SELECT i.ID, i.Type, e.Text AS Eng, s.Text AS Spa
FROM Items i
LEFT JOIN Eng e ON i.ID = e.ItemID
LEFT JOIN Spa s ON i.ID = s.ItemID
Even you can use a dynamic sql query.
Query
DECLARE #query VARCHAR(MAX)
SELECT #query = 'SELECT t1.itemid, MAX(t2.[Type]) AS [Type], ' +
STUFF
(
(
SELECT DISTINCT ',MAX(CASE WHEN t1.[language] = '''+ [language]
+ ''' THEN t1.[Text] END) AS ' + [Language]
FROM Texts
FOR XML PATH('')
),
1,1,'');
SELECT #query += ' FROM texts t1 JOIN items t2 ON t1.ItemId = t2.ID GROUP BY t1.itemid;';
EXECUTE(#query);

SQL - Change NULL value to 0

I have this pivot table that gives me null value on SUM(contar) and I want to change them to 0
SELECT Description AS Categoria,
[ACS],
[URO]
FROM
(SELECT GroupType.Description,
Speciality.Shortname,
SUM(1) AS contar
FROM DoctorEnterpriseDetails
INNER JOIN Speciality ON DoctorEnterpriseDetails.Speciality1 = Speciality.SpecialityId
INNER JOIN GroupType ON DoctorEnterpriseDetails.GroupId = GroupType.GroupId
WHERE (DoctorEnterpriseDetails.EnterpriseId = 48)
GROUP BY GroupType.Description,
Speciality.Shortname) AS ps PIVOT (SUM(contar)
FOR Shortname IN ([ACS],[URO])) pvt
ORDER BY description
I tried make this way but it gives me Null values again
SELECT Description AS Categoria,
[ACS],
[URO]
FROM
(SELECT GroupType.Description,
Speciality.Shortname,
GroupType.GroupId,
(CASE WHEN (SUM(1) IS NULL) THEN 0 ELSE SUM(1) END) AS contar
FROM DoctorEnterpriseDetails
INNER JOIN Speciality ON DoctorEnterpriseDetails.Speciality1 = Speciality.SpecialityId
INNER JOIN GroupType ON DoctorEnterpriseDetails.GroupId = GroupType.GroupId
WHERE (DoctorEnterpriseDetails.EnterpriseId = 48)
GROUP BY GroupType.Description,
Speciality.Shortname,
DoctorEnterpriseDetails.GroupId,
GroupType.GroupId) AS ps PIVOT (SUM(contar)
FOR Shortname IN ([ACS],[URO])) pvt
ORDER BY GroupId;
I don't understand what is wrong. Thank you
You define aliases within the CASE, which is not allowed. Additionally you mix numeric and string:
It's either
CASE WHEN SUM(1) IS NULL THEN 0 ELSE SUM(1) END AS contador
or
COALESCE(SUM(1), 0) END AS contador
Btw, SUM(1) is the same as COUNT(*)

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)