Case statement is ignoring where clause - sql

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.

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.

Select "case when"... yields grouped results... how to obtain aggregate data?

Newer SQL user working with SQL Server 2014 SP2.
I'm trying to determine how to improve my results on a particular query.
The problem is that when I select data with a Case When, it requires me to use group by to aggregate the data.
I am trying to eliminate this group by, and have not been able to determine a viable method for this query.
Query yields these results:
StoreID Devices playing devices FullScreenPlays PIPPlays
--------------------------------------------------------
1296 1 0 0 0
1296 7 7 7 0
1296 7 7 0 7
I am trying to achieve something more like this:
StoreID Devices playing devices FullScreenPlays PIPPlays
--------------------------------------------------------------
1296 8 7 7 7
I've tried several variants of calling the group by, and I've tried several variants of the case when, but I can't seem to figure out what I'm doing wrong...
Any insight would be appreciated!
SQL code is here:
DECLARE #Location varchar(50) = '1296'
SELECT
Location.ExternalCode As [StoreID],
COUNT(DISTINCT imdd.DeviceID) AS Devices,
COUNT(DISTINCT esr.DeviceID) AS [playing devices],
(CASE
WHEN (esr.EventID = 925)
THEN (COUNT(DISTINCT esr.DeviceID))
ELSE 0
END) AS [FullScreenPlays],
(CASE
WHEN (esr.EventID = 926)
THEN (COUNT(DISTINCT esr.DeviceID))
ELSE 0
END) AS [PIPPlays]
FROM
[iSenseMD].dbo.Location WITH (NOLOCK)
INNER JOIN
iSenseMD.dbo.LocationAttribute la WITH (NOLOCK) ON Location.LocationID = la.LocationID
AND la.AttributeID = 7
AND la.Value = 1
LEFT JOIN
[iSenseMD].[dbo].Device imdd WITH (NOLOCK) ON location.LocationID = imdd.LocationID
AND imdd.DeviceName NOT IN ('iX Gateway', 'A312778', 'A294874', '334873')
LEFT JOIN
[iSenseAnalytics].[dbo].[EventStringRollup] esr WITH (NOLOCK) ON imdd.LocationID = esr.LocationID
AND imdd.DeviceID = esr.DeviceID
AND esr.IntervalID = 1
AND esr.EventID IN (925, 926) --all plays
AND esr.RollupTimestamp >= dateadd(day, datediff(day, 1, GETDATE()), 0)
AND esr.RollupTimestamp < dateadd(day, datediff(day, 0, GETDATE()), 0)
WHERE
Location.IsActive = 1
AND Location.LocationName NOT LIKE '%duplicate%'
AND Location.ExternalCode = #Location --this to add the declaration above as site constraint
GROUP BY
Location.ExternalCode, esr.EventID
ORDER BY
iSenseMD.dbo.Location.ExternalCode
Any insight would be appreciated!
Thanks!
I would put the query in a CTE and then aggregate it from there
WITH CTE_Example
AS
( use iSenseMD
go
DECLARE #Location varchar(50) = '1296'
SELECT Location.ExternalCode As [StoreID]
,count(distinct imdd.DeviceID) as Devices
,count(distinct esr.DeviceID) as [playing devices]
,(case when (esr.EventID = 925) Then (count (distinct esr.DeviceID)) Else 0 End) As [FullScreenPlays]
,(case when (esr.EventID = 926) Then (count (distinct esr.DeviceID)) Else 0 End) As [PIPPlays]
FROM [iSenseMD].dbo.Location WITH (NOLOCK)
INNER JOIN iSenseMD.dbo.LocationAttribute la WITH (NOLOCK)
ON Location.LocationID = la.LocationID
AND la.AttributeID = 7
AND la.Value = 1
left JOIN [iSenseMD].[dbo].Device imdd WITH (NOLOCK) ON location.LocationID = imdd.LocationID
AND imdd.DeviceName NOT IN ('iX Gateway','A312778','A294874','334873')
left JOIN [iSenseAnalytics].[dbo].[EventStringRollup] esr WITH (NOLOCK) ON imdd.LocationID = esr.LocationID and imdd.DeviceID = esr.DeviceID
AND esr.IntervalID = 1
AND esr.EventID IN (925, 926) --all plays
AND esr.RollupTimestamp >= dateadd(day,datediff(day,1,GETDATE()),0)
AND esr.RollupTimestamp < dateadd(day,datediff(day,0,GETDATE()),0)
WHERE Location.IsActive = 1
AND Location.LocationName NOT LIKE '%duplicate%'
AND Location.ExternalCode = #Location --this to add the declaration above as site constraint
GROUP BY Location.ExternalCode
,esr.EventID)
--A GROUP BY might not be allowed either, I can't remember. But I think it'll be fine, let me know if there are any issues
/* ORDER BY
iSenseMD.dbo.Location.ExternalCode */
--I don't think an ORDER BY is allowed in a CTE so I have it commented out for now. Is it an absolute within the query?
SELECT StoreID, SUM(Devices), SUM([playing devices]), SUM(FullScreenPlays),
SUM(PIPPlays)
FROM CTE_Example
GROUP BY StoreID
Hope this helps and please let me know if there are any errors!
You need to place your case statement inside your count aggregate. This way you can remove the EventID in the group by
Example:
COUNT(DISTINCT
CASE WHEN (esr.EventID = 925) THEN esr.DeviceID
ELSE NULL
END) AS [FullScreenPlays]
Original:
(CASE
WHEN (esr.EventID = 925)
THEN (COUNT(DISTINCT esr.DeviceID))
ELSE 0
END) AS [FullScreenPlays]

How to use a SQL sub query?

I am attempting to use a sub query to query our order database and return 3 columns for example:
Date Orders Replacements
09-MAY-14 100 5
... ... ...
Each order that is created can be given a reason, which basically means that it is a replacement product i.e. orders without a reason are new orders and orders with a reason are replacement orders.
I am using the below query in an attempt to get this information, but I'm getting lots of error messages, and each time I think I've fixed one I create another 10, so assume I completely have the wrong idea here.
SELECT Orders.EntryDate AS "Date", COUNT(Orders.OrderNo) AS "Orders",
(SELECT COUNT(Orders.OrderNo) AS "Replacements"
FROM Orders
WHERE Orders.Reason IS NOT NULL
AND Orders.EntryDate = '09-MAY-2014'
AND Orders.CustomerNo = 'A001'
GROUP BY Orders.EntryDate
)
FROM Orders
WHERE Orders.Reason IS NULL
AND Orders.EntryDate = '09-MAY-2014'
AND Orders.CustomerNo = 'A001'
GROUP BY Orders.EntryDate
;
Why the sub query use a case!
SELECT Orders.EntryDate AS "Date", COUNT(Orders.OrderNo) AS "Orders",
sum(CASE WHEN Orders.reason is null then 1 else 0 end) as "Replacements"
FROM Orders
WHERE Orders.Reason IS NULL
AND Orders.EntryDate = '09-MAY-2014'
AND Orders.CustomerNo = 'A001'
GROUP BY Orders.EntryDate
The subquery has to execute each time, since you need to evaluate each record the case can do that for you and then sum the results. If you need to get a count of -non replacement orders then just do a different case instead of a count.
You could could sum a case expression instead of having a another subquery with another where clause:
SELECT Orders.EntryDate AS "Date",
SUM (CASE WHEN Orders.Reason IS NULL THEN 1 ELSE 0 END) AS "Orders",
SUM (CASE WHEN Orders.Reason IS NOT NULL THEN 1 ELSE 0 END) AS "Replacements"
FROM Orders
WHERE Orders.EntryDate = '09-MAY-2014'
AND Orders.CustomerNo = 'A001'
GROUP BY Orders.EntryDate
Your errors were probably due to the fact that you did not include the subquery in your group by clause. You can try that approach but this one is simpler:
select entrydate "date"
, count(orderno) "orders"
, sum(case when reason is not null then 1 else 0 end) "replacements"
etc
group by entrydate
Is this what you are trying to do?
SELECT Orders.EntryDate
, COUNT(case when Orders.reason is null then 1 end) AS orders
, COUNT(case when Orders.reason is not null then 1 end) AS Replacements
FROM Orders
WHERE Orders.EntryDate = '09-MAY-2014'
AND Orders.CustomerNo = 'A001'
GROUP BY Orders.EntryDate
The Replacements expression can be simplified to:
COUNT(Orders.reason)

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;

SQL Query to compare 2 weeks

I've got to design a query in visual studio where I have 2 data sets.
basically it goes like this.
I want to compare this weeks call total to last week per country calling.
the only thing is last weeks calls may have come from 20 diff countries while this weeks might only have come from 15.
How can I make the query such that the 20 countries will show up for both while having "0" value in for countries that do not appear this week.
below is my query:
Select country,
Sum(Case When actstatus in (5,105) Then 1 Else 0 End) As TotalCalls,
Sum(Case When actstatus = 105 Then 1 Else 0 End) As FailedCalls
From termactivity(nolock)
INNER JOIN termconfig(NOLOCK) ON cfgterminalID = actterminalID
INNER JOIN Country (nolock) on country = cycode
Where actstatus in (5,105)
and (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
Group By country
order By country asc
When Act status = 105 it means the call was not completed and when it = 5 it means the call was successful. I am doing this to get a successful call % rate per week.
Thanks in Advance!
Apply the same logic as you did to total calls and failed calls as you did to the this week and last week.
SELECT country,
COUNT(CASE WHEN actTerminalDateTime < #StartDate THEN 1 END) [LastWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime < #StartDate THEN 1 END) [LastWeekFailedCalls],
COUNT(CASE WHEN actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekFailedCalls]
FROM termactivity (NOLOCK)
INNER JOIN termconfig (NOLOCK)
ON cfgterminalID = actterminalID
INNER JOIN Country (NOLOCK)
ON country = cycode
WHERE actstatus in (5,105)
AND actTerminalDateTime BETWEEN DATEADD(DAY, -7, #StartDate) AND #EndDate
GROUP BY country
ORDER BY country ASC
I've also tidied up your query slightly, for example there is no point in specifying
WHEN ActStatus IN (5, 105) ...
When your WHERE clause already limits all results to 5, 105, therefore this is a redundant predicate in your case expression
From what I understand, you want to perform separate queries for two weeks, and you want both queries to produce rows for all countries, regardless of whether all countries had any calls. To achieve this, you need to use LEFT OUTER JOINS. The below code should guarantee that every country found in the Country table has a row, even if both sums are 0.
SELECT country,
SUM(CASE WHEN actstatus IN (5,105) THEN 1 ELSE 0 END) AS TotalCalls,
SUM(CASE WHEN actstatus = 105 THEN 1 ELSE 0 END) AS FailedCalls
FROM Country (NOLOCK)
LEFT OUTER JOIN termconfig (NOLOCK) ON country = cycode
LEFT OUTER JOIN termactivity (NOLOCK) ON cfgterminalID = actterminalID
WHERE (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
GROUP BY country
ORDER BY country ASC
If this was not what you wanted, perhaps you need to clarify your question. Many others have assumed that you want to combine the results into a single query.