How to recursively compute ratio of remaining amounts based on rounded values from preceding rows? - sql

I need to split 1 amount into 2 fields. I know the total sums of the resulting fields = the ratio to split the first row, but i need to round the resulting sums and only then compute the ratio for next row (so the total sum of the rounded values will be correct).
How can i write this algorithm in Oracle 10g PL/SQL? I need to test some migrated data. Here is what i came up with (so far):
with temp as (
select 1 id, 200 amount, 642 total_a from dual union all
select 2, 200, 642 from dual union all
select 3, 200, 642 from dual union all
select 4, 200, 642 from dual union all
select 5, 200, 642 from dual
)
select
temp2.*,
remaining_a / remaining_amount ratio,
round(amount * remaining_a / remaining_amount, 0) rounded_a,
round(amount - amount * remaining_a / remaining_amount, 0) rounded_b
from (
select
temp.id,
temp.amount,
sum(amount) over (
order by id
range between current row and unbounded following
) remaining_amount,
case when id=1 then total_a /* else ??? */ end remaining_a
from temp
) temp2
Update: If you can't see the image above, expected rounded_A values are:
1 128
2 129
3 128
4 129
5 128

Here is my suggestion. It is not getting exactly what you want . . . by my calculation the 129 doesn't come until the 3rd row.
The idea is to add more columns. For each row, calculate the estimated split. Then, keep track of the accumulative fraction. When the cum remainder exceeds an integer, then bump up the A amount by 1. Once you have the A amount, you can calculate the rest:
WITH temp AS (
SELECT 1 id, 200 amount, 642 total_a FROM dual UNION ALL
SELECT 2, 200, 642 FROM dual UNION ALL
SELECT 3, 200, 642 FROM dual UNION ALL
SELECT 4, 200, 642 FROM dual UNION ALL
SELECT 5, 200, 642 FROM dual
)
select temp3.*,
sum(estArem) over (order by id) as cumrem,
trunc(estA) + (case when trunc(sum(estArem) over (order by id)) > trunc(- estArem + sum(estArem) over (order by id))
then 1 else 0 end)
from (SELECT temp2.*,
trunc(Aratio*amount) as estA,
Aratio*amount - trunc(ARatio*amount) as estArem
FROM (SELECT temp.id, temp.amount,
sum(amount) over (ORDER BY id range BETWEEN CURRENT ROW AND unbounded following
) remaining_amount,
sum(amount) over (partition by null) as total_amount,
max(total_a) over (partition by null)as maxA,
(max(total_a) over (partition by null) /
sum(amount) over (partition by null)
) as ARatio
FROM temp
) temp2
) temp3
This isn't exactly a partitioning problem. This is an integer approximation problem.
If you are rounding the values rather than truncating them, then you need a slight tweak to the logic.
trunc(estA) + (case when trunc(sum(0.5+estArem) over (order by id)) > trunc(0.5 - estArem + sum(estArem) over (order by id))
This statement was originally just looking for the cumulative remainder passing over the integer threshhold. This should do rounding instead of truncation.

Related

Oracle SQL : Calculating weighted probability

I'm struggling to retrieve a "weighted probability" from a database table in my SQL statement.
What do I need to do:
I have tabular information of probable financial values like:
Table my_table
ID
P [%]
Value [$]
1
50
200
2
50
200
3
60
100
I need to calculate the weighted probability of reasonable worst case financial value to occur.
The formula is:
P_weighted = 1 - (1 - P_1 * Value_1/Max(Value_1-n) * (1 - P_2 * Value_2/Max(Value_1-n) * ...
i.e.
P_weighted = 1 - Product(1 - P_i * Value_i / Max(Value_1-n)
P_weighted = 1 - (1 - 50% * 200 / 200) * (1 - 50% * 200 / 200) * (1 - 60% * 100 / 200) = 82.5%
I know the is not product function in (Oracle) SQL, and this can be substituted by EXP( SUM LN(x))) ensuring x is always positive.
Hence, if I were only to calculate the combined probability I could (regardless of the value I could do like:
SELECT EXP(SUM(LN(1 - t.P))) FROM FROM my_table t WHERE condition
When I need to include the Max(t.Value) I've got the following problem:
A SELECT list cannot include both a group function, such as AVG, COUNT, MAX, MIN, SUM, STDDEV, or VARIANCE, and an individual column expression, unless the individual column expression is included in a GROUP BY clause.
So I tried the following:
SELECT ROUND(1-EXP(SUM(LN(1 - t.P*t.Value/max(t.Value)))),1) FROM FROM my_table t WHERE condition GROUP BY t.P, t.Value
But this does obviously group the output by probability rather than multiplying it and just returns 0.5 or 50% instead of the product which should be 0.825 or 82.5%.
How do I get the weighted probability from by table above using (Oracle) SQL?
Does this do it:
with da as (select .50 as p, 200 as v from dual union all select .50 , 200 from dual union all select .60,100 from dual),
mx as (select max(v) mx from da)
select exp(sum(ln(1-da.p*da.v/mx))) from da, mx;
EXP(SUM(LN(1-DA.P*DA.V/MX)))
----------------------------
.175
with
test1 as(
select max(value) v_max from my_table
),
test2 as(
select 1-(my.p/100* value/t1.v_max) rez
from my_table my, test1 t1
)
select to_char(round((1-(EXP (SUM (LN (rez)))))*100,2))||'%' "Weighted probability"
from test2
RESULT:
Weighted probability
--------------------
82,5%
If you want the calculation per-row then you can use an analytic SUM:
SELECT id,
ROUND(1 - EXP(SUM(LN(1 - wp)) OVER (ORDER BY id)), 3) AS cwp
FROM (
SELECT id,
p * value / MAX(value) OVER () AS wp
FROM table_name
)
Which, for the sample data:
CREATE TABLE table_name (ID, P, Value) AS
SELECT 1, .50, 200 FROM DUAL UNION ALL
SELECT 2, .50, 200 FROM DUAL UNION ALL
SELECT 3, .60, 100 FROM DUAL;
Outputs the cumulative weighted probabilities:
ID
CWP
1
.5
2
.75
3
.825
If you just want the total weighted probability then:
SELECT ROUND(1 - EXP(SUM(LN(1 - wp))), 3) AS twp
FROM (
SELECT id,
p * value / MAX(value) OVER () AS wp
FROM table_name
)
Which, for the sample data, outputs:
TWP
.825
db<>fiddle here

Calculate distance in Bigquery

I'm trying to calculate the distance between sequential points and partitioned by the ID number in BigQuery.
Here's what my table looks like:
OBJECTID ID DateAndTime Lat Long
1 1 2002-11-26T12:00:00 38.82551095 -109.9709871
2 1 2002-11-29T13:00:00 38.541137 -109.677575
3 2 2002-11-03T10:00:00 38.550676 -109.901774
4 2 2002-11-04T10:00:00 38.53689 -109.683531
5 2 2002-11-05T10:00:00 38.45689 -109.683531
Based on the above table, I'd want the query to calculate the distance between ObjectID 1 & 2, and then the distance between ObjectID 3 & 4 and then 4 & 5
Here's a query I've started for ordering by DateAndTime and finding the time difference. In this query I was trying to find time differences over 12hours. Is it similar logic to this? How can I calculate distances between sequenced points in BigQuery?
SELECT *,
DATETIME_DIFF( prev_DateAndTime, DateAndTime, hour) as diff_hours
FROM
(SELECT points.ID, points.DateAndTime,
LAG(DateAndTime) OVER (PARTITION BY points.ID ORDER BY points.DateAndTime) as prev_DateAndTime
FROM `table1` AS table1 INNER JOIN
`table2` AS points ON table1.ID = points.ID
WHERE
(points.DateAndTime BETWEEN table1.BeginDate AND COALESCE (table1.EndDate, CURRENT_DATE() + 1))
And points.DateAndTime between '2020-12-01T00:00:00' and CURRENT_DATE()
) d
WHERE
DATETIME_DIFF(prev_DateAndTime, DateAndTime, hour) > 12
Below example for BigQuery Standard SQL
#standardSQL
with `project.dataset.table` as (
select 1 objectid, 1 id, timestamp '2002-11-26T12:00:00' DateAndTime, 38.82551095 lat, -109.9709871 long union all
select 2, 1, '2002-11-29T13:00:00', 38.541137, -109.677575 union all
select 3, 2, '2002-11-03T10:00:00', 38.550676, -109.901774 union all
select 4, 2, '2002-11-04T10:00:00', 38.53689, -109.683531 union all
select 5, 2, '2002-11-05T10:00:00', 38.45689, -109.683531
)
select *,
objectid as objectid_start,
lead(objectid) over next as objectid_next,
round(st_distance(st_geogpoint(long, lat), lead(st_geogpoint(long, lat)) over next), 2) as distance
from `project.dataset.table`
window next as (partition by id order by DateAndTime)
-- order by id, DateAndTime
with output

to find minimum missing number in oracle

i want to find the minimum missing number of a column named (s_no) and the table named (test_table) in oracle and I write the following code..
select
min_s_no-1+level missing_number
from (
select min(s_no) min_s_no, max(s_no) max_s_no
from test_table
) connect by level <= max_s_no-min_s_no+1
minus
select s_no from test_table
;
it gives me all the missing number as a result. But I want to select the minimum
number. Can any one help me please.
thanks in advance.
Using analytical function LEAD you can get the number from the next row in ascending order. Comparing of this value with with the original number increased by 1 you get the missing values (if two numbers do not match).
To get the first missing value in ascending order is the same selecting the MIN value:
select
num,
lead(num) over (order by num) num_lead,
case when num + 1 != lead(num) over (order by num) then num + 1 end as missing_num
from test_data
order by num;
NUM NUM_LEAD MISSING_NUM
---------- ---------- -----------
4 5
5 6
6 9 7
9 10
10 13 11
13
-- first missing number = MIN missing number
select min(missing_num)
from (
select
case when num + 1 != lead(num) over (order by num) then num + 1 end as missing_num
from test_data
);
MIN(MISSING_NUM)
----------------
7
ADDENDUM
A good practice in writing SQL is to consider edge cases - here a table that contains a complete interval without holes. The first missing value will be the successor of the last number.
select nvl(min(missing_num),max(num)+1) first_missing_value
from (
select
num,
case when num + 1 != lead(num) over (order by num) then num + 1 end as missing_num
from test_data
);
A complete table return no MISSING_NUM, so the original query return NULL. Using the NVL the expected result is provided.
The best way to find the gaps is to use analytic functiions lead or lag. An example with lag:
with test_data as (
select 1 num from dual union all
select 4 from dual union all
select 6 from dual union all
select 8 from dual union all
select 3 from dual union all
select 9 from dual union all
select 0 from dual
)
select min(gap) min_gap
from (
select num, lag(num) over (order by num)+1 gap
from test_data
)
where num != gap
;
MIN_GAP
------------------
2
More about how to find the gaps here
In Oracle 12.1 and above, MATCH_RECOGNIZE can do quick work of this kind of problems:
Edited. Initially I was picking the "next number" where a gap exists (in the example, the value 9). But that is not what the OP wants, he wants the first missing number (7 in this case). I edited to change the measures clause, to find the first missing number as requested. End Edit
with test_data (num) as (
select 4 from dual union all
select 5 from dual union all
select 6 from dual union all
select 9 from dual union all
select 10 from dual union all
select 13 from dual
)
-- end of test data; when you use the SQL query below,
-- replace test_data and num with your actual table and column names.
select result as num
from test_data
match_recognize (
order by num
measures last(b.num) + 1 as result
pattern ( ^ a b* c )
define b as num = prev(num) + 1,
c as num > prev(num) + 1
)
;
NUM
---
7

Remove + - value records in SQL where clause

I need to remove the + - values records mean to say
I need only Blue colored two records from the output windows.
Hope its clear what exactly I want.
User5 | -15
User6 | -10
The idea is to get rows whose second column, in my case it's Val, is are cancelled out. You can do it by getting the absolute value and assign a row number grouped by absolute value and the value itself. Those row number that does not have a match should be the result.
WITH SampleData(UserID, Val) AS(
SELECT 'User1', -10 UNION ALL
SELECT 'User2', 10 UNION ALL
SELECT 'User3', -15 UNION ALL
SELECT 'User4', -10 UNION ALL
SELECT 'User5', -15 UNION ALL
SELECT 'User6', -10 UNION ALL
SELECT 'User7', 10 UNION ALL
SELECT 'User8', 15
)
,Numbered AS(
SELECT
UserID,
Val,
BaseVal = ABS(Val),
RN = ROW_NUMBER() OVER(PARTITION BY ABS(Val), Val ORDER BY UserId)
FROM SampleData
)
SELECT
n1.UserID,
n1.Val
FROM Numbered n1
LEFT JOIN Numbered n2
ON n2.BaseVal = n1.BaseVal
AND n2.RN = n1.rn
AND n2.UserID <> n1.UserID
WHERE n2.UserID IS NULL
ORDER BY n1.UserID
Appears that you want rows where the total does not equal 0?
select
userName,
userValue
from
yourTable
where
userName in (
select userName from yourTable
group by userName
having sum (userValue) <> 0
)

Obtaining the percentage in sqllite

I made a query with the following statement :
select mood, count(*) * 100/ (select count(*) from entry)from entry group by mood having data>data-30 order by mood asc
mood is an integer from 0 to 2
the output is :
mood count
0 96,55
1 3,44
is there a way to add a row with mood 2 count 0?
SELECT MOOD, SUM (COUNTER) TOTAL
FROM ( SELECT 0 MOOD, 0 COUNTER FROM DUAL
UNION ALL
SELECT 1 MOOD, 0 COUNTER FROM DUAL
UNION ALL
SELECT 2 MOOD, 0 COUNTER FROM DUAL
UNION ALL
SELECT MOOD, COUNT ( * )
* 100.0
/ (SELECT COUNT ( * )
FROM ENTRY
WHERE DATA > DATE ('now') - 30)
FROM (SELECT *
FROM ENTRY
WHERE DATA > DATE ('now') - 30)
GROUP BY MOOD, DATA)
GROUP BY MOOD
ORDER BY MOOD ASC;
You have to enumerate (0, 1, 2, .....) all the possible numbers, associating a counter = 0.
Then, you sum the counters grouping by mood.
Please note that your condition having data>data-30 is absurd.
You have to select from ENTRY all the records satisfying the condition data > date('now') - 30, for example.
SQLite: A VIEW named "dual" that works the same as the Oracle "dual" table can be created as follows: "CREATE VIEW dual AS SELECT 'x' AS dummy;"