oracle adding, then avg with null - sql

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".

Related

SQL: How to get max over column but exclude certain values?

I have a numeric column and all I want is the maximum value in that column that does NOT exceed a certain number. I am doing this along with a group by statement, using the MAX function.
So basically if for each group, the column is 1, 2, 3, 4, 5, and I want the maximum that does not exceed 4, then in this case, the maximum for this group would be 4.
However, if the column equals 5, 6, 7, 8, then since all values exceed 4, I … actually don't care, this won't end up being displayed, so just return anything.
How do I do this? Using SQL/Oracle.
You can use conditional aggregation as follows:
Select case when count(case when col > 4 then 1 end) = count(*)
then max(col)
else max(case when col <= 4 then col end)
end as res_
From your_table t
select max(case when col>4 then 0 else col end) from table1
If that is all that the query needs to do, it is best to filter the rows with col > 4 before aggregation, in a where clause. This will reduce the amount of work done by the aggregation itself, which is the most expensive operation in the whole query.
As a side effect, "groups" where all values in the column are > 4 will not be included at all in the output. For some reporting tasks this would be a problem, but you said in your case you wouldn't show anything in the output for those groups anyway.
So, you could do something like this:
select agg_col1, agg_col2, ..., max(col) as max_col_up_to_4
from your_table
WHERE col <= 4 -- DO THE FILTERING HERE!
group by agg_col1, agg_col2, ...
;
(Here agg_col1, agg_col2, ... are, obviously, the columns by which you group for your aggregation.)

How to select first matching row from the CASE statement in SQL Server

From a simple table in SQL Server with two columns like below
Key Value
------------
A 5000
B NULL
C 6000
I want to get the first record in the order B,A,C (i.e. get value of B and if null then value of A, and if null, value of C ) where Value is not null. From the above list I expect the output to be 5000
I'm trying with this code - without any luck:
SELECT
CASE
WHEN [Key] = 'B' AND Value IS NOT NULL
THEN Value
WHEN [Key] = 'A' AND Value IS NOT NULL
THEN Value
WHEN [Key] = 'C' AND Value IS NOT NULL
THEN Value
END
FROM
temporary
Hmmmm . . . one method uses coalesce():
select coalesce(max(case when [Key] = 'B' then value end),
max(case when [Key] = 'A' then value end),
max(case when [Key] = 'C' then value end)
)
from temporary;
But, I think I would do:
select top 1 t.value
from temporary t
where value is not null and [Key] in ('A', 'B', 'C')
order by charindex([Key], 'B,A,C');
Note that the order by is just a shorthand for getting the preferred ordering. It works for "A", "B", and "C", but might not generalize to all strings.
You can use a where clause to omit null values, order by your custom sorting (using a case expression) and just take the top 1 row:
SELECT TOP 1 value
FROM mytable
WHERE value IS NOT NULL
ORDER BY CASE key WHEN 'B' THEN 0
WHEN 'A' THEN 1
WHEN 'C' THEN 2
ELSE 3
END ASC

SQL statement - use avg and count together but in different conditions

Assume we have the table as follows,
id Col-1 Col-2
A 1 some text
B 0 some other text
C 3
...
Take the table above as example, I want to build one SQL statement which would output the result: 2, 2.
The first value is the avg of all col-1 values except for 0, that is (1+3)/2 = 2. (If 0 is counted, then the result would be (1+0+3)/3 = 1, which is not what I want.)
The second value is the total number of all col-2 that is not empty. So the value is 2.
P.S, I know how to create them separately. What I prefer is to create only 1 statement to get both results.
For the first you can use NULLIF as null values are ignored in aggregations such as AVG.
For the second I assume you want to only count values not NULL or empty string.
SELECT AVG(NULLIF(Col1, 0)),
COUNT(CASE WHEN Col2 <> '' THEN 1 END)
FROM T
You want conditional aggregation:
select avg(case when col1 <> 0 then col1 end) as avg_not_zero,
count(col2) as num_not_empty
from table t;
As a note: 0 does not mean that the value is empty. Often NULL is used for this purpose in SQL, although strictly speaking, NULL means an unknown value.
Note: If "empty" could mean the empty string instead of NULL:
select avg(case when col1 <> 0 then col1 end) as avg_not_zero,
count(nullif(col2, '')) as num_not_empty
from table t;

PostgreSQL, SELECT CASE COALESCE

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.

Coalesce function not selecting data value from series when it exists

My code is as follows:
Insert Into dbo.database (Period, Amount)
Select coalesce (date_1, date_2, date_3), Amount FROM Source.dbo.[10]
I'm 100% a value exists in one of the 3 variables: date_1, date_2, date_3, all as strings (var char 100), yet I am still getting blanks when I call Period.
Any help?
Coalesce is designed to return the first NOT NULL field from the list or NULL if none of the fields are NOT NULL, follow the link for full details http://msdn.microsoft.com/en-us/library/ms190349.aspx
I would guess that you have blank values (' ') in one of the columns instead of NULL values. If you are trying to find the first not null non-blank column you can use a case statement.
select
case
when len(rtrim(ltrim(date_1))) > 0 then date_1
when len(rtrim(ltrim(date_2))) > 0 then date_2
when len(rtrim(ltrim(date_3))) > 0 then date_3
else null
end,
Amount
from Source.dbo.[10]