SUM in Oracle: return NULL only when all fields are NULL - sql

I have a rather complicated select query that contains two types of SUM expressions. The Simple SUM:
SUM(X)
And the more complicated SUM/CASE:
SUM(CASE WHEN TYPE IN (
'ORANGES',
'LEMONS',
'LIMES')
THEN X
ELSE 0 END) AS SUM_CITRUS
Now, here's the problem: I need both of these SUMS to evaluate to NULL only if all of the values summed are null. If any one of the values summed is a value then the sum needs to be returned as if all null values were substituted for 0.
For the cases shown, how might this be achieved? My concerns are efficiency and simplicity, in that order.

I need both of these SUMS to evaluate to NULL only if all of the
values summed are null.
It will by default. Just replace 0 with null in your ELSE condition.
09:43:30 SYSTEM#dwal> ed
Wrote file S:\spool\dwal\BUFFER_SYSTEM_65.sql
1 with t (x, y) as (
2 select null, 1 from dual union all
3 select null, 1 from dual union all
4 select null, 2 from dual
5 )
6* select sum(x), sum(case y when 1 then x else null end ) from t
09:43:40 SYSTEM#dwal> /
SUM(X) SUM(CASEYWHEN1THENXELSENULLEND)
---------- -------------------------------
Elapsed: 00:00:00.00
09:43:41 SYSTEM#dwal> 2
2* select null, 1 from dual union all
09:43:59 SYSTEM#dwal> c/null/1
2* select 1, 1 from dual union all
09:44:03 SYSTEM#dwal> /
SUM(X) SUM(CASEYWHEN1THENXELSENULLEND)
---------- -------------------------------
1 1
Elapsed: 00:00:00.01

Oracle itself is doing what you want: for sum(), if all elements are null, sum() will return null
Therefore, you should be able to do something like:
SELECT FOO,
SUM(CASE
WHEN TYPE IN (
'ORANGES',
'LEMONS',
'LIMES') THEN X
WHEN TYPE IS NULL THEN NULL
ELSE 0
END) AS SUM_CITRUS
FROM YOUR_TABLE
GROUP BY FOO
(Here I assume there are lots of different other type, and you want orange/lemon/limes to count as X, other type count as 0, null count as null)

Related

SQL literal value that is alternative to NULL

Are there other special literal values besides NULL in SQL / PostgresQL?
NULL is nice in that we can interpret NULL as the concept of "nothing" (i.e. missing, not available, not asked, not answered, etc.), and data columns of any type can have NULL values.
I would like another value that I can interpret as representing another concept (here the idea of "everything"), in the same result set.
Is there another special value that I can return in a query, which like NULL doesn't type conflict?
Basically anything that doesn't throw ERROR: For 'UNION', types varchar and numeric are inconsistent in this toy query:
select 1 as numeral, 'one' as name UNION ALL
select 2 as numeral, 'two' as name UNION ALL
select NULL as numeral, NULL as name UNION ALL
select -999 as numeral, -999 as name UNION ALL -- type conflict
select '?' as numeral, 'x' as name -- type conflict
Here,
-999 doesn't work as its type conflicts with varchar columns
'~' doesn't work as its type conflicts with numeric columns
NULL doesn't work as it needs
More specifically here's my actual case, counting combinations of values and also include "Overall" rows in the same query. Generally I won't know or control the types of columns A, B, C in advance. And A, B, or C might also have NULL values which I would would still want to count separately.
SELECT A, COUNT(*) FROM table GROUP BY 1
UNION ALL
SELECT ?, COUNT(*) FROM table GROUP BY 1
and get a result set like:
A
COUNT
NULL
2
1
3
2
5
3
10
(all)
20
SELECT B, COUNT(*) FROM table GROUP BY 1
UNION ALL
SELECT ?, COUNT(*) FROM table GROUP BY 1
and get a result set like:
B
COUNT
NULL
2
'Circle'
3
'Line'
5
'Triangle'
10
(all)
20
You can use function CAST to convert the format to VARCHAR to be considered as string.
NOTE: Thanks to the comments above, I should completely rephrase this question as "How to COUNT/GROUP BY with ROLLUP using multiple columns of mixed/arbitrary/unknown types, and differentiate true NULL values from ROLLUP placeholders?"
The correct answer I believe is provided by #a_horse_with_no_name: use ROLLUP with GROUPING.
Below is is just me drafting that more completely with a revised example:
This toy example has an integer and a string
WITH table AS (
select 1 as numeral, 'one' as name UNION ALL
select 2 as numeral, 'two' as name UNION ALL
select 2 as numeral, 'two' as name UNION ALL
select NULL as numeral, NULL as name UNION ALL
select NULL as numeral, NULL as name UNION ALL
select NULL as numeral, NULL as name
)
select name, numeral, COUNT(*), GROUPING_ID()
FROM table
GROUP BY ROLLUP(1,2)
ORDER BY GROUPING_ID, name, numeral ;
It returns the following result:
numeral
name
count
grouping_id
note
NULL
NULL
3
0
both are true NULLs as grouping is 0
1
one
1
0
2
two
2
0
NULL
NULL
3
1
first is a true NULL, second is a ROLLUP
1
NULL
1
1
2
NULL
2
1
NULL
NULL
6
3
both NULLs are ROLLUPs

How to use LAG function in oracle pl sql until non zero or last row is reached?

I want to write LAG function for table column in such a way that if column value is zero it should lag value for that id if it is again zero for previous row it should LAG again until it reaches a non zero row or last row please help.Thanks in advance
Use LAG() IGNORE NULLS ... combined with a CASE statement:
LAG(
CASE COLUMN_NAME
WHEN 0
THEN NULL
ELSE COLUMN_NAME
END
) IGNORE NULLS OVER ( ORDER BY OTHER_COLUMN )
For example:
WITH DATA ( id, value ) AS (
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 0 FROM DUAL UNION ALL
SELECT 3, 0 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 0 FROM DUAL
)
SELECT id,
value,
LAG(
CASE VALUE
WHEN 0
THEN NULL
ELSE VALUE
END
) IGNORE NULLS
OVER ( ORDER BY id )
AS prev_non_zero_value
FROM DATA;
Outputs:
ID VALUE PREV_NON_ZERO_VALUE
---------- ---------- -------------------
1 1
2 0 1
3 0 1
4 2 1
5 0 2
Wouldn't it be simpler to call the same table twice, (for example, call them A and B).
In B, filter out all the rows where the value equals zero, and then do a simple LAG(field,1).
Then just JOIN the two tables.
Voila!

SQL AVG with Case

I've a DB which stores a value from C to AAA, while C is the worst and AAA the best.
Now I need the average of this value and I don't know how to first convert the values into an int, calculate the average, round the average to an int and convert it back.
Definitions:
C = 1
B = 2
A = 3
AA = 4
AAA = 5
Is that even possible with an SQL statement? I tried to combine AVG and CASE, but I don't bring it to work...
Thanks for your help!
Regards,
select avg(case score
when 'C' then 1
when 'B' then 2
when 'A' then 3
when 'AA' then 4
when 'AAA' then 5
end) as avg_score
from the_table;
(this assumes that the column is called score)
To convert this back into the "character value", wrap the output in another case:
select case cast(avg_score as int)
when 1 then 'C'
when 2 then 'B'
when 3 then 'A'
when 4 then 'AA'
when 5 then 'AAA'
end as avg_score_value
from (
select avg(case score
when 'C' then 1
when 'B' then 2
when 'A' then 3
when 'AA' then 4
when 'AAA' then 5
end) as avg_score
from the_table;
) t
The above cast(avg_score as int) assumes ANSI SQL. Your DBMS might have different ways to cast a value to an integer.
I've created this example for u.
u can cast ur ranking into temp table, then calculate and when ur done, drop it.
create table sof (id int identity,a nvarchar (10))
insert into sof values ('a')
insert into sof values ('b')
insert into sof values ('c')
select case a when 'AAA ' then 5
when 'AA' then 4
when 'A' then 3
when 'B' then 2
else 1
end as av
into #temp
from sof
----for rounded
select ROUND(AVG(CAST(av AS FLOAT)), 4)
from #temp
--not rounded
select AVG (av)
from #temp

Can I get the minimum of 2 columns which is greater than a given value using only one scan of a table

This is my example data (there are no indexes and I do not want to create any):
CREATE TABLE tblTest ( a INT , b INT );
INSERT INTO tblTest ( a, b ) VALUES
( 1 , 2 ),
( 5 , 1 ),
( 1 , 4 ),
( 3 , 2 )
I want the minimum value in of both column a and column b which is greater then a given value. E.g. if the given value is 3 then I want 4 to be returned.
This is my current solution:
SELECT MIN (subMin) FROM
(
SELECT MIN (a) as subMin FROM tblTest
WHERE a > 3 -- Returns 5
UNION
SELECT MIN (b) as subMin FROM tblTest
WHERE b > 3 -- Returns 4
)
This searches the table twice - once to get min(a) once to get min(b).
I believe it should be faster to do this with just one pass. Is this possible?
You want to use conditional aggregatino for this:
select min(case when a > 3 then a end) as minA,
min(case when b > 3 then b end) as minB
from tblTest;
To get the minimum of both values, you can use a SQLite extension, which handles multiple values for min():
select min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
The only issue is that the min will return NULL if either argument is NULL. You can fix this by doing:
select coalesce(min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
),
min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
This version will return the minimum value, subject to your conditions. If one of the conditions has no rows, it will still return the minimum of the other value.
From the top of my head, you could modify the table and add a min value column to store the minimum value of the two columns. then query that column.
Or you can do this:
select min(val)
from
(
select min(col1, col2) as val
from table1
)
where
val > 3
The outer SELECT, queries the memory, not the table itself.
Check SQL Fiddle

SQL - Combining incomplete

I'm using Oracle 10g. I have a table with a number of fields of varying types. The fields contain observations that have been made by made about a particular thing on a particular date by a particular site.
So:
ItemID, Date, Observation1, Observation2, Observation3...
There are about 40 Observations in each record. The table structure cannot be changed at this point in time.
Unfortunately not all the Observations have been populated (either accidentally or because the site is incapable of making that recording). I need to combine all the records about a particular item into a single record in a query, making it as complete as possible.
A simple way to do this would be something like
SELECT
ItemID,
MAX(Date),
MAX(Observation1),
MAX(Observation2)
etc.
FROM
Table
GROUP BY
ItemID
But ideally I would like it to pick the most recent observation available, not the max/min value. I could do this by writing sub queries in the form
SELECT
ItemID,
ObservationX,
ROW_NUMBER() OVER (PARTITION BY ItemID ORDER BY Date DESC) ROWNUMBER
FROM
Table
WHERE
ObservationX IS NOT NULL
And joining all the ROWNUMBER 1s together for an ItemID but because of the number of fields this would require 40 subqueries.
My question is whether there's a more concise way of doing this that I'm missing.
Create the table and the sample date
SQL> create table observation(
2 item_id number,
3 dt date,
4 val1 number,
5 val2 number );
Table created.
SQL> insert into observation values( 1, date '2011-12-01', 1, null );
1 row created.
SQL> insert into observation values( 1, date '2011-12-02', null, 2 );
1 row created.
SQL> insert into observation values( 1, date '2011-12-03', 3, null );
1 row created.
SQL> insert into observation values( 2, date '2011-12-01', 4, null );
1 row created.
SQL> insert into observation values( 2, date '2011-12-02', 5, 6 );
1 row created.
And then use the KEEP clause on the MAX aggregate function with an ORDER BY that puts the rows with NULL observations at the end. whatever date you use in the ORDER BY needs to be earlier than the earliest real observation in the table.
SQL> ed
Wrote file afiedt.buf
1 select item_id,
2 max(val1) keep( dense_rank last
3 order by (case when val1 is not null
4 then dt
5 else date '1900-01-01'
6 end) ) val1,
7 max(val2) keep( dense_rank last
8 order by (case when val2 is not null
9 then dt
10 else date '1900-01-01'
11 end) ) val2
12 from observation
13* group by item_id
SQL> /
ITEM_ID VAL1 VAL2
---------- ---------- ----------
1 3 2
2 5 6
I suspect that there is a more elegant solution to ignore the NULL values than adding the CASE statement to the ORDER BY but the CASE gets the job done.
i dont know about commands in oracle but in sql you could use some how that
first use pivot table is contains consecutives numbers 0,1,2...
i'm not sure but in oracle the function "isnull" is "NVL"
select items.ItemId,
case p.i = 0 then observation1 else '' end as observation1,
case p.i = 0 then observation1 else '' end as observation2,
case p.i = 0 then observation1 else '' end as observation3,
...
case p.i = 39 then observation4 else '' as observation40
from (
select items.ItemId
from table as items
where items.item = _paramerter_for_retrive_only_one_item /* select one item o more item where you filter items here*/
group by items.ItemId) itemgroup
left join
(
select
items.ItemId,
p.i,
isnull( max ( case p.i = 0 then observation1 else '' end ), '' ) as observation1,
isnull( max ( case p.i = 1 then observation2 else '' end ), '' ) as observation2,
isnull( max ( case p.i = 2 then observation3 else '' end), '' ) as observation3,
...
isnull( max ( case p.i = 39 then observation4), '' ) as observation40,
from
(select i from pivot where id < 40 /*you number of columns of observations, that attach one index*/
)
as p
cross join table as items
lef join table as itemcombinations
on item.itemid = itemcombinations.itemid
where items.item = _paramerter_for_retrive_only_one_item /* select one item o more item where you filter items here*/
and (p.i = 0 and not itemcombinations.observation1 is null) /* column 1 */
and (p.i = 1 and not itemcombinations.observation2 is null) /* column 2 */
and (p.i = 2 and not itemcombinations.observation3 is null) /* column 3 */
....
and (p.i = 39 and not itemcombinations.observation3 is null) /* column 39 */
group by p.i, items.ItemId
) as itemsimplified
on itemsimplified.ItemId = itemgroup.itemId
group by itemgroup.itemId
About pivot table
create an pivot table, Take a look at that
pivot table schema
name: pivot columns: {i : datatype int}
How populate
create foo table
schema foo
name: foo column: value datatype varchar
insert into foo
values('0'),
values('1'),
values('2'),
values('3'),
values('4'),
values('5'),
values('6'),
values('7'),
values('8'),
values('9');
/* insert 100 values */
insert into pivot
select concat(a.value, a.value) /* mysql */
a.value + a.value /* sql server */
a.value | a.value /* Oracle im not sure about that sintax */
from foo a, foo b
/* insert 1000 values */
insert into pivot
select concat(a.value, b.value, c.value) /* mysql */
a.value + b.value + c.value /* sql server */
a.value | b.value | c.value /* Oracle im not sure about that sintax */
from foo a, foo b, foo c
the idea about pivot table can consult in "Transact-SQL Cookbook By Jonathan Gennick, Ales Spetic"
I have to admit that the above solution (by Justin Cave) is simpler and easier to understand but this is another good option
at the end like you said you solved