Get the previous calculated record divided by group - SQL - sqlite

I'm strugging to build two calculated columns (named balance and avg). My original SQLite base is:
name seq side price qnt
groupA 1 B 30 100
groupA 2 B 36 200
groupA 3 S 23 300
groupA 4 B 30 100
groupA 5 B 54 400
groupB 1 B 70 300
groupB 2 B 84 300
groupB 3 B 74 600
groupB 4 S 90 100
Rational for the 2 calculated new columns:
balance: the first line of each group (seq = 1), must have the same value of qnt. The next records follow the below formula (Excel-based scheme):
if(side="B"; `previous balance record` + `qnt`; `previous balance record` - `qnt`)
avg: the first line of each group (seq = 1), must have the same value of price. The next records follow the below formula (Excel-based scheme):
if(side="B"; ((`price` \* `qnt`) + (`previous balance record` \* `previous avg record`)) / (`qnt` + `previous balance record`); `previous avg record`)
Example with numbers (the second row of groupA is calculated below):
--> balance: 100 + 200 = 300
--> avg: ((36 * 200) + (100 * 30)) / (200 + 100) = 34
I think this problem must be solved with CTE because I need the previous record, which is in being calculated every time.
I wouldn't like to aggregate groups - my goal is to display every record.
Finally, this is what I expect as the output:
name seq side price qnt balance avg
groupA 1 B 30 100 100 30
groupA 2 B 36 200 300 34
groupA 3 S 23 300 0 34
groupA 4 B 30 100 100 30
groupA 5 B 54 400 500 49,2
groupB 1 B 70 300 300 70
groupB 2 B 84 300 600 77
groupB 3 B 74 600 1200 75,5
groupB 4 S 90 100 1100 75,5
Thank you in advance!
Here is my dbfiddle test: https://dbfiddle.uk/TSarc3Nl
I tried to explain part of the coding (commented) to make things easier.

The balance can be derived from a cumulative sum (using a case expression for when to deduct instead of add).
Then the recursive part just needs a case expression of its own.
WITH
adjust_table AS
(
SELECT
*,
SUM(
CASE WHEN side='B'
THEN qnt
ELSE -qnt
END
)
OVER (
PARTITION BY name
ORDER BY seq
)
AS balance
FROM
mytable
),
recurse AS
(
SELECT adjust_table.*, price AS avg FROM adjust_table WHERE seq = 1
UNION ALL
SELECT
n.*,
CASE WHEN n.side='B'
THEN ((n.price * n.qnt * 1.0) + (s.balance * s.avg)) / (n.qnt + s.balance)
ELSE s.avg
END
AS avg
FROM
adjust_table n
INNER JOIN
recurse s
ON n.seq = s.seq + 1
AND n.name = s.name
)
SELECT
*
FROM
recurse
ORDER BY
name,
seq
https://dbfiddle.uk/mWz945pG
Though I'm not sure what the avg is meant to be doing, so it's possible I got that wrong and/or it could possibly be simplified to not need recursion.
NOTE: Never use , to join tables.
EDIT: Recursion-less version
Use window functions to accumulate the balance, and also the total spent.
Then, use that to a enable the use of another window function to accumulate how much 'spend' is being 'recouped' by sales.
Your avg is then the adjusted spend divided by the current balance.
WITH
accumulate AS
(
SELECT
*,
SUM(
CASE WHEN side='B' THEN qnt ELSE -qnt END
)
OVER (
PARTITION BY name
ORDER BY seq
)
AS balance,
1.0
*
SUM(
CASE WHEN side='B' THEN price * qnt END
)
OVER (
PARTITION BY name
ORDER BY seq
)
AS total_spend
FROM
mytable
)
SELECT
*,
(
total_spend
-
SUM(
CASE WHEN side='S'
THEN qnt * total_spend / (balance+qnt)
ELSE 0
END
)
OVER (
PARTITION BY name
ORDER BY seq
)
-- cumulative sum for amount 'recouped' by sales
)
/ NULLIF(balance, 0)
AS avg
FROM
accumulate
https://dbfiddle.uk/O0HEr556
Note: I still don't understand why you're calculating the avg price this way, but it matched your desired results/formulae, without recursion.

Related

SQLite - Join returning duplicate result

I've read through dozens of posts on this so apologies if it's easily answered but I'm just not getting it.
Table Name: users
user_id group_id house_name
==========================================
923828395 1 Alpha
722161580 2 Beta
923828395 1 Gamma
Users can be in multiple Groups, and in a different House per Group.
Table Name: points
user_id group_id points term_id
=====================================================================================
722161580 1 18 02078e51
923828395 1 11 02078e51
923828395 2 140 81450fc1
Users can accumulate points in each Group they reside in. In the data above, user_id 923828395 exists in both group_id 1 and 2, accumulating points in both.
Query:
SELECT users.user_id, users.house_name, points.points, points.group_id, points.term_id
FROM users
JOIN points ON points.user_id = users.user_id
WHERE points.term_id = "02078e51" AND points.group_id = "1"
I'm trying to get this to just return 2 rows. However it's returning:
users.user_id users.house_name points.points points.group_id points.term_id
722161580 Beta 18 1 02078e51
923828395 Alpha 11 1 02078e51
923828395 Gamma 11 1 02078e51
I think this is because I've got something wrong with my WHERE .. I've tried flipping that to just an AND statement but I get the same results.
You should join the tables on group_id also:
SELECT u.user_id, u.house_name, p.points, p.group_id, p.term_id
FROM users u JOIN points p
ON p.user_id = u.user_id AND p.group_id = u.group_id
WHERE p.term_id = '02078e51' AND p.group_id = '1'
See the demo.

Retain values only for certain section of data in teradata

Below is the link of my previous quetsion.
Retain values till there is a change in value in Teradata
It worked as suggested by one of the community members #Dnoeth. Can this retention be done only for certain section of data?
I.e, Retain data only for data where Dep is A or B . When Dep is C just use same value as input and no need to retain till certain value.
Data:
Cust_id Balance st_ts Dep
123 1000 27MAY2018 A
123 350 31MAY2018 A
256 2000 29MAY2018 B
345 1000 28APR2018 C
345 1200 26MAY2018 C
Output reqd:
Cust_id Balance st_ts Dep
123 1000 27MAY2018 A
123 1000 28MAY2018 A
123 1000 29MAY2018 A
123 1000 30MAY2018 A
123 350 31MAY2018 A
256 2000 29MAY2018 B
256 2000 30MAY2018 B
256 2000 31MAY2018 B
345 1000 28APR2018 C
345 1200 26MAY2018 C
Query used:
Wth cte
{
SELECT customer_id, bal, st_ts,
-- return the next row's date
Coalesce(Min(st_ts)
Over (PARTITION BY customer_id
ORDER BY st_ts
ROWS BETWEEN 1 Following AND 1 Following)
,Date '2018-06-01') AS next_Txn_dt
FROM BAL_DET;
}
SELECT customer_id, bal
,Last(pd) -- last day of the period
FROM cTE
-- make a period of the current and next row's date
-- and return one row per day
EXPAND ON PERIOD(ST_TS, next_Txn_dt) AS pd;
Thanks
Sandy
You can add a CASE to check for Dep = 'C':
WITH cte AS
( SELECT customer_id, bal, st_ts, dep,
-- return the next row's date
CASE
WHEN dep = 'C'
THEN st_ts +1 -- simply increase date
ELSE
Coalesce(Min(st_ts)
Over (PARTITION BY customer_id
ORDER BY st_ts
ROWS BETWEEN 1 Following AND 1 Following)
,DATE '2018-06-01')
END AS next_Txn_dt
FROM BAL_DET
)
SELECT customer_id, bal
,Last(pd) -- last day of the period
,dep
FROM cTE
-- make a period of the current and next row's date
-- and return one row per day
EXPAND ON PERIOD(ST_TS, next_Txn_dt) AS pd

Creating even ranges based on values in an oracle table

I have a big table which is 100k rows in size and the PRIMARY KEY is of the datatype NUMBER. The way data is populated in this column is using a random number generator.
So my question is, can there be a possibility to have a SQL query that can help me with getting partition the table evenly with the range of values. Eg: If my column value is like this:
1
2
3
4
5
6
7
8
9
10
And I would like this to be broken into three partitions, then I would expect an output like this:
Range 1 1-3
Range 2 4-7
Range 3 8-10
It sounds like you want the WIDTH_BUCKET() function. Find out more.
This query will give you the start and end range for a table of 1250 rows split into 20 buckets based on id:
with bkt as (
select id
, width_bucket(id, 1, 1251, 20) as id_bucket
from t23
)
select id_bucket
, min(id) as bkt_start
, max(id) as bkt_end
, count(*)
from bkt
group by id_bucket
order by 1
;
The two middle parameters specify min and max values; the last parameter specifies the number of buckets. The output is the rows between the minimum and maximum bows split as evenly as possible into the specified number of buckets. Be careful with the min and max parameters; I've found poorly chosen bounds can have an odd effect on the split.
This solution works without width_bucket function. While it is more verbose and certainly less efficient it will split the data as evenly as possible, even if some ID values are missing.
CREATE TABLE t AS
SELECT rownum AS id
FROM dual
CONNECT BY level <= 10;
WITH
data AS (
SELECT id, rownum as row_num
FROM t
),
total AS (
SELECT count(*) AS total_rows
FROM data
),
parts AS (
SELECT rownum as part_no, total.total_rows, total.total_rows / 3 as part_rows
FROM dual, total
CONNECT BY level <= 3
),
bounds AS (
SELECT parts.part_no,
parts.total_rows,
parts.part_rows,
COALESCE(LAG(data.row_num) OVER (ORDER BY parts.part_no) + 1, 1) AS start_row_num,
data.row_num AS end_row_num
FROM data
JOIN parts
ON data.row_num = ROUND(parts.part_no * parts.part_rows, 0)
)
SELECT bounds.part_no, d1.ID AS start_id, d2.ID AS end_id
FROM bounds
JOIN data d1
ON d1.row_num = bounds.start_row_num
JOIN data d2
ON d2.row_num = bounds.end_row_num
ORDER BY bounds.part_no;
PART_NO START_ID END_ID
---------- ---------- ----------
1 1 3
2 4 7
3 8 10

Find local minima/maxima

I need a query to update the value at the point from which the metrics rises or decreases .For example I have a table with
ID METRICS INDICATOR
1 204.4
2 205
3 206 H
4 204
5 199
6 198 L
7 204
8 205 H
9 201
10 199
If you see the above table the metrics column the reversal of metrics happens . The point the reversal happens should be updated with the indicator value H/L as shown in the indicator column.
You want a "H" when both the preceding and the following rows have smaller values:
UPDATE MyTable
SET Indicator = 'H'
WHERE Metrics > (SELECT Metrics
FROM MyTable AS T2
WHERE T2.ID < MyTable.ID
ORDER BY ID DESC
LIMIT 1)
AND Metrics > (SELECT Metrics
FROM MyTable AS T2
WHERE T2.ID > MyTable.ID
ORDER BY ID ASC
LIMIT 1);
You want a "L" when both the preceding and the following rows have larger values; use a similar query.

SQLite Query to group continuous range of numbers into different grouping sets

I want get the islands of this table below:
Group MemberNo
A 100
A 101
A 200
A 201
A 202
A 203
X 100
X 101
A 204
X 301
X 302
A 500
A 600
I want get this results using SQL (the islands):
Group FromMemberNo ToMemberNo
A 100 101
A 200 204
X 100 101
X 301 302
A 500 500
A 600 600
I have seen a lot of codes/forums for this but not working with SQLite because SQLite doesn't have CTEs.
100-101 is continuous so that it will be group into one.
Does anyone know how to do it in SQLite?
The fastest way to do this would be to go through the ordered records of this table in a loop and collect the islands manually.
In pure SQL (as a set-oriented language), this is not so easy.
First, we find out which records are the first in an island. The first record does not have a previous record, i.e., a record with the same group but with a MemberNo one smaller:
SELECT "Group",
MemberNo AS FromMemberNo
FROM ThisTable AS t1
WHERE NOT EXISTS (SELECT 1
FROM ThisTable AS t2
WHERE t2."Group" = t1."Group"
AND t2.MemberNo = t1.MemberNo - 1)
To find the last record of an island, we have to find the record with the largest MemberNo that still belongs to the same island, i.e., has the same group, and where all MemberNos in the island are continuous.
We detect continuous MemberNos by computing the difference between their values in the first and last records.
The last MemberNo of the island with group G and first MemberNo M can be computed like this:
SELECT MAX(MemberNo) AS LastMemberNo
FROM ThisTable AS t3
WHERE t3."Group" = G
AND t3.MemberNo - M + 1 = (SELECT COUNT(*)
FROM ThisTable AS t4
WHERE t4."Group" = G
AND t4.MemberNo BETWEEN M AND t3.MemberNo)
Finally, plug this into the first query:
SELECT "Group",
MemberNo AS FromMemberNo,
(SELECT MAX(MemberNo)
FROM ThisTable AS t3
WHERE t3."Group" = t1."Group"
AND t3.MemberNo - t1.MemberNo + 1 = (SELECT COUNT(*)
FROM ThisTable AS t4
WHERE t4."Group" = t1."Group"
AND t4.MemberNo BETWEEN t1.MemberNo AND t3.MemberNo)
) AS LastMemberNo
FROM ThisTable AS t1
WHERE NOT EXISTS (SELECT 1
FROM ThisTable AS t2
WHERE t2."Group" = t1."Group"
AND t2.MemberNo = t1.MemberNo - 1)

Resources