Value Calculation on oracle sql - sql

I have this table:
CREATE TABLE TEST
(
TITLE VARCHAR2(199 BYTE),
AMOUNT NUMBER,
VALUE NUMBER
)
and this INSERT statement:
INSERT INTO TEST (TITLE, AMOUNT, VAL)
VALUES (Switch, 3000, 12);
COMMIT;
We have an amount = 3000 up to 12, now we need to calculate.
So
3000 multiplied by 1 = 3000
3000 multiplied by 2 = 6000
3000 multiplied by 3 = 9000
3000 multiplied by 4 = 12000
3000 multiplied by 5 = 15000
3000 multiplied by 6 = 18000
3000 multiplied by 7 = 21000
3000 multiplied by 8 = 24000
3000 multiplied by 9 = 27000
3000 multiplied by 10 = 30000
3000 multiplied by 11 = 33000
3000 multiplied by 12 = 36000
Regards
Output is needed in the following format.
Title Amount Total
Switch 30000 3000 6000 9000 12000 15000 18000 21000 24000 27000 30000 33000 36000 231000
plug
board
Can somebody help me how to get this output in SQL?

You can use a recursive query:
WITH data (title, amount, value, idx) AS (
SELECT title, amount, value, 1
FROM test
UNION ALL
SELECT title, amount, value, idx + 1
FROM data
WHERE idx < value
) SEARCH DEPTH FIRST BY title SET order_num
SELECT title, amount * idx AS value
FROM data;
Or a correlated hierarchical query:
SELECT t.title, t.amount * l.idx AS value
FROM test t
CROSS JOIN LATERAL (
SELECT LEVEL AS idx FROM DUAL CONNECT BY LEVEL <= t.value
) l;
Which, for the sample data:
CREATE TABLE TEST ( TITLE VARCHAR2(199 BYTE), AMOUNT NUMBER, VALUE NUMBER )
INSERT INTO TEST ( TITLE, AMOUNT, VALUE ) VALUES ( 'Switch', 3000, 12);
Both output:
TITLE
VALUE
Switch
3000
Switch
6000
Switch
9000
Switch
12000
Switch
15000
Switch
18000
Switch
21000
Switch
24000
Switch
27000
Switch
30000
Switch
33000
Switch
36000
fiddle
Or for your output format:
WITH data (title, amount, value, idx) AS (
SELECT title, amount, value, 1
FROM test
UNION ALL
SELECT title, amount, value, idx + 1
FROM data
WHERE idx < value
) SEARCH DEPTH FIRST BY title SET order_num
SELECT title,
LISTAGG(amount * idx, ' ') WITHIN GROUP (ORDER BY idx) AS amounts,
SUM(amount*idx) AS total
FROM data
GROUP BY title;
or
SELECT t.title,
l.amounts,
t.amount * t.value * (t.value + 1) / 2 AS total
FROM test t
CROSS JOIN LATERAL (
SELECT LISTAGG(LEVEL * t.amount, ' ') WITHIN GROUP (ORDER BY LEVEL) AS amounts
FROM DUAL CONNECT BY LEVEL <= t.value
) l;
Which both output:
TITLE
AMOUNTS
TOTAL
Switch
3000 6000 9000 12000 15000 18000 21000 24000 27000 30000 33000 36000
234000
fiddle

Try it like here:
WITH
tbl AS
(
Select 'Switch' "TITLE", 3000 "AMOUNT", 12 "VAL" From Dual
)
--
Select TITLE, AMOUNT, TOTAL
From (Select LEVEL "ID", TITLE "TITLE", Sum(AMOUNT * LEVEL) OVER() "TOTAL",
LISTAGG(AMOUNT * LEVEL, ' ') WITHIN GROUP (ORDER BY LEVEL) OVER() "AMOUNT"
From tbl
Connect By LEVEL <= VAL )
Where ID = 1
--
-- R e s u l t :
-- TITLE AMOUNT TOTAL
-- ------- --------------------------------------------------------------------- ------
-- Switch 3000 6000 9000 12000 15000 18000 21000 24000 27000 30000 33000 36000 234000

Dear Respectful Experts
Thanks for your Cooperation,
I have oracle 10 G which do not support LISTAGG, what i need to do in 10G,
Thanks
Regards

Related

How to split data in ratio using SQL

Suppose we have a table with columns X and Y where Y is the total count of values present in X.
Column X
Column Y
3000
23
8000
50
4000
20
9000
70
5000
64
How to split the data with 8:1:1 ratio of column Y.
Example: Y is 23. Therefore 8:1:1 of Y will be nearly 18,2,3. There will be 18 rows for train, 3 rows for test and 2 rows for val.
Similarly 8:1:1 of 64 will be 51,7 and 6.
Expected output table is like this:
Column X
Column Y
Column Z
3000
1
Train
3000
.
Train
3000
.
Train
3000
18
Train
3000
1
Test
3000
.
Test
3000
3
Test
3000
1
Val
3000
2
Val
8000
1
Train
8000
.
Train
8000
40
Train
8000
1
Test
8000
.
Test
8000
5
Test
8000
1
Val
8000
.
Val
8000
5
Val
4000
1
Train
4000
.
Train
4000
.
Train
4000
16
Train
4000
1
Test
4000
2
Test
4000
1
Val
4000
2
Val
5000
1
Train
5000
.
Train
5000
51
Train
5000
1
Test
5000
.
Test
5000
.
Test
5000
7
Test
5000
1
Val
5000
.
Val
5000
6
Val
To summarize, I want to split the all rows in train, test, val set in proportion of 8:1:1 using column Y value.
I tried using Pandas for similar task, but unable to do it in SQL
Here's a brute force method that will work. I had to make an assumption on what you mean by 8:1:1, but result in integers:
Val8 = FLOOR(ColumnY * 0.8)
Val1a = FLOOR(ColumnY * 0.1)
Val1b = ColumnY - Val8 - Val1a
So, you may need to adjust if you have clearer requirements.
The code below is written for SQL Server. Among other possible differences, other RDBMSs (like Oracle) may require WITH RECURSIVE for the recursive CTE to process without error.
with a as (
select *
from (
values (1, 3000, 23)
, (2, 8000, 50)
, (3, 4000, 20)
, (4, 9000, 70)
, (5, 5000, 64)
) t (id, ColumnX, ColumnY)
),
b as (
select id
, 'Train' as ColumnZ
, ColumnX
, cast(ColumnY * 0.8 as int) as ColumnY
from a
union all
select id
, 'Test' as ColumnZ
, ColumnX
, cast(ColumnY * 0.1 as int) as ColumnY
from a
),
c as (
select b.id
, 'Val' as ColumnZ
, b.ColumnX
, a.ColumnY - sum(b.ColumnY) as ColumnY
from b
inner join a on a.ColumnX = b.ColumnX
group by b.id
, b.ColumnX
, a.ColumnY
),
iterator as (
select 1 as ColumnY
, max(ColumnY) as m
from c
union all
select ColumnY + 1
, m
from iterator
where ColumnY <= m
)
select b.ColumnX
, i.ColumnY
, b.ColumnZ
from b
inner join iterator i on i.ColumnY <= b.ColumnY
union all
select c.ColumnX
, i.ColumnY
, c.ColumnZ
from c
inner join iterator i on i.ColumnY <= c.ColumnY
order by 1, 3, 2

An integer 1000 in a column which to be divide by example 100, and then split into multiple rows accordingly

Oracle SQL
Example 950/100
The table should able to populate as below
100
100
100
100
100
100
100
100
100
50
Had try CONNECT BY LEVEL , however it seems to be taking too long with huge data stored tables. Any other alternatives way instead of this ?
Thanks !
You can use a recursive sub-query factoring clause:
WITH data ( rn, value, remainder, divisor ) AS (
SELECT ROWNUM, value, value, divisor FROM table_name
UNION ALL
SELECT rn, value, remainder - divisor, divisor FROM data WHERE remainder > divisor
)
SEARCH DEPTH FIRST BY rn SET rn_order
SELECT value, divisor, LEAST( remainder, divisor ) AS split_value
FROM data;
Or, from Oracle 12, a hierarchical query:
SELECT *
FROM table_name t
CROSS APPLY (
SELECT LEAST( t.divisor, t.value - (LEVEL - 1) * t.divisor ) AS split_value
FROM DUAL
CONNECT BY LEVEL <= CEIL(t.value/t.divisor)
)
Which, for the sample data:
CREATE TABLE table_name ( value, divisor ) AS
SELECT 950, 100 FROM DUAL UNION ALL
SELECT 2.4, 0.5 FROM DUAL
Both output:
VALUE
DIVISOR
SPLIT_VALUE
950
100
100
950
100
100
950
100
100
950
100
100
950
100
100
950
100
100
950
100
100
950
100
100
950
100
100
950
100
50
2.4
0.5
0.5
2.4
0.5
0.5
2.4
0.5
0.5
2.4
0.5
0.5
2.4
0.5
0.4
db<>fiddle here
One approach which is worth a try is to determine the highest number of rows you may need for any input value, then to generate rows (with connect by or by any other means) just once, and then use a join to this "helper" table to generate the rows. This approach reads the base data twice, but I assume that's not the big bottleneck in this problem. In any case - worth a try.
Note that the solution also minimizes the number of arithmetic operations - it compares "row number" to the ratio of value to divisor for each row, but otherwise the output value is just the divisor in most cases.
with
h (rn) as (
select level
from dual
connect by level <= (select max(ceil(value/divisor)) from table_name)
)
select tn.id, tn.value, tn.divisor, h.rn,
case when h.rn <= tn.value/tn.divisor
then tn.divisor else mod(tn.value, tn.divisor) end as split_value
from table_name tn join h on h.rn <= ceil(tn.value/tn.divisor)
order by tn.id, h.rn
;
Which, for the data below, produces the output shown at the end. Note that I assumed there is an id column too, as primary key; in the query, I only need that to get a proper order by clause. If ordering is not needed, you don't need such an id (although you probably have it already, as an invoice number, loan number, or similar). You can also use rowid instead of id, if the only use is in the order by clause.
Sample data:
create table table_name (id, value, divisor) as
select 2301, 450, 100 from dual union all
select 2302, 2.3, 0.5 from dual union all
select 2303, 300, 100 from dual union all
select 2304, 8, 20 from dual union all
select 2305, 150, 150 from dual
;
Output from the query, using this sample data:
ID VALUE DIVISOR RN SPLIT_VALUE
---------- ---------- ---------- ---------- -----------
2301 450 100 1 100
2301 450 100 2 100
2301 450 100 3 100
2301 450 100 4 100
2301 450 100 5 50
2302 2.3 .5 1 .5
2302 2.3 .5 2 .5
2302 2.3 .5 3 .5
2302 2.3 .5 4 .5
2302 2.3 .5 5 .3
2303 300 100 1 100
2303 300 100 2 100
2303 300 100 3 100
2304 8 20 1 8
2305 150 150 1 150

How to transpose data to hive

This Is My hive table:
dept_id emp_cnt emp_cnt_prev_yr sales_cnt sales_cnt_prev_yr
1 10 8 10000 5000
2 15 9 20000 12000
3 12 10 30000 15000
4 6 12 40000 20000
I want to store data into another hive table like below:
dept_id metric_nm metric_val metric_val_prev-yr
1 emp_cnt 10 8
2 emp_cnt 15 9
3 emp_cnt 12 10
4 emp_cnt 6 12
1 sales_cnt 10000 5000
2 sales_cnt 20000 12000
3 sales_cnt 30000 15000
4 sales_cnt 40000 20000
What I tried so far:
SELECT dept_id,
metric_nm,
Substr(metrics,1,Locate('#',metrics,1)-1) AS metric_val,
Substr(metrics,Locate('#',metrics,1)+1) AS metric_val_prev_yr
FROM (
SELECT dept_id,
Map('emp_cnt', Concat(emp_cnt,'#',emp_cnt_prev_yr),'sales_cnt', Concat(sales_cnt,'#',sales_cnt_prev_yr)) AS metrc
FROM <TABLE>) a lateral VIEW explode(metric) ext AS metric_nm,
metrics;
Use UNION ALL to combine two metric_nm datasets into single one:
insert overwrite table table_name
select dept_id, 'emp_cnt' as metric_nm,
emp_cnt as metric_val, emp_cnt_prev_yr as metric_val_prev_yr
from your_table
UNION ALL
select dept_id, 'sales_cnt' as metric_nm,
sales_cnt as metric_val, sales_cnt_prev_yr as metric_val_prev_yr
from your_table;
One more approach (cross join with stack metric_nm to multiply rows x number of metric_nm values ), this CROSS JOIN will be executed as map-join:
--configuration
set hive.cli.print.header=true;
set hive.execution.engine=tez;
set hive.mapred.reduce.tasks.speculative.execution=false;
set mapred.reduce.tasks.speculative.execution=false;
set hive.exec.parallel=true;
set hive.exec.parallel.thread.number=36;
set hive.vectorized.execution.enabled=true;
set hive.vectorized.execution.reduce.enabled=true;
set hive.auto.convert.join=true; --this enables map-join
select dept_id, s.metric_nm,
case s.metric_nm when 'emp_cnt' then emp_cnt
when 'sales_cnt' then sales_cnt
--add more cases
end as metric_val,
case s.metric_nm when 'emp_cnt' then emp_cnt_prev_yr
when 'sales_cnt' then sales_cnt_prev_yr
--add more cases
end as metric_val_prev_yr
from your_table
cross join
(select stack (2, --number of values, add more
'sales_cnt',
'emp_cnt'
--add more values
) as metric_nm
)s

Teradata query pagination - batch every 1000 records

I've this Teradata query:
WITH ID(ROW_NUM) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY PRSN_ID) AS ROW_NUM
FROM MyTable
WHERE ACTIVE_IND = 'Y'
GROUP BY PRSN_ID
)
SELECT ROW_NUM-ROW_NUM MOD 2 AS FirstIndex,
ROW_NUM-(ROW_NUM-1) MOD 2 AS SecondIndex
FROM ID
WHERE ROW_NUM MOD 2=1
This query will generate an ID column, and the result will be something like this:
FirstIndex SecondIndex
0 1
2 3
4 5
. .
. .
etc etc
I would like to change the selection to be a batch through a 1000 records like below:
FirstIndex SecondIndex
0 1000
1001 2000
2001 3000
3001 4000
. .
. .
etc etc
Your help is appreciated.
Your calculation is way to complex:
ROW_NUM-1 AS FirstIndex,
ROW_NUM AS SecondIndex
To get your requested range you simply need to modify the calculation, e.g.
SELECT
CASE WHEN ROW_NUM = 1 THEN 0 ELSE SecondIndex-999 END AS FirstIndex,
(ROW_NUM+2)/2 * 1000 AS SecondIndex
FROM ID
WHERE ROW_NUM MOD 2=1
But what do you want to do with that result, batch what?
Edit:
It's still unclear why you need a 2nd table to calculate the ranges, but this creates ranges starting from 1 for any pagesize of n rows:
WITH ID(ROW_NUM) AS
( -- just to get some rows
SELECT day_of_calendar AS row_num
FROM sys_calendar.CALENDAR
WHERE row_num BETWEEN 1 AND 10
)
SELECT 1000 AS RowsPerPage,
Row_Num AS page_num,
rownum_to - (RowsPerPage-1) AS rownum_from,
page_num * RowsPerPage AS rownum_to
FROM ID
GROUP BY page_num
ORDER BY page_num
RowsPerPage page_num rownum_from rownum_to
1000 1 1 1000
1000 2 1001 2000
1000 3 2001 3000
1000 4 3001 4000
1000 5 4001 5000
1000 6 5001 6000
1000 7 6001 7000
1000 8 7001 8000
1000 9 8001 9000
1000 10 9001 10000
Hi you can try the below query once, this will give you the desired sequence.
select ROW_NUM,FirstIndex, coalesce(case when SecondIndex<1000 then (SecondIndex+2000) else (SecondIndex+1000) end,FirstIndex+1001) as SecondIndex from
(SELECT ROW_NUM,
case when (FirstIndex < 1000) then (FirstIndex+1001) else (FirstIndex+1) end as FirstIndex,
(min(FirstIndex) OVER(ORDER BY FirstIndex ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING)) SecondIndex
FROM (Select (ROW_NUMBER() OVER (ORDER BY PRSN_ID)-1) AS ROW_NUM,(ROW_NUMBER() OVER (ORDER BY PRSN_ID)-1) AS FirstIndex from Mytable GROUP BY PRSN_ID) A) B
order by ROW_NUM;

SQL - Select number of record based on SUM conditions

if you have a order table, is that possible to select all the records which have sum of amount smaller than $1000, assuming that the table already ordered by amount desc
Example:
id product_id amount
1 1 500
2 3 400
3 2 300
4 1 200
Get all the orders which have sum of amount smaller than $1000
then it should return 1 & 2, because the sum of amount of 1 and 2 is 900 < 1000
Get all the orders which have sum of amount smaller than $1300
then it should return 1,2,3 because the sum of amount of 1,2,3 is 1200 < 1300
You are looking for a cumulative sum:
select t.*
from (select t.*, sum(t.amount) over (order by t.amount desc) as cume_amount
from t
) t
where cume_amount < 1000;
with a as (
select sum(amount) over (order by amount desc ) as amount_1 from test_order
)
select * from a where amount_1 < 1000;