Oracle SQL running total on change of field (SUM on column only when field changes) - sql

I have a question in regards to how to SUM on a column only when a field is changing.
Take for example the table below:
Note that Column A and Column B are different tables. I.e. A was selected from Table X and B was selected from Table Y
SELECT X.A, Y.B
FROM X
INNER JOIN Y ON X.DATE = Y.DATE AND X.VAL1 =
Y.VAL1 AND X.VAL2 = Y.VAL2
A B
123 5
123 5
456 10
789 15
789 15
I need to sum column B on change of field on column A:
I.e. the query should return 5 + 10 + 15 = 30 (5 the first time because value in column A is 123, 10 the second time because column A changed from 123 to 456 - note that the second row was skipped because column A still contains value 123 - hence the change of field logic and so on).
I can't do a simple SUM(B) because that would return 50. I also cannot do SUM(B) OVER (PARTITION BY A) because that would do a running total by group, not by change of field.
My output needs to look like this:
A B X
123 5 5
123 5 5
456 10 15
789 15 30
789 15 30
I am trying to do this within a simple query. Is there a particular function I can use to do this?

For the simple data set provided, the following should work. You will, of course, want to review the ORDER BY clauses for correctness in your exact use case.
SELECT a
,b
,SUM(CASE WHEN a = prev_a THEN 0 ELSE b END) OVER (ORDER BY a RANGE UNBOUNDED PRECEDING) AS x
FROM (
SELECT a
,b
,LAG(a) OVER (ORDER BY a) AS prev_a
FROM {your_query}
)
This solution makes use of the LAG function, which returns the specified column from the previous result. Then the outer query's SUM gives the value only when the previous row didn't have the same value. And there is also the windowing clause involved in the SUM because you specified that you needed a running total.

Ta-daaa?
SQL> with test (a, b) as
2 (select 123, 5 from dual union all
3 select 123, 5 from dual union all
4 select 456, 10 from dual union all
5 select 789, 15 from dual union all
6 select 789, 15 from dual
7 ),
8 proba as(
9 select a, b,
10 case when a <> nvl(lag(a) over (order by a), 0) then 'Y' else 'N' end switch
11 from test
12 )
13 select a, b,
14 sum(decode(switch, 'Y', b, 0)) over (partition by null order by a) x
15 from proba
16 order by a;
A B X
---------- ---------- ----------
123 5 5
123 5 5
456 10 15
789 15 30
789 15 30
SQL>

you can also create a function and use it, see sample below,
create package test_pkg123
as
a number;
r_sum NUMBER;
function get_r_sum(p_a number, p_val NUMBER, rown NUMBER) return number;
end;
/
create or replace package body test_pkg123
as
function get_r_sum(p_a number, p_val NUMBER, rown NUMBER) return number
is
begin
if rown = 1 then
r_sum := p_val;
return r_sum;
end if;
if p_a != a then
r_sum := nvl(r_sum, 0) + nvl(p_val, 0);
end if;
a := p_a;
return r_sum;
end;
end;
/
with test (a, b) as
(select 123, 5 from dual union all
select 123, 5 from dual union all
select 456, 10 from dual union all
select 789, 15 from dual union all
select 789, 15 from dual union all
select 789, 15 from dual union all
select 123, 2 from dual
)
select a, b, test_pkg123.get_r_sum(a, b, rownum) r_sum
from test;
Output:
A B R_SUM
123 5 5
123 5 5
456 10 15
789 15 30
789 15 30
789 15 30
123 2 32
7 rows selected

Related

SQL query to to check uniqueness of a column value

Need a query to check(Select) if every combination of Value in colA and B has a unique value in col C. Please help. Thank you
This is the query I tried which doesn't give me the desired result:
select unique CONCAT(A,B),C
from tab1
group by CONCAT(A,B),C
having count(distinct (CONCAT(A,B)))>1;
Sample table:
A B C
Tim 123 1
Jill 123 1
Jill 456 2
John 456 1
Jill 456 1
Here row 3 and 5 with same values in col A and B have different values in col C which is incorrect. I need to select those
Something like this? (Sample data in lines #1 - 7; query begins at line #8):
SQL> with test (a, b, c) as
2 (select 'Tim' , 123, 1 from dual union all
3 select 'Jill', 123, 1 from dual union all
4 select 'Jill', 456, 2 from dual union all
5 select 'John', 456, 1 from dual union all
6 select 'Jill', 456, 1 from dual
7 )
8 select t.*
9 from test t
10 where (t.a, t.b) in (select x.a, x.b
11 from test x
12 group by x.a, x.b
13 having count(distinct x.c) > 1
14 );
A B C
---- ---------- ----------
Jill 456 1
Jill 456 2
SQL>

Oracle SQL to Update rows in repeating pattern

How to update rows with a given repeating number sequence.
my table is as follows
line_type
line_val
line_pattern
A
1
null
A
2
null
B
5
null
B
6
null
C
3
null
C
4
null
Now I want to update the column value with the repeating pattern of 8532
So the table after the update will look like
line_type
line_val
line_pattern
A
1
8
A
2
5
B
5
3
B
6
2
C
3
8
C
4
5
How can I achieve this in a update statement ?
With the data you have provided it is not possible to satisfy your requirement. The data in a table is not stored in a specific order. If you want the order to be guaranteed in a select statement, you need to provide an ORDER BY clause.
In the code below there is an additional column "ORDER_BY" to specify the order in which the records need to be processed. The repeating pattern is calculated using the MOD function to convert the row number to a repeating sequence of 4 numbers and then CASE maps each of those numbers to its respective pattern location.
WITH test_data (order_by, line_type, line_val)
AS
(
SELECT 1, 'A',1 FROM DUAL UNION ALL
SELECT 2, 'A',2 FROM DUAL UNION ALL
SELECT 3, 'B',5 FROM DUAL UNION ALL
SELECT 4, 'B',6 FROM DUAL UNION ALL
SELECT 5, 'C',3 FROM DUAL UNION ALL
SELECT 6, 'C',4 FROM DUAL
)
SELECT
CASE MOD(ROW_NUMBER() OVER (ORDER BY order_by),4)
WHEN 1 THEN 8
WHEN 2 THEN 5
WHEN 3 THEN 3
WHEN 0 THEN 2
END as line_pattern,
t.*
FROM
test_data t
LINE_PATTERN ORDER_BY L LINE_VAL
------------ ---------- - ----------
8 1 A 1
5 2 A 2
3 3 B 5
2 4 B 6
8 5 C 3
5 6 C 4
If you don't care about the order then use this form:
UPDATE mytable
SET line_pattern =
CASE MOD (ROWNUM, 4)
WHEN 1 THEN 8
WHEN 2 THEN 5
WHEN 3 THEN 3
WHEN 0 THEN 2
END

Oracle query to get all entries based on sum of two column value

Let's assume I have the below table in my Oracle db:
ID foo bar
1 100 1
2 50 1
3 75 1
4 200 1
I want to create an SQL query to return those rows for which the sum of foo + bar < 100. So in this I should get rows 2 and 3 back, since for row 2, (50 + 1) <100 and also for row 3, (75 + 1) < 100.
I looked at the SUM operator but that will do a summation for all the rows for the column which is not what I need.
SELECT * FROM xxx WHERE foo + bar <100;
sum sums an expression across multiple lines. To add values within a row, you could just use the arithmetic + operator:
SELECT *
FROM mytable
WHERE foo + bar < 100
Just for amusement, a few other options.
A simple, negation one:
SQL> with test (id, foo, bar) as
2 (select 1, 100, 1 from dual union
3 select 2, 50, 1 from dual union
4 select 3, 75, 1 from dual union
5 select 4, 200, 1 from dual
6 )
7 select *
8 from test
9 where not (foo + bar) >= 100
10 order by id;
ID FOO BAR
---------- ---------- ----------
2 50 1
3 75 1
SQL>
Or
select *
from test
where (foo + bar) / 100 < 1;
A little bit more typing which covers your
I looked at the SUM operator ...
intention (though, SUM is a function, not an operator):
SQL> with test (id, foo, bar) as
2 (select 1, 100, 1 from dual union
3 select 2, 50, 1 from dual union
4 select 3, 75, 1 from dual union
5 select 4, 200, 1 from dual
6 ),
7 all_in_1 as
8 (select id, foo foobar from test
9 union all
10 select id, bar from test
11 )
12 select t.id, t.foo, t.bar
13 from test t
14 where t.id in (select a.id
15 from all_in_1 a
16 group by a.id
17 having sum(a.foobar) < 100
18 )
19 order by t.id;
ID FOO BAR
---------- ---------- ----------
2 50 1
3 75 1
SQL>
There certainly are some more nice tricks people - who are way better in mathematics than me - might discover.

Oracle - Split records based on overlapping ranges

Data sample:
id lowerlimt upperlimit
1 5 10 ---Master Record
2 8 12
3 3 8
4 8 9
5 11 15
In the above table, Let us assume record with id=1 as Master Record. I want Compare the other records with the first record in the same table, and Split each record if it overlaps the range and assign a flag. If it overlaps assign 'Y', else 'N'.
If it overlaps partially split the record in to two, one for overlapping range and other for the non-overlapping range.
id lowerlimt upperlimit flag
2 8 10 y
2 10 12 n
3 3 5 n
3 5 8 y
4 8 9 y
5 11 15 n
I added more "test data" for testing and illustration. The main computation is to break down the input ranges into up to three pieces (some of those don't make sense and are eliminated in the final stage - the maximum of three is reached when the input range is strictly overlapping the Master Record in both directions).
For efficiency, it is best if each input row is accessed just once. So, instead of using union all (the easiest route), I prefer to create all three subranges and corresponding flags simultaneously, with the result having nine columns instead of three for the subranges and flags. Then I use unpivot to put them into separate rows.
with
test_data ( id, lowerlimit, upperlimit ) as (
select 1, 5, 10 from dual union all ---Master Record
select 2, 8, 12 from dual union all
select 3, 3, 8 from dual union all
select 4, 8, 9 from dual union all
select 5, 11, 15 from dual union all
select 6, 2, 5 from dual union all
select 7, 1, 14 from dual
)
-- end of test data (not part of the solution)
-- SQL query begins BELOW THIS LINE (use your actual table name)
select id, lowerlimit, upperlimit, flag
from (
select id,
t.lowerlimit as x1, least(t.upperlimit, m.ll) as y1, 'n' as f1,
greatest(t.lowerlimit, m.ll) as x2, least(t.upperlimit, m.ul) as y2, 'y' as f2,
greatest(t.lowerlimit, m.ul) as x3, t.upperlimit as y3, 'n' as f3
from test_data t cross join
( select lowerlimit ll, upperlimit ul
from test_data
where id = 1
) m
where t.id != 1
)
unpivot ( ( lowerlimit, upperlimit, flag )
for ( x, y, f ) in ( ( x1, y1, f1), (x2, y2, f2), (x3, y3, f3) ) )
where lowerlimit < upperlimit
order by id, lowerlimit -- if needed
;
Output:
ID LOWERLIMIT UPPERLIMIT FLAG
-- ---------- ---------- ----
2 8 10 y
2 10 12 n
3 3 5 n
3 5 8 y
4 8 9 y
5 11 15 n
6 2 5 n
7 1 5 n
7 5 10 y
7 10 14 n
10 rows selected.
One method is to split this into three overlapping conditions, essentially "before", "during", and "after". Because you want multiple different rows for each existing row, you can do this using union all:
select t.id, t.lowerlimit,
least(tm.lowerlimit, t.upperlimit) as upperlimit,
'n' as overlaps
from t join
t tm
on t.id <> 1 and tm.id = 1 and
t.lowerlimit < tm.lowerlimit
union all
select t.id,
greatest(t.lowerlimit, tm.lowerlimit),
least(t.upperlimit, tm.upperlimit), 'y' as overlaps
from t join
t tm
on t.id <> 1 and tm.id = 1 and
t.lowerlimit <= tm.lowerlimit and
t.upperlimit >= tm.upperlimit
union all
select t.id, greatest(tm.upperlimit, t.upperlimit),
t.upperlimit, 'n'
from t join
t tm
on t.id <> 1 and tm.id = 1 and
t.upperlimit > tm.upperlimit;

where condition scenario in oracle

i have requirement in where condition
if my id is in 1 then it should check id 4,5,6,7 or it should check value which is in id
this id i will pass as parameter to query
select * from table_a where id
Help me in this
You can use the below
select * from individual ip
where (
( :p_prs_nat = 219 and ip.prs_nationality_id in (231,259,343) )
or (:p_prs_nat <> 219 and :p_prs_nat=ip.prs_nationality_id
))
where ip.prs_nationality_id =case when :p_prs_nat in( 219) then it shud check (231,259,343) else :p_prs_nat end how to achieve this functionality
You cannot directly use IN while returning the result in the THEN clause of CASE expression. However, you could first check the condition itself using AND operator and return TRUE whenever it matches.
For example,
SQL> WITH DATA AS
2 (
3 SELECT 1 ID, 'X' STR FROM DUAL UNION ALL
4 SELECT 2 ID, 'A' STR FROM DUAL UNION ALL
5 SELECT 3 ID ,'P' STR FROM DUAL UNION ALL
6 SELECT 4 ID ,'Q' STR FROM DUAL
7 )
8 SELECT *
9 FROM DATA
10 WHERE (
11 CASE
12 WHEN ID = 1
13 AND str IN ('A','Y','Z')
14 THEN 1
15 WHEN ID <> 1
16 THEN 1
17 END ) =1
18 /
ID S
---------- -
2 A
3 P
4 Q
SQL>
So, you did not get the row with ID = 1,since it did not match the condition AND str IN ('A','Y','Z').
If it would match, it will return those matching rows too:
SQL> WITH DATA AS
2 (
3 SELECT 1 ID, 'X' STR FROM DUAL UNION ALL
4 SELECT 2 ID, 'A' STR FROM DUAL UNION ALL
5 SELECT 3 ID ,'P' STR FROM DUAL UNION ALL
6 SELECT 4 ID ,'Q' STR FROM DUAL
7 )
8 SELECT *
9 FROM DATA
10 WHERE (
11 CASE
12 WHEN ID = 1
13 AND str IN ('X','Y','Z')
14 THEN 1
15 WHEN ID <> 1
16 THEN 1
17 END ) =1
18 /
ID S
---------- -
1 X
2 A
3 P
4 Q
SQL>