Oracle: Analytical functions Sub totals after each change in value - sql

I have the following data (order of records as in the example):
A B
1 10
1 20
1 30
1 40
2 50
2 65
2 75
1 89
1 100
from SQL:
with x as (
select A, B
from (
select 1 as A, 10 as B from dual
union all
select 1 as A, 20 as B from dual
union all
select 1 as A, 30 as B from dual
union all
select 1 as A, 40 as B from dual
union all
select 2 as A, 50 as B from dual
union all
select 2 as A, 65 as B from dual
union all
select 2 as A, 75 as B from dual
union all
select 1 as A, 89 as B from dual
union all
select 1 as A, 100 as B from dual
)
)
select A, B
from X
I want to group the data for each change of value in column A,
I want to get the following result:
A MIN(B) MAX(B)
1 10 40
2 50 75
1 89 100
How to get such a result in the ORACLE 11. I would expect a simple implementation...

This is a gaps and islands problem, solved using row_number analytic function
SELECT a,
MIN(b),
MAX(b)
FROM (
SELECT x.*,
ROW_NUMBER() OVER(
ORDER BY b
) - ROW_NUMBER() OVER(
PARTITION BY a
ORDER BY b
) AS seq
FROM x
)
GROUP BY a,
seq;
Demo

Related

First negative number row in Oracle

How can I find the first row where the negative value starts in Oracle? Below is an example.
77
74
67
56
42
24
20
19
-17
-28
-31
-36
I would like to read the row -17 and do some operations on that row.
Any help is greatly appreciated. Thanks
If you want the complete row, you can use:
select t.*
from t
where n < 0
order by n asc
fetch first 1 row only;
If you have another column that specifies the ordering of the rows, then:
select t.*
from t
where n < 0
order by <ordering col> asc
fetch first 1 row only;
To select the maximum negative number you can do:
select max(n)
from t
where n < 0
Remember that in relational database tables, rows do not have inherent ordering. Therefore, in the absence of an ordering criteria, there's no such a thing as "first row where the negative value starts".
Assuming you have a column for sorting, which defines the order of the rows, it could look like this:
with t as (
select 77 a, 1 row_order from dual union all
select 74 a, 2 row_order from dual union all
select 67 a, 3 row_order from dual union all
select 56 a, 4 row_order from dual union all
select 42 a, 5 row_order from dual union all
select 24 a, 6 row_order from dual union all
select 20 a, 7 row_order from dual union all
select 19 a, 8 row_order from dual union all
select -17 a, 9 row_order from dual union all
select -28 a, 10 row_order from dual union all
select -31 a, 11 row_order from dual union all
select -36 a, 12 row_order from dual
), t1 as (
select a, row_number() over (partition by case when a < 0 then 0 else 1 end order by row_order) rn from t
)
select * from t1 where rn = 1 and a < 0;
It's using a window function in order to determine the first rows (here for positive a's and negative a's)
Then it selects the first row encountered that is negative.

Oracle-Making condition optional in Select statement

How to make a condition optional?
That is, if a condition of the where clause is FALSE, how to ignore the condition in a simplest way?
The expected result of my code is,
RR
--
100
200
My Code is,
WITH DATASET AS (
SELECT 1 A, 10 B, 100 RR FROM DUAL
UNION
SELECT NULL A, 10 B, 200 RR FROM DUAL
)
SELECT RR
FROM DATASET
WHERE
A=2 -- How to make this condition optional(that is, as 2 is not found then,ignoring this condition)
AND
B=10
;
I understood that if not exists 2 in all data you do not want to filter but if there is a 2 in A column then you want to it.
so try this:
SOLUTION 1:
SELECT * FROM
(
WITH DATASET AS (
SELECT 1 A, 10 B, 100 RR FROM DUAL
UNION
SELECT NULL A, 10 B, 200 RR FROM DUAL
)
SELECT
RR
FROM
DATASET
WHERE B=10
AND EXISTS
(
SELECT 1 FROM DATASET WHERE A=2
)
)
UNION
SELECT * FROM
(
WITH DATASET AS (
SELECT 1 A, 10 B, 100 RR FROM DUAL
UNION
SELECT NULL A, 10 B, 200 RR FROM DUAL
)
SELECT
RR
FROM
DATASET
WHERE B=10
AND NOT EXISTS
(
SELECT 1 FROM DATASET WHERE A=2
)
)
SOLUTION2 :
WITH DATASET AS (
SELECT 1 A, 10 B, 100 RR FROM DUAL
UNION
SELECT NULL A, 10 B, 200 RR FROM DUAL
)
,SUB AS (
SELECT DISTINCT 1 A_EXISTS,RR
FROM DATASET
WHERE A=2
),
SUB2 AS (
SELECT DISTINCT
CASE
WHEN SUB.A_EXISTS IS NOT NULL AND DATASET.A=2
THEN SUB.RR
WHEN SUB.A_EXISTS IS NOT NULL AND DATASET.A<>2
THEN NULL
ELSE DATASET.RR
END RR
FROM
DATASET
FULL OUTER JOIN SUB ON A_EXISTS=1
WHERE DATASET.B=10
)
SELECT
*
FROM SUB2
WHERE RR IS NOT NULL

How to combine and count two columns in oracle?

My table seems like this;
A B
1 100
1 102
1 105
2 100
2 105
3 100
3 102
I want output like this:
A Count(B)
1 3
1,2 2
1,2,3 3
2 2
3 2
2,3 2
How can i do this?
I try to use listagg but it didn't work.
I suspect that you want to count the number of sets of A that are in the data -- and that your sample results are messed up.
If so:
select grp, count(*)
from (select listagg(a, ',') within group (order by a) as grp
from t
group by b
) b;
This gives you the counts for the full combinations present in the data. The results would be:
1,2,3 1
1,3 1
1,2 1
You can get the original number of rows by doing:
select grp, sum(cnt)
from (select listagg(a, ',') within group (order by a) as grp, count(*) as cnt
from t
group by b
) b;
Oracle Setup:
CREATE TABLE table_name ( A, B ) AS
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 1, 102 FROM DUAL UNION ALL
SELECT 1, 105 FROM DUAL UNION ALL
SELECT 2, 100 FROM DUAL UNION ALL
SELECT 2, 105 FROM DUAL UNION ALL
SELECT 3, 100 FROM DUAL UNION ALL
SELECT 3, 102 FROM DUAL;
Query:
SELECT A,
COUNT(B)
FROM (
SELECT SUBSTR( SYS_CONNECT_BY_PATH( A, ',' ), 2 ) AS A,
B
FROM table_name
CONNECT BY PRIOR B = B
AND PRIOR A + 1 = A
)
GROUP BY A
ORDER BY A;
Output:
A COUNT(B)
----- ----------
1 3
1,2 2
1,2,3 1
2 2
2,3 1
3 2

Oracle advanced union

Is there any advanced Oracle SQL methods to solve this kind of situation?
Simplified:
Two queries returns primary_key_value and other_value.
Both queries always return primary_key_value but other_value might be null.
So how I can union those two queries so that it returns always those rows which has other_value, but if both queries are having other_value = null with same primary key, then only one row should be returned.
I know this is so stupid case. But specifications were like this :)
Example:
First query:
A | B
=======
1 | X
2 |
3 |
4 | Z
Second query:
A | B
=======
1 | Y
2 |
3 | Z
4 |
So result need to be like this:
A | B
=======
1 | X
1 | Y
2 |
3 | Z
4 | Z
You could use analytics:
SQL> WITH q1 AS (
2 SELECT 1 a, 'X' b FROM DUAL UNION ALL
3 SELECT 2 a, '' b FROM DUAL UNION ALL
4 SELECT 3 a, '' b FROM DUAL UNION ALL
5 SELECT 4 a, 'Z' b FROM DUAL
6 ), q2 AS (
7 SELECT 1 a, 'Y' b FROM DUAL UNION ALL
8 SELECT 2 a, '' b FROM DUAL UNION ALL
9 SELECT 3 a, 'Z' b FROM DUAL UNION ALL
10 SELECT 4 a, '' b FROM DUAL
11 )
12 SELECT a, b
13 FROM (SELECT a, b,
14 rank() over(PARTITION BY a
15 ORDER BY decode(b, NULL, 2, 1)) rnk
16 FROM (SELECT * FROM q1
17 UNION
18 SELECT * FROM q2))
19 WHERE rnk = 1;
A B
---------- -
1 X
1 Y
2
3 Z
4 Z
If you want use something really advanced, use model clause http://rwijk.blogspot.com/2007/10/sql-model-clause-tutorial-part-one.html
But, in real life, using such things usually means bad-designed data model
Another way to look at is that you want all possible values from the union of column A then left outer outer join these with the non-null values from column B, thus only showing null in B when there is no non-null value to display.
roughly:
WITH q1 as (whatever),
q2 as (whatever)
SELECT All_A.A, BVals.B
FROM (SELECT DISTINCT A FROM (SELECT A FROM q1 UNION SELECT A FROM q2)) All_A,
(SELECT A,B FROM q1 WHERE B IS NOT NULL
UNION
SELECT A,B FROM q2 WHERE B IS NOT NULL) BVals
WHERE All_A.A = BVals.A (+)
Also pruning the unwanted nulls explicitly could do the same job:
WITH q3 AS (q1_SELECT UNION q2_SELECT)
SELECT A,B
FROM q3 main
WHERE NOT ( B IS NULL AND
EXISTS (SELECT 1 FROM q3 x WHERE main.A = x.A and x.B IS NOT NULL) )

Use a calculated field in the where clause

Is there a way to use a calculated field in the where clause?
I want to do something like
SELECT a, b, a+b as TOTAL FROM (
select 7 as a, 8 as b FROM DUAL
UNION ALL
select 8 as a, 8 as b FROM DUAL
UNION ALL
select 0 as a, 0 as b FROM DUAL
)
WHERE TOTAL <> 0
;
but I get ORA-00904: "TOTAL": invalid identifier.
So I have to use
SELECT a, b, a+b as TOTAL FROM (
select 7 as a, 8 as b FROM DUAL
UNION ALL
select 8 as a, 8 as b FROM DUAL
UNION ALL
select 0 as a, 0 as b FROM DUAL
)
WHERE a+b <> 0
;
Logically, the select clause is one of the last parts of a query evaluated, so the aliases and derived columns are not available. (Except to order by, which logically happens last.)
Using a derived table is away around this:
select *
from (SELECT a, b, a+b as TOTAL FROM (
select 7 as a, 8 as b FROM DUAL
UNION ALL
select 8 as a, 8 as b FROM DUAL
UNION ALL
select 0 as a, 0 as b FROM DUAL)
)
WHERE TOTAL <> 0
;
This will work...
select *
from (SELECT a, b, a+b as TOTAL FROM (
select 7 as a, 8 as b FROM DUAL
UNION ALL
select 8 as a, 8 as b FROM DUAL
UNION ALL
select 0 as a, 0 as b FROM DUAL)
) as Temp
WHERE TOTAL <> 0;