How to show NULL instead of 0 when using sum aggregate / pivoting - sql

Consider this 24-hour summary. The problem is that I'm getting 0s returned for hours where values haven't been inserted yet. I believe this is due to the SUM() function call.
SELECT
section,
[21], [22], [23], [0], [1], [2], [3], [4], [5], [6], [7], [8],
[21] + [22] + [23] + [0] + [1] + [2] + [3] + [4] + [5] + [6] + [7] + [8] as s1_total
-- shift 2 fields are ommitted for brievity
FROM (
SELECT
section,
-- hours from 21:00 (1st shift) to 20:00 (2nd shift)
SUM(CASE WHEN prTime = '21:00:00' THEN Amount ELSE 0 END) AS [21],
SUM(CASE WHEN prTime = '22:00:00' THEN Amount ELSE 0 END) AS [22],
SUM(CASE WHEN prTime = '23:00:00' THEN Amount ELSE 0 END) AS [23],
SUM(CASE WHEN prTime = '00:00:00' THEN Amount ELSE 0 END) AS [0],
SUM(CASE WHEN prTime = '01:00:00' THEN Amount ELSE 0 END) AS [1],
SUM(CASE WHEN prTime = '02:00:00' THEN Amount ELSE 0 END) AS [2],
-- ... similar cases are omitted for brieviety
SUM(CASE WHEN prTime = '20:00:00' THEN Amount ELSE 0 END) AS [20]
FROM (
SELECT prTime, prDate, section01 AS Amount, 'section 1' as [Section] FROM SectionsHourlyValues
UNION
SELECT prTime, prDate, section02 AS Amount, 'section 2' as [Section] FROM SectionsHourlyValues
UNION
SELECT prTime, prDate, section03 AS Amount, 'section 3' as [Section] FROM SectionsHourlyValues
) AS U
WHERE
(prDate = CONVERT(DATE, DATEADD(HOUR, -4, CONVERT(DATETIME2(7), #dt, 104))) and prTime > '20:00:00') or
(prDate = CONVERT(DATE, #dt, 104) and prTime <= '20:00:00')
GROUP BY section
) t;
For example, running the query
DECLARE #dt varchar(10) = 'certain_date';
SELECT * from [dbo].[SectionsHourlyValues] WHERE
(prDate = CONVERT(DATE, DATEADD(HOUR, -4, CONVERT(DATETIME2(7), #dt, 104))) and prTime > '20:00:00') or
(prDate = CONVERT(DATE, #dt, 104) and prTime <= '20:00:00');
wouldn't return us the data for say 09:00:00 / section1 whereas the summary would show us 0.
Then I want to show NULL (for not yet inserted records) instead of 0. How do I do that?

How about replacing the 0 with NULL in your conditional aggregation ?
SUM(CASE WHEN etc... ELSE NULL END)

Use NULLIF
ex
NULLIF(SUM(CASE WHEN prTime = '21:00:00' THEN Amount ELSE 0 END),0)
or simply do not use ELSE
SUM(CASE WHEN prTime = '21:00:00' THEN Amount END)

You're getting 0 values not due to the SUM function for values not yet inserted (that would be NULL and would ignored by the SUM function), but because the ELSE of your CASE statement returns 0:
SUM(CASE WHEN prTime = '21:00:00' THEN Amount ELSE 0 END) AS [21]
To solve your problem, you can use NULLIF that is rather similar to CASE:
NULLIF(MyExpressionThatCouldReturn0, 0)
That in your case would be:
NULLIF(SUM(CASE WHEN prTime = '01:00:00' THEN Amount ELSE 0 END),0)
Otherwise, as said before, do not use ELSE in the CASE statement, used as parameter of your SUM:
SUM(CASE WHEN prTime = '21:00:00' THEN Amount END)

Related

Aggregate function on an expression containing an aggregate or a subquery

I'm getting an error
"Cannot perform an aggregate function on an expression containing an aggregate or a subquery"
which is this line:
,MIN(CASE WHEN DateCompleted IS NULL THEN MIN(DateReceived) ELSE '' END) AS Oldest_Claim
I'm not sure how to handle this because I can't include DateCompleted in the GROUP BY clause and get the results that I need. Thanks
DECLARE #StartDate smalldatetime = '1/1/2000'
DECLARE #EndDate smalldatetime = '3/13/2019'
SELECT
CASE
WHEN DischargeType = 'atb' THEN 'Ability to Benefit'
WHEN DischargeType = 'cls' THEN 'Closed School'
WHEN DischargeType = 'death' THEN 'Death'
WHEN DischargeType = 'dqs' THEN 'Disqualifying Status'
WHEN DischargeType = 'fraud' THEN 'Fraud'
WHEN DischargeType = 'id theft' THEN 'ID Theft'
WHEN DischargeType = 'ineligible borrower' THEN 'Ineligible Borrower'
WHEN DischargeType = 'tlf' THEN 'Teacher Loan Forgiveness'
WHEN DischargeType = 'uns' THEN 'Unauthorized Signature/Payment'
WHEN DischargeType = 'unp' THEN 'Unpaid Refund'
END AS DischargeType
,UPPER(Servicer) AS Servicer
,'' AS Outstanding
--Intentionally using DateLoaded here instead of DateReceived
,SUM(CASE WHEN (DateLoaded > #StartDate AND DateLoaded < DATEADD(dd, 1, #EndDate)) THEN 1 ELSE 0 END) AS Claims_Loaded
,SUM(CASE WHEN DateCompleted IS NOT NULL AND Approve = 1 AND DateCompleted > #StartDate AND DateCompleted < DATEADD(dd, 1, #EndDate) THEN 1 ELSE 0 END) AS Claims_Approved
,SUM(CASE WHEN DateCompleted IS NOT NULL AND Approve = 0 AND DateCompleted > #StartDate AND DateCompleted < DATEADD(dd, 1, #EndDate) THEN 1 ELSE 0 END) AS Claims_Denied
,SUM(CASE WHEN DateCompleted IS NULL THEN 1 ELSE 0 END) AS Claims_Pending
,MIN(CASE WHEN DateCompleted IS NULL THEN MIN(DateReceived) ELSE '' END) AS Oldest_Claim
,ROUND((SUM(CASE WHEN DateCompleted IS NOT NULL AND Approve = 0 AND DateCompleted > #StartDate AND DateCompleted < DATEADD(dd, 1, #EndDate) THEN 1 ELSE 0 END) /
NULLIF(CAST(SUM(CASE WHEN (DateCompleted > #StartDate AND DateCompleted < DATEADD(dd, 1, #EndDate)) THEN 1 ELSE 0 END)AS Float),0) *100),2) AS Percent_Denied
FROM
Claims
WHERE
DischargeType IN ('atb','cls','death','dqs','fraud','id theft','ineligible borrower','tlf','uns','unp')
GROUP BY
DischargeType, Servicer
ORDER BY DischargeType, Servicer
You must change the expression:
MIN(CASE WHEN DateCompleted IS NULL THEN MIN(DateReceived) ELSE '' END)
To something like:
MIN(CASE WHEN DateCompleted IS NULL THEN (
select MIN(DateReceived) from ...
) ELSE '' END)
Probably you'll be better off including that subquery in a CTE.

Sorting data by YEAR

I am completely new to SQL and have really only run statements with minimal modifications. I am currently trying to modify this specific query:
SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
SUM (CASE WHEN month = 1 THEN 1 ELSE NULL END) AS January,
SUM (CASE WHEN month = 2 THEN 1 ELSE NULL END) AS February,
SUM (CASE WHEN month = 3 THEN 1 ELSE NULL END) AS March,
SUM (CASE WHEN month = 4 THEN 1 ELSE NULL END) AS April,
SUM (CASE WHEN month = 5 THEN 1 ELSE NULL END) AS May,
SUM (CASE WHEN month = 6 THEN 1 ELSE NULL END) AS June,
SUM (CASE WHEN month = 7 THEN 1 ELSE NULL END) AS July,
SUM (CASE WHEN month = 8 THEN 1 ELSE NULL END) AS August,
SUM (CASE WHEN month = 9 THEN 1 ELSE NULL END) AS September,
SUM (CASE WHEN month = 10 THEN 1 ELSE NULL END) AS October,
SUM (CASE WHEN month = 11 THEN 1 ELSE NULL END) AS November,
SUM (CASE WHEN month = 12 THEN 1 ELSE NULL END) AS December
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY ROUND (windspeed * 2, -1) / 2
ORDER BY ROUND (windspeed * 2, -1) / 2;
What I want from the query is to instead of sorting by months, sort by all the years that are available for the specific location (platformid). So far I have just modified the script so that it looks like this:
SELECT DISTINCT
(ROUND (windspeed * 2, -1) / 2) AS wndspd,
SUM (
CASE
WHEN TO_CHAR (observationtime, 'YYYY') = 1957 THEN 1
ELSE NULL
END)
AS given_year,
SUM (
CASE
WHEN TO_CHAR (observationtime, 'YYYY') = 1958 THEN 1
ELSE NULL
END)
AS GIVEN_YEAR2
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY ROUND (windspeed * 2, -1) / 2
ORDER BY ROUND (windspeed * 2, -1) / 2;
The problem is that I know that the years go from 1957-2015. I'm pretty sure that there is a more efficient way to list out the information that I want without creating a specific SUM string for every single year. I have no idea how to do that however. Please help!
You can try creating a PIVOT query
SELECT wndspd, [1957], [1958], [1959],...etc....[2013], [2014], [2015] FROM
(
SELECT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
YEAR(observationtime) yr
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
) src
PIVOT (
COUNT(yr)
FOR yr IN ([1957],[1958], [1959],...etc....[2013], [2014], [2015])
) pvt
you'll have to fill in all of the other years between 1959 and 2013.
here's an easier way to create all of the columns from 1957 to the current year.
DECLARE #PivotCols VARCHAR(MAX),
#MinYear INT = 1957,
#CurYear INT = YEAR(GETDATE())
WHILE #MinYear < #CurYear
BEGIN
SET #PivotCols = COALESCE( #PivotCols + '],[', '[') + CONVERT(VARCHAR, #MinYear)
SET #MinYear = #MinYear + 1
END
SET #PivotCols = CONCAT(#PivotCols,'],[',CONVERT(VARCHAR, #MinYear),']')
DECLARE #Sql VARCHAR(MAX) = '
SELECT wndspd, ' + #PivotCols + ' FROM
(
SELECT (ROUND (windspeed * 2, -1) / 2) AS wndspd,
YEAR(observationtime) yr
FROM table1
WHERE platformid = ''coollocation''
AND networktype = ''typeofcoollocation''
AND (windspeedqc <> ''2'' OR windspeedqc IS NULL)
) src
PIVOT (
COUNT(yr)
FOR yr IN (' + #PivotCols + ')
) pvt
'
EXEC (#Sql)
I think you are essentially trying to do this:
SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd
,TO_CHAR(observationtime, 'YYYY') as year,
,COUNT(1) as occurrences
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY (ROUND(windspeed * 2, -1) / 2), TO_CHAR(observationtime, 'YYYY')
ORDER BY 1, 2;
...however the count of occurrences in each year would then be represented by a row instead of a column.
Unfortunately there is no easy way to pivot the rows in this query into columns because you fundamentally need to know how many columns there are and ensure each row has the values populated properly.
At the end of the day you'll need to add a line for each year to your SELECT clause. It could look like this:
SELECT yearly.wndspd
,SUM(CASE WHEN yearly.year='1957' THEN yearly.occurrences ELSE 0 END) as 1957
,SUM(CASE WHEN yearly.year='1958' THEN yearly.occurrences ELSE 0 END) as 1958
...
,SUM(CASE WHEN yearly.year='2015' THEN yearly.occurrences ELSE 0 END) as 2015
FROM
(SELECT DISTINCT (ROUND (windspeed * 2, -1) / 2) AS wndspd
,TO_CHAR(observationtime, 'YYYY') as year,
,COUNT(1) as occurrences
FROM table1
WHERE platformid = 'coollocation'
AND networktype = 'typeofcoollocation'
AND (windspeedqc <> '2' OR windspeedqc IS NULL)
GROUP BY (ROUND(windspeed * 2, -1) / 2), TO_CHAR(observationtime, 'YYYY')) yearly
GROUP BY yearly.wndspd
ORDER BY 1, 2;

How to achieve this model

I have a table like this
I am trying to achieve a table like this:
this was my query.
select name,token , sum(qty) [Qty],sum(amount)from #temp
group by token,name
tried like this..
SELECT *
FROM (SELECT name,
qty,amt,
Token
FROM #temp1
) AS D
PIVOT(Sum(amt)
FOR Token IN ([10],[20],[100],[40],[5])) AS P
it is not working though.
Any help will be appreciated.
I think you can achieve it by the following statement:
SELECT
[Name],
SUM((CASE WHEN [Token] = 10 THEN [QTY] ELSE 0 END)) AS Token_10_QTY,
SUM((CASE WHEN [Token] = 10 THEN [amt] ELSE 0 END)) AS Token_10_amt,
SUM((CASE WHEN [Token] = 50 THEN [QTY] ELSE 0 END)) AS Token_50_QTY,
SUM((CASE WHEN [Token] = 50 THEN [amt] ELSE 0 END)) AS Token_50_amt,
SUM((CASE WHEN [Token] = 100 THEN [QTY] ELSE 0 END)) AS Token_100_QTY,
SUM((CASE WHEN [Token] = 100 THEN [amt] ELSE 0 END)) AS Token_100_amt
FROM
#temp1
GROUP BY
[Name]
assuming your columns QTY and amt are NOT NULL.
I hope it will help you some way.

Improve SQL Server query

I have to improve this query, that works very well.
DECLARE #timTimeout int,
#iniDate varchar(20),
#endDate varchar(20)
SET #iniDate = '2014-07-20 00:00:00'
SET #endDate = '2014-11-24 23:59:59'
SET #timTimeout = 4000
SET ANSI_WARNINGS OFF
SELECT
'Approved (0200)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0200' THEN 1 END), 0),
'Approved Off (0220)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0220' THEN 1 END), 0),
'Cancel (0400)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0400' THEN 1 END), 0),
'Regret (0420)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0420' THEN 1 END), 0),
'TOTAL' = COUNT(*),
'Time-outs' = ISNULL(SUM(CASE WHEN DATEDIFF(ms, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0),
'Disponibility (%)' = (1 - CAST(ISNULL(SUM(CASE WHEN DATEDIFF(ms, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0) as money) / COUNT(*)) * 100
FROM Message (NOLOCK)
WHERE DateMsgIncome BETWEEN #iniDate AND #endDate
AND CodMsgIncome IN ('0200', '0220', '0400', '0420', '0800', '0900', '9080', '9085')
AND DescMsgIncome <> '0220'
Now, I have to prepare a report with Total data organized by month.
The output disered seems like this:
Approved (0200) | Approved Off (0220) | Cancel | Total | Time-outs | Disponibility (%)
July | 35 15 12 62 0 100.00
.
.
.
EDIT:
It is only one table on my query.
Table Message:
DateMsgIncome date,
DateMsgSent date,
CodMsgIncome varchar(4),
DescMsgIncome varchar(4),
CodMsgAnswer int.
Any suggestion is welcome.
Thanks in advance.
I ran your query through a code formatter to help clean it up. I also change the variable declaration since you didn't seem to understand what I was saying. For the record, the way you had it coded you might have missed some rows in the last few milliseconds of the day.
I changed the DATEDIFF function to use the datepart name spelled out because it is just too easy use the wrong abbreviation and get it wrong. I also simplified the calculation for the last column. The cast to money was not needed if you change the 1 - to 1.0 -. You should avoid using reserved words for object names and avoid spaces in column names. Let the front end do this kind of pretty formatting.
I also added the soon the be required WITH keyword when using table hints. (I would recommend understand what NOLOCK really means before using it).
DECLARE #timTimeout int
, #iniDate date
, #endDate date
SET #iniDate = '2014-07-20'
SET #endDate = '2014-11-25'
SET #timTimeout = 4000
SELECT MONTH(DateMsgIncome) as MyMonthColumn
, 'Approved (0200)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0200' THEN 1 END), 0)
, 'Approved Off (0220)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0220' THEN 1 END), 0)
, 'Cancel (0400)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0400' THEN 1 END), 0)
, 'Regret (0420)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0420' THEN 1 END), 0)
, 'TOTAL' = COUNT(*)
, 'Time-outs' = ISNULL(SUM(CASE WHEN DATEDIFF(MILLISECOND, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0)
, 'Disponibility (%)' = (1.0 - ISNULL(SUM(CASE WHEN DATEDIFF(MILLISECOND, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0) / COUNT(*)) * 100
FROM [Message] WITH (NOLOCK) --Ack!!! I wouldn't let this fly on my system due to inconsistencies with this hint unless accuracy is not important (like
WHERE DateMsgIncome >= #iniDate
AND DateMsgIncome < #endDate
AND CodMsgIncome IN
(
'0200'
, '0220'
, '0400'
, '0420'
, '0800'
, '0900'
, '9080'
, '9085'
)
AND DescMsgIncome <> '0220'
GROUP BY MONTH(DateMsgIncome)

Joining two queries into one

I have two working queries. Query 1 performs a filter on a large table and returns exactly the data that I need, it looks like this:
/****** QUERY #1 - This query will filter the data ******/
SELECT [WacnId],
[StartDT]
,[EndDT]
,[Group]
,[ID_Agency]
,[TargetUnit_Agency],
case [Group]
when 1 then 'in'
when 0 then 'out'
end as traffic
FROM [GW_20140315].[dbo].[ARC_Calls_ReportView]
WHERE [GroupDisplayID] = 'T802149' OR [ID_Agency] = 'Dispatch' or [TargetUnit_Agency] = 'Dispatch'
order by StartDT
Query #2 acts on the filtered data from Query 1 and produces a 1/2 hourly report. Query 2 looks like this:
/******Query #2- This query will take the filtered data and process it as needed ******/
SELECT dateadd(mi, (datediff(mi, 0, StartDT) / 30) * 30, 0) as HalfHour
, sum(DATEDIFF ( s , [StartDT] , [EndDT] )) as [Total Time (Seconds)],
SUM(CASE WHEN [TargetUnit_Agency] = 'Dispatch' then 1 ELSE 0 END ) AS InCount,
SUM(CASE WHEN [ID_Agency] = 'Dispatch' then 1 ELSE 0 END ) AS OutCount
FROM [Radio].[dbo].[Filter_Data]--This is how I did it before, but now I want to combine the two queries
GROUP BY dateadd(mi, (datediff(mi, 0, StartDT) / 30) * 30, 0)
ORDER BY 1
How may I combine these two queries into one?
You may use a CTE to describe your filtered data (first query) and then query using the CTE as your main table (second query):
;WITH FilteredCTE AS
(
SELECT [WacnId],
[StartDT]
,[EndDT]
,[Group]
,[ID_Agency]
,[TargetUnit_Agency],
case [Group]
when 1 then 'in'
when 0 then 'out'
end as traffic
FROM [GW_20140315].[dbo].[ARC_Calls_ReportView]
WHERE [GroupDisplayID] = 'T802149'
OR [ID_Agency] = 'Dispatch'
or [TargetUnit_Agency] = 'Dispatch'
)
SELECT dateadd(mi, (datediff(mi, 0, StartDT) / 30) * 30, 0) as HalfHour,
sum(DATEDIFF ( s , [StartDT] , [EndDT] )) as [Total Time (Seconds)],
SUM(CASE WHEN [TargetUnit_Agency] = 'Dispatch' then 1 ELSE 0 END ) AS InCount,
SUM(CASE WHEN [ID_Agency] = 'Dispatch' then 1 ELSE 0 END ) AS OutCount
FROM FilteredCTE
GROUP BY dateadd(mi, (datediff(mi, 0, StartDT) / 30) * 30, 0)
ORDER BY StartDT
Just select Query1 FROM Query2:
SELECT Dateadd(mi, ( Datediff(mi, 0, startdt) / 30 ) * 30, 0) AS HalfHour,
Sum(Datediff (s, [startdt], [enddt])) AS
[Total Time (Seconds)],
Sum(CASE
WHEN [targetunit_agency] = 'Dispatch' THEN 1
ELSE 0
end) AS InCount,
Sum(CASE
WHEN [id_agency] = 'Dispatch' THEN 1
ELSE 0
end) AS OutCount
FROM (SELECT [wacnid],
[startdt],
[enddt],
[group],
[id_agency],
[targetunit_agency],
CASE [group]
WHEN 1 THEN 'in'
WHEN 0 THEN 'out'
end AS traffic
FROM [GW_20140315].[dbo].[arc_calls_reportview]
WHERE [groupdisplayid] = 'T802149'
OR [id_agency] = 'Dispatch'
OR [targetunit_agency] = 'Dispatch'
ORDER BY startdt)
GROUP BY Dateadd(mi, ( Datediff(mi, 0, startdt) / 30 ) * 30, 0)
ORDER BY 1