How to show 0 value using COUNT and SELECTon a SQL query - sql

I have ONLY 1 table called Meeting that stores all meeting requests.
This table can be EMPTY.
It has several columns including requestType (which can only be "MT") meetingStatus (can only be either pending, approved, denied or canceled) and meetingCreatedTime
I want to count how many requests of each status's type (in other words how many requests are pending, how many are approved, denied and canceled) for the last 30 days
Problem is that if there is no request then nothing display but I want to display 0, how do I do it? Here is my query now:
SELECT [requestType],
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Approved') As 'Approved',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Pending') As 'Pending',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Canceled') As 'Canceled',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Denied') As 'Denied'
FROM [Meeting]
WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) GROUP BY [requestType]
Result:
What I want is:

SELECT
RT.requestType,
SUM(CASE WHEN M.meetingStatus = 'Approved' THEN 1 ELSE 0 END) AS Approved,
SUM(CASE WHEN M.meetingStatus = 'Pending' THEN 1 ELSE 0 END) AS Pending,
SUM(CASE WHEN M.meetingStatus = 'Canceled' THEN 1 ELSE 0 END) AS Canceled,
SUM(CASE WHEN M.meetingStatus = 'Denied' THEN 1 ELSE 0 END) AS Denied,
FROM
(SELECT DISTINCT requestType FROM Meeting) RT
LEFT OUTER JOIN Meeting M ON
M.requestType = RT.requestType AND
M.meetingCreatedTime >= DATEADD(DAY, -30, GETDATE())
GROUP BY
RT.requestType
The SUMs are a much clearer (IMO) and much more efficient way of getting the counts that you need. Using the requestType table (assuming that you have one) lets you get results for every request type even if there are no meetings of that type in the date range. The LEFT OUTER JOIN to the meeting table allows the request type to still show up even if there are no meetings for that time period.
All of your CASTs between date values seem unnecessary.

Move those subqueries into simple sum/case statements:
select rt.request_type,
sum(case when [meetingStatus] = 'Approved' then 1 else 0 end),
sum(case when [meetingStatus] = 'Pending' then 1 else 0 end),
sum(case when [meetingStatus] = 'Canceled' then 1 else 0 end),
sum(case when [meetingStatus] = 'Denied' then 1 else 0 end)
from ( select 'MT' ) rt (request_type) --hopefully you have lookup table for this
left
join [Meeting] m on
rt.request_type = m.request_type and
CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE)
group
by rt.request_type;

This is one possible approach to force one line to be visible in any case. Adapt this to your needs...
Copy it into an empty query window and execute... play around with the WHERE part...
DECLARE #Test TABLE (ID INT IDENTITY, GroupingKey VARCHAR(100));
INSERT INTO #Test VALUES ('a'),('a'),('b');
SELECT TOP 1 tbl.CountOfA
,tbl.CountOfB
,tbl.CountOfC
FROM
(
SELECT 1 AS Marker
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='a') AS CountOfA
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='b') AS CountOfB
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='c') AS CountOfC
WHERE (1=1) --play here with (1=0) and (1=1)
UNION ALL
SELECT 2,0,0,0
) AS tbl
ORDER BY Marker

Related

Aggregation, grouping error even using OVER PARTITION BY

I'm getting this error
'PRINTING_DATE' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
but by using OVER PARTITION BY I expected this issues won't appear, why am I still getting this error?
DECLARE #REPORT AS NVARCHAR(50) = '2019-06-19 00:00:00.000'
SELECT
SUM(CASE
WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS SHIPPED,
SUM(CASE
WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) - SUM(CASE
WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END) AS TO_SHIP
FROM
TABLE1 T1
INNER JOIN
TABLE2 T2 ON T1.DELIVERY = T2.DELIVERY
WHERE
PLANT = 'XXX'
As HoneyBadger pointed out, you only have a "OVER PARTITION BY" in the first SUM, not the second.
Try this as a simplified version:
DECLARE #T1 TABLE(PLANT INT,PARTS INT,P_DATE DATE,QTY1 INT, QTY2 INT)
INSERT INTO #T1 VALUES(1,1,'2019-07-03',40,60)
INSERT INTO #T1 VALUES(1,1,'2019-07-03',50,80)
SELECT
SUM(QTY1) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS SHIPPED
,SUM(QTY1) OVER (PARTITION BY PLANT, PARTS, P_DATE) -
SUM(QTY2) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS TOSHIP
,SUM(QTY1) - SUM(QTY2) AS TOSHIP2
FROM #T1
WHERE PLANT = 1
This will raise an error:
Column '#T1.PLANT' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If you comment/delete the line
,SUM(QTY1) - SUM(QTY2) AS TOSHIP2
it will produce a result.
BTW: in this example the error also points to the first SUM, but this one is not the problem.
It is rather unclear what you are trying to do. But I can give a hint: window functions are of no use if your result set is an aggregation that returns a single row.
So, you might want this:
SELECT SUM(CASE WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) AS SHIPPED,
(SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) -
SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END)
) AS TO_SHIP
FROM TABLE1 T1 INNER JOIN
TABLE2 T2
ON T1.DELIVERY = T2.DELIVERY
WHERE PLANT = 'XXX';
Of course, you might want an aggregation query that returns more than one row:
SELECT PLANT, PARTS, P_DATE,
SUM(CASE WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) AS SHIPPED,
(SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) -
SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END)
) AS TO_SHIP
FROM TABLE1 T1 INNER JOIN
TABLE2 T2
ON T1.DELIVERY = T2.DELIVERY
WHERE PLANT = 'XXX'
GROUP BY PLANT, PARTS, P_DATE;

SQL joining most recent event by criteria to missing value

I have a SQL table that records interactions and the changes that happen in an interaction by interactionkey, user, group, and skill. I want to find the duration of each of the actions (A,B,C) by the grouping variables. Whenever action C happens, the skillKey is left blank (not NULL) and I need it to take the value of the last Skill in that interaction by the user and group so it is grouped together. The first table is the raw SQL data for 1 interaction, and the second is how I need it to look. Edit: I'm using Microsoft SQL Server Management Studio.
Here's what I have so far but it doesn't account for the last skill the user used in the group and interactionkey so it remains blank and adds it up seperately
SELECT
[InteractionKey],
[User],
[StartTime],
[SkillKey],
[GroupKey],
SUM(CASE WHEN ActionKey = 'A' THEN ActionDuration ELSE 0 END) AS 'ActionADuration',
SUM(CASE WHEN ActionKey = 'B' THEN ActionDuration ELSE 0 END) AS 'ActionBDuration',
SUM(CASE WHEN ActionKey = 'C' THEN ActionDuration ELSE 0 END) AS 'ActionCDuration'
FROM
(SELECT
[ActionKey],
[InteractionKey],
[SkillKey],
[GroupKey],
SUM(ActionDuration) AS 'ActionDuration',
[User],
CAST(StartTime AS DATE)
FROM
[InteractionTable]
GROUP BY
InteractionKey, User, SkillKey, GroupKey, ActionKey,
CAST(StartTime AS DATE)) sub
GROUP BY
InteractionKey, User, Date, SkillKey, GroupKey
ORDER BY
InteractionKey
Raw SQL Server table:
Desired output:
All you need is to prepare another "table" with SkillKey already filled as you need, and then use this table in your query.
I will use CTE in my code,
and it is different for divverent versions of SQL Server.
The first one is preferable, but it's for servers starting with 2012.
If you are on lower version use the second query.
-- for ##version >= 2012
with cte as
(
select *,
case
when SkillKey <> ''
then SkillKey
else lag(SkillKey) over(partition by InteractionKey, [User], GroupKey order by [Date])
end as SkillKey
from InteractionTable
)
SELECT
[InteractionKey],
[User],
[StartTime],
[SkillKey],
[GroupKey],
SUM(CASE WHEN ActionKey = 'A' THEN ActionDuration ELSE 0 END) AS 'ActionADuration',
SUM(CASE WHEN ActionKey = 'B' THEN ActionDuration ELSE 0 END) AS 'ActionBDuration',
SUM(CASE WHEN ActionKey = 'C' THEN ActionDuration ELSE 0 END) AS 'ActionCDuration'
FROM
(SELECT
[ActionKey],
[InteractionKey],
[SkillKey],
[GroupKey],
SUM(ActionDuration) AS 'ActionDuration',
[User],
CAST(StartTime AS DATE)
FROM
cte
GROUP BY
InteractionKey, User, SkillKey, GroupKey, ActionKey,
CAST(StartTime AS DATE)) sub
GROUP BY
InteractionKey, User, Date, SkillKey, GroupKey
ORDER BY
InteractionKey
The second:
-- for ##version >= 2005
with r as
(
select *,
row_number() over(partition by InteractionKey, [User], GroupKey order by [Date]) as rn
from InteractionTable
)
,cte as
(
select r1.*,
case
when r1.SkillKey <> ''
then r1.SkillKey
else r2.SkillKey
end as SkillKey
from r r1
left join r r2
on r1.rn = r2.rn + 1
)
SELECT
[InteractionKey],
[User],
[StartTime],
[SkillKey],
[GroupKey],
SUM(CASE WHEN ActionKey = 'A' THEN ActionDuration ELSE 0 END) AS 'ActionADuration',
SUM(CASE WHEN ActionKey = 'B' THEN ActionDuration ELSE 0 END) AS 'ActionBDuration',
SUM(CASE WHEN ActionKey = 'C' THEN ActionDuration ELSE 0 END) AS 'ActionCDuration'
FROM
(SELECT
[ActionKey],
[InteractionKey],
[SkillKey],
[GroupKey],
SUM(ActionDuration) AS 'ActionDuration',
[User],
CAST(StartTime AS DATE)
FROM
cte
GROUP BY
InteractionKey, User, SkillKey, GroupKey, ActionKey,
CAST(StartTime AS DATE)) sub
GROUP BY
InteractionKey, User, Date, SkillKey, GroupKey
ORDER BY
InteractionKey

Revenue for two months date wise

I am trying to get data for last 2 month ...but the query does not give perfect result....
SELECT DAY(table_A.PaymentDate) as date1 ,
(case when MONTH(table_A.PaymentDate) = MONTH(CURRENT_TIMESTAMP) - 1
then CAST(SUM(table_A.Total_Amount) AS INT)
else 0
end) AS last_month_CNT,
(case when MONTH(table_A.PaymentDate) = MONTH(CURRENT_TIMESTAMP)
then CAST(SUM(table_A.Total_Amount) As INT)
else 0
end) as This_month_CNT
FROM Tbl_Pan_Paymentdetails table_A
FULL OUTER JOIN Tbl_Pan_Paymentdetails table_B
ON table_A.PaymentDate=table_B.PaymentDate
WHERE YEAR(table_A.PaymentDate) = YEAR(CURRENT_TIMESTAMP)
AND
table_A.PaymentDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY
DAY(table_A.PaymentDate) ,MONTH(table_A.PaymentDate)
order by
DAY(table_A.PaymentDate);
Move the entire case expression inside the sum function and don't include the month in the group by. Also, the full outer join seems unnecessary so I removed it.
This should be what you are looking for:
SELECT
DAY(PaymentDate) as date1 ,
SUM(CASE WHEN MONTH(PaymentDate) = MONTH(CURRENT_TIMESTAMP)-1 THEN CAST(Total_Amount AS INT) ELSE 0 END) AS last_month_CNT,
SUM(CASE WHEN MONTH(PaymentDate) = MONTH(CURRENT_TIMESTAMP) THEN CAST(Total_Amount AS INT) ELSE 0 END) AS This_month_CNT
FROM Tbl_Pan_Paymentdetails
WHERE YEAR(PaymentDate) = YEAR(CURRENT_TIMESTAMP)
AND PaymentDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY DAY(PaymentDate)
ORDER BY DAY(PaymentDate);

sql join and group by generated date range

I have Table1 and I need a query to populate Table2:
Problem here is with Date column. I want to know the process of location/partner combination per day. Main issue here is that I can't pick DateCreated and make it as default date since it doesn't necessarily cover whole date range, like in this example where it doesn't have 2015-01-07 and 2015-01-09. Same case with other dates.
So, my idea is to first select dates from some table which contains needed date range and then perform calculation for each day/location/partner combination from cte but in that case I can't figure out how to make a join for LocationId and PartnerId.
Columns:
Date - CreatedItems - number of created items where Table1.DateCreated = Table2.Date
DeliveredItems - number of delivered items where Table1.DateDateOut = Table2.Date
CycleTime - number of days delivered item was in the location (DateOut - DateIn + 1)
I started with something like this but it's very like that I completely missed the point with it:
with d as
(
select date from DimDate
where date between DATEADD(DAY, -365, getdate()) and getdate()
),
cr as -- created items
(
select
DateCreated,
LocationId,
PartnerId,
CreatedItems = count(*)
from Table1
where DateCreated is not null
group by DateCreated,
LocationId,
PartnerId
),
del as -- delivered items
(
select
DateOut,
LocationId,
ParnerId,
DeliveredItems = count(*),
CycleTime = DATEDIFF(Day, DateOut, DateIn)
from Table1
where DateOut is not null
and Datein is not null
group by DateOut,
LocationId,
PartnerId
)
select
d.Date
from d
LEFT OUTER JOIN cr on cr.DateCreated = d.Date -- MISSING JOIN PER LocationId and PartnerId
LEFT OUTER JOIN del on del.DateCompleted = d.Date -- MISSING JOIN PER LocationId and PartnerId
with range(days) as (
select 0 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 /* extend as necessary */
)
select dateadd(day, r.days, t.DateCreated) as "Date", locationId, PartnerId,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.days between 0 and datediff(day, t.DateCreated, t.DateOut)
group by dateadd(day, r.days, t.DateCreated), LocationId, PartnerId;
If you only want the end dates (rather than all the dates in between) this is probably a better approach:
with range(dt) as (
select distinct DateCreated from T union
select distinct DateOut from T
)
select r.dt as "Date", locationId, PartnerId,
sum(
case
when r.dt = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when r.dt = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when r.dt = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.dt in (t.DateCreated, t.DateOut)
group by r.dt, LocationId, PartnerId;
If to specify WHERE clause? Something Like that:
WHERE cr.LocationId = del.LocationId AND
cr.PartnerId = del.PartnerId

SQL Query: Cannot perform aggregate functions on sub queries

I have the following SQL query
SELECT
[Date],
DATENAME(dw,[Date]) AS Day,
SUM(CASE WHEN ChargeCode IN (SELECT ChargeCode FROM tblChargeCodes WHERE Chargeable = 1) THEN Units ELSE 0 END) ChargeableTotal,
SUM(CASE WHEN ChargeCode IN (SELECT ChargeCode FROM tblChargeCodes WHERE Chargeable = 0) THEN Units ELSE 0 END) NotChargeableTotal,
SUM(Units) AS TotalUnits
FROM
tblTimesheetEntries
WHERE
UserID = 'PJW'
AND Date >= '2013-01-01'
GROUP BY
[Date]
ORDER BY
[Date] DESC;
But I get the error message:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Because I am using sub queries in the Case Else Summation.
How can I revise my query to get 2 x Sums of [Units] one for Chargeable = true, and one for Chargeable = false, even though the Chargeable field is in a different table to all the other information. The two tables are linked by ChargeCode which appears in both tblTimesheetEntries and tblChargeCodes.
Have you tried joining the tables on the chargeCode:
SELECT e.[Date],
DATENAME(dw,e.[Date]) AS Day,
SUM(CASE WHEN c.Chargeable = 1 THEN e.Units ELSE 0 END) ChargeableTotal,
SUM(CASE WHEN c.Chargeable = 0 THEN e.Units ELSE 0 END) NotChargeableTotal,
SUM(e.Units) AS TotalUnits
FROM tblTimesheetEntries e
LEFT JOIN tblChargeCodes c
on e.ChargeCode = c.ChargeCode
WHERE e.UserID = 'PJW'
AND e.Date >= '2013-01-01'
GROUP BY e.[Date]
ORDER BY e.[Date] DESC;