I have a relational table where I need to loop through and sum amount values until a specified amount criteria is met. The data looks like this:
Company | Account | Amount |
---|---|---|
Coastal | A384 | 500 |
Coastal | C940 | 200 |
Coastal | Z934 | 200 |
American | U202 | 200 |
American | I034 | 300 |
Tester | P034 | 300 |
I want to have a function where I can loop through this data and specify an amount criteria that either returns 1 or more accounts once the threshold is met for each company. If the threshold is not met there should be some output that specifies the criteria was not met and the largest account value should be returned. I am thinking I would need to convert Account + Amount into an array to loop through. Pseudo code would look like:
var threshold = 500
var amount_total = 0
var array_result = {}
for each item in account_array
array_result(account: amount)
amount_total += amount
if amount_total = threshold
return array_result
My expected output from this would be:
Company | array_result | status |
---|---|---|
Coastal | {"A384": 500} | "Met Criteria" |
American | {"U202": 200, "I034": 300} | "Met Criteria" |
Tester | {"P034": 300} | "Did Not Meet Criteria" |
Ideally, I would be able to have this as as a function to be able to use in a select statement:
Select Company, find_accounts_that_meet_threshold(account_array_field, threshold) from company_table
-
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer.Community– Community Bot2021年09月22日 14:25:38 +00:00Commented Sep 22, 2021 at 14:25
-
Your version of Postgres? What defines sort order of rows? Get result for a given company or for whole table?Erwin Brandstetter– Erwin Brandstetter2021年09月23日 04:40:08 +00:00Commented Sep 23, 2021 at 4:40
1 Answer 1
A set-based solution instead of looping:
SELECT company
, CASE WHEN sum(amount) >= 500
THEN json_agg(json_build_object(account, amount))
ELSE json_agg(json_build_object(account, amount)) FILTER (WHERE amount_rn = 1) END AS json_array
, CASE WHEN sum(amount) >= 500
THEN array_agg((account, amount))
ELSE array_agg((account, amount)) FILTER (WHERE amount_rn = 1) END AS plain_array
, CASE WHEN sum(amount) >= 500 THEN 'Met' ELSE 'Failed' END AS status
FROM (
SELECT *
, sum(amount) OVER (PARTITION BY company ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS running_sum
, row_number() OVER (PARTITION BY company ORDER BY amount DESC NULLS LAST, id) AS amount_rn
FROM tbl
ORDER BY company, id
) sub
WHERE running_sum >= 500 IS NOT TRUE
GROUP BY 1;
db<>fiddle here
I added two variants for your array to pick from.
In the subquery, form running sums per company with a window function. For lack of information, assuming an id
column that defines sort order. Substitute with your actual order. Exclude the current row from the running sum, so we can conveniently filter rows WHERE running_sum >= 500 IS NOT TRUE
in the outer query. That includes rows until the required sum is reached.
Also determine the row with biggest amount with row_number()
. Using id
to split ties if any. amount_rn = 1
is the winner - for the failed case.
The total sum indicates whether the criteria was met. If it was, return an array of contributing "account: amount". Else, just the one chosen by amount_rn
.
-
1Nitpick: The specification states, "If the threshold is not met there should be some output that specifies the criteria was not met and the largest account value should be returned." Your query returns all the accounts for companies that don't meet the threshold. Add a couple of amount 50 accounts to Tester to see.user234725– user2347252021年09月23日 05:48:24 +00:00Commented Sep 23, 2021 at 5:48
-
1@dwhitemv: True. I adapted accordingly. In my defense: neither pseudocode nor test data nor desired result are clear about that. And the largest account value is ambiguous. Add a couple of amount 50 and remove the 300 from Tester, and you need a tiebreaker or an arbitrary pick ...Erwin Brandstetter– Erwin Brandstetter2021年09月23日 07:03:35 +00:00Commented Sep 23, 2021 at 7:03
-
Nicely done. I knew you were up for the challenge. :-)user234725– user2347252021年09月23日 14:33:24 +00:00Commented Sep 23, 2021 at 14:33