SQL Running Total Reset on Condition - sql

I have the following table:
Transaction History Table
TransactionHistoryId ProductCode Type Quantity PurchasePrice CurrentPrice
1 Product1 B 10 3.00 2.00
2 Product1 B 5 7.00 2.00
3 Product1 S -7 7.00 2.00
4 Product1 S -8 3.00 3.00
5 Product1 B 4 10.00 10.00
6 Product1 B 5 12.00 12.00
8 Product2 B 8 20.00 20.00
I would like to acheive the following table:
TransactionHistoryId ProductCode Type Quantity PurchasePrice QtyRunning PriceRunning
1 Product1 B 10 3.00 10 30.00
2 Product1 B 5 7.00 15 65.00
3 Product1 S -7 7.00 8 65.00
4 Product1 S -8 3.00 0 0.00
5 Product1 B 4 10.00 4 40.00
6 Product1 B 5 12.00 9 100.00
8 Product2 B 8 20.00 8 160.00
Create Table SQL
IF OBJECT_ID('TEMPDB..#TransactionHistory') IS NOT NULL
DROP TABLE #TransactionHistory
create table #TransactionHistory
(TransactionHistoryId int,
ProductCode varchar(10),
Type char(1),
Quantity smallint,
PurchasePrice decimal(18,2),
CurrentPrice decimal(18,2)
)
insert into #TransactionHistory
values
(1,'Product1','B',10,3.00,2.00),
(2,'Product1','B',5,7.00,2.00),
(3,'Product1','S',-7,7.00,2.00),
(4,'Product1','S',-8,3.00,3.00),
(5,'Product1','B',4,10.00,10.00),
(6,'Product1','B',5,12.00,12.00),
(8,'Product2','B',8,20.00,20.00)
Rules
PriceRunningTotal resets when the quantity running total is 0
PriceRunningTotal sums up only Type = 'B' (buys), when Type = 'S' (sold) keep the previous purchase price running total
Notice there is a Product 2 so it should have it's own running count independent of Product 1
Purpose
A query to ultimately find out the following:
Product Quantity AdjustedPurchasePrice
Product1 9 $11.11
Product2 8 $20
I used the following SQL Server 2012 query to get the result, but I feel it could be done much better:
Query
SELECT *,
PriceRunningTotalFinal =
SUM(CASE
WHEN QuantityRunningTotal = 0 THEN -1 * PriceRunningTotal
WHEN Quantity < 0 THEN 0 ELSE PurchasePrice * Quantity END) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
)
FROM (
SELECT TransactionHistoryId, ProductCode, Type, Quantity, PurchasePrice,
QuantityRunningTotal = SUM(Quantity) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
),
PriceRunningTotal = SUM(CASE WHEN Quantity < 0 THEN 0 ELSE PurchasePrice * Quantity END) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
)
FROM TransactionHistory
) AS Results1
ORDER BY ProductCode;
Problem
Ideally I would of liked to use the QuantityRunningTotal within another query but I can't nest windowed functions.
Anyone know of a more efficient way to achieve this result?

Hmmm. I think something like this:
select th.*,
sum(case when type = 'B' then Quantity * PurchasePrice
else 0
end) over (partition by grp, ProductCode order by TransactionHistoryId
) as PriceRunningTotal
from (select th.*,
sum(case when running_quantity = 0 then 1 else 0 end) over (partition by ProductCode order by TransactionHistoryId) as grp
from (select th.*,
sum(quantity) over (partition by ProductCode order by TransactionHistoryId
) as running_quantity
from TransactionHistory th
) th;
I'm not sure if this is the same logic as your query. For this query:
The innermost subquery calculates the running quantity.
The middle subquery calculates a group based on the number of times the running quantity has been 0.
The outermost query then calculates the running price.

Related

SQL Server price table weighted to buying price

I have 3 tables:
Buys
ID
Item
qty
price
1
1001
10
1.00
2
1001
10
2.00
3
1001
10
3.00
4
1002
10
2.00
5
1002
10
1.00
6
1003
10
1.00
7
1004
10
1.00
8
1004
10
2.00
Fallback
Item
price
1001
3.00
1002
3.00
1003
4.00
Stock
Item
stock
1001
15
1002
5
1003
25
1004
15
I have to calculate actually price each item. For that, I have to check the table "buys" each row from the biggest ID to smallest ID and take all prices as long as the stock is sufficient. If not enough buys in the table, I have to use the fallback prices for part of stock, I don't have price in first table.
So for item no. 1001, stock is 15. Price for 10 pcs found in ID 3 (3.00 USD); price for rest 5 pieces in row ID 2 (2.00 USD). So correct actually stockprice is 2.66 USD.
For item no. 1002, stock is 5. Price for latest buy is 1.00 USD in row ID 5 with quantity more than 5. So correct actually stockprice is 1.00 USD.
For item no. 1003, stock is 25. Only one entry in row ID 6 with 10 pcs for 1.00 USD each. so price for missing 15pcs have to take from fallback table 4.00 USD. So correct actually stockprice is 2.80 USD.
Result should be like this:
Item
stock
value
1001
15
2.66
1002
5
1.00
1003
25
2.80
But I have no idea how that works. Thank you very much for help.
Using conditional aggregation when comparring stock to buys runnig totals, finally apply the fallback
select t.item, (s + t.qf * f.price) s, stock, (s + t.qf * f.price) / stock price
from (
select s.Item, s.Stock,
sum(coalesce(case when b.qe <= Stock then b.qty else Stock - b.qs end * b.price, 0)) s,
-- qty for fallback
min(case when Stock > coalesce(b.qe,0) then Stock - coalesce(b.qe,0) else 0 end) qf
from Stock s
left join (
select Item, qty, price, ID,
sum(qty) over(partition by Item order by ID desc) - qty qs, -- starting runnig total
sum(qty) over(partition by Item order by ID desc) qe -- ending runnig total
from Buys
) b on s.Item = b.Item and s.Stock > b.qs
group by s.Item, s.Stock
) t
join Fallback f on f.Item = t.Item;
order by t.Item;
Provided a fallback can be missing for an item a minor tweak is requierd.
select t.item, (s + t.qf * coalesce(f.price, 0)) s, stock, (s + t.qf * coalesce(f.price, 0)) / stock price
from (
select s.Item, s.Stock,
sum(coalesce(case when b.qe <= Stock then b.qty else Stock - b.qs end * b.price, 0)) s,
-- qty for fallback
min(case when Stock > coalesce(b.qe,0) then Stock - coalesce(b.qe,0) else 0 end) qf
from Stock s
left join (
select Item, qty, price, ID,
sum(qty) over(partition by Item order by ID desc) - qty qs, -- starting runnig total
sum(qty) over(partition by Item order by ID desc) qe -- ending runnig total
from Buys
) b on s.Item = b.Item and s.Stock > b.qs
group by s.Item, s.Stock
) t
left join Fallback f on f.Item = t.Item
where t.qf = 0 or f.item is not null
order by t.Item;
The query will not return a row if a fallback is required but is missing. Otherwise the row is returned.
db<>fiddle
You need to create a running sum of the quantity in Buys and calculate the price based off that.
This is somewhat complicated by the fact that you may have too many, or not enough, rows in Buys to fulfil the stock.
SELECT
s.Item,
s.stock,
(
ISNULL(b.FoundStockPrice, 0)
+ CASE WHEN s.stock > ISNULL(b.FoundStock, 0)
THEN s.stock - ISNULL(b.FoundStock, 0)
ELSE 0 END * f.price
) / s.stock
FROM Stock s
JOIN Fallback f ON f.Item = s.Item
OUTER APPLY (
SELECT
FoundStock = SUM(b.qty),
FoundStockPrice = SUM(
CASE WHEN b.FullStock > b.RunningSum THEN b.qty
ELSE b.FullStock - (b.RunningSum - b.qty) END
* b.price)
FROM (
SELECT *,
RunningSum = SUM(b.qty) OVER (PARTITION BY b.Item
ORDER BY b.ID DESC ROWS UNBOUNDED PRECEDING),
FullStock = s.stock
FROM Buys b
WHERE b.Item = s.Item
) b
WHERE b.RunningSum - b.qty < s.stock
) b;
Steps are as follows:
For every Stock take all relevant Buys rows.
Calculate a running sum of qty, and then filter to only rows where the running sum includes the final stock (in other words it must up to the previous running sum).
Sum these Buys rows multiplied by their price, taking into account that we need to net off anything over the necessary stock. Take also a total sum of the quantity.
The final price is: the previous calculated total, plus any remaining unfound stock multiplied by the fallback.price, all divided by the total stock.
db<>fiddle

Select From Multiple tables with group by in SQL

I have two views:
Purchase_Details view:
InvoiceId
ItemId
Quantity
Price
ExpireDate
ItemCode
StoreId
and Sale_Details view
InvoiceId
ItemId
Quantity
Price
ItemCode
StoreId
and Item Table:
ID
Name
SalePrice
PurchasePrice
Purchase_Detail View data
Id StoreId ItemCode Price Quantity ItemId ExpireDate
51 1 345 300.00 1.00 5 2019-10-25
52 1 348 300.00 22.00 5 2019-10-04
53 2 348 300.00 17.00 5 2019-11-11
54 1 345 300.00 8.00 5 2019-12-12
Sale_Details View data
Id StoreId ItemCode Price Quantity ItemId
55 1 345 300.00 4.00 5
56 1 348 300.00 3.00 5
I want to found sum of quantity of each item grouped by ItemId and ItemCode.
Second: is it better to put the sales and purchase and the same table or separate them (as I did?
I think you'll want to union the data together first, and then group by so your sum crosses the sets. You can do this with a CTE. Something like this:
WITH CTE_Detail AS (
SELECT ItemId, ItemCode, Quantity FROM Purchase_Details
UNION ALL
SELECT ItemId, ItemCode, Quantity FROM Sale_Details
)
SELECT ItemId, ItemCode, SUM(Quantity) AS Quantity
FROM CTE_Detail
GROUP BY ItemId, ItemCode
As far as your question about "what is better", that depends on how you want to use it. Obviously, there is a little additional complexity in this query by having them separate, but it might help you in other areas. I believe you said they are views, so can't you have it both ways?
This should do it
with purchases as (
select
ItemId,
ItemCode,
sum(quantity) as purchase_sum
from
Purchase_Detail
group by
1,
2
),
sales as (
select
ItemId,
ItemCode,
sum(quantity) as sales_sum
from
Sales_Details
group by
1,
2
)
select
ItemId,
ItemCode,
(
purchase_sum - sales_sum
) as net
from
purchases
join sales
using (
ItemId,
ItemCode
)

Calculation of balance after each transaction

I have table like this:
cust_id acc_no trans_id trans_type amount
1111 1001 10 credit 2000.0
1111 1001 11 credit 1000.0
1111 1001 12 debit 1000.0
2222 1002 13 credit 2000.0
2222 1002 14 debit 1000.0
I want a Hive query or sql query for every transaction done by a customer the balance should be calculated so.
I want output as follows:
cust_id acc_no trans_id trans_type amount balance
1111.0 1001.0 10.0 credit 2000.0 2000.0
1111.0 1001.0 11.0 credit 1000.0 3000.0
1111.0 1001.0 12.0 debit 1000.0 2000.0
2222.0 1002.0 13.0 credit 2000.0 2000.0
2222.0 1002.0 14.0 debit 1000.0 1000.0
I've tried
SELECT *
FROM (SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
CASE
WHEN Trim(trans_type) = 'credit' THEN ball =
Trim(bal) + Trim(amt)
ELSE ball = Trim(bal) - Trim(amt)
end
FROM ban) l;
This query will do the trick :
SELECT t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount,
sum(t2.amount*case when t2.trans_type = 'credit' then 1
else -1 end) as balance
FROM Table1 t1
INNER JOIN Table1 t2 ON t1.cust_id = t2.cust_id AND
t1.acc_no = t2.acc_no AND
t1.trans_id >= t2.trans_id
GROUP BY t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount
See SQLFIDDLE : http://www.sqlfiddle.com/#!2/3b5d8/15/0
EDIT :
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE Table1
(`cust_id` int, `acc_no` int, `trans_id` int,
`trans_type` varchar(6), `amount` int)
;
INSERT INTO Table1
(`cust_id`, `acc_no`, `trans_id`, `trans_type`, `amount`)
VALUES
(1111, 1001, 10, 'credit', 2000.0),
(1111, 1001, 11, 'credit', 1000.0),
(1111, 1001, 12, 'debit', 1000.0),
(2222, 1002, 13, 'credit', 2000.0),
(2222, 1002, 14, 'debit', 1000.0)
;
Query 1:
SELECT t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount,
sum(t2.amount*case when t2.trans_type = 'credit' then 1
else -1 end) as balance
FROM Table1 t1
INNER JOIN Table1 t2 ON t1.cust_id = t2.cust_id AND
t1.acc_no = t2.acc_no AND
t1.trans_id >= t2.trans_id
GROUP BY t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount
Results:
| CUST_ID | ACC_NO | TRANS_ID | TRANS_TYPE | AMOUNT | BALANCE |
|---------|--------|----------|------------|--------|---------|
| 1111 | 1001 | 10 | credit | 2000 | 2000 |
| 1111 | 1001 | 11 | credit | 1000 | 3000 |
| 1111 | 1001 | 12 | debit | 1000 | 2000 |
| 2222 | 1002 | 13 | credit | 2000 | 2000 |
| 2222 | 1002 | 14 | debit | 1000 | 1000 |
A simple solution is to quantify each transaction (- or +) based on trans_type and then get cumulative sum using window function .
SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
Sum (real_amount)
OVER (ORDER BY cust_id) AS balance
FROM (SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
( CASE trans_type
WHEN 'credit' THEN amount
WHEN 'debit' THEN amount *- 1
END ) AS real_amount
FROM test) t
You could do this easily through a View, calculating this directly on the table is possible but leads to performance and scalability issues (the database will slow down as the table grows). By using a View the calculation is performed as-needed; if you index the view you can keep the balances up to date without impacting the performance of the transaction table.
If you really insist on it being in the transaction table itself you could possibly use a calculated column which runs a user-defined function to determine the current balance. However this will depend largey on the specific SQL backend you're using.
Here's a basic SELECT Statement which calculates the current balance by Account:
select
acc_no,
sum(case trans_type
when 'credit' then amount
when 'debit' then amount * -1
end) as Amount
from Transactions
group by acc_no
You can use window function:
select cust_id,
acc_no, trans_id, trans_type, amount,
sum(pre_balance) over (partition by cust_id order by trans_id) as balance
from
(select cust_id, acc_no, trans_id, trans_type,
amount,
amount as pre_balance from test
where trans_type = 'credit'
union
select cust_id, acc_no, trans_id, trans_type,
amount, -amount as pre_balance from
test where trans_type = 'debit'
order by trans_id) as sub;
with current_balances as (
SELECT
id,
user_id,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created ASC) as current_balance
FROM payments_transaction pt
ORDER BY created DESC
)
SELECT
pt.id,
amount,
pt.user_id,
cb.current_balance as running_balance
FROM
payments_transaction pt
INNER JOIN
current_balances cb
ON pt.id = cb.id
ORDER BY created DESC
LIMIT 10;
This will work very efficiently for big returns, and won't break on filtering or limiting. Please note that if you select only for one user or a subset of them, provide user_id filter in both current_balances cte, and the main select to omit whole table scan.
Table (Transaction)
-
"id" "amount" "is_credit"
1 10000 1
2 2000 0
3 5000 1
Query :
SELECT *
FROM (
SELECT id, amount, SUM(CASE When is_credit=1 Then amount Else -amount End) OVER (ORDER BY id) AS balance
FROM `Transaction`
GROUP BY id, amount
)
ORDER BY id ;
Output :
"id" "amount" "is_credit" "balance"
1 10000 1 10000
2 2000 0 8000
3 5000 1 13000

Query to transform table in SQL

My query is as below
SELECT SupplierId,StakeholderSupplierId,Percentage
FROM std_Stakeholders
ORDER BY SupplierId
which yields me result as below
SupplierId StakeholderSupplierId Percentage
1 3175 68.00
2929 5504 25.00
5504 1 68.25
5504 3238 50.00
5504 2810 23.00
I want to transform this output as .
SupplierId StakeholderSupplierId1 Percentage StakeholderSupplierId2 Percentage StakeholderSupplierId3 Percentage
1 3175 68.00
2929 5504 25.00
5504 1 68.68 3238 50.00 2810 23.00
I tried with joins and pivoting . couldn't succeed in writing a proper query to get the desired output . Can anyone help me out
Note: StakeholderSupplierId is not static . it may vary from 1 to n
I don't know if this is a solution for your problem (depends on what you need to do after this query), but for problems like this I have use the MySQL GROUP_CONCAT function. For a similar solution in SQL Server check this question.
With a GROUP_CONCAT function you can get a result table like this:
SupplierId StakeholderSupplierIds Percentages
1 3175 68.00
2929 5504 25.00
5504 1, 3238, 2810 68.68, 50.00, 23.00
Well, you can do this with a two-step process but equally I expect it is possible with a PIVOT - however as an example (old style approach for the last step):
declare #stakeholdersForPivot table
(
SupplierId int,
StakeholderId int,
StakeholderSupplierId int,
Percentage decimal(19,2)
)
; with orderedStakeholders as
(
select SupplierId
, ROW_NUMBER() over (partition by SupplierId order By StakeholderSupplierId) StakeholderId
,StakeholderSupplierId, Percentage
from std_Stakeholders
)
insert into #stakeholdersForPivot (SupplierId, StakeholderId, StakeholderSupplierId, Percentage)
select SupplierId, StakeholderId, StakeholderSupplierId, Percentage
from orderedStakeholders
select SupplierId
, sum(case when StakeholderId = 1 then StakeholderSupplierId else null end) StakeholderSupplierId1
, sum(case when StakeholderId = 1 then Percentage else null end) Percentage1
, sum(case when StakeholderId = 2 then StakeholderSupplierId else null end) StakeholderSupplierId2
, sum(case when StakeholderId = 2 then Percentage else null end) Percentage2
, sum(case when StakeholderId = 3 then StakeholderSupplierId else null end) StakeholderSupplierId3
, sum(case when StakeholderId = 3 then Percentage else null end) Percentage3
from #stakeholdersForPivot
group by SupplierId
order by SupplierId
Obviously you have to be explicit about the maximum number of stakeholder suppliers you're expecting per supplier but you can extend this as required.
Here's the query that uses the "GROUP BY MAX" trick. Should work across databases. The drawback is that the columns list is static.
SELECT
supplierid,
max(StakeholderSupplierId1) AS StakeholderSupplierId1,
max(percentage1) AS percentage,
max(StakeholderSupplierId2) AS StakeholderSupplierId2,
max(percentage2) AS percentage,
max(StakeholderSupplierId3) AS StakeholderSupplierId3,
max(percentage3) AS percentage
FROM
(
SELECT
supplierid,
CASE WHEN ranking = 1 THEN stakeholdersupplierid END AS StakeholderSupplierId1,
CASE WHEN ranking = 1 THEN percentage END AS percentage1,
CASE WHEN ranking = 2 THEN stakeholdersupplierid END AS StakeholderSupplierId2,
CASE WHEN ranking = 2 THEN percentage END AS percentage2,
CASE WHEN ranking = 3 THEN stakeholdersupplierid END AS StakeholderSupplierId3,
CASE WHEN ranking = 3 THEN percentage END AS percentage3
FROM
(
SELECT
supplierid,
stakeholdersupplierid,
percentage,
rank() OVER (PARTITION BY supplierid ORDER BY percentage DESC) AS ranking
FROM
std_stakeholders
) AS t
) AS t
GROUP BY
supplierid
;

Running Total on date column

I have the following data in my table:
id invoice_id date ammount
1 1 2012-01-01 100.00
20 1 2012-01-31 50.00
470 1 2012-01-15 300.00
Now, I need to calculate running total for an invoice in some period. So, the output for this data sample should look like this:
id invoice_id date ammount running_total
1 1 2012-01-01 100.00 100.00
470 1 2012-01-15 300.00 400.00
20 1 2012-01-31 50.00 450.00
I tried with this samples http://www.sqlusa.com/bestpractices/runningtotal/ and several others, but the problem is that I could have entries like id 20, date 2012-01-31 and id 120, date 2012-01-01, and then I couldn't use NO = ROW_NUMBER(over by date)... in first select and then ID < NO in second select for calculating running total.
DECLARE #DateStart DATE='2012-01-01';
WITH cte
AS (SELECT id = Row_number() OVER(ORDER BY [date]),
DATE,
myid = id,
invoice_id,
orderdate = CONVERT(DATE, DATE),
ammount
FROM [Table_2]
WHERE DATE >= #DateStart)
SELECT myid,
invoice_id,
DATE,
ammount,
runningtotal = (SELECT SUM(ammount)
FROM cte
WHERE id <= a.id)
FROM cte AS a
ORDER BY id