Oracle - Rank and transform on columns - sql

I got a sample data as below in this cte_orders. Each row is a order and contains the user_id, zone_code and zone_name.
I need to rank the top 3 zone_code for each user_id, and I need the output be on columns.
Like this
|user_id | top1_zone | top2_zone | top3_zone |
|--------+-------------+-------------+-------------+
|1000 | 5555-ABCD | 4567-ZMNY | 7888-IXPO |
|--------+-------------+-------------+-------------+
|9999 | 3456-JJKL | 7688-HBGT | 5555-ABCD |
|--------+-------------+-------------+-------------+
Here's the SQL with the test data and the query that I'm trying.
The problem with this query is the result is given a row for each position of the rank.
(
SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
)
select
user_id,
CASE WHEN rank_zones = 1 then zone_concat end as top1_zone,
CASE WHEN rank_zones = 2 then zone_concat end as top2_zone,
CASE WHEN rank_zones = 3 then zone_concat end as top3_zone
from
(
select
user_id,
zone_code||'-'||zone_name as zone_concat,
row_number() over (partition by user_id order by count(*) desc) rank_zones
from cte_orders
group by
user_id,
zone_code||'-'||zone_name
)
where rank_zones <= 3
group by
user_id,
CASE WHEN rank_zones = 1 then zone_concat end,
CASE WHEN rank_zones = 2 then zone_concat end,
CASE WHEN rank_zones = 3 then zone_concat end
order by user_id;
The output that i getting
|user_id | top1_zone | top2_zone | top3_zone |
|--------+-------------+-------------+-------------+
|1000 | 5555-ABCD | (null) | (null) |
|--------+-------------+-------------+-------------+
|1000 | (null) | 4567-ZMNY | (null) |
|--------+-------------+-------------+-------------+
|1000 | (null) | (null) | 7888-IXPO |
|--------+-------------+-------------+-------------+
|9999 | 3456-JJKL | (null) | (null) |
|--------+-------------+-------------+-------------+
|9999 | (null) | 7688-HBGT | (null) |
|--------+-------------+-------------+-------------+
|9999 | (null) | (null) | 5555-ABCD |
|--------+-------------+-------------+-------------+
How can i fix my query to get one row for each user_id without the nulls?

You can use oracle pivot as below:
with cte_orders as(
SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
),
cte as(select
user_id,
zone_code||'-'||zone_name as zone_concat,
row_number() over (partition by user_id order by count(*) desc ) rank_zones
from cte_orders
group by
user_id,
zone_code||'-'||zone_name)
select user_id,"1" top1_zone, "2" top2_zone, "3" top3_zone
from cte
PIVOT (
MAX(ZONE_CONCAT) FOR rank_zones IN (1,2,3)
)
Output:
USER_ID
TOP1_ZONE
TOP2_ZONE
TOP3_ZONE
1000
5555-ABCD
4567-ZMNY
5599-HZTR
9999
3456-JJKL
5555-ABCD
7688-HBGT
db<>fiddle here
Or you can use decode in aggregation with group by:
with cte_orders as(
SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
),
cte as(select
user_id,
zone_code||'-'||zone_name as zone_concat,
row_number() over (partition by user_id order by count(*) desc ) rank_zones
from cte_orders
group by
user_id,
zone_code||'-'||zone_name)
select user_id,
max(decode(rank_zones,1,ZONE_CONCAT)) top1_zone,
max(decode(rank_zones,2,ZONE_CONCAT)) top2_zone,
max(decode(rank_zones,3,ZONE_CONCAT)) top3_zone
from cte
group by user_id
Output:
USER_ID
TOP1_ZONE
TOP2_ZONE
TOP3_ZONE
1000
5555-ABCD
4567-ZMNY
5599-HZTR
9999
3456-JJKL
5555-ABCD
7688-HBGT
db<>fiddle here

How can i fix my query to get one row for each user_id without the nulls?
With a little bit of aggregation (and comments, as its consequence).
Sample data:
SQL> with cte_orders as (
2 SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
3 SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
4 SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
5 SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
6 SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
7 SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
8 SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
9 SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
10 SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
11 SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
12 SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
13 SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
14 SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
15 SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
16 SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
17 SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
18 SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
19 SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
20 SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
21 SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
22 SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
23 SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
24 SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
25 )
This is what makes what you ask for (the MAX aggregate function; lines #28 - 30):
26 select
27 user_id,
28 max(CASE WHEN rank_zones = 1 then zone_concat end) as top1_zone,
29 max(CASE WHEN rank_zones = 2 then zone_concat end) as top2_zone,
30 max(CASE WHEN rank_zones = 3 then zone_concat end) as top3_zone
31 from
32 (
33 select
34 user_id,
35 zone_code||'-'||zone_name as zone_concat,
36 row_number() over (partition by user_id order by count(*) desc) rank_zones
37 from cte_orders
38 group by
39 user_id,
40 zone_code||'-'||zone_name
41 )
42 where rank_zones <= 5
Comment CASE expressions (lines #45 - 47):
43 group by
44 user_id
45 -- CASE WHEN rank_zones = 1 then zone_concat end,
46 -- CASE WHEN rank_zones = 2 then zone_concat end,
47 -- CASE WHEN rank_zones = 3 then zone_concat end
48 order by user_id;
And the result is then:
USER TOP1_ZONE TOP2_ZONE TOP3_ZONE
---- --------- --------- ---------
1000 5555-ABCD 4567-ZMNY 5599-HZTR
9999 3456-JJKL 5555-ABCD 7688-HBGT
SQL>

Related

Oracle SQL - How to compare the number of rows between 2 sets in the same table?

in the table security_privileges I want to compare the number of rows for the last version_number with the number of rows for version_number BEFORE last_update = 01.03.2020.
I highlighted the two sets of rows that the result should identify. In the first set, there are 8 rows, in the second set there are 6 rows.
The output of what I expect from this example is right at the bottom.
Below I tried this sequel, but it didn't really work:
SELECT
cur.user_id,
cur.version_number,
cur.last_update,
(cur.last_update- prv.last_update)
FROM
security_privileges prv
INNER JOIN security_privileges cur
ON cur.version_number = prv.version_number +2
[![output][3]][3]
You can use window functions and conditional aggregation. The inner query calculates the maximum version number overall and before the specified date:
select user_id, max_vn, max_vn_pre,
sum(case when version_number = max_vn then 1 else 0 end) as max_vn_cnt,
sum(case when version_number = max_vn_pre then 1 else 0 end) as max_vn_pre_cnt
from (select sp.*,
max(version_number) over (partition by user_id) as max_vn,
max(case when last_update < date '2020-03-01' then version_number end) over (partition by user_id) as max_vn_pre
from security_privileges sp
) sp
group by user_id, max_vn, max_vn_pre;
This assumes that you want the results per user_id. If not, just remove the partition by in the two windowing clauses.
I would use a bit different approach, but still with window analytic functions:
at first, I'd filtered only rows you want, using dense_rank with partition by user_id, case when last_update>=date'2020-03-01' then 1 else 2 end, so this dense_rank will return 1 for all required rows and we can easily filter then using drnk=1:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=b6925860bfa16d2f222d428f508c1b50
select *
from
(
SELECT
prv.*
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1;
Results:
USER_ID SECURITY_PRIVILEGE_ID VERSION_NUMBER LAST_USER_UPDATE_ID LAST_UPDATE DRNK
---------- --------------------- -------------- ------------------- ------------------- ----------
9867 20011 16 9954 2020-08-31 00:00:00 1
9867 20059 16 9955 2020-08-31 00:00:00 1
9867 20003 16 9956 2020-08-31 00:00:00 1
9867 20069 16 9957 2020-08-31 00:00:00 1
9867 20004 16 9958 2020-08-31 00:00:00 1
9867 20046 16 9959 2020-08-31 00:00:00 1
9867 20003 14 9832 2017-06-28 00:00:00 1
9867 20059 14 9833 2017-06-28 00:00:00 1
9867 20046 14 9834 2017-06-28 00:00:00 1
9867 20004 14 9835 2017-06-28 00:00:00 1
9867 20045 14 9836 2017-06-28 00:00:00 1
9867 20002 14 9837 2017-06-28 00:00:00 1
9867 20011 14 9838 2017-06-28 00:00:00 1
9867 20069 14 9839 2017-06-28 00:00:00 1
14 rows selected.
Second step even easier: we just need to aggregate those rows:
either using conditional aggregation: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=6467247bfde1dd978da4ce51067e3d70
select
user_id,
min(version_number) version_number1,
max(version_number) version_number2,
count(decode(grp,1,0)) cnt1,
count(decode(grp,2,0)) cnt2,
listagg(decode(grp,1,security_privilege_id), ',')
within group(order by security_privilege_id) sec_priv_ids1,
listagg(decode(grp,2,security_privilege_id), ',')
within group(order by security_privilege_id) sec_priv_ids2
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id;
Results:
USER_ID VERSION_NUMBER1 VERSION_NUMBER2 CNT1 CNT2 SEC_PRIV_IDS1 SEC_PRIV_IDS2
---------- --------------- --------------- ---------- ---------- ---------------------------------------- --------------------------------------------------
9867 14 16 6 8 20003,20004,20011,20046,20059,20069 20002,20003,20004,20011,20045,20046,20059,20069
or standard aggregation as your last screenshot shows: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=1eb736011e32754dc444c120946e8bea
select
user_id,
grp,
version_number,
last_update,
count(*) cnt,
listagg(security_privilege_id, ',')
within group(order by security_privilege_id) sec_priv_ids
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id,grp,version_number,last_update
order by 1,2;
Results:
USER_ID GRP VERSION_NUMBER LAST_UPDATE CNT SEC_PRIV_IDS
---------- ---------- -------------- ------------------- ---------- --------------------------------------------------
9867 1 16 2020-08-31 00:00:00 6 20003,20004,20011,20046,20059,20069
9867 2 14 2017-06-28 00:00:00 8 20002,20003,20004,20011,20045,20046,20059,20069
Full test case with sample data in CTE:
with security_privileges(user_id, security_privilege_id, version_number, last_user_update_id, last_update) as (
select 9867, 20011, 16, 9954, date'2020-08-31' from dual union all
select 9867, 20059, 16, 9955, date'2020-08-31' from dual union all
select 9867, 20003, 16, 9956, date'2020-08-31' from dual union all
select 9867, 20069, 16, 9957, date'2020-08-31' from dual union all
select 9867, 20004, 16, 9958, date'2020-08-31' from dual union all
select 9867, 20046, 16, 9959, date'2020-08-31' from dual union all
select 9867, 20011, 15, 9960, date'2020-08-13' from dual union all
select 9867, 20059, 15, 9961, date'2020-08-13' from dual union all
select 9867, 20004, 15, 9962, date'2020-08-13' from dual union all
select 9867, 20003, 15, 9963, date'2020-08-13' from dual union all
select 9867, 20046, 15, 9964, date'2020-08-13' from dual union all
select 9867, 20003, 14, 9832, date'2017-06-28' from dual union all
select 9867, 20059, 14, 9833, date'2017-06-28' from dual union all
select 9867, 20046, 14, 9834, date'2017-06-28' from dual union all
select 9867, 20004, 14, 9835, date'2017-06-28' from dual union all
select 9867, 20045, 14, 9836, date'2017-06-28' from dual union all
select 9867, 20002, 14, 9837, date'2017-06-28' from dual union all
select 9867, 20011, 14, 9838, date'2017-06-28' from dual union all
select 9867, 20069, 14, 9839, date'2017-06-28' from dual union all
select 9867, 20059, 13, 9840, date'2017-06-21' from dual union all
select 9867, 20011, 13, 9841, date'2017-06-21' from dual union all
select 9867, 20045, 13, 9842, date'2017-06-21' from dual union all
select 9867, 20003, 13, 9843, date'2017-06-21' from dual union all
select 9867, 20046, 13, 9844, date'2017-06-21' from dual union all
select 9867, 20002, 13, 9845, date'2017-06-21' from dual union all
select 9867, 20069, 13, 9846, date'2017-06-21' from dual union all
select 9867, 20069, 12, 9127, date'2017-06-02' from dual union all
select 9867, 20046, 12, 9128, date'2017-06-02' from dual union all
select 9867, 20003, 12, 9127, date'2017-06-02' from dual union all
select 9867, 20059, 12, 9128, date'2017-06-02' from dual union all
select 9867, 20011, 12, 9128, date'2017-06-02' from dual
)
select
user_id,
grp,
version_number,
last_update,
count(*) cnt,
listagg(security_privilege_id, ',')
within group(order by security_privilege_id) sec_priv_ids
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id,grp,version_number,last_update
order by 1,2;

Oracle SQL to find population by percentage range

I have a table with customers, purchase date and zip code. Key is (customer_id, purchase_dt and zip_cd)
I am trying to find zip codes where customers are doing business, ranges like 80% and above, 60 - 80%, 40-60%. Can someone help me out with a query to achieve this.
with tmp as
(
select 123 as cust_id, date '2017-01-01' purchase_dt, '10035' zip_cd from dual
union
select 1234 as cust_id, date '2019-06-01' purchase_dt, '11377' zip_cd from dual
union
select 12345 as cust_id, date '2019-07-01' purchase_dt, '11377' zip_cd from dual
union
select 234 as cust_id, date '2019-08-01' purchase_dt, '11377' zip_cd from dual
union
select 2345 as cust_id, date '2019-09-01' purchase_dt, '11417' zip_cd from dual
)
select * from tmp;
Expected output:
80% and above zip code: 11377 and so on..
You can use the combination of the average and analytical function count as follows:
with tmp as
(
select 123 as cust_id, date '2017-01-01' purchase_dt, '10035' zip_cd from dual
union
select 1234 as cust_id, date '2019-06-01' purchase_dt, '11377' zip_cd from dual
union
select 12345 as cust_id, date '2019-07-01' purchase_dt, '11377' zip_cd from dual
union
select 234 as cust_id, date '2019-08-01' purchase_dt, '11377' zip_cd from dual
union
select 2345 as cust_id, date '2019-09-01' purchase_dt, '11417' zip_cd from dual
)
select zip_cd, 100*(count(1)/cnt) percntg from
(select zip_cd, count(1) over () cnt from tmp)
group by zip_cd, cnt
order by percntg desc;
This answer will take into account if a customer has made purchases on multiple days and won't double count them. Additionally, this response adds in the grouping discussed in the question:
with tmp as
(
select 123 as cust_id, date '2017-01-01' purchase_dt, '10035' zip_cd from dual
union
select 1234 as cust_id, date '2019-06-01' purchase_dt, '11377' zip_cd from dual
union
select 12345 as cust_id, date '2019-07-01' purchase_dt, '11377' zip_cd from dual
union
select 234 as cust_id, date '2019-08-01' purchase_dt, '11377' zip_cd from dual
union
select 2345 as cust_id, date '2019-09-01' purchase_dt, '11417' zip_cd from dual
)
SELECT sub2.pct_range, listagg(sub2.zip_cd||' ('||sub2.zip_pct||')', ', ') WITHIN GROUP (ORDER BY zip_pct DESC) AS ZIP_CODES
FROM (SELECT CASE
WHEN sub.zip_pct BETWEEN 80 AND 100 THEN '80% and above'
WHEN sub.zip_pct BETWEEN 60 AND 79 THEN '60% to 79%'
WHEN sub.zip_pct BETWEEN 40 AND 59 THEN '40% to 59%'
WHEN sub.zip_pct BETWEEN 20 AND 39 THEN '20% to 39%'
ELSE 'Below 20%'
END AS PCT_RANGE,
sub.zip_cd,
sub.zip_pct
FROM (SELECT DISTINCT
zip_cd,
100*COUNT(DISTINCT cust_id) OVER (PARTITION BY zip_cd)/COUNT(DISTINCT cust_id) OVER () AS ZIP_PCT
FROM tmp) sub) sub2
GROUP BY pct_range
ORDER BY pct_range DESC;

oracle query to retrieve the date of the highest value which are grouped by month and year

Data looks similar to this..
Here I need to retrieve the highest amount by grouping them by month and year but I couldn't retrieve the exact date of the highest amount....
The code I tried for the highest amount is ...
SELECT ACCT_ID AS ACCOUNT_ID,
COUNT(TRANS_AMOUNT) AS NUMBER_OF_TRANSACTION,
Sum(Trans_Amount) As Transaction_Amount,
To_Char( Trans_Date, 'MON-YYYY') As Month_Date,
Max(Trans_Amount) As Highest
From Trans_Amt
group by ACCT_ID, To_Char( Trans_Date, 'MON-YYYY');
And this worked but I could not retrieve the date here. If I try for the date I get "not a group by variable" error ...
Group by account and month(I used trunc at month level).
Select max(amount) for amount and use keep dense_rank first to get the date of maximum amount:
SELECT
ACCT_ID AS ACCOUNT_ID,
trunc(Trans_Date, 'MM') As Month_Date,
max(trans_date) keep (dense_rank first order by trans_amount desc) as trans_date
Max(Trans_Amount) As Highest
FROM Trans_Amt
GROUP BY
ACCT_ID, trunc(Trans_Date, 'MM');
with t as (
select 1000 ACCT_ID, to_date('11-JAN-2000', 'dd-MM-yyyy') TRANS_DATE, 201 TRANS_AMOUNT from dual union all
select 1000, to_date('22-JAN-2000', 'dd-MM-yyyy'), 209 from dual union all
select 1000, to_date('31-JAN-2000', 'dd-MM-yyyy'), 4504 from dual union all
select 1000, to_date('10-FEB-2000', 'dd-MM-yyyy'), 487 from dual union all
select 1001, to_date('10-FEB-2000', 'dd-MM-yyyy'), 4287 from dual union all
select 1001, to_date('17-FEB-2000', 'dd-MM-yyyy'), 4501 from dual union all
select 1001, to_date('22-FEB-2000', 'dd-MM-yyyy'), 1209 from dual union all
select 1000, to_date('22-FEB-2000', 'dd-MM-yyyy'), 4550 from dual union all
select 1001, to_date('23-FEB-2000', 'dd-MM-yyyy'), 120 from dual union all
select 1001, to_date('26-FEB-2000', 'dd-MM-yyyy'), 245 from dual union all
select 1000, to_date('28-FEB-2000', 'dd-MM-yyyy'), 4500 from dual union all
select 1000, to_date('08-MAR-2000', 'dd-MM-yyyy'), 256 from dual union all
select 1001, to_date('08-MAR-2000', 'dd-MM-yyyy'), 2561 from dual union all
select 1000, to_date('24-MAR-2000', 'dd-MM-yyyy'), 987 from dual union all
select 1001, to_date('24-MAR-2000', 'dd-MM-yyyy'), 75000 from dual union all
select 1000, to_date('31-MAR-2000', 'dd-MM-yyyy'), 1100 from dual union all
select 1001, to_date('31-MAR-2000', 'dd-MM-yyyy'), 11000 from dual union all
select 1001, to_date('04-APR-2000', 'dd-MM-yyyy'), 4287 from dual union all
select 1000, to_date('04-APR-2000', 'dd-MM-yyyy'), 487 from dual union all
select 1001, to_date('12-APR-2000', 'dd-MM-yyyy'), 1209 from dual union all
select 1001, to_date('14-APR-2000', 'dd-MM-yyyy'), 1092 from dual union all
select 1001, to_date('20-APR-2000', 'dd-MM-yyyy'), 1245 from dual union all
select 1000, to_date('20-APR-2000', 'dd-MM-yyyy'), 7500 from dual union all
select 1000, to_date('22-APR-2000', 'dd-MM-yyyy'), 1205 from dual union all
select 1000, to_date('26-APR-2000', 'dd-MM-yyyy'), 245 from dual
)
select
*
from
t t
where
not exists(
select
*
from
t n
where
trunc(t.TRANS_DATE, 'mm') = trunc(n.TRANS_DATE, 'mm')
and n.TRANS_AMOUNT > t.TRANS_AMOUNT
and t.ACCT_ID = n.ACCT_ID
)
order by
t.TRANS_DATE, t.ACCT_ID
Output

How to get correct summaries with analytics?

I want to get summary numbers from the cust_detail table if a specific invoice_code appears in invoice_detail.
In this example, I'd like to report cust_detail summaries only for batches 10 and 20 because they are the ones with invoice_code='9999'. But the duplication in the invoice_detail table is skewing my numbers.
with
invoice_detail as
(
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '20' as invoice_batch, '1111' as invoice_code from dual union all
select '30' as invoice_batch, '9999' as invoice_code from dual
),
cust_detail as
(
select '1' as cust_id, '10' as invoice_batch, 40 as points_paid, 30 as points_earned, 30 as points_delivered from dual union all
select '1' as cust_id, '20' as invoice_batch, 10 as points_paid, 10 as points_earned, 10 as points_delivered from dual union all
select '1' as cust_id, '30' as invoice_batch, 20 as points_paid, 15 as points_earned, 5 as points_delivered from dual
)
select cust_id,
sum(points_paid) over (partition by c.invoice_batch
order by cust_id) batch_total
from cust_detail c
inner join invoice_detail i on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999';
Desired results:
CUST_ID PAID EARNED DELIVERED TOT_PAID TOT_EARNED TOT_DELIVERED
--------- ------ -------- ----------- ---------- ------------ ---------------
1 40 30 30 60 45 40
1 20 15 5 60 45 40
You can remove duplications from invoice_detail with distinct before join:
with invoice_detail as
(
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '20' as invoice_batch, '1111' as invoice_code from dual union all
select '30' as invoice_batch, '9999' as invoice_code from dual
),
cust_detail as
(
select '1' as cust_id, '10' as invoice_batch, 40 as points_paid, 30 as points_earned, 30 as points_delivered from dual union all
select '1' as cust_id, '20' as invoice_batch, 10 as points_paid, 10 as points_earned, 10 as points_delivered from dual union all
select '1' as cust_id, '30' as invoice_batch, 20 as points_paid, 15 as points_earned, 5 as points_delivered from dual
)
select cust_id
,points_paid
,points_earned
,points_delivered
,sum(points_paid) over (partition by c.cust_id) as tot_paid
,sum(points_earned) over (partition by c.cust_id) as tot_earned
,sum(points_delivered) over (partition by c.cust_id) as tot_delivered
from cust_detail c
join (select distinct * from invoice_detail) i
on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999';
Note that summaries include batches 10 and 30 because batch 20 with invoice_code='1111'.
SQL Fiddle
I am not sure what your desired results have to do with your query. But, I would expect your query to look more like this:
select cust_id,
sum(points_paid) over (partition by cust_id) as batch_total
from cust_detail c inner join
invoice_detail i
on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999' ;

How to get summary based on another table group summary?

Business Rules: Get total cust_points for customer only if total(trans_amount) for a trans_code grouping > 0.
For customer #1, the summary at date_code level (code 10) is > 0 so cust_points total = 70.
For customer #2 only code 20 group totals > 0 so total only 75 total cust_points
Here's my query:
with customers as
(select '1' as cust_id, 10 as date_code, 30 as cust_points from dual union all
select '1' as cust_id, 10 as date_code, 40 as cust_points from dual union all
select '1' as cust_id, 20 as date_code, 22 as cust_points from dual union all --These points should not total because trans_amount sum for code 20 is less than 0
select '1' as cust_id, 40 as date_code, 33 as cust_points from dual union all -- These points should not total because there is not trans_amounts > 0 for date_code
select '2' as cust_id, 10 as date_code, 20 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 65 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 10 as cust_points from dual
),
transactions_row as
(
select '1' as cust_id, '10' as trans_code, 10.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -15.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -10.00 as trans_amount from dual union all
select '1' as cust_id, '30' as trans_code, 30.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '2' as cust_id, '10' as trans_code, -50.00 as trans_amount from dual union all
select '2' as cust_id, '20' as trans_code, 20.00 as trans_amount from dual
)
select cust_id,
sum(cust_points)
from customers
where cust_id in
(
select cust_id
from (
select cust_id, trans_code, sum(trans_amount)
from transactions_row
group by cust_id, trans_code
having sum(trans_amount) > 0
)
)
group by cust_id
Desired Results
CUST_ID CUST_POINTS
1 70 /* (30 because total trans_amount for tran_code(10) > 0 +
40 because total trans_amount for tran_code(10) > 0) */
2 75 /* Do not include the 20 points because total trans_amt for 10 < 0 */
Here's one way using exists:
select cust_id,
sum(cust_points)
from customers c
where exists (
select 1
from transactions_row tr
where tr.trans_code = c.date_code
and tr.cust_id = c.cust_id
group by tr.trans_code, tr.cust_id
having sum(tr.trans_amount) > 0
)
group by cust_id
SQL Fiddle Demo