Sum/divide values from View results - oracle

I have 4 Views with columns and results like this:
___________________________________
| Account_no | Region_code | Total |
|-----------------------------------
|123456789 | 123 | 321,34 |
|212234567 | 543 | 214 |
|076948329 | 100 | 310 |
|093290432 | 320 | 1200,44|
|346574554 | 123 | 542,01 |
|___________________________________|
All these views has calculated column Total, but in order to get a complete result for each account in region I need to calculate Total again, with a formula like this : View1.Total - View2.Total + View3.Total - View4.Total.
Can somebody show me an example on how to achieve this in Oracle ?

Something like this (simplified)?
SQL> with
2 view1 (accno, total) as
3 (select 1, 100 from dual union
4 select 2, 200 from dual
5 ),
6 view2 (accno, total) as
7 (select 1, 10 from dual union
8 select 2, 30 from dual
9 ),
10 view3 (accno, total) as
11 (select 1, 20 from dual union
12 select 2, 40 from dual
13 ),
14 view4 (accno, total) as
15 (select 1, 40 from dual union
16 select 2, 30 from dual
17 )
18 --
19 select v1.accno,
20 (v1.total - v2.total + v3.total - v4.total) total_total
21 from view1 v1 join view2 v2 on v1.accno = v2.accno
22 join view3 v3 on v1.accno = v3.accno
23 join view4 v4 on v1.accno = v4.accno;
ACCNO TOTAL_TOTAL
---------- -----------
1 70
2 180
SQL>

Related

Oracle - Parent - child + fill mising hierarchy levels

I have created my fiddle example here: FIDDLE
Here is also athe code from the fiddle:
CREATE TABLE T1(ID INT, CODE INT, CODE_NAME VARCHAR(100), PARENT_ID INT);
INSERT INTO T1 VALUES(100,1,'LEVEL 1', NULL);
INSERT INTO T1 VALUES(110,11,'LEVEL 2', 100);
INSERT INTO T1 VALUES(120,111,'LEVEL 3', 110);
INSERT INTO T1 VALUES(125,112,'LEVEL 3', 110);
INSERT INTO T1 VALUES(130,1111,'LEVEL 4', 120);
INSERT INTO T1 VALUES(200,2,'LEVEL 1', NULL);
INSERT INTO T1 VALUES(210,21,'LEVEL 2', 200);
INSERT INTO T1 VALUES(300,3,'LEVEL 1', NULL);
I have trouble finding the soultuin how to get from that table this result:
| CODE | CODE NAME | CODE 1 |CODE NAME 1| CODE 2 | CODE NAME 2| CODE 3 | CODE NAME 3 |
+--------+------------+--------+-----------+--------+------------+--------+-------------+
| 1 | LEVEL 1 | 11 | LEVEL 2 | 111 | LEVEL 3 | 1111 | LEVEL 4 |
| 1 | LEVEL 1 | 11 | LEVEL 2 | 112 | LEVEL 3 | 112 | LEVEL 3 |
| 2 | LEVEL 1 | 21 | LEVEL 2 | 21 | LEVEL 2 | 21 | LEVEL 2 |
| 3 | LEVEL 1 | 3 | LEVEL 1 | 3 | LEVEL 1 | 3 | LEVEL 1 |
I have tried something with connect by but that is not what I need(I think)...
The max I will ever have is 4 levels and if there are only two levels in the data then the 3rd and the 4th level should be filled wiht the values of the last existing value. The same rule is valid if there are 3 levels or 1 level.
You can use a recursive sub-query:
WITH hierarchy (
code, code_name,
code1, code_name1,
code2, code_name2,
code3, code_name3,
id, depth
) AS (
SELECT code,
code_name,
CAST(NULL AS INT),
CAST(NULL AS VARCHAR2(100)),
CAST(NULL AS INT),
CAST(NULL AS VARCHAR2(100)),
CAST(NULL AS INT),
CAST(NULL AS VARCHAR2(100)),
id,
1
FROM t1
WHERE parent_id IS NULL
UNION ALL
SELECT h.code,
h.code_name,
CASE depth WHEN 1 THEN COALESCE(t1.code, h.code) ELSE h.code1 END,
CASE depth WHEN 1 THEN COALESCE(t1.code_name, h.code_name) ELSE h.code_name1 END,
CASE depth WHEN 2 THEN COALESCE(t1.code, h.code1) ELSE h.code2 END,
CASE depth WHEN 2 THEN COALESCE(t1.code_name, h.code_name1) ELSE h.code_name2 END,
CASE depth WHEN 3 THEN COALESCE(t1.code, h.code2) ELSE h.code3 END,
CASE depth WHEN 3 THEN COALESCE(t1.code_name, h.code_name2) ELSE h.code_name3 END,
t1.id,
h.depth + 1
FROM hierarchy h
LEFT OUTER JOIN t1
ON (h.id = t1.parent_id)
WHERE depth < 4
)
CYCLE code, depth SET is_cycle TO 1 DEFAULT 0
SELECT code, code_name,
code1, code_name1,
code2, code_name2,
code3, code_name3
FROM hierarchy
WHERE depth = 4;
Which, for the sample data:
CREATE TABLE T1(ID, CODE, CODE_NAME, PARENT_ID) AS
SELECT 100, 1, 'LEVEL 1', NULL FROM DUAL UNION ALL
SELECT 110, 11, 'LEVEL 2', 100 FROM DUAL UNION ALL
SELECT 120, 111, 'LEVEL 3', 110 FROM DUAL UNION ALL
SELECT 130, 1111, 'LEVEL 4', 120 FROM DUAL UNION ALL
SELECT 200, 2, 'LEVEL 1', NULL FROM DUAL UNION ALL
SELECT 210, 21, 'LEVEL 2a', 200 FROM DUAL UNION ALL
SELECT 220, 22, 'LEVEL 2b', 200 FROM DUAL UNION ALL
SELECT 230, 221, 'LEVEL 3', 220 FROM DUAL UNION ALL
SELECT 300, 3, 'LEVEL 1', NULL FROM DUAL;
Outputs:
CODE
CODE_NAME
CODE1
CODE_NAME1
CODE2
CODE_NAME2
CODE3
CODE_NAME3
1
LEVEL 1
11
LEVEL 2
111
LEVEL 3
1111
LEVEL 4
3
LEVEL 1
3
LEVEL 1
3
LEVEL 1
3
LEVEL 1
2
LEVEL 1
21
LEVEL 2a
21
LEVEL 2a
21
LEVEL 2a
2
LEVEL 1
22
LEVEL 2b
221
LEVEL 3
221
LEVEL 3
db<>fiddle here
For sample data you posted:
SQL> select * from t1;
ID CODE CODE_NAME PARENT_ID
---------- ---------- ---------- ----------
100 1 LEVEL 1
110 11 LEVEL 2 100
120 111 LEVEL 3 110
130 1111 LEVEL 4 120
200 2 LEVEL 1
210 21 LEVEL 2 200
6 rows selected.
SQL>
an ugly (and who-knows-how-performant) query that, though, returns desired result is
with temp as
(select id, code, code_name, parent_id, level lvl,
row_number() over (partition by level order by id) rn
from t1
start with parent_id is null
connect by prior id = parent_id
),
a as
(select * from temp where lvl = 1),
b as
(select * from temp where lvl = 2),
c as
(select * from temp where lvl = 3),
d as
(select * from temp where lvl = 4)
select
a.code code1, a.code_name code_name1,
coalesce(b.code, a.code) code2, coalesce(b.code_name, a.code_name) code_name2,
coalesce(c.code, b.code, a.code) code3, coalesce(c.code_name, b.code_name, a.code_name) code_name3,
coalesce(d.code, c.code, b.code, a.code) code4, coalesce(d.code_name, c.code_name, b.code_name, a.code_name) code_name4
from a join b on b.rn = a.rn
left join c on c.rn = b.rn
left join d on d.rn = c.rn;
which results in
CODE1 CODE_NAME1 CODE2 CODE_NAME2 CODE3 CODE_NAME3 CODE4 CODE_NAME4
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1 LEVEL 1 11 LEVEL 2 111 LEVEL 3 1111 LEVEL 4
2 LEVEL 1 21 LEVEL 2 21 LEVEL 2 21 LEVEL 2
What does it do?
temp CTE creates a hierarchy; additionally, row_number function numbers each row within the same level
a, b, c, d CTEs extract values belonging to their own level value (you said there can be up to 4 levels)
finally, coalesce on column names along with outer join do the job
From your example I assume you want to see one row per root key as your example is not realy a tree but a bamboo
If so this is a trivial PIVOT query - unfortunately limited to some level deep (here example for your 4 levels)
with p (ROOT_CODE, CODE, CODE_NAME, ID, PARENT_ID, LVL) as (
select CODE, CODE, CODE_NAME, ID, PARENT_ID, 1 LVL from t1 where PARENT_ID is NULL
union all
select p.ROOT_CODE, c.CODE, c.CODE_NAME, c.ID, c.PARENT_ID, p.LVL+1 from t1 c
join p on c.PARENT_ID = p.ID),
t2 as (
select ROOT_CODE, CODE,CODE_NAME,LVL from p)
select * from t2
PIVOT
(max(CODE) code, max(CODE_NAME) code_name
for LVL in (1 as "LEV1",2 as "LEV2",3 as "LEV3",4 as "LEV4")
);
ROOT_CODE LEV1_CODE LEV1_CODE_ LEV2_CODE LEV2_CODE_ LEV3_CODE LEV3_CODE_ LEV4_CODE LEV4_CODE_
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1 1 LEVEL 1 11 LEVEL 2 111 LEVEL 3 1111 LEVEL 4
2 2 LEVEL 1 21 LEVEL 2
The recursive CTE calculates the ROOT_CODE required for the pivot.
I' leaving as an exercise to fill the not defined levels (with COALESCE) with the previous values as in your example.
In case (as commented) you nedd oner row for each leave key a simple solution based on CONNECT_BY_PATHis possible.
I'm using again *recursive CTEcalculating the path from *root* to the *current node* and finaly filtering in the result the *leaves* (IDthat are notPARENT_ID`)
with p ( CODE, CODE_NAME, ID, PARENT_ID, PATH) as (
select CODE, CODE_NAME, ID, PARENT_ID, to_char(CODE)||'|'||CODE_NAME PATH from t1 where PARENT_ID is NULL
union all
select c.CODE, c.CODE_NAME, c.ID, c.PARENT_ID, p.PATH ||'|'||to_char(c.CODE)||'|'||c.CODE_NAME from t1 c
join p on c.PARENT_ID = p.ID)
select PATH from p
where ID in (select ID from T1 MINUS select PARENT_ID from T1)
order by 1;
The result holds for any level deepness and is concatenated string with delimiter
PATH
----------------------------------------------
1|LEVEL 1|11|LEVEL 2|111|LEVEL 3|1111|LEVEL 4
1|LEVEL 1|11|LEVEL 2|112|LEVEL 3
2|LEVEL 1|21|LEVEL 2
3|LEVEL 1
Use substr instr to extract and coalesce for the default values.
Solution using a hierarchical query - we record the code and code_name paths, then we break them apart. Level is used to decide whether we populate data from the paths or from the leaf node. The solution assumes the codes and code names do not contain the forward-slash character (if they may, use another separator in the paths - perhaps some control character like chr(31), the unit separator character in ASCII and Unicode).
To break apart the paths, I used regexp_substr as it's easier to work with (and, moreover, I assumed all codes and code names are non-null - if they may be null, the solution can be adapted easily). If this proves to be slow, that can be changed to use standard string functions.
with
p (code, code_name, parent_id, lvl, code_pth, code_name_pth) as (
select code, code_name, parent_id, level,
sys_connect_by_path(code, '/') || ',' ,
sys_connect_by_path(code_name, '/') || ','
from t1
where connect_by_isleaf = 1
start with parent_id is null
connect by parent_id = prior id
)
select case when lvl = 1 then code
else to_number(regexp_substr(code_pth, '[^/]+', 1, 1)) end as code,
case when lvl =1 then code_name
else regexp_substr(code_name_pth, '[^/]+', 1, 1) end as code_name,
case when lvl <= 2 then code
else to_number(regexp_substr(code_pth, '[^/]+', 1, 2)) end as code_1,
case when lvl <= 2 then code_name
else regexp_substr(code_name_pth, '[^/]+', 1, 2) end as code_name_1,
case when lvl <= 3 then code
else to_number(regexp_substr(code_pth, '[^/]+', 1, 3)) end as code_2,
case when lvl <= 3 then code_name
else regexp_substr(code_name_pth, '[^/]+', 1, 3) end as code_name_2,
code as code_3,
code_name as code_name_3
from p;

Oracle SQL multi-level pivot groups

EDIT: To those saying this is a clear & obvious "No": Sure, I figured that was the case, and hierarchical headers were beyond the scope of SQL query results. However, apart from some Mysql work, I've just made the jump from an old legacy SQL Server 2000 platform to Oracle 12g, and finding things there that I could never have imagined doing in SS 2000, so I thought I'd ask. I write loads of SQL to feed my presentation layer in a few report creation systems, and so I'm exploring this leap forward in capabilities from SS 2000.
I may be asking too much of the Oracle Pivot function, but this is what I'm trying to do. I can pivot at a single level but I want a hierarchy of column grouping with multiple measures the way you could easily do in a spreadsheet crosstab. Here's sample data & desired output:
select *
from(
select 'A' rws, 'X' cols, 2 v1, 90 v2 from dual union
select 'A' rws, 'Y' cols, 25 v1, 112 v2 from dual union
select 'A' rws, 'Y' cols, 7 v1, 64 v2 from dual union
select 'B' rws, 'X' cols, 4 v1, 117 v2 from dual union
select 'B' rws, 'Y' cols, 46 v1, 32 v2 from dual union
select 'B' rws, 'X' cols, 0 v1, 18 v2 from dual
)
Here is the output I would like:
-----------------------------------------------------------
| A | B |
-----------------------------------------------------------
| X | Y | X | Y |
-----------------------------------------------------------
| v1 | v2 | v1 | v2 | v1 | v2 | v1 | v2 |
-----------------------------------------------------------
| 2 | 90 | 32 | 176 | 4 | 135 | 46 | 32 |
-----------------------------------------------------------
Of course, you can pivot data as you want, but you need to format header yourself, since onviously Oracle return standard table data:
select *
from(
select 'A' rws, 'X' cols, 2 v1, 90 v2 from dual union
select 'A' rws, 'Y' cols, 25 v1, 112 v2 from dual union
select 'A' rws, 'Y' cols, 7 v1, 64 v2 from dual union
select 'B' rws, 'X' cols, 4 v1, 117 v2 from dual union
select 'B' rws, 'Y' cols, 46 v1, 32 v2 from dual union
select 'B' rws, 'X' cols, 0 v1, 18 v2 from dual
) t
pivot
(
max(v1) as v1_,max(v2) as v2_
for (rws,cols) in (
('A','X') as A_X,
('A','Y') as A_Y,
('B','X') as B_X,
('B','Y') as B_Y
)
);
Result:
A_X_V1_ A_X_V2_ A_Y_V1_ A_Y_V2_ B_X_V1_ B_X_V2_ B_Y_V1_ B_Y_V2_
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
2 90 25 112 4 117 46 32
You can use the conditional aggregation as follows:
SQL> SELECT SUM(CASE WHEN RWS = 'A' AND COLS = 'X' THEN V1 END) AS AXV1,
2 SUM(CASE WHEN RWS = 'A' AND COLS = 'X' THEN V2 END) AS AXV2,
3 SUM(CASE WHEN RWS = 'A' AND COLS = 'Y' THEN V1 END) AS AYV1,
4 SUM(CASE WHEN RWS = 'A' AND COLS = 'Y' THEN V2 END) AS AYV2,
5 SUM(CASE WHEN RWS = 'B' AND COLS = 'X' THEN V1 END) AS BXV1,
6 SUM(CASE WHEN RWS = 'B' AND COLS = 'X' THEN V2 END) AS BXV2,
7 SUM(CASE WHEN RWS = 'B' AND COLS = 'Y' THEN V1 END) AS BYV1,
8 SUM(CASE WHEN RWS = 'B' AND COLS = 'Y' THEN V2 END) AS BYV2
9 FROM YOUR_TABLE;
AXV1 AXV2 AYV1 AYV2 BXV1 BXV2 BYV1 BYV2
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
2 90 32 176 4 135 46 32
SQL>
Showing the multi-line headers must be taken care from application side.

How to iterate through dict with multiple columns in PL/SQL to take values from it and inject them into where statement in query

I've got problem as follows:
In oracle database I would like to run query which iterate through some kind of dictionary and take values from it to fill in where statement in query and return set of records for each single query, meaning if dict has 3 rows it should return three sets of record and union them afterwards. I have following tables:
dict(table)
country | filter_value
US | y
GE | N
UK | Y,N
data(table 1)
seller_id | country | flag | type
1 | US | y | w
2 | US | n | w
3 | GE | y | w
4 | GE | n | w
5 | UK | y | w
6 | UK | n | w
7 | PT | y | w
8 | GR | n | w
data(table 2)
seller_id | country | flag | type
19 | US | y | d
28 | US | n | d
33 | GE | y | d
44 | GE | n | d
54 | UK | y | d
66 | UK | n | d
71 | PT | y | d
82 | GR | n | d
and I would like to run something like this:
union_data=empty table
for row in dict:
select * from data(table1) where country=row.country and flag in (row.filter_value)
select * from data(table2) where country=row.country and flag in (row.filter_value)
union results from this both queries is one table meaning this union_data
I'm completely new in PL/SQL and to be honest don't know which functionality would work out the issue.
There are (at least) two options.
The first one is simpler and looks like this: create a function (returns a refcursor) which accepts country, finds filter value and applies both "parameters" to select statement(s):
SQL> create or replace function f_test (par_country in varchar2)
2 return sys_refcursor
3 is
4 l_filter_value dict.filter_value%type;
5 l_rc sys_refcursor;
6 begin
7 select filter_value
8 into l_filter_value
9 from dict
10 where country = par_country;
11
12 open l_rc for
13 select seller_id, country, flag, type
14 from data
15 where country = par_country
16 and flag = l_filter_value
17 union all
18 select seller_id, country, flag, type
19 from data2
20 where country = par_country
21 and flag = l_filter_value;
22
23 return l_rc;
24 end;
25 /
Function created.
Testing:
SQL> select f_test('US') from dual;
F_TEST('US')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
SELLER_ID COUNTRY FLAG TYPE
---------- ---------- ---- ----
1 US Y W
19 US Y D
SQL>
If that's too simple (meaning: you can't/don't want to do it that way), use dynamic SQL. The principle is similar to previous. This example includes "what if DICT table contains both Y,N in filter_value" - in that case, split fetched filter_value to rows (that's what hierarchical subqueries do).
SQL> create or replace function f_test (par_country in varchar2)
2 return sys_refcursor
3 is
4 l_filter_value dict.filter_value%type;
5 l_rc sys_refcursor;
6 begin
7 select filter_value
8 into l_filter_value
9 from dict
10 where country = par_country;
11
12 open l_rc for
13 select seller_id, country, flag, type
14 from data
15 where country = par_country
16 and flag in (select regexp_substr(l_filter_value, '[^,]+', 1, level)
17 from dual
18 connect by level <= regexp_count(l_filter_value, ',') + 1
19 )
20 union all
21 select seller_id, country, flag, type
22 from data2
23 where country = par_country
24 and flag in (select regexp_substr(l_filter_value, '[^,]+', 1, level)
25 from dual
26 connect by level <= regexp_count(l_filter_value, ',') + 1
27 );
28
29 return l_rc;
30 end;
31 /
Function created.
Testing:
SQL> select f_test('US') from dual;
F_TEST('US')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
SELLER_ID COUNTRY FLAG TYPE
---------- ---------- ---- ----
1 US Y W
19 US Y D
SQL> select f_test('UK') from dual;
F_TEST('UK')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
SELLER_ID COUNTRY FLAG TYPE
---------- ---------- ---- ----
5 UK Y W
6 UK N W
54 UK Y D
66 UK N D
SQL>

multiple columns' row split in oracle

I got a single string row split into rows
For example,
A,B,C,D,E
into
A
B
C
D
E
but what I would like to is multiple columns' single row
| A,B,C | H,I,J,K,L | Q,R,X,Y,Z |
into
A | H | Q
B | I | R
C | J | X
| K | Y
| L |
How can I do this in oracle?
You can use hiearchy query as follows:
SQL> WITH DATAA ( D ) AS (
2 SELECT '| A,B,C | H,I,J,K,L | Q,R,X,Y,Z |'
3 FROM DUAL
4 )
5 -- your query starts from here
6 SELECT TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 1), '[^,]+', 1, LEVEL)) AS COL1,
7 TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 2), '[^,]+', 1, LEVEL)) AS COL2,
8 TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 3), '[^,]+', 1, LEVEL)) AS COL3
9 FROM DATAA D
10 CONNECT BY LEVEL <= (
11 SELECT MAX(REGEXP_COUNT((REGEXP_SUBSTR(D.D, '[^|]+', 1, COLUMN_VALUE)), ',')) + 1
12 FROM DATAA D
13 CROSS JOIN TABLE ( CAST(MULTISET(
14 SELECT LEVEL LVL
15 FROM DUAL
16 CONNECT BY LEVEL <= REGEXP_COUNT(D.D, '[^|]+')
17 ) AS SYS.ODCIVARCHAR2LIST) ) LVLS
18 );
COL1 COL2 COL3
------- ----------- -----------
A H Q
B I R
C J X
K Y
L Z
SQL>

Age analysis on excel

I would like to distribute a set of data into a table shown below:
RANGE(days) No of days. Amount
0-30 - 0
31-180 1 4,185.78
181-365 2 74,056.86
366 and above 6 587,198.35
TOTAL 9 665,440.99
SOURCE Data
S/N START DATE Details Tran Amt END DATE
1 22/05/2015 A 448,749.14 30/06/2018
2 22/09/2015 B 4,883.02 30/06/2018
3 04/11/2015 C 45,646.27 30/06/2018
4 26/04/2016 D 42,861.99 30/06/2018
5 16/06/2016 E 23,144.23 30/06/2018
6 27/07/2016 F 21,913.70 30/06/2018
7 11/08/2017 G 61,396.94 30/06/2018
8 30/11/2017 H 12,659.92 30/06/2018
9 19/03/2018 I 4,185.78 30/06/2018
TOTAL 665,440.99
Thanks
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE data ( SN, START_DATE, Details, TranAmt ) AS
SELECT 1, DATE '2015-05-22', 'A', 448749.14 FROM DUAL UNION ALL
SELECT 2, DATE '2015-09-22', 'B', 4883.02 FROM DUAL UNION ALL
SELECT 3, DATE '2015-11-04', 'C', 45646.27 FROM DUAL UNION ALL
SELECT 4, DATE '2016-04-26', 'D', 42861.99 FROM DUAL UNION ALL
SELECT 5, DATE '2016-06-16', 'E', 23144.23 FROM DUAL UNION ALL
SELECT 6, DATE '2016-07-27', 'F', 21913.70 FROM DUAL UNION ALL
SELECT 7, DATE '2017-08-11', 'G', 61396.94 FROM DUAL UNION ALL
SELECT 8, DATE '2017-11-30', 'H', 12659.92 FROM DUAL UNION ALL
SELECT 9, DATE '2018-03-19', 'I', 4185.78 FROM DUAL;
Query 1:
WITH end_date ( end_date ) AS (
SELECT DATE '2018-06-30' FROM DUAL
),
ranges ( first, last ) AS (
SELECT 0, 30 FROM DUAL UNION ALL
SELECT 31, 180 FROM DUAL UNION ALL
SELECT 181, 365 FROM DUAL UNION ALL
SELECT 366, NULL FROM DUAL
)
SELECT NVL2(
r.first,
r.first || NVL2(
MAX( r.last ),
' - ' || MAX( r.last ),
' and above'
),
'Total'
) AS range,
COUNT( d.sn ) AS "No of days",
COALESCE( SUM( d.TranAmt ), 0 ) AS Amount
FROM data d
CROSS JOIN end_date e
RIGHT OUTER JOIN ranges r
ON ( d.start_date < e.end_date + 1 - r.first
AND ( r.last IS NULL
OR d.start_date >= e.end_date - r.last ) )
GROUP BY ROLLUP( r.first )
ORDER BY r.first
Results:
| RANGE | No of days | AMOUNT |
|---------------|------------|-----------|
| 0 - 30 | 0 | 0 |
| 31 - 180 | 1 | 4185.78 |
| 181 - 365 | 2 | 74056.86 |
| 366 and above | 6 | 587198.35 |
| Total | 9 | 665440.99 |

Resources