How to calculate a percentage on different values from same column with different criteria - sql

I'm trying to write a query in SSRS (using SQL) to calculate an income statement percentage of sales for each month (the year is a parameter chosen by the user at runtime). However, the table I have to use for the data lists all of the years, months, accounts, dollars, etc together and looks like this:
ACCT_YEAR
ACCT_PERIOD
ACCOUNT_ID
CREDIT_AMOUNT
2021
1
4000
20000
2021
2
4000
25000
2021
1
5000
5000
2021
2
5000
7500
2021
1
6000
4000
2021
2
6000
8000
etc, etc (ACCOUNT_ID =4000 happens to be the sales account)
As an example,
I need to calculate
CREDIT_AMOUNT when ACCT_YEAR = 2021, ACCT_PERIOD=1, and ACCOUNT_ID=5000
/
CREDIT_AMOUNT when ACCT_YEAR = 2021, ACCT_PERIOD=1, and ACCOUNT_ID=4000
* 100
I would then do that for each ACCT_PERIOD in the ACCT_YEAR.
Hope that makes sense...What I want would look like this:
ACCT_YEAR
ACCT_PERIOD
ACCOUNT_ID
PERCENTAGE
2021
1
5000
0.25
2021
2
5000
0.30
2021
1
6000
0.20
2021
2
6000
0.32
I'm trying to create a graph that shows the percentage of sales of roughly 10 different accounts (I know their specific account_ID's and will filter by those ID's) and use the line chart widget to show the trends by month.
I've tried CASE scenarios, OVER scenarios, and nested subqueries. I feel like this should be simple but I'm being hardheaded and not seeing the obvious solution.
Any suggestions?
Thank you!

One important behaviour to note is that window functions are applied after the where clause.
Because you need the window functions to be applied before any where clause (which would filter account 4000 out), they need to be used in one scope, and the where clause in another scope.
WITH
perc AS
(
SELECT
*,
credit_amount * 100.0
/
SUM(
CASE WHEN account_id = 4000 THEN credit_amount END
)
OVER (
PARTITION BY acct_year, accr_period
)
AS credit_percentage
FROM
your_table
)
SELECT
*
FROM
perc
WHERE
account_id IN (5000,6000)

You just to use a matrix with a parent column group for ACCT_YEAR and a child column group for ACCT_PERIOD. Then you can use your calculation. If you format the textbox for percentage, you won't need to multiply it by 100.
Textbox value: =IIF(ACCOUNT_ID=4000, Sum(CREDIT_AMOUNT), 0) = 0, 0, IIF(ACCOUNT_ID=5000, Sum(CREDIT_AMOUNT), 0) / IIF(ACCOUNT_ID=4000, Sum(CREDIT_AMOUNT), 0)

Related

sharing cash with priority to creditors in sql

I have a table in sql 2014 with name "tblPaymentPlan" like this:
Creditors PlanToPay َAmount
----------------------------------
A 2017-01-20 2000
A 2017-02-20 1500
A 2017-03-20 3000
B 2017-01-25 3000
B 2017-02-25 1000
and also another table with name "tblPaid" like following:
Creditors Paid َ
-----------------
A 4500
B 3500
and the result that I expect:
Creditors PlanToPay َRemain
----------------------------------
A 2017-01-20 0
A 2017-02-20 0
A 2017-03-20 2000
B 2017-01-25 0
B 2017-02-25 500
I have no idea for doing this job at all! Would you please to help me to perform this job. Please informed that I have a lot of records in my tables.
I need this query for budget planing. (We can use numbers for defining priority instead of dates)
What you want is a running total of what is owing, from that you can subtract what has been paid.
SELECT Creditors, PlanToPay, IIF(ABS(Remain)!=Remain,0,IIF(Remain<Amount,Remain,Amount)) as Remain
FROM (SELECT pp.Creditors, pp.PlanToPay, pp.Amount,
SUM(pp.Amount) OVER(PARTITION BY pp.Creditors ORDER BY pp.PlanToPay ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)-tp.paid AS Remain
FROM tblPaymentPlan pp
JOIN (SELECT creditors, sum(paid) as paid from tblpaid group by creditors) tp
ON pp.creditors = tp.creditors) ss
ORDER By Creditors, PlanToPay
SQLFiddle
In the windowing function (SUM OVER) the PARTITION separates the creditors, the ORDER determines how the rows are arranged (by date), and the ROWS clause tells it to use all the rows in the partition before this row and include this row in the running total. We then subtract the sum of everything paid to that creditor from this running total.
This of course gives us alot of negative numbers, so we do it in a subquery. The main query checks if the absolute value of that remaining is equal to the value, true if it's positive, false if it is not, and returns the value remaining if true, or 0 if not.
UPDATE - added handling for multiple rows with value still owing
You can subtract the running total from amount in paid table and if it is less than 0, set remain to 0 else the difference of amount from the running total.
select pp.creditors,pp.plantopay,
case when sum(pp.amount) over(partition by pp.creditors order by pp.plantopay)-coalesce(pd.paid,0) <= 0 then 0
else sum(pp.amount) over(partition by pp.creditors order by pp.plantopay)-coalesce(pd.paid,0) end as remain
from tblpaymentplan pp
left join tblPaid pd on pp.creditors=pd.creditors

SQL query to choose row before two consecutive true values

Here is my table.
Price Volume
------------------
60 0
70 10
80 0
90 0
100 40
200 40
300 40
400 0
500 50
600 60
It is ordered by Price.
I need to choose all rows before two consecutive zeros in Volume column. So the result should look like
Price Volume
------------------
100 40
200 40
300 40
400 0
500 50
600 60
I really do not know where to start with this type of query. I could only think go pulling data into C# and the loading back into table.
Regards,
You didn't state your DBMS so this is ANSI SQL:
with flagged as (
select price,
volume,
case
when volume + lag(volume) over (order by price) = 0 then 1
else 0
end as rn
from prices
), counted as (
select price,
volume,
sum(rn) over (order by price) as cn,
rn
from flagged
)
select price, volume
from counted
where cn > 0 and rn = 0
order by price
Here is a SQLFiddle example: http://sqlfiddle.com/#!6/b2434/4
Standard SQL cannot handle this. You need a cursor to cycle through the ordered rows and decide what to output, which requires the use of some variables.
Depending on the RDBMS you are using, you could write a stored procedure that reads the rows and filters them. Otherwise you must have the application do the filtering.
In sql server 2012+ you can do this with a couple LAG statements.
Lag for the 2 previous values and where when they are both 0.

Calculating Cumulative Sum in PostgreSQL

I want to find the cumulative or running amount of field and insert it from staging to table. My staging structure is something like this:
ea_month id amount ea_year circle_id
April 92570 1000 2014 1
April 92571 3000 2014 2
April 92572 2000 2014 3
March 92573 3000 2014 1
March 92574 2500 2014 2
March 92575 3750 2014 3
February 92576 2000 2014 1
February 92577 2500 2014 2
February 92578 1450 2014 3
I want my target table to look something like this:
ea_month id amount ea_year circle_id cum_amt
February 92576 1000 2014 1 1000
March 92573 3000 2014 1 4000
April 92570 2000 2014 1 6000
February 92577 3000 2014 2 3000
March 92574 2500 2014 2 5500
April 92571 3750 2014 2 9250
February 92578 2000 2014 3 2000
March 92575 2500 2014 3 4500
April 92572 1450 2014 3 5950
I am really very much confused with how to go about achieving this result. I want to achieve this result using PostgreSQL.
Can anyone suggest how to go about achieving this result-set?
Basically, you need a window function. That's a standard feature nowadays. In addition to genuine window functions, you can use any aggregate function as window function in Postgres by appending an OVER clause.
The special difficulty here is to get partitions and sort order right:
SELECT ea_month, id, amount, ea_year, circle_id
, sum(amount) OVER (PARTITION BY circle_id
ORDER BY ea_year, ea_month) AS cum_amt
FROM tbl
ORDER BY circle_id, ea_year, ea_month;
And no GROUP BY.
The sum for each row is calculated from the first row in the partition to the current row - or quoting the manual to be precise:
The default framing option is RANGE UNBOUNDED PRECEDING, which is
the same as RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. With
ORDER BY, this sets the frame to be all rows from the partition
start up through the current row's last ORDER BY peer.
Bold emphasis mine.
This is the cumulative (or "running") sum you are after.
In default RANGE mode, rows with the same rank in the sort order are "peers" - same (circle_id, ea_year, ea_month) in this query. All of those show the same running sum with all peers added to the sum. But I assume your table is UNIQUE on (circle_id, ea_year, ea_month), then the sort order is deterministic and no row has peers. (And you might as well use the cheaper ROWS mode.)
Postgres 11 added tools to include / exclude peers with the new frame_exclusion options. See:
Aggregating all values not in the same group
Now, ORDER BY ... ea_month won't work with strings for month names. Postgres would sort alphabetically according to the locale setting.
If you have actual date values stored in your table you can sort properly. If not, I suggest to replace ea_year and ea_month with a single column the_date of type date in your table.
Transform what you have with to_date():
to_date(ea_year || ea_month , 'YYYYMonth') AS the_date
For display, you can get original strings with to_char():
to_char(the_date, 'Month') AS ea_month
to_char(the_date, 'YYYY') AS ea_year
While stuck with the unfortunate design, this will work:
SELECT ea_month, id, amount, ea_year, circle_id
, sum(amount) OVER (PARTITION BY circle_id ORDER BY the_date) AS cum_amt
FROM (SELECT *, to_date(ea_year || ea_month, 'YYYYMonth') AS the_date FROM tbl) sub
ORDER BY circle_id, mon;

sql DB calculation moving summary‏‏‏‏‏

I would like to calculate moving summary‏‏‏‏‏:
Total amount:100
first receipt: 20
second receipt: 10
the first row in calculation column is a difference between total amount and the first receipt: 100-20=80
the second row in calculation column is a difference between the first calculated_row and the first receip: 80-10=70
The presentation is supposed to present receipt_amount, balance:
receipt_amount | balance
20 | 80
10 | 70
I'll be glad to use your help
Thanks :-)
You didn't really give us much information about your tables and how they are structured.
I'm assuming that there is an orders table that contains the total_amount and a receipt_table that contains each receipt (as a positive value):
As you also didn't specify your DBMS, this is ANSI SQL:
select sum(amount) over (order by receipt_nr) as running_sum
from (
select total_amount as amount
from orders
where order_no = 1
union all
select -1 * receipt_amount
from the_receipt_table
where order_no =
) t
First of all- thanks for your response.
I work with Cache DB which can be used both SQL and ORACLE syntax.
Basically, the data is locaed in two different tables, but I have them in one join query.
Couple of rows with different receipt amounts and each row (receipt) has the same total amount.
Foe example:
Receipt_no Receipt_amount Total_amount Balance
1 20 100 80
1 10 100 70
1 30 100 40
2 20 50 30
2 10 50 20
So, the calculation is supposed to be in a way that in the first receipt the difference calculation is made from the total_amount and all other receipts (in the same receipt_no) are being reduced from the balance
Thanks!

Why is there no "product()" aggregate function in SQL? [duplicate]

This question already has answers here:
is there a PRODUCT function like there is a SUM function in Oracle SQL?
(7 answers)
Closed 7 years ago.
When there are Sum(), min(), max(), avg(), count() functions, can someone help understand why there is no product() built-in function. And what will be the most efficient user-implementation of this aggregate function ?
Thanks,
Trinity
If you have exponential and log functions available, then:
PRODUCT(TheColumn) = EXP(SUM(LN(TheColumn)))
One can make a user-defined aggregate in SQL 2005 and up by using CLR. In Postgresql, you can do it in Postgres itself, likewise with Oracle
I'll focus on the question why it's not a standard function.
Aggregate function are basic statistical functions and product is not
Applied to common numerical data, the result will be in most cases out of range (overflow) so it is of little general use
It's probably left out because most people don't need it and it can be defined easily in most databases.
Solution for PostgreSQL:
CREATE OR REPLACE FUNCTION product_sfunc(state numeric, factor numeric)
RETURNS numeric AS $$
SELECT $1 * $2
$$ LANGUAGE sql;
CREATE AGGREGATE product (
sfunc = product_sfunc,
basetype = numeric,
stype = numeric,
initcond = '1'
);
You can simulate product() using cursors. If you let us know which database platform you're using, then we might be able to give you some sample code.
I can confirm that it is indeed rare to use a product() aggregate function, but I have a quite valid example, especially working with highly aggregated data that must be presented to users in a report.
It utilizes the exp(sum(ln( multiplyTheseColumnValues ))) "trick" as mentioned in another post and other internet sources.
The report (which should care about the display, and contain as least data calculation logic as possible, to provide better maintainability and flexibility) is basically displaying this data along with some graphics:
DESCR SUM
---------------------------------- ----------
money available in 2013 33233235.3
money spent in 2013 4253235.3
money bound to contracts in 2013 34333500
money spent 2013 in % of available 12
money bound 2013 in % of available 103
(In real life its a bit more complex and used in state budget scenarios.)
It aggregates quite some complex data found in the first 3 rows.
I do not want to calculate the percentage values of the following rows (4th and 5th) by:
doing it in the quite dumb (as it should be) report (which just takes any number of such rows with a descripiton descr and a number sum) with some fancy logic (using JasperReports, BIRT Reports or alike)
neither do I want to calculate the underlying data (money available, money spent, money bound) multiple times (since these are quite expensive operations) just to calculate the percentage values
So I used another trick involving the use of the product()-functionality.
(If somebody does know of a better way to achive this considering the above mentioned restrictions, I would be happy to know :-) )
The whole simplified example is available as one executable SQL below.
Maybe it could help convice some Oracle guys that this functionality is not as rare, or not worth providing, as it may seem at first thoughts.
with
-- we have some 10g database without pivot/unpivot functionality
-- what is interesting for various summary reports
sum_data_meta as (
select 'MA' as sum_id, 'money available in 2013' as descr, 1 as agg_lvl from dual
union all select 'MS', 'money spent in 2013', 1 from dual
union all select 'MB', 'money bound to contracts in 2013', 1 from dual
union all select 'MSP', 'money spent 2013 in % of available', 2 from dual
union all select 'MBP', 'money bound 2013 in % of available', 2 from dual
)
/* select * from sum_data_meta
SUM_ID DESCR AGG_LVL
------ ---------------------------------- -------
MA money available in 2013 1
MS money spent in 2013 1
MB money bound to contracts in 2013 1
MSP money spent 2013 in % of available 2
MBP money bound 2013 in % of available 2
*/
-- 1st level of aggregation with the base data (the data actually comes from complex (sub)SQLs)
,sum_data_lvl1_base as (
select 'MA' as sum_id, 33233235.3 as sum from dual
union all select 'MS', 4253235.3 from dual
union all select 'MB', 34333500 from dual
)
/* select * from sum_data_lvl1_base
SUM_ID SUM
------ ----------
MA 33233235.3
MS 4253235.3
MB 34333500.0
*/
-- 1st level of aggregation with enhanced meta data infos
,sum_data_lvl1 as (
select
m.descr,
b.sum,
m.agg_lvl,
m.sum_id
from sum_data_meta m
left outer join sum_data_lvl1_base b on (b.sum_id=m.sum_id)
)
/* select * from sum_data_lvl1
DESCR SUM AGG_LVL SUM_ID
---------------------------------- ---------- ------- ------
money available in 2013 33233235.3 1 MA
money spent in 2013 4253235.3 1 MS
money bound to contracts in 2013 34333500.0 1 MB
money spent 2013 in % of available - 2 MSP
money bound 2013 in % of available - 2 MBP
*/
select
descr,
case
when agg_lvl < 2 then sum
when agg_lvl = 2 then -- our level where we have to calculate some things based on the previous level calculations < 2
case
when sum_id = 'MSP' then
-- we want to calculate MS/MA by tricky aggregating the product of
-- (MA row:) 1/33233235.3 * (MS:) 4253235.3/1 * (MB:) 1/1 * (MSP:) 1/1 * (MBP:) * 1/1
trunc( -- cut of fractions, e.g. 12.7981 => 12
exp(sum(ln( -- trick simulating product(...) as mentioned here: http://stackoverflow.com/a/404761/1915920
case when sum_id = 'MS' then sum else 1 end
/ case when sum_id = 'MA' then sum else 1 end
)) over ()) -- "over()" => look at all resulting rows like an aggregate function
* 100 -- % display style
)
when sum_id = 'MBP' then
-- we want to calculate MB/MA by tricky aggregating the product as shown above with MSP
trunc(
exp(sum(ln(
case when sum_id = 'MB' then sum else 1 end
/ case when sum_id = 'MA' then sum else 1 end
)) over ())
* 100
)
else -1 -- indicates problem
end
else null -- will be calculated in a further step later on
end as sum,
agg_lvl,
sum_id
from sum_data_lvl1
/*
DESCR SUM AGG_LVL SUM_ID
---------------------------------- ---------- ------- ------
money available in 2013 33233235.3 1 MA
money spent in 2013 4253235.3 1 MS
money bound to contracts in 2013 34333500 1 MB
money spent 2013 in % of available 12 2 MSP
money bound 2013 in % of available 103 2 MBP
*/
Since the Product is noting but the multiple of SUM, so in SQL they didnot introduce the Product aggregate function
For example: 6 * 4 can be acheived by
either adding 6, 4 times to itself like 6+6+6+6
or
adding 4, 6 times to itself like 4+4+4+4+4+4
thus giving the same result