PostgreSQL, SELECT CASE COALESCE - sql

I need to 'name' categories: mycat is a text column with possible values '0' to '4'.
SELECT CASE mycat
WHEN '0' THEN 'ZERO'
WHEN '1' THEN 'ONE'
WHEN '2' THEN 'TWO'
WHEN '3' THEN 'THREE'
WHEN '4' THEN 'OTHER'
END AS my_category,
COALESCE(SUM(col1), 0),
COALESCE(SUM(col2), 0),
COALESCE(SUM(col3), 0)
FROM mytable
GROUP BY mycat
ORDER BY mycat;
That works OK, but I have some an error in my program which very rarely writes null (or '' as I can see in pgAdmin). In such cases I have to treat that '' the same as '0'.
But I can't get that!
I try like this:
SELECT CASE COALESCE(mycat, '0')
But this doesn't solve it at all.
How to get that '' will be summed and grouped together with '0' category?
PostgreSQL 9.3, Windows.

you need to use COALESCE in the group by and order by also similar to how you planned to change the case expression, but postgres is giving error , so another option is to wrap your statement in a subquery and do group by
SELECT my_category,
COALESCE(SUM(col1), 0),
COALESCE(SUM(col2), 0),
COALESCE(SUM(col3), 0)
FROM
(
SELECT CASE coalesce(mycat ,'0')
WHEN '0' THEN 'ZERO'
WHEN '1' THEN 'ONE'
WHEN '2' THEN 'TWO'
WHEN '3' THEN 'THREE'
WHEN '4' THEN 'OTHER'
WHEN '' THEN 'ZERO'
END AS my_category,
col1,
col2,
col3
FROM mytable
) T
GROUP BY my_category
ORDER BY my_category

You can have this without subquery. You could repeat the expression in the GROUP BY and ORDER BY clause. But it's much simpler to use the ordinal number of the output column instead:
SELECT CASE mycat
WHEN '1' THEN 'ONE'
WHEN '2' THEN 'TWO'
WHEN '3' THEN 'THREE'
WHEN '4' THEN 'OTHER'
ELSE 'ZERO' -- catches all other values
END AS my_category
, COALESCE(SUM(col1), 0) AS sum1
, COALESCE(SUM(col2), 0) AS sum2
, COALESCE(SUM(col3), 0) AS sum3
FROM mytable
GROUP BY 1
ORDER BY 1;
I chose the simplest and fastest code. The ELSE branch catches 0, '' and NULL - or any other value not yet filtered! But you say there are no others.
A couple of rants:
mycat is 'text' column with possible values '0' to '4'.
This is wrong in two ways.
Obviously, there are empty strings ('') and / or NULL values, too.
With that fixed, integer, smallint, of "char" with a CHECK cnstraint would be sensible choices for the data type. (Maybe even enum.) text, not so much.
To find out your actual range of values:
SELECT mycat, count(*) AS ct
FROM mytable
GROUP BY 1
ORDER BY 2 DESC;
If your client obfuscates NULL and empty values, test with mycat IS NULL. You need to know and understand the difference in many situations.
This orders by the resulting text in my_category like: ONE, OTHER, THREE, TWO, ZERO? I doubt you want that.

Related

SQL Server : SUM of column with alphanumeric values

I want to get the sum of a column that is alphanumeric. I want to add numeric values and return column values if not numeric. What I did is a added a CASE WHEN that looks like this
CASE
WHEN intAllocatedResourceperDivision NOT IN ('CL', 'HS', 'HV', 'ML', 'SL', 'VL', 'HC', 'S', '*')
THEN CAST(SUM(ISNULL(CAST(intAllocatedResourceperDivision AS DECIMAL), 0.00)) AS NVARCHAR(250))
ELSE intAllocatedResourceperDivision
END intAllocatedResourceperDivision
So I assume that all numeric values will be added and if values is in ('CL', 'HS', 'HV', 'ML', 'SL', 'VL', 'HC', 'S', '*') it will be returned as is.
But I'm getting
Error converting data type nvarchar to numeric.
Looks like your SUM aggregation is out of place. You only sum if the condition in the case statement is true. Try this:
SUM(case when intAllocatedResourceperDivision NOT IN ('CL','HS','HV','ML','SL','VL','HC','S','*') THEN intAllocatedResourceperDivision else 0 end)
In case you don't know the exact potential combination of non numeric values, you can use the ISNUMERIC function (assuming you're using SQL Server) to test whether or not the value is a number and assign a 0 if it's not, aggregating the final result.
SUM(case when ISNUMERIC(intAllocatedResourceperDivision) = 1 then intAllocatedResourceperDivision else 0 end)
To aggregate the numerical values and also keep non-numerical, you can use a union query as such:
select
cast(SUM(cast(intAllocatedResourceperDivision as decimal(18,2))) as varchar)
from
YOUR_TABLE
where
ISNUMERIC(intAllocatedResourceperDivision) = 1
UNION ALL
select
intAllocatedResourceperDivision
from
YOUR_TABLE
where
ISNUMERIC(intAllocatedResourceperDivision) = 0
This looks like SQL Server syntax. I would recommend using TRY_CONVERT():
TRY_CONVERT(DECIMAL, intAllocatedResourceperDivision) as intAllocatedResourceperDivision
Try this:
select 'CL' as intAllocatedResourceperDivision into #tmp
union select 'HS'union select 'HV'union select 'ML'union select 'SL'union select 'VL'union select 'HC'union select 'S'union select '*'union select '1'union select '4'
select CAST(SUM(ISNULL(CAST(intAllocatedResourceperDivision AS DECIMAL),0.00)) AS nvarchar(250)) as intAllocatedResourceperDivision
from #tmp where intAllocatedResourceperDivision NOT IN ('CL','HS','HV','ML','SL','VL','HC','S','*')
union
select intAllocatedResourceperDivision
from #tmp where intAllocatedResourceperDivision IN ('CL','HS','HV','ML','SL','VL','HC','S','*')

Case Statement embedded in NVL

I am trying to setup the below logic in as a divisor. I need to use NVL() to avoid the divide by zero error. I need to get a distinct count of the case statement.
Here is what I have:
(NVL(COUNT(DISTINCT
CASE WHEN ({field1} = 'a' OR {field1} = 'b' OR
{field1} = 'c' OR {field1} = 'd') AND
({field2} = 'a' OR {field2} = 'b') AND
({field3} > TO_DATE('01-JAN-2017', 'DD-MON-YYYY'))
THEN 1 END), 1))
There is something wrong with my syntax but cannot figure it out.
Any help would be appreciated. Thank you!
I changed lots of ORs to simpler IN
COUNT(DISTINCT
CASE
WHEN field1 IN ('a','b','c','d')
AND field2 IN ('a','b')
AND field3 > TO_DATE('01-JAN-2017', 'DD-MON-YYYY')
THEN 1
END)
COUNT(DISTINCT ...) doesn't return null, so removed redundant NVL
Now, the above will result is either 0 or 1.
You probably want this without distinct:
COUNT(
CASE
WHEN field1 IN ('a','b','c','d')
AND field2 IN ('a','b')
AND field3 > TO_DATE('01-JAN-2017', 'DD-MON-YYYY')
THEN 1
END)
Since, it is a divisor, it can't be 0, so you'll have to place safeguard for that. One sensible output in case the divisor is 0 is null. One way to achieve that is:
case when (expression) <> 0 then dividend/(expression) end

missing expression sql

SELECT
create_date
,resolved_date
,item
,site
,status
,contact_time
,impact_label
FROM
mytable
WHERE
create_date BETWEEN to_date('2013/03/01','YYYY/MM/DD')
AND to_date('2015/08/06','YYYY/MM/DD')
and CASE item
WHEN in ('A','B') then '1'
WHEN in ('C') then '2'
WHEN in ('D') then '3'
ELSE null
END
GROUP BY
create_date
,resolved_date
,item
,site
,status
,contact_time
,impact_label
It says I have problem at the first when in, could someone please help?
If you want to use IN inside CASE you need different syntax :
...
case
when item in ('A','B') then '1'
when item in ('C') then '2'
when item in ('D') then '3'
--else null -- no need , ELSE NULL is default behaviour of CASE
end ...
Or change CASE to correct simple search syntax:
CASE item
WHEN 'A' THEN '1'
WHEN 'B' THEN '1'
WHEN 'C' THEN '2'
WHEN 'D' THEN '3'
ELSE NULL
END
CASE is an expression that returns a value. In your case, it is returning a string value.
The WHERE clause consists of boolean expressions. A string is not a boolean expression. I'm not sure what you intend, perhaps:
(CASE WHEN item in ('A','B') then '1'
WHEN item in ('C') then '2'
WHEN item in ('D') then '3'
ELSE null
END) IS NOT NULL
(Note the changes both to the CASE and the addition of IS NOT NULL.)
However, that is a silly expression, because it is equivalent to:
item in ('A', 'B', 'C', 'D')
Perhaps you want to add the CASE to the SELECT, giving it a proper column alias.

Oracle conditional select

Im sure this is an easy one.
How would I do a conditional value select on a column.
Basically if column1 ='Y' then display as "FOO" else if 'n' display "foobar"
select column1 from table1;
With a simple case expression:
select case column1 when 'Y' then 'FOO' else 'foorbar' end
from table1;
That assume a simple if/else. Your question specifies two values, and you can check both:
select case column1 when 'Y' then 'FOO' when 'n' then 'foorbar' end
from table1;
If you have any column1 values other than Y and n you'd get null; you can still specify a different value with an else even if you're testing for more than one explicit value:
select case column1 when 'Y' then 'FOO' when 'n' then 'foorbar' else 'bar' end
from table1;
An alternative syntax in Oracle is decode, I like this because it's nice and concise, but basically does the same as the case statement suggested above:
select decode(column1,'Y','FOO','foobar') from table1;
The syntax is as follows: decode( expression , search , result [, search , result]... [, default] ).
Further examples are in Oracles docs: http://www.techonthenet.com/oracle/functions/decode.php

oracle adding, then avg with null

I have sql like:
select avg(decode(type, 'A', value, null) + decode(type, 'B', value, null)) from table;
The problem with this is some of these types can be null, so the addition part will result in null because adding anything to null makes it null. So you might think I could change the decode from null to 0, but that seems to make the avg() count it as part of it's averaging, but it shouldn't/I don't want it counted as part of the average.
Ideally the addition would just ignore the nulls and just not try to add them to the rest of the values.
So let's say my numbers are:
5 + 6 + 5
3 + 2 + 1
4 + null + 2
They total 28 and I'd want to divide by 8 (ignore the null), but if I change the null to 0 in the decode, the avg will then divide by 9 which isn't what I want.
As written, your code should always return null, since if the first decode returns value, then the second decode must always return null. I'm going to assume that you made an error in genericizing your code and that what you really meant was this:
avg(decode(type1, 'A', value1, null) + decode(type2, 'B', value2, null))
(Or, instead of type1, it could be a.type. The point is that the fields in the two decodes are meant to be separate fields)
In this case, I think the easisest thing to do is check for nulls first:
avg(case when type1 is null and type2 is null then null
else case type1 when 'A' then value1 else 0 end
+ case type2 when 'B' then value2 else 0 end
end)
(I replaced decode with case because I find it easier to read, but, in this case decode would work just as well.)
This is overcomplicated to do a sum here. Juste output the values with a CASE, and you are done.
SELECT AVG(
CASE WHEN type = 'A' OR type = 'B'
THEN value
ELSE null
END
)
FROM table
A simple workaround would be to calculate the average yourself:
select
-- The sum of all values with type 'A' or 'B'
sum(decode(type, 'A', value, 'B', value, 0)) /
-- ... divided by the "count" of all values with type 'A' or 'B'
sum(decode(type, 'A', 1, 'B', 1, 0))
from table;
A SQLFiddle example
But the way AVG() works, it would probably be sufficient, if you just removed the addition and put everything in a single DECODE()
select avg(decode(type, 'A', value, 'B', value, null)) from table
The logic here is a bit complicated:
select avg((case when type = 'A' then value else 0 end) + (case when type = 'B' then value else 0 end))
from table
where type in ('A', 'B')
The where clause guarantees that you have at least one "A" or "B". The problem is arising when you have no examples of "A" or "B".