Changing a SUM returned NULL to zero - sql

I have a stored procedure as follows:
CREATE PROC [dbo].[Incidents]
(#SiteName varchar(200))
AS
SELECT
(
SELECT SUM(i.Logged)
FROM tbl_Sites s
INNER JOIN tbl_Incidents i
ON s.Location = i.Location
WHERE s.Sites = #SiteName AND i.[month] = DATEADD(mm, DATEDIFF(mm, 0, GetDate()) -1,0)
GROUP BY s.Sites
) AS LoggedIncidents
'tbl_Sites contains a list of reported on sites.
'tbl_Incidents contains a generated list of total incidents by site/date (monthly)
'If a site doesn't have any incidents that month it wont be listed.
The problem I'm having is that a site doesn't have any Incidents this month and as such i got a NULL value returned for that site when i run this proc, but i need to have a zero/0 returned to be used within a chart in SSRS.
I've tried using coalesce and isnull to no avail.
SELECT COALESCE(SUM(c.Logged,0))
SELECT SUM(ISNULL(c.Logged,0))
Is there a way to get this formatted correctly?
Cheers,
Lee

Put it outside:
SELECT COALESCE(
(
SELECT SUM(i.Logged)
FROM tbl_Sites s
INNER JOIN tbl_Incidents i
ON s.Location = i.Location
WHERE s.Sites = #SiteName AND i.[month] = DATEADD(mm, DATEDIFF(mm, 0, GetDate()) -1,0)
GROUP BY s.Sites
), 0) AS LoggedIncidents
If you are returning multiple rows, change INNER JOIN to LEFT JOIN
SELECT COALESCE(SUM(i.Logged),0)
FROM tbl_Sites s
LEFT JOIN tbl_Incidents i
ON s.Location = i.Location
WHERE s.Sites = #SiteName AND i.[month] = DATEADD(mm, DATEDIFF(mm, 0, GetDate()) -1,0)
GROUP BY s.Sites
By the way, don't put any function or expression inside aggregate functions if it's not warranted, e.g. don't put ISNULL, COALESCE inside of SUM, using function/expression inside aggregation cripples performance, the query will be executed with table scan

You'll have to use ISNULL like this -
ISNULL(SUM(c.Logged), 0)
Or, as Michael said, you can use a Left Outer Join.

I encountered this problem in Oracle.
Oracle does not have an ISNULL() function. However, we can use the NVL() function to achieve the same result:
NVL(SUM(c.Logged), 0)

The easiest, and most readable, way I've found to accomplish this is through:
CREATE PROC [dbo].[Incidents]
(#SiteName varchar(200))
AS
SELECT SUM(COALESCE(i.Logged, 0)) AS LoggedIncidents
FROM tbl_Sites s
INNER JOIN tbl_Incidents i
ON s.Location = i.Location
WHERE s.Sites = #SiteName
AND i.[month] = DATEADD(mm, DATEDIFF(mm, 0, GetDate()) -1,0)
GROUP BY s.Sites

You could wrap the SELECT in another SELECT like so:
CREATE PROC [dbo].[Incidents]
(#SiteName varchar(200))
AS
SELECT COALESCE(TotalIncidents ,0)
FROM (
SELECT
(
SELECT SUM(i.Logged) as TotalIncidents
FROM tbl_Sites s
INNER JOIN tbl_Incidents i
ON s.Location = i.Location
WHERE s.Sites = #SiteName AND i.[month] = DATEADD(mm, DATEDIFF(mm, 0, GetDate()) -1,0)
GROUP BY s.Sites
) AS LoggedIncidents
)

Just ran into this problem, Kirtan's solution worked for me well, but the syntax was a little off. I did like this:
ISNULL(SUM(c.Logged), 0)
Post helped me solve my problem though so thanks to all.

The code you've posted above
SELECT SUM(ISNULL(c.Logged,0))
would not work due to wrong order
what would work is the following
SELECT ISNULL(SUM(c.Logged),0)
It evaulates the expression SUM then if it returns NULL it is replaced with 0.

Related

Not include this specific date in the query when using BETWEEN operator in SQL Server 2008

There is one specific date that I do not want to include in this which is "04/08/2020". I want to filter all the records from the beginning of the year till now but not 04/08/2020. How can I do that?
SELECT
dbo.PID_RespiratorFit.PTIDNum,
rtrim(acp.LastName) + ', ' + rtrim(acp.FirstName) AS Name,
acp.BadgeID AS ID,
dbo.Patient.PcFLD01 as Organization,
dbo.PID_RespiratorFit.Risk AS Exposure,
UPPER(dbo.PID_RespiratorFit.Type) AS Mask_Type,
dbo.PID_RespiratorFit.OHSNotes AS Comments,
dbo.PID_RespiratorFit.MedicallyClearedDate AS Medically_Cleared,
dbo.PID_RespiratorFit.TrainingCompletedDate,
dbo.PID_RespiratorFit.DateIssued AS N95_Fittest_Completed,
dbo.PID_RespiratorFit.Mask AS N95_respirator,
dbo.PID_RespiratorFit.PAPR,
dbo.PID_RespiratorFit.PaprFitTestDate AS PAPR_Fittest_Completed,
dbo.PID_RespiratorFit.Active,
DATEADD(year, 1, dbo.PID_RespiratorFit.MedicallyClearedDate) AS Compliant_Through
FROM dbo.PID_RespiratorFit LEFT JOIN
dbo.Patient ON dbo.PID_RespiratorFit.PTIDNum = dbo.Patient.PTIDNUM LEFT JOIN
dbo.aspnet_CustomProfile acp ON dbo.Patient.PGuid = acp.UserId
WHERE (dbo.PID_RespiratorFit.DateIssued BETWEEN '1-1-2020' AND Getdate())
Use standard date formats! Then:
WHERE dbo.PID_RespiratorFit.DateIssued BETWEEN '2020-01-01' AND Getdate() and
dbo.PID_RespiratorFit.DateIssued <> '2020-04-08' -- or is that 2020-08-04???
If the date is really a date/time, then use:
WHERE dbo.PID_RespiratorFit.DateIssued BETWEEN '2020-01-01' AND Getdate() and
CONVERT(DATE, dbo.PID_RespiratorFit.DateIssued) <> '2020-04-08' -- or is that 2020-08-04???
I also strongly recommend table aliases so the query is much easier to write and read. For example:
SELECT . . .
FROM dbo.PID_RespiratorFit rf LEFT JOIN
dbo.Patient p
ON rf.PTIDNum = p.PTIDNUM LEFT JOIN
dbo.aspnet_CustomProfile acp
ON p.PGuid = acp.UserId
WHERE rf.DateIssued BETWEEN '2020-01-01' AND Getdate() AND
CONVERT(DATE, rf.DateIssued) <> '2020-04-08' -- or is that 2020-08-04???
You can try the below - adding an extra condition
SELECT
dbo.PID_RespiratorFit.PTIDNum,
rtrim(acp.LastName) + ', ' + rtrim(acp.FirstName) AS Name,
acp.BadgeID AS ID,
dbo.Patient.PcFLD01 as Organization,
dbo.PID_RespiratorFit.Risk AS Exposure,
UPPER(dbo.PID_RespiratorFit.Type) AS Mask_Type,
dbo.PID_RespiratorFit.OHSNotes AS Comments,
dbo.PID_RespiratorFit.MedicallyClearedDate AS Medically_Cleared,
dbo.PID_RespiratorFit.TrainingCompletedDate,
dbo.PID_RespiratorFit.DateIssued AS N95_Fittest_Completed,
dbo.PID_RespiratorFit.Mask AS N95_respirator,
dbo.PID_RespiratorFit.PAPR,
dbo.PID_RespiratorFit.PaprFitTestDate AS PAPR_Fittest_Completed,
dbo.PID_RespiratorFit.Active,
DATEADD(year, 1, dbo.PID_RespiratorFit.MedicallyClearedDate) AS Compliant_Through
FROM dbo.PID_RespiratorFit LEFT JOIN
dbo.Patient ON dbo.PID_RespiratorFit.PTIDNum = dbo.Patient.PTIDNUM LEFT JOIN
dbo.aspnet_CustomProfile acp ON dbo.Patient.PGuid = acp.UserId
WHERE (dbo.PID_RespiratorFit.DateIssued BETWEEN '1-1-2020' AND Getdate())
AND cast(dbo.PID_RespiratorFit.DateIssued as date)<>'2020-04-08'

I need to add additional columns to my application that are not part of GROUPBY statement

I need some help with my SQL command. I have a VB.net application that uses few sql tables to get the final result. I need to add few additional columns from TableC into my application, but these columns are not part of GROUPBY function.
I tried to add simple select function to the code, but I always get the same error:
$"
SET NOCOUNT ON;
SELECT CONVERT(VARCHAR(10), r.PossibleDate, 120) as [Date],
**SELECT r.GStart as GStart,**
SUM(ISNULL(r.ElecCheckIn, 0)) as CheckIn,
SUM(ISNULL(r.ElecCheckOut, 0)) as CheckOut,
SUM(ISNULL(r.JPAmount, 0)) as AttAmountJP,
SUM(ISNULL(r.MeteredAttAmountCC, 0)) as AttAmountCC,
SUM(ISNULL(r.MeteredMachAmount, 0)) as MachAmount,
SUM(ISNULL(r.MeteredAttAmount, 0)) as AttAmount,
SUM(ISNULL(r.ElecCheckIn, 0) - ISNULL(r.ElecCheckOut, 0) - ISNULL(r.JPAmount, 0) - ISNULL(r.MeteredAttAmountCC, 0) - ISNULL(r.MeteredMachAmount, 0) - ISNULL(r.MeteredAttAmount, 0)) as NetWin
FROM dbo.CDS_TableA sm (NOLOCK)
INNER JOIN dbo.bb_tableB st (NOLOCK)
ON sm.TableB_Id=st.SlotB_Id
AND sm.TableBRevision=st.TabelBRevision
INNER JOIN dbo.TableC r (NOLOCK)
ON sm.TableA_ID=r.TableA_ID
AND r.PossibleDate BETWEEN '{dtStart.Value.ToString("yyyy-MM-dd")} 00:00:00' AND '{dtEnd.Value.ToString("yyyy-MM-dd")} 23:59:59'
AND r.Period_ID=4
INNER JOIN dbo.BB_TableD rh (NOLOCK)
ON sm.TableA_ID=rh.TableA_ID
AND r.PossibleDate=rh.PossibleDate
AND sm.Revision=rh.Revision
WHERE sm.OnFloorFlag = 1
AND sm.Calc_ID NOT IN (2,5)
GROUP BY r.PossibleDate
ORDER BY r.PossibleDate;
I always get an error that GStart is not contained in either an aggregate function or the GROUP BY clause.
Simply JOIN the aggregate level to unit level which can be facilitated with a CTE. Run this entire statement below including WITH clause.
WITH agg AS (
SELECT CONVERT(VARCHAR(10), r.PossibleDate, 120) as [Date],
SUM(ISNULL(r.ElecCheckIn, 0)) as CheckIn,
SUM(ISNULL(r.ElecCheckOut, 0)) as CheckOut,
SUM(ISNULL(r.JPAmount, 0)) as AttAmountJP,
SUM(ISNULL(r.MeteredAttAmountCC, 0)) as AttAmountCC,
SUM(ISNULL(r.MeteredMachAmount, 0)) as MachAmount,
SUM(ISNULL(r.MeteredAttAmount, 0)) as AttAmount,
SUM(ISNULL(r.ElecCheckIn, 0) -
ISNULL(r.ElecCheckOut, 0) -
ISNULL(r.JPAmount, 0) -
ISNULL(r.MeteredAttAmountCC, 0) -
ISNULL(r.MeteredMachAmount, 0) -
ISNULL(r.MeteredAttAmount, 0)) as NetWin
FROM dbo.CDS_TableA sm (NOLOCK)
INNER JOIN dbo.bb_tableB st (NOLOCK)
ON sm.TableB_Id =s t.SlotB_Id
AND sm.TableBRevision=st.TabelBRevision
INNER JOIN dbo.TableC r (NOLOCK)
ON sm.TableA_ID= r.TableA_ID
AND r.PossibleDate BETWEEN '{dtStart.Value.ToString("yyyy-MM-dd")} 00:00:00'
AND '{dtEnd.Value.ToString("yyyy-MM-dd")} 23:59:59'
AND r.Period_ID=4
INNER JOIN dbo.BB_TableD rh (NOLOCK)
ON sm.TableA_ID=rh.TableA_ID
AND r.PossibleDate=rh.PossibleDate
AND sm.Revision=rh.Revision
WHERE sm.OnFloorFlag = 1
AND sm.Calc_ID NOT IN (2,5)
GROUP BY r.PossibleDate
)
SELECT r.GStart as GStart, agg.* --- ADD OTHER r FIELDS
FROM dbo.TableC r
INNER JOIN agg ON CONVERT(VARCHAR(10), r.PossibleDate, 120) = agg.[Date]
ORDER BY r.PossibleDate
Aside: While I know nothing of vb.net, I do know running SQL at application layer and your concatenation of dates above should be parameterized values which is a programming industry best practice. See How do I create a parameterized SQL query? Why Should I? Also, use NOLOCK with caution.

multi part indentifier could not be bound?

I'm trying to use two tables in a stored procedure but am getting this error on the final line
''multi part identifier T.HireDate could not be bound''
I'm guessing its to do with the joining of the tables but am a little lost. Heres my code:
CREATE PROC spPayIncreaseCheck
AS
SELECT T.ID,FName,LName,HireDate,Payrate
FROM Assignment.dbo.Payments pay
join Assignment.dbo.Teachers T
ON pay.ID = T.ID
UPDATE Assignment.dbo.Payments
SET Payrate = 'High'
where T.HireDate < dateadd(year, -3, GETDATE())
Are you trying to update, or are you trying to select?
That is the wrong form for an update using a join in sql server.
The correct form would be like so:
update pay
set Payrate = 'High'
from Assignment.dbo.Payments pay
inner join Assignment.dbo.Teachers T
on pay.ID = T.ID
where T.HireDate < dateadd(year, -3, getdate());
Also, are you sure on pay.ID = T.ID is the correct join clause? Normally you would expect to see something like on pay.TeacherID = T.ID instead.
You are updating the Assignment.dbo.Payments table and in the WHERE condition you have a t.HireDate but t is not an alias that is used in the UPDATE statement.
In UPDATE you cannot use aliases.
Also, please notice that the SELECT and UPDATE statements are completely non-related inside your stored procedure. It is not clear though if you need them to be related.
You need an UPDATE FROM to do a set-based update:
UPDATE Assignment.dbo.Payments
SET Payrate = 'High'
FROM
SELECT T.ID,FName,LName,HireDate,Payrate
FROM Assignment.dbo.Payments pay
JOIN Assignment.dbo.Teachers T ON pay.ID = T.ID
WHERE T.HireDate < DATEADD(YEAR, -3, GETDATE())
The simplest way to achieve this, is using an exists clause to check if the payment record matches the teacher record:
update Assignment.dbo.Payments
set Payrate = 'High'
where exists ( select 1
from Assignment.dbo.Teachers
where Payments.ID = Teachers.ID
and Teachers.HireDate < dateadd(year, -3, getdate()) )

SQL - replace returned data with other data

I am retrieving data using the SQL syntax below:
SELECT TOP 5 EventId, EventTime, DeviceName, Comment, Tenant, TenantName, Individual,
InetDb.dbo.Individuals.FirstName, InetDb.dbo.Individuals.LastName, InetDb.dbo.IndivImages.UserImage
FROM taclogdata.dbo.Event
LEFT JOIN InetDb.dbo.Tenants
ON taclogdata.dbo.Event.Tenant = InetDb.dbo.Tenants.TenantId
LEFT JOIN InetDb.dbo.Individuals
ON taclogdata.dbo.Event.Individual = InetDb.dbo.Individuals.IndivId
AND taclogdata.dbo.Event.Tenant = InetDb.dbo.Individuals.TenantNdx
LEFT JOIN InetDb.dbo.IndivImages
ON InetDb.dbo.Individuals.IndivId = InetDb.dbo.IndivImages.IndivNdx
AND InetDb.dbo.Individuals.TenantNdx = InetDb.dbo.IndivImages.TenantNdx
WHERE (taclogdata.dbo.Event.EventTime > DATEADD(hh, -3, GETDATE())AND taclogdata.dbo.Event.EventTime < GETDATE())
AND (taclogdata.dbo.Event.Comment='Reader entry' OR taclogdata.dbo.Event.Comment='Reader exit')
AND (taclogdata.dbo.Event.DeviceName = 'L9 1/4/1'
OR taclogdata.dbo.Event.DeviceName='L1 2/1/1-2 MainD'
OR taclogdata.dbo.Event.DeviceName='L1 2/1/3-4 MainD'
OR taclogdata.dbo.Event.DeviceName='L1 2/6/1-2 Stair'
OR taclogdata.dbo.Event.DeviceName='L1 2/2/1-2 FDT1')
ORDER BY taclogdata.dbo.Event.EventTime DESC
This code works fine, however I'm trying to simplify the results.
I'm trying to simplify what the query returns, by replacing the DeviceName value from e.g. L1 2/1/3-4 MainD to Main Door when the results are shown (not replace the actual data in the database)
How may I achieve this please ?
Thanks in advance,
J
try this use replace function
SELECT TOP 5 EventId, EventTime, replace(DeviceName,'L1 2/1/3-4 MainD','L1 2/1/3-4 Main Door') as DeviceName, Comment, Tenant, TenantName, Individual,
InetDb.dbo.Individuals.FirstName, InetDb.dbo.Individuals.LastName, InetDb.dbo.IndivImages.UserImage
FROM taclogdata.dbo.Event
LEFT JOIN InetDb.dbo.Tenants
ON taclogdata.dbo.Event.Tenant = InetDb.dbo.Tenants.TenantId
LEFT JOIN InetDb.dbo.Individuals
ON taclogdata.dbo.Event.Individual = InetDb.dbo.Individuals.IndivId
AND taclogdata.dbo.Event.Tenant = InetDb.dbo.Individuals.TenantNdx
LEFT JOIN InetDb.dbo.IndivImages
ON InetDb.dbo.Individuals.IndivId = InetDb.dbo.IndivImages.IndivNdx
AND InetDb.dbo.Individuals.TenantNdx = InetDb.dbo.IndivImages.TenantNdx
WHERE (taclogdata.dbo.Event.EventTime > DATEADD(hh, -3, GETDATE())AND taclogdata.dbo.Event.EventTime < GETDATE())
AND (taclogdata.dbo.Event.Comment='Reader entry' OR taclogdata.dbo.Event.Comment='Reader exit')
AND (taclogdata.dbo.Event.DeviceName = 'L9 1/4/1'
OR taclogdata.dbo.Event.DeviceName='L1 2/1/1-2 MainD'
OR taclogdata.dbo.Event.DeviceName='L1 2/1/3-4 MainD'
OR taclogdata.dbo.Event.DeviceName='L1 2/6/1-2 Stair'
OR taclogdata.dbo.Event.DeviceName='L1 2/2/1-2 FDT1')
ORDER BY taclogdata.dbo.Event.EventTime DESC
Here my suggestions:
If you can replace the data in your SELECT for example:
SELECT REPLACE(DeviceName,N'2/1/3-4 MainD',N'Main Door')
If you have many replacements, I would suggest to create a temporary table, join it and take the replacement from the temporary table.
The specific answer to your question is to use REPLACE() or a CASE statement. However, you should also change the WHERE clause to use IN and use table aliases so the code is easier to write and to read:
FROM taclogdata.dbo.Event e LEFT JOIN
InetDb.dbo.Tenants t
ON e.Tenant = t.TenantId LEFT JOIN
InetDb.dbo.Individuals i
ON e.Individual = i.IndivId AND e.Tenant = i.TenantNdx LEFT JOIN
InetDb.dbo.IndivImages ii
ON i.IndivId = ii.IndivNdx AND Ii.TenantNdx = ii.TenantNdx
WHERE (e.EventTime > DATEADD(hour, -3, GETDATE()) AND
e.EventTime < GETDATE()
) AND
e.Comment IN ('Reader entry', 'Reader exit') AND
e.DeviceName IN ('L9 1/4/1', 'L1 2/1/1-2 MainD', 'L1 2/1/3-4 MainD',
'L1 2/6/1-2 Stair', 'L1 2/2/1-2 FDT1'
)

How to use Group By in certain Select in SQL Server?

I haven't used Group By that much before and always get an error when trying to group the following by the UserID
Error:
Column 'Log.Version' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
How can I avoid this ? What I am trying to get is a count for each user with every user only appearing once for each tool instead of listing every usage separately.
My query (working except for the above + simplified for demo):
ALTER PROCEDURE [dbo].Reporting_FetchUsage
AS
BEGIN
SET NOCOUNT ON;
SELECT
A.UserID, A.Tool, A.[Version], A.Note, A.[TimeStamp],
B.ADesc, B.SDesc, B.DDesc, B.ALN
FROM
Log A
LEFT JOIN
EmployeeTable B ON B.NTID = A.UserID
WHERE
A.[TimeStamp] > DATEADD(YEAR, -1, GETDATE())
AND (A.Tool LIKE 'abc%'
OR
A.Tool LIKE 'def%'
OR
A.Tool LIKE 'ghi%')
AND (B.DDesc = 'CB'
OR
B.DDesc = 'PS')
GROUP BY
A.Tool, A.UserID
ORDER BY
A.Tool, A.UserID
FOR XML PATH('reporting'), ELEMENTS, TYPE, ROOT('ranks')
END
What I am trying to get is a count for each user with every user only
appearing once for each tool instead of listing every usage
separately.
If this is the case, I would expect to see a count() somewhere. Perhaps this is closer to what you want:
SELECT l.UserID, l.Tool, e.ADesc, e.SDesc, e.DDesc, e.ALN,
COUNT(*) as NumTools
FROM Log l LEFT JOIN
EmployeeTable e
ON e.NTID = l.UserID
WHERE l.[TimeStamp] > DATEADD(YEAR, -1, GETDATE()) AND
(l.Tool LIKE 'abc%' OR l.Tool LIKE 'def%' or l.Tool LIKE 'ghi%') AND
B.DDesc IN ( 'CB', 'PS')
GROUP BY l.UserID, l.Tool, e.ADesc, e.SDesc, e.DDesc, e.ALN,
ORDER BY l.Tool, l.UserID;
This assumes that the employee table does not have duplicates, with respect to the join conditions on the Log.
Include all the fields in your GROUP BY clause that are in your SELECT statement that are not being used in an aggregate.
ALTER PROCEDURE [dbo].Reporting_FetchUsage
AS
BEGIN
SET NOCOUNT ON;
SELECT A.UserID,
A.Tool,
A.[Version],
A.Note,
A.[TimeStamp],
B.ADesc,
B.SDesc,
B.DDesc,
B.ALN
FROM Log A
LEFT JOIN EmployeeTable B
ON B.NTID = A.UserID
WHERE A.[TimeStamp] > DATEADD(YEAR, -1, GETDATE())
AND (
A.Tool LIKE 'abc%'
OR
A.Tool LIKE 'def%'
OR
A.Tool LIKE 'ghi%'
)
AND (
B.DDesc = 'CB'
OR
B.DDesc = 'PS'
)
GROUP BY A.Tool,
A.UserID,
A.[Version],
A.Note,
A.[TimeStamp],
B.ADesc,
B.SDesc,
B.DDesc,
B.ALN
ORDER BY A.Tool, A.UserID
FOR XML PATH('reporting'), ELEMENTS, TYPE, ROOT('ranks')
END