First Negative Value Row in Oracle - sql

How do I pick the first negative value row using CASE Statement? I do not want to use Where condition since it applies the condition to the entire data.
In the below sample, I just want to make the maximum of negative values (-469) to ABS(-469) which is 469 and leave the rest as is.
My MAX(CASE WHEN -469)...does not seem to work. Assume there is an ordering field.
Sample
------
1281
-469
-469
-509
-1359
-1359
-2759
-2829

CASE is no different from WHERE, it's checked against every row in your data (unless there is an index on this column). So the best I can think of is
UPDATE t
SET Sample = ABS(Sample)
WHERE Sample = (SELECT MAX(Sample) FROM t WHERE Sample < 0)
EDIT: just read that you actually need a "maximum of negatives", if you know what is that value, you can manually use CASE
SELECT CASE WHEN Sample = -469 THEN ABS(Sample) ELSE Sample END AS Sample
FROM t
To do the same automatically (i.e. to find the maximum of negatives and change it):
WITH MaxNeg AS (SELECT MAX(Sample) as v FROM t WHERE Sample < 0)
SELECT CASE WHEN Sample = MaxNeg.v THEN ABS(Sample) ELSE Sample END AS Sample
FROM t

If you want to change values in your row. You can use the following query.
update
t
set
sample = abs(sample)
where
sample = (select max(t.sample) from t where t.sample < 0)
But it you only need retrieve absolute value for maximum over negatives then use the following query. Really I think that clause where hasn't scary things and you can use it.
select abs(max(t.sample)) from t where t.sample < 0
Update
If you can use only select statement, then following decision can helps you.
with
t as
(
select 1281 as sample from dual union all
select -469 as sample from dual union all
select -469 as sample from dual union all
select -509 as sample from dual union all
select -1359 as sample from dual union all
select -1359 as sample from dual union all
select -2759 as sample from dual union all
select -2829 as sample from dual
)
select
case
when t.sample = (select max(t.sample) from t where t.sample < 0) then
abs(t.sample)
else
t.sample
end sample
from
t
And the result of this query is:
sample
-------
1281
469
469
-509
-1359
-1359
-2759
-2829

You can try the below SQL which filters all negative number. Then order by descending order of the negative numbers and select only the first row.
update tab
set tab.val = ABS(tab.val)
WHERE
tab.val < 0
ORDER BY tab.val DESC;

Related

Oracle- Need some changes to the Query

I have the below Query. My Expected output would be as below. Please help me make changes to the Query
select
ID,TERM,
case
when TERM like '____1_' then
function_toget_hrs(ID, TERM,sysdate) else null
end fall_hours,
case
when TERM like '____2_' then
function_toget_hrs(ID, TERM,sysdate) else null
end winter_hours
from TABLE_TERM
where ID='12087762'
Expecting one row for each ID. Please help me the ways
Pivoting is what you need:
WITH TABLE_TERM AS
(
SELECT 12087762 AS ID, '202110____1_' AS term, 12 AS func FROM dual UNION ALL
SELECT 12087762 AS ID, '202120____2_' AS term, 16 FROM dual UNION ALL
SELECT 12087762 AS ID, '202140____1_' AS term, 0 FROM dual
)
SELECT *
FROM (SELECT ID
, DECODE(SUBSTR(term,-6),'____1_','fall_hours','winter_hours') AS hrs
, func --function_toget_hrs(ID, TERM,sysdate) for test purposes
FROM TABLE_TERM
WHERE ID = '12087762'
)
PIVOT (SUM(func) FOR hrs IN ('fall_hours','winter_hours'));

Oracle Pivot rows to columns pattern matching

I want to rearrange the rows to columns (in tbl2 below) to count the number of occurrences of EXEN for the EXEN col, and any code starting with MPA for the MPACODE column.
SELECT *
FROM (select code from tbl2 where pidm='4062161')
PIVOT (count(*) FOR (code) IN ('EXEN' AS EXEN, 'MPA%' AS MPACODE));
tbl2:
Desired output:
Actual output:
You must perform an intermediate step to transform all MPA%to MPAsee subquery dt2
with dt as (
select 'EXEN' code from dual union all
select 'MPA'||rownum from dual connect by level <= 10),
dt2 as (
select
case when code like 'MPA%' then 'MPA' else code end as code
from dt)
select *
from dt2
pivot (
count(*) for
(code) IN ('EXEN' AS EXEN, 'MPA' AS MPACODE));
EXEN MPACODE
---------- ----------
1 10
PIVOT perform an equal comparison (not LIKE), so this is not valid: 'MPA%' AS MPACODE and the reason why the query fails.
for example:
select
count(case when code='EXEN' then 1 end) exen,
count(case when code like 'MPA%' then 1 end) mpacode
from tbl2 where pidm='4062161';

Oracle SQL Min in Select Clause

Can some one please help me in writing a sql query that should do a oracle min function based on the following conditions.
For eg for column values
0,0,0,0 then output should be 0
0,null,0,null then output should be o
0,2,4,5,6 then output should be 2 (Note that we are excluding Zero here)
0,2,null,4,5 then output should be 2 (same here we are excluding zero)
null,null,null, null then output should be null.
I wrote query already that satisfies all the above cases but failing for last case when all the column values are null. Instead of returning null it is returning 0. Can some one modify the below query to fit for the last case as well?
select NVL(MIN(NULLIF(columnname,0)),0) from tablename;
Please also keep in mind that the query should be runnable in oracle as well as hsqldb as we are using hsql db for running junits.
If all 4 cases satisfied by your query then just a case will solve your problem.
SELECT CASE WHEN MIN(COLUMNNAME) IS NULL THEN NULL ELSE NVL(MIN(NULLIF(COLUMNNAME,0)),0) END FROM TABLENAME;
Note:- assuming all the cases satisfied by your query except 5th.
I will show below an input table with two columns, ID and VAL, to illustrate the various possibilities. You want a single result per ID (or even for the entire table), so this must be a job for GROUP BY and some aggregate function. You want to distinguish between three types of values: Greater than zero, zero, and null (in this order); you want to pick the "highest priority group" that exists for each ID (in this order of priority), and for that priority group only, you want to pick the min value. This is exactly what the aggregate FIRST/LAST function does. To order by the three "classes" of values, we use a CASE expression in the ORDER BY clause of the aggregate LAST function.
The WITH clause below is not part of the solution - I only include it to create test data (in your real life situation, use your actual table and column names and remove the entire WITH clause).
with
inputs ( id, val ) as (
select 1, 0 from dual union all
select 1, 0 from dual union all
select 1, 0 from dual union all
select 2, 0 from dual union all
select 2, null from dual union all
select 2, 0 from dual union all
select 3, 0 from dual union all
select 3, 2 from dual union all
select 3, 5 from dual union all
select 4, 0 from dual union all
select 4, 3 from dual union all
select 4, null from dual union all
select 5, null from dual union all
select 5, null from dual
)
select id,
min(val) keep (dense_rank last order by case when val > 0 then 2
when val = 0 then 1
else 0
end
) as min_val
from inputs
group by id
order by id
;
ID MIN_VAL
---------- ----------
1 0
2 0
3 2
4 3
5

MS SQL does not return the expected top row when ordering by DIFFERENCE()

I have noticed strange behaviour in some SQL code used for address matching at the company I work for & have created some test SQL to illustrate the issue.
; WITH Temp (Id, Diff) AS (
SELECT 9218, 0
UNION
SELECT 9219, 0
UNION
SELECT 9220, 0
)
SELECT TOP 1 * FROM Temp ORDER BY Diff DESC
Returns 9218 but
; WITH Temp (Id, Name) AS (
SELECT 9218, 'Sonnedal'
UNION
SELECT 9219, 'Lammermoor'
UNION
SELECT 9220, 'Honeydew'
)
SELECT TOP 1 *, DIFFERENCE(Name, '') FROM Temp ORDER BY DIFFERENCE(Name, '') DESC
returns 9219 even though the Difference() is 0 for all records as you can see here:
; WITH Temp (Id, Name) AS (
SELECT 9218, 'Sonnedal'
UNION
SELECT 9219, 'Lammermoor'
UNION
SELECT 9220, 'Honeydew'
)
SELECT *, DIFFERENCE(Name, '') FROM Temp ORDER BY DIFFERENCE(Name, '') DESC
which returns
9218 Sonnedal 0
9219 Lammermoor 0
9220 Honeydew 0
Does anyone know why this happens? I am writing C# to replace existing SQL & need to return the same results so I can test that my code produces the same results. But I can't see why the actual SQL used returns 9219 rather than 9218 & it doesn't seem to make sense. It seems it's down to the Difference() function but it returns 0 for all the record in question.
When you call:
SELECT TOP 1 *, DIFFERENCE(Name, '')
FROM Temp l
ORDER BY DIFFERENCE(Name, '') DESC
All three records have a DIFFERENCE value of zero, and hence SQL Server is free to choose from any of the three records for ordering. That is to say, there is no guarantee which order you will get. The same is true for your second query. Actually, it is possible that the ordering for the same query could even change over time. In practice, if you expect a certain ordering, you should provide exact logic for it, e.g.
SELECT TOP 1 *
FROM Temp
ORDER BY Id;

use SUM on certain conditions

I have a script that extracts transactions and their details from a database. But my users complain that the file size being generated is too large, and so they asked for certain transactions to be just summed up/consolidated instead if they are of a certain classification, say Checking Accounts. That means there should only be one line in the result set named "Checking" which contains the sum of all transactions under Checking Accounts. Is there a way for an SQL script to go like:
CASE
WHEN Acct_class = 'Checking'
then sum(tran_amount)
ELSE tran_amount
END
I already have the proper GROUP BY and ORDER BY statements, but I can't seem to get my desired output. There is still more than one "Checking" line in the result set. Any ideas would be very much appreciated.
Try This,
Select sum(tran_amount) From tran_amount Where Acct_class = 'Checking'
You can try to achieve this using UNION ALL
SELECT tran_amount, .... FROM table WHERE NOT Acct_class = 'Checking'
UNION ALL
SELECT SUM(tran_amount), .... FROM table WHERE Acct_class = 'Checking' GROUP BY Acct_class, ...;
hi you can try below sql
select account_class,
case when account_class = 'saving' then listagg(trans_detail, ',') within group (order by emp_name) -- will give you all details transactions
when account_class = 'checking' then to_char(sum(trans_detail)) -- will give you only sum of transactions
end as trans_det from emp group by account_class;
Or, if your desired output is getting either the sum, either the actual column value based on another column value, the solution would be to use an analytical function to get the sum together with the actual value:
select
decode(acct_class, 'Checking', tran_amount_sum, tran_amount)
from (
select
sum(tran_amount) over (partition by acct_class) as tran_amount_sum,
tran_amount,
acct_class
from
YOUR_TABLE
)
You can try something like the following, by keeping single rows for some classes, and aggregating for some others:
with test (id, class, amount) as
(
select 1, 'a' , 100 from dual union all
select 2, 'a' , 100 from dual union all
select 3, 'Checking', 100 from dual union all
select 4, 'Checking', 100 from dual union all
select 5, 'c' , 100 from dual union all
select 6, 'c' , 100 from dual union all
select 7, 'c' , 100 from dual union all
select 8, 'd' , 100 from dual
)
select sum(amount), class
from test
group by case
when class = 'Checking' then null /* aggregates elements of class 'b' */
else id /* keeps elements of other classes not aggregated */
end,
class