SQL GROUP BY CASE statement to aviod erroring out - sql

I am tryin to write the following case statement in my SELECT list:
CASE
WHEN SUM(up.[Number_Of_Stops]) = 0 THEN 0
ELSE SUM(up.[Total_Trip_Time] / up.[Number_Of_Stops])
END
I keep getting divde by zero errors though. This is the whole point of thise case statement to avoid this. Any other ideas?

You're checking for a different case than the one that's causing the error. Note:
WHEN SUM(up.[Number_Of_Stops]) = 0
Will only be true when all records in the grouping have Number_Of_Stops = 0. When that isn't the case, but some records do have Number_Of_Stops = 0, you'll divide by zero.
Instead, try this:
SUM(CASE
WHEN up.[Number_Of_Stops] = 0 THEN 0
ELSE up.[Total_Trip_Time] / up.[Number_Of_Stops]
END)

The zero check is based on SUM, an aggregate function, which means it is not executing per row -- which is when the division is occurring.
You're going to have to review the GROUP BY clause, or run the division (and zero check) in a subquery before applying SUM to result. IE:
SELECT SUM(x.result)
FROM (SELECT CASE
WHEN up.[Number_Of_Stops]) > 0 THEN
up.[Total_Trip_Time] / up.[Number_Of_Stops]
ELSE
0
END AS result
FROM TABLE) x

Related

SQL selecting with conditioning from a subquery

I am trying to perform two sum functions from a query. However, I want to only perform one of the sum functions if it meets a certain condition without affecting the other sum function.
What I was thinking is to use something similar to select x where condition = 1 from AC which is however not possible.
Here is the sample query where I want the second [sum(t.match)] selection to only calculate if the result in the subquery: match = 1 while still getting the total sum of all qqty.
select
sum(t.qqty), sum(t.qqty)
from
(select
car, cqty, qqty,
case when cqty = qqty then 1 else 0 end as match,
location, state) t
Use conditional aggregation -- that is case as the argument to the sum():
select sum(t.qqty), sum(case when condition = 1 then t.qqty else 0 end)
from t;

Using Case to sum NULL instances gives missing expression error

I'm attempting to generate a list of vehicles that don't have a price or mileage listed using the below query. When I attempt to run the query, I get an error "ORA-00936: missing expression", but can't seem to find out why. From other posts here, I can see that using IS NULL should be the appropriate term for the WHEN portion, but I am not seeing anything wrong with the query itself. Any help would be appreciated!
Select
SUM(CASE vehicles.mileage WHEN IS NULL THEN 1 ELSE 0 END) NO_MILEAGE,
SUM(CASE vehicles.price WHEN IS NULL THEN 1 ELSE 0 END) NO_PRICE
From
[data]
Simple syntax error:
Select
SUM(CASE WHEN vehicles.mileage IS NULL THEN 1 ELSE 0 END) NO_MILEAGE,
SUM(CASE WHEN vehicles.price IS NULL THEN 1 ELSE 0 END) NO_PRICE
From
[data];
This is assuming a table named vehicles in your FROM clause or a columns with an object or nested table type in [data] named vehicles. Else the qualification vehicles. would not make sense.
Use a "searched" CASE for a decision between two alternatives.
Details about "simple" and "searched" CASE in the Oracle online reference.
You can also use COUNT for your particular case. The online reference again:
If you specify expr, then COUNT returns the number of rows where expr is not null.
If you specify the asterisk (*), then this function returns all rows,
including duplicates and nulls. COUNT never returns null.
So you need the difference:
Select
COUNT(*) - COUNT(vehicles.mileage) AS NO_MILEAGE,
COUNT(*) - COUNT(vehicles.price) AS NO_PRICE
From
[data];
You could also use Oracle's NVL2 function:
Select
SUM(NVL2(vehicles.mileage, 0, 1)) NO_MILEAGE,
SUM(NVL2(vehicles.price, 0, 1)) NO_PRICE
From
[data]

Return NULL instead of 0 when using COUNT(column) SQL Server

I have query which running fine and its doing two types of work, COUNT and SUM.
Something like
select
id,
Count (contracts) as countcontracts,
count(something1),
count(something1),
count(something1),
sum(cost) as sumCost
from
table
group by
id
My problem is: if there is no contract for a given ID, it will return 0 for COUNT and Null for SUM. I want to see null instead of 0
I was thinking about case when Count (contracts) = 0 then null else Count (contracts) end but I don't want to do it this way because I have more than 12 count positions in query and its prepossessing big amount of records so I think it may slow down query performance.
Is there any other ways to replace 0 with NULL?
Try this:
select NULLIF ( Count(something) , 0)
Here are three methods:
1. (case when count(contracts) > 0 then count(contracts) end) as countcontracts
2. sum(case when contracts is not null then 1 end) as countcontracts
3. nullif(count(contracts), 0)
All three of these require writing more complicated expressions. However, this really isn't that difficult. Just copy the line multiple times, and change the name of the variable on each one. Or, take the current query, put it into a spreadsheet and use spreadsheet functions to make the transformation. Then copy the function down. (Spreadsheets are really good code generators for repeated lines of code.)

Case statement in where clause with "not equal" condition

This is just a very simple version of my actual query and I'd like to know how I can write just one query instead of two for the following select?
If #ShowZero = 0
Select Value From Metrics
Else
Select Value From Metrics Where Value <> 0
I have tried something as
Select Value From Metrics Where Value = Case #ShowZero = 0 then Value Else (here I'm stuck).
#ShowZero is a flag of 0 and 1.
Thank you!
You don't need a case for this:
Select Value
From Metrics
Where #ShowZero = 0 or Value <> 0;
EDIT:
This works, because, uhh, that is how boolean logic works. In other words, if you did a logic table for your expression, it would look like:
#ShowZero Value Action
0 0 Show row
0 other Show row
other 0 Filter out row
other other Show row
There are multiple ways to express this. The if statement is one of them. You could say: "I'm going to show a row unless #ShowZero is not 0 and value is 0". The where clause would be:
where not (#ShowZero <> 0 and Value = 0)
Alternatively, you could say: "I'm going to show a row when either of the following is true:
#ShowZero = 0
Value <> 0
If both are true great! I'll still show the row." This is the where clause that I used.

Selecting COUNT from different criteria on a table

I have a table named 'jobs'. For a particular user a job can be active, archived, overdue, pending, or closed. Right now every page request is generating 5 COUNT queries and in an attempt at optimization I'm trying to reduce this to a single query. This is what I have so far but it is barely faster than the 5 individual queries. Note that I've simplified the conditions for each subquery to make it easier to understand, the full query acts the same however.
Is there a way to get these 5 counts in the same query without using the inefficient subqueries?
SELECT
(SELECT count(*)
FROM "jobs"
WHERE
jobs.creator_id = 5 AND
jobs.status_id NOT IN (8,3,11) /* 8,3,11 being 'inactive' related statuses */
) AS active_count,
(SELECT count(*)
FROM "jobs"
WHERE
jobs.creator_id = 5 AND
jobs.due_date < '2011-06-14' AND
jobs.status_id NOT IN(8,11,5,3) /* Grabs the overdue active jobs
('5' means completed successfully) */
) AS overdue_count,
(SELECT count(*)
FROM "jobs"
WHERE
jobs.creator_id = 5 AND
jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000'
) AS due_today_count
This goes on for 2 more subqueries but I think you get the idea.
Is there an easier way to collect this data since it's basically 5 different COUNT's off of the same subset of data from the jobs table?
The subset of data is 'creator_id = 5', after that each count is basically just 1-2 additional conditions. Note that right now we're using Postgres but may be moving to MySQL in the near future. So if you can provide an ANSI-compatible solution I'd be gratetful :)
This is the typical solution. Use a case statement to break out the different conditions. If a record meets it gets a 1 else a 0. Then do a SUM on the values
SELECT
SUM(active_count) active_count,
SUM(overdue_count) overdue_count
SUM(due_today_count) due_today_count
FROM
(
SELECT
CASE WHEN jobs.status_id NOT IN (8,3,11) THEN 1 ELSE 0 END active_count,
CASE WHEN jobs.due_date < '2011-06-14' AND jobs.status_id NOT IN(8,11,5,3) THEN 1 ELSE 0 END overdue_count,
CASE WHEN jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000' THEN 1 ELSE 0 END due_today_count
FROM "jobs"
WHERE
jobs.creator_id = 5 ) t
UPDATE
As noted when 0 records are returned as t this result in as single result of Nulls in all the values. You have three options
1) Add A Having clause so that you have No records returned rather than result of all NULLS
HAVING SUM(active_count) is not null
2) If you want all zeros returned than you could add coalesce to all your sums
For example
SELECT
COALESCE(SUM(active_count)) active_count,
COALESCE(SUM(overdue_count)) overdue_count
COALESCE(SUM(due_today_count)) due_today_count
3) Take advantage of the fact that COUNT(NULL) = 0 as sbarro's demonstrated. You should note that the not-null value could be anything it doesn't have to be a 1
for example
SELECT
COUNT(CASE WHEN
jobs.status_id NOT IN (8,3,11) THEN 'Manticores Rock' ELSE NULL
END) as [active_count]
I would use this approach, use COUNT in combination with CASE WHEN.
SELECT
COUNT(CASE WHEN
jobs.status_id NOT IN (8,3,11) THEN 1
END) as [Count1],
COUNT(CASE WHEN
jobs.due_date < '2011-06-14'
AND jobs.status_id NOT IN(8,11,5,3) THEN 1
END) as [COUNT2],
COUNT(CASE WHEN
jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000'
END) as [COUNT3]
FROM
"jobs"
WHERE
jobs.creator_id = 5
Brief
SQL Server 2012 introduced the IIF logical function. Using SQL Server 2012 or greater you can now use this new function instead of a CASE expression. The IIF function also works with Azure SQL Database (but at the moment it does not work with Azure SQL Data Warehouse or Parallel Data Warehouse). It's shorthand for the CASE expression.
I find myself using the IIF function rather than the CASE expression when there is only one case. This alleviates the pain of having to write CASE WHEN condition THEN x ELSE y END and instead writing it as IIF(condition, x, y). If multiple conditions may be met (multiple WHENs), you should instead consider using the regular CASE expression rather than nested IIF functions.
Returns one of two values, depending on whether the Boolean expression
evaluates to true or false in SQL Server.
Syntax
IIF ( boolean_expression, true_value, false_value )
Arguments
boolean_expression A valid Boolean expression.
If this argument is not a Boolean expression, then a syntax error is
raised.
true_value Value to return if boolean_expression evaluates to
true.
false_value Value to return if boolean_expression evaluates
to false.
Remarks
IIF is a shorthand way for writing a CASE expression. It evaluates
the Boolean expression passed as the first argument, and then returns
either of the other two arguments based on the result of the
evaluation. That is, the true_value is returned if the Boolean
expression is true, and the false_value is returned if the Boolean
expression is false or unknown. true_value and false_value can be
of any type. The same rules that apply to the CASE expression for
Boolean expressions, null handling, and return types also apply to
IIF. For more information, see CASE (Transact-SQL).
The fact that IIF is translated into CASE also has an impact on
other aspects of the behavior of this function. Since CASE
expressions can be nested only up to the level of 10, IIF statements
can also be nested only up to the maximum level of 10. Also, IIF is
remoted to other servers as a semantically equivalent CASE
expression, with all the behaviors of a remoted CASE expression.
Code
Implementation of the IIF function in SQL would resemble the following (using the same logic presented by #rsbarro in his answer):
SELECT
COUNT(
IIF(jobs.status_id NOT IN (8,3,11), 1, 0)
) as active_count,
COUNT(
IIF(jobs.due_date < '2011-06-14' AND jobs.status_id NOT IN(8,11,5,3), 1, 0)
) as overdue_count,
COUNT(
IIF(jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000', 1, 0)
) as due_today_count
FROM
"jobs"
WHERE
jobs.creator_id = 5