How to do a Range Bucket on a Count returned by a Group By query - oracle

I was hoping if you can help me.
I am in a situation where I first need to do a Count Distinct of Funds and Group By Policies. Once I have done that, I need to put the count of policies in Range of Number of Funds.
This is the data available, where you can see the policies and different funds linked to it
**PolicyNum Fund**
1201 AB
1202 AC
1203 AB
1203 AC
1203 AD
1204 AB
1204 BC
1204 AC
1204 AD
1204 AE
1204 AF
Now I need to do a Count Distinct of Fund Grouped by Policy.
I have used this query to do that:
select fv, policy, count(distinct fv.fund)
from policy_fund fv
group by fv.policy
order by count(distinct fv.fund) desc
After using the above code, the following would come up
This is a view where you can see the number of funds linked to each policy
**Policy No. of Funds**
1201 1
1202 1
1203 3
1204 6
Now, the problem part, I want to reach to this, which is the Range of Number of Funds and how many policies fall under that range of funds:
Help required to achieve this view of Range of Number of Funds and how many policies are present in each range
**Range of Number of funds Number of policies**
0 to 1 2
2 to 3 1
4 to 5 0
5 to 6 1

You can left join your query to a derived table defining the ranges on the count being in the range and then group by the range and count the policies.
SELECT r.l || ' to ' || r.u "RANGE",
count(p) "COUNT"
FROM (SELECT 0 l,
1 u
FROM dual
UNION ALL
SELECT 2 l,
3 u
FROM dual
UNION ALL
SELECT 4 l,
5 u
FROM dual
UNION ALL
SELECT 6 l,
7 u
FROM dual) r
LEFT JOIN (SELECT fv.policy p,
count(distinct fv.fund) cof
FROM policy_fund fv
GROUP BY fv.policy) fpp
ON fpp.cof >= r.l
AND fpp.cof <= r.u
GROUP BY r.l,
r.u
ORDER BY r.l,
r.u;
db<>fiddle

Related

is it uniform distribution HBC ORACLE HISTOGRAME?

We have this data : Table R(A,..) with attribute A, nbLine of R is 1000, distinct value for A are 500.
data are displayed like this : bucket -> end_point_value.
1 -> 800
2 -> 900
3 -> 1000
4 -> 1200
5 -> 1500
6 -> 2000
7 -> 2300
8 -> 2400
9 -> 2550
10 -> 2590
the question is : Does this histogram confirm or deny the hypothesis of a uniform distribution uniform?
I think I can not confirm nor deny, what do you think ?
First you must ask, which histogram type is defined on the column.
Oracle provides four different histogram types and is you want to claim about uniform distribution the frequency histogram must be defined.
The frequency histogram has one bucket for each distinct value (stored in ENDPOINT_VALUE and the frequency is (additive) stored in the column ENDPOINT_NUMBER)
So if you histogram has only 10 buckets (as you show in the data) you are ready and you can say nothing about the distribution.
Example of a Uniform Distribution
create table r as
select
1 + trunc((rownum-1)/2) A
from dual connect by level <= 1000;
select count(*), count(distinct a), min(a), max(a) from r;
COUNT(*) COUNT(DISTINCTA) MIN(A) MAX(A)
---------- ---------------- ---------- ----------
1000 500 1 500
Create FREQUENCY Histogram with 500 Buckets
exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'R', method_opt=>'for all columns size 500');
select NUM_BUCKETS, HISTOGRAM from user_tab_columns where table_name = 'R';
NUM_BUCKETS HISTOGRAM
----------- ---------------
500 FREQUENCY
select ENDPOINT_VALUE, ENDPOINT_NUMBER from user_histograms where table_name = 'R' order by ENDPOINT_VALUE;
ENDPOINT_VALUE, ENDPOINT_NUMBER
1 2
2 4
3 6
4 8
...
498 996
499 998
500 1000

Is it possible to add distinct to part of a sum clause in Oracle?

I have a pretty lengthy SQL query which I'm going to run on Oracle via hibernate. It consists of two nested selects. In the first select statement, a number of sums are calculated, but in one of them I want to filter the results using unique ids.
SELECT ...
SUM(NVL(CASE WHEN SECOND_STATUS= 50 OR SECOND_STATUS IS NULL THEN RECEIVE_AMOUNT END, 0) +
NVL(CASE WHEN FIRST_STATUS = 1010 THEN AMOUNT END, 0) +
NVL(CASE WHEN FIRST_STATUS = 1030 THEN AMOUNT END, 0) -
NVL(CASE WHEN FIRST_STATUS = 1010 AND (SECOND_STATUS= 50 OR SECOND_STATUS IS NULL) THEN RECEIVE_AMOUNT END, 0)) TOTAL, ...
And at the end:
... FROM (SELECT s.*, p.* FROM FIRST_TABLE s
JOIN SECOND_TABLE p ON s.ID = p.FIRST_ID
In one of the lines that start with NVL (second line actually), I want to add a distinct clause that sums the amounts only if first table ids are unique. But I don't know if this is possible or not. If yes, how would it be?
Assume such setup
select * from first;
ID AMOUNT
---------- ----------
1 10
2 20
select * from second;
SECOND_ID FIRST_ID AMOUNT2
---------- ---------- ----------
1 1 100
2 1 100
3 2 100
After the join you get the total sum of both amounts too high because the amount from the first table is duplicated.
select *
from first
join second on first.id = second.first_id;
ID AMOUNT SECOND_ID FIRST_ID AMOUNT2
---------- ---------- ---------- ---------- ----------
1 10 1 1 100
1 10 2 1 100
2 20 3 2 100
You must add a row_number that identifies the first occurence in the parent table and consider in the AMOUNT only the first row and resets it to NULL in the duplicated rows.
select ID,
case when row_number() over (partition by id order by second_id) = 1 then AMOUNT end as AMOUNT,
SECOND_ID, FIRST_ID, AMOUNT2
from first
join second on first.id = second.first_id;
ID AMOUNT SECOND_ID FIRST_ID AMOUNT2
---------- ---------- ---------- ---------- ----------
1 10 1 1 100
1 2 1 100
2 20 3 2 100
Now you can safely sum in a separate subquery
with tab as (
select ID,
case when row_number() over (partition by id order by second_id) = 1 then AMOUNT end as AMOUNT,
SECOND_ID, FIRST_ID, AMOUNT2
from first
join second on first.id = second.first_id
)
select id, sum(nvl(amount,0) + nvl(amount2,0))
from tab
group by id
;
ID SUM(NVL(AMOUNT,0)+NVL(AMOUNT2,0))
---------- ---------------------------------
1 210
2 120
Note also that this is an answer to your question. Some will argue that in your situation you should first aggregate and than join. This will also resolve your problem possible more elegantly.

How can I query to get the rows according to the certain column's value in oracle? [duplicate]

This question already has an answer here:
how to duplicate my sql results? [duplicate]
(1 answer)
Closed 2 years ago.
Table A is:
--------------
C1 C2
--------------
A 3
B 2
--------------
select * from
(
select 'A' as C1, 3 as C2 from dual
union all
select 'B' as C1, 2 as C2 from dual
)
I want to get the following result view with one query statement:
--------------
C1 N1
--------------
A 1
A 2
A 3
B 1
B 2
--------------
I need to generate rows as many as C2 value
Is this possible?
Thank you.
We can handle this via the use of a calendar/sequence table. Consider:
WITH nums AS (
SELECT 1 AS val FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
)
SELECT
a.C1,
n.val AS N1
FROM TableA a
INNER JOIN nums n
ON n.val <= a.C2
ORDER BY
a.C1,
n.val;
Demo
Note that in practice, you might use a dedicated table containing a sequence of numbers to cover all possible values in your table. Or, you might use an Oracle sequence.
Alternatively:
SQL> with test as
2 (select 'A' as C1, 3 as C2 from dual
3 union all
4 select 'B' as C1, 2 as C2 from dual
5 )
6 select c1, column_value n1
7 from test cross join table(cast(multiset(select level from dual
8 connect by level <= c2
9 ) as sys.odcinumberlist))
10 order by c1, column_value;
C N1
- ----------
A 1
A 2
A 3
B 1
B 2
SQL>

sum of column based on distinct value of other column in Oracle

Table A
A1 A2
1 7
2 8
1 9
Table B
A1 B2
1 2
2 3
i want something like this
select A.A1,sum(case when distinct A.A1 then B2),sum(A.A2) from
A,B
where A.A1=B.A1(+)
group by A.A1
After joining my table will be
A1 A2 B2
1 7 2
2 8 3
1 9 2
Resulting Table
A1 A2 B2
1 7+9 2(only once)
2 8 3
how to get sum of B2 when distinct A1 after joining the tables as stated above.
Thanks in advance
Use JOIN and GROUP BY.
Query
SELECT t1.A1, SUM(t1.A2) AS A1, SUM(t2.B2) AS B2
FROM TableA t1
JOIN TableB t2
ON t1.A1 = t2.A1
GROUP BY t1.A1;
Since table_b.a1 is unique, the best way to do this would be to work out the sum of table_a.a2 first to reduce the number of rows you're joining against, and then join to table_b. Then you don't need to worry about summing the distinct table_b.b2 values, which you would otherwise have to do.
WITH table_a AS (SELECT 1 a1, 7 a2 FROM dual UNION ALL
SELECT 2 a1, 8 a2 FROM dual UNION ALL
SELECT 1 a1, 9 a2 FROM dual),
table_b AS (SELECT 1 a1, 2 b2 FROM dual UNION ALL
SELECT 2 a1, 3 b2 FROM dual)
-- end of mimicking your two tables with sample_data in them;
-- see the sql below:
SELECT ta.a1,
ta.a2,
tb.b2
FROM (SELECT a1, SUM(a2) a2
FROM table_a
GROUP BY a1) ta
INNER JOIN table_b tb ON ta.a1 = tb.a1;
A1 A2 B2
---------- ---------- ----------
1 16 2
2 8 3
If you absolutely must join the two tables first (I don't recommend; this is making more work for the database to do), then you could do something like:
WITH table_a AS (SELECT 1 a1, 7 a2 FROM dual UNION ALL
SELECT 2 a1, 8 a2 FROM dual UNION ALL
SELECT 1 a1, 9 a2 FROM dual),
table_b AS (SELECT 1 a1, 2 b2 FROM dual UNION ALL
SELECT 2 a1, 3 b2 FROM dual)
SELECT ta.a1,
SUM(ta.a2) a2,
MAX(tb.b2) b2
FROM table_a ta
INNER JOIN table_b tb ON ta.a1 = tb.a1
GROUP BY ta.a1;
A1 A2 B2
---------- ---------- ----------
1 16 2
2 8 3
Since there can only be one distinct value for table_b.b2 per table_a.a1, we can just pick one of the values to use via MAX (we could have used MIN or SUM(distinct tb.b2) instead, fyi).

Order by query results in specific row positions

This is an extension to a previous quesiton, Order by depending on 2 col values. I have a query which returns the output as below,
SELECT DISTINCT a.rev_date ,fruitname,
fruit_id , primary_fruit_id
FROM fruits a, fruit_lookup s,fruit_reference r
WHERE a.id = s.id(+)
and primary_fruit_id = r.fruit_id(+)
AND (fruit_id = 24 or fruit_id = 0)
ORDER BY case when fruit_id = primary_fruit_id then 0 else 1 end,
fruit_id desc,
a.rev_date desc
How to handle this such that I get the desired output in the below fashion. So, where ever the fruit_id is 0, those rows need to go in the 2nd and the 5th row in the result set of say 10 rows. If totals results are around 50, each page will have 10 results, and in 2nd and the 5th position I need to get the output as below. Hope this makes sense. Any ideas are appreciated. thx
NAME FRUIT_ID PRIMARY_FRUIT_ID
--------------------------------------
apple 24 24
apple 24 24
apple 24 24
apple 24 24
orange 24 12
pear 24 7
kiwi 24 6
melon 24 2
grape 0 90
banana 0 45
carrot 0 30
Desired output
NAME FRUIT_ID PRIMARY_FRUIT_ID
--------------------------------------
apple 24 24
grape 0 90
apple 24 24
apple 24 24
banana 0 45
apple 24 24
kiwi 24 6
orange 24 12
melon 24 2
pear 24 7
carrot 0 30
This is much more complicated question than your previous, so answer is somewhat complicated too.
Here is something what I managed to do:
with t as (
select x.*,
row_number() over (
partition by fid order by decode(fid , pfid, 1, 2), rd desc) rbr
from (
select distinct a.rev_date rd, s.fruitname fn,
a.fruit_id fid, primary_fruit_id pfid
from fruits a left join fruit_lookup s on a.id = s.id
left join fruit_reference r
on primary_fruit_id = r.fruit_id and r.fruit_id in (0, 24) ) x),
ca as (select count(1) cnt from t),
cx as (
select row_number() over (partition by cwm order by lvl) rn, cwm, lvl
from (
select level lvl, case when mod(level, 10) in (2, 5) then 0 else 24 end cwm
from ca connect by level <= cnt*5))
select rd, fn, fid, pfid
from t join cx on cx.rn = t.rbr and cx.cwm = t.fid
order by lvl
You did not provide data structures and sample rows, so I tried to reproduce them to get original input, here is SQLFiddle with data and query.
Subquery cx generates numbers, according to your rules, then these numbers are assigned to your original query
and final select sorts data using these numbers.
The general logic is: insert rows with fruit_id=0 numbered as 2, 5, 12, 15, 22... between other rows.

Resources