Return 0 when there is no row returned - sql

I have this query which returns Null, to show 0 i can use ISNULL on outer query around CAST, i don't know if it is better to use ISNULL in the inner query.
I have tried using ISNULL with inner query but it returns no rows instead of showing 0. I have tried removing group by clause but still same results.
SELECT CAST((SUM(q.AssingWithPO * 1.0) / SUM(q.TotalAssign * 1.0)) * 100
AS NUMERIC (10,2))
FROM
(
SELECT COUNT(DISTINCT a.AssignmentID) AS TotalAssign
,(SELECT COUNT(DISTINCT a2.AssignmentID)FROM Assignments a2
WHERE a2.PODeliveryDate <> '19001212'
AND a2.AssignmentID = a.AssignmentID ) AS AssingWithPO
FROM Assignments a
WHERE a.StaffID = 59
AND (a.CreatedDate BETWEEN '20130101' AND '20141231')
GROUP BY a.AssignmentID
)q;
ADDED
I have simplified this query, thanks to #Gordon
SELECT SUM(case when a.PODeliveryDate <> '19001212' then 1.0 else 0.0 end) / COUNT(*)) * 100 as AssignWithPO
FROM Assignments a
WHERE a.StaffID = 59 AND
a.CreatedDate BETWEEN '20130101' AND '20141231';
Now would it be okay to use ISNULL like that?
ISNULL((SUM(case when a.PODeliveryDate <> '19001212' then 1.0 else 0.0 end) / COUNT(*)) * 100 as AssignWithPO,0)
Execution Plan of both queries

You don't need the second level of subqueries. You can use conditional aggregation instead. I think the following will do what you want:
SELECT CAST((SUM(a.AssignWithPO * 1.0) / SUM(a.TotalAssign * 1.0)) * 100 as NUMERIC (10,2))
FROM (SELECT COUNT(*) AS TotalAssign,
SUM(case when a.PODeliveryDate <> '19001212' then 1 else 0 end) as AssignWithPO
FROM Assignments a
WHERE a.StaffID = 59 AND
a.CreatedDate BETWEEN '20130101' AND '20141231'
GROUP BY a.AssignmentID
) a;
I'm not 100% sure, because I don't understand the relationships between AssignmentId, StaffId, and CreatedDate, but my assumption is that the rows counted for AssignWithPO are subject to the same conditions as the TotalCount.
You don't need the count(distinct) because AssignmentId is necessarily unique because of the group by. Assuming there is no overlap between the values, you don't need the group by either, nor the outer query:
SELECT COUNT(*) AS TotalAssign,
SUM(case when a.PODeliveryDate <> '19001212' then 1.0 else 0.0 end) / COUNT(*) as AssignWithPO
FROM Assignments a
WHERE a.StaffID = 59 AND
a.CreatedDate BETWEEN '20130101' AND '20141231';

Related

How do I include records in a Summary query to include those that don't have data?

I have a query on a transaction table that returns the Summarized total on a column for each ID based on a data range. The query works great except it doesn't include those IDs that don't have data in the transaction table. How can I include those IDs in my result filled with a zero total. Here's a simplified version of my query.
SELECT tblID.IDName
,SUM(CASE
WHEN tblTransactions.idxTransType = 30
THEN CAST(tblTransactions.TimeAmount AS FLOAT) / 60.0
ELSE 0
END) AS 'Vacation'
FROM tblTransactions
INNER JOIN tblTransTypes ON tblTransactions.idxTransType = tblTransTypes.IdxTransType
INNER JOIN tblID ON tblTransactions.idxID = tblID.IdxID
WHERE (tblTransactions.Deleted = 0)
AND (tblTransactions.NotCurrent = 0)
AND (tblTransactions.TransDate >= CONVERT(DATETIME, 'March 1, 2018', 102))
AND (tblTransactions.TransDate <= CONVERT(DATETIME, 'April 11, 2018', 102))
GROUP BY tblID.IDName
Actually it's slightly more complicated than that:
SELECT
i.IDName,
SUM(CASE WHEN t.idxTransType = 30 THEN CAST(t.TimeAmount AS FLOAT) / 60.0 ELSE 0 END) AS 'Vacation'
FROM
tblID i
LEFT JOIN tblTransactions t ON t.idxID = i.IdxID AND t.Deleted = 0 AND t.NotCurrent = 0 AND t.TransDate BETWEEN '20180301' AND '20180411'
LEFT JOIN tblTransTypes tt ON tt.IdxTransType = t.idxTransType
GROUP BY
i.IDName;
You want left joins:
SELECT i.IDName,
SUM(CASE WHEN t.idxTransType = 30 THEN CAST(t.TimeAmount AS Float) / 60.0 ELSE 0 END) AS Vacation
FROM tblID i LEFT JOIN
tblTransactions t
ON t.idxID = i.IdxID AND
t.Deleted = 0 AND
t.NotCurrent = 0 AND
t.TransDate >= '2018-03-01' AND
t.TransDate <= '2018-04-11'
tblTransTypes tt
ON t.idxTransType = tt.IdxTransType
GROUP BY i.IDName;
Notes:
Table aliases make the query much easier to write and to read.
Use ISO/ANSI standard date formats.
The filter conditions on all but the first table belong in the ON clauses.

Doing math on sql count

I am trying to do some mathematical operations on the results of two queries.
This is my query:
select (x.failed/y.total) * 100 as failure_rate, x.failed, y.total
from
(
select count(*) as failed from status where cast(ins As Date) = cast(getDate() As Date) and fail_flg = 'Y'
) x
join
(
select count(*) as total from status where cast(ins As Date) = cast(getDate() As Date)
) y on 1=1
This is the result im getting back:
failure_rate failed total
0 1 2
I should have a failure rate of 50, where am I going wrong? I have a gut suspicion the problem is somewhere in my count(*)....do I need to cast this as a number somewhere?
SQL Server does integer arithmetic. Convert to a non-integer number. I do this as:
select (x.failed * 100.0 /y.total) as failure_rate, x.failed, y.total
I should add that I would write the query without subqueries:
select sum(case when fail_flag = 'Y' then 1 else 0 end) as failed,
count(*) as total,
avg(case when fail_flag = 'Y' then 100.0 else 0 end) as failed_rate
from status
where cast(ins As Date) = cast(getDate() As Date) ;
Normally, I would recommend not doing the cast() or any other function on a column. That precludes the use of indexes. However, SQL Server makes an exception for cast(as date), so your code is still index-safe (or "sargable" in the lingo of SQL Server).

Case statement is ignoring where clause

I am trying to create a SQL statement that returns multiple counts. The count below works as I expect, but the case statement is ignoring the where clause for my query.
I'm trying to get the total number of PacketId's that meet the where criteria. Then get a second total showing the sum of PacketId's that meet the where criteria and have a StatusId of 3.
*edit Table1 and Table2 both share PacketId as a foreign key.
Select
Count(Distinct wpq.PacketId) AS Total,
SUM(Case When wpq.StatusId = 3 THEN 1 ELSE 0 END) as OtherCount
FROM [Table1] ppo JOIN [Table2] wpq ON ppo.PacketId = wpq.PacketId
WHERE wpq.CreateDate between '11/1/2017' and '1/1/2018' and ppo.IsSelected = 1
I suspect you may be getting a higher number than expected for the othercount but that may be due to the use of count(distinct...) which reduces the first column result, but not the second. Perhaps introducing a subquery to select only distinct values would help?
SELECT DISTINCT
wpq.PacketId
, wpq.StatusId
FROM [Table1] ppo
JOIN [Table2] wpq ON ppo.PacketId = wpq.PacketId
WHERE wpq.CreateDate BETWEEN '11/1/2017' AND '1/1/2018'
AND ppo.IsSelected = 1
;
then count from that, e.g:
SELECT
COUNT(PacketId) AS total
, COUNT(CASE WHEN StatusId = 3 THEN StatusId END) AS othercount
, SUM(CASE WHEN StatusId = 3 THEN 1 ELSE 0 END) AS othersum
FROM (
SELECT DISTINCT
wpq.PacketId
, wpq.StatusId
FROM [Table1] ppo
JOIN [Table2] wpq ON ppo.PacketId = wpq.PacketId
WHERE wpq.CreateDate BETWEEN '11/1/2017' AND '1/1/2018'
AND ppo.IsSelected = 1
) AS d
;
Note: the COUNT() function ignores nulls, so I have added an alternative calculation method to consider. I prefer to use COUNT() in such a query.
Also I would like to note that your use of what appears to be M/D/YYYY date literals is NOT safe. The safest date literal format in T-SQL is YYYYMMDD. Similarly using between is not best practice for date ranges and wpuld encourage you to use >= and < instead, like so:
SELECT
COUNT(PacketId) AS total
, COUNT(CASE WHEN StatusId = 3 THEN StatusId END) AS othercount
, SUM(CASE WHEN StatusId = 3 THEN 1 ELSE 0 END) AS othersum
FROM (
SELECT DISTINCT
wpq.PacketId
, wpq.StatusId
FROM [Table1] ppo
JOIN [Table2] wpq ON ppo.PacketId = wpq.PacketId
WHERE wpq.CreateDate >= '20171101' AND wpq.CreateDate < '20180101'
AND ppo.IsSelected = 1
) AS d
;
Note I'm not sure if you do want to include 1/1/2018, if you do then use < '20180102' instead
I would suggest that you use standard date formats. Most databases support YYYY-MM-DD:
SELECT COUNT(DISTINCT wpq.PacketId) AS Total,
SUM(Case When wpq.StatusId = 3 THEN 1 ELSE 0 END) as OtherCount
FROM [Table1] ppo JOIN
[Table2] wpq
ON ppo.PacketId = wpq.PacketId
WHERE wpq.CreateDate >= '2017-11-01' AND
wpq.CreateDate <= '2018-01-01' AND
ppo.IsSelected = 1;
It is possible that the date comparisons are really being done as strings, so they do not do what you expect.

Firebird SQL: query slow due to coalesce or can it be rewritten

i'm having some performance problems with a frequently used query.
SELECT
v.id,
coalesce((SELECT sum(amount) FROM artjournal WHERE variant_ref=v.id AND storage_ref=1 AND atype_ref in (1,3,4)), 0) "fv",
coalesce((SELECT sum(amount) FROM artjournal WHERE variant_ref=v.id AND storage_ref=1 AND atype_ref=2), 0) "ivo",
coalesce((SELECT sum(amount) FROM artjournal WHERE variant_ref=v.id AND storage_ref=1 AND atype_ref=5), 0) "iio",
coalesce((SELECT sum(amount * mvalue) FROM artjournal WHERE variant_ref=v.id AND storage_ref=1), 0) "vw"
FROM productvariant v
since artjournal is a big table and gets thousands of new records each day the performance is getting terrible.
I have indices on all ID fields.
Is there a way to rewrite this statement to speed things up? Or can i use a different way to retrieve the data from the artjournal table and return 0 if result is null?
Thanks for your thoughts,
Christiaan
Looks like you want a filtered aggregate:
SELECT v.id,
sum(case when a.atype_ref in (1,3,4) then a.amount else 0 end) as "fv",
sum(case when a.atype_ref = 2 then a.amount else 0 end) as "ivo",
sum(case when a.atype_ref = 5 then a.amount else 0 end) as "iio",
sum(a.amount * a.mvalue) as "vw"
FROM productvariant v
LEFT JOIN artjournal a ON a.variant_ref = v.id
WHERE storage_ref = 1
GROUP BY v.id;

Using CASE in SQL Server - getting "because it is not contained in either an aggregate function or the GROUP BY clause."

I want to show the days since the last customer order from a certain storenumber, I have been told to use CASE.
I don't want to use MAX or MIN because it may ignore other records for said customer.
SELECT ms.CustomerID AS email,
AS last_txn_days_online,
CASE
WHEN ST2.StoreNumber != '100799' THEN CEILING(Round(DateDiff(DAY, Min(st2.PurchaseDate), Max(st2.PurchaseDate)) / NULLIF(Count(st2.CustomerID) - 1, 0),0))
ELSE NULL
END AS last_txn_days_instore
FROM [Not MS] ms
LEFT JOIN [ORDER_HEADER] st2 ON ms.CustomerID = st2.CustomerID
GROUP BY MS.CustomerID
I think you want conditional aggregation. The query would look something like this:
SELECT ms.CustomerID AS email,
CEILING(Round(DateDiff(DAY,
Min(CASE WHEN ST2.StoreNumber <> '100799' THEN st2.PurchaseDate END),
M MAX(CASE WHEN ST2.StoreNumber <> '100799' THEN st2.PurchaseDate END)
) / NULLIF(Count(CASE WHEN ST2.StoreNumber <> '100799' THEN st2.CustomerID END) - 1, 0), 0
)
)
END AS last_txn_days_instore
FROM [Not MS] ms LEFT JOIN
[ORDER_HEADER] st2
ON ms.CustomerID = st2.CustomerID
GROUP BY MS.CustomerID