问题
I have data like this:
Id Price Quantity Value
1 1000 4 4000
2 1000 4.5 4500
3 1000 5 5000
and I would ask for database row when the price is the sum of for example 2500.
I expected to answer this:
Id Price Quantity Value
1 1000 4 4000
2 1000 4.5 4500
3 500 5 2500
Of could I could use while loop but I think select will be more smart. My database is Firebird.
I have 3 lines in database for example buy dollars.
- first, I bought $ 1000 for 4 PLN,
- second I bought $ 1000 for 4.5 PLN,
- and the third I bought $ 1000 for 5 PLN.
(PLN is Polish currency).
And now I would like to sell $ 2500 and of course I would like know how much is dollars cost in PLN.
In my opinion this transaction cost: (1000 * 4) + (1000 * 4.5) + (500 * 5) = 12000 PLN.
This is very important to know how much dollars is cost in PLN to Polish accountant.
回答1:
The following shows the essentials of how to operate a FIFO model of dollar purchases and sales in SQL. I've written and tested it for MS SQL Server translated it into Firebird SQL and tested it
So, start with a DollarPurchases table as follows
CREATE TABLE [dbo].[DollarPurchases](
[ID] [int] NULL,
[Price] [float] NULL,
[Quantity] [float] NULL,
[Cost] AS ([Price]*[Quantity])
)
and add a few rows to it
insert dollarpurchases(id, price, quantity) values (1, 1000, 4)
insert dollarpurchases(id, price, quantity) values (2, 1100, 4.5)
insert dollarpurchases(id, price, quantity) values (3, 1500, 5)
Then, we can create a view which includes a running total
create view vwcosts as
select
* ,
cumulativecost = (select sum(Cost) from dollarpurchases p2 where p2.id <= p1.id)
from
dollarpurchases p1
The contents of the view would look like this
ID Price Quantity Cost cumulativecost
1 1000 4 4000 4000
2 1100 4.5 4950 8950
3 1500 5 7500 16450
Now, suppose we want to sell a certain amount of dollars. An algorithm to do this might be as follows, assuming the ID column value reflects the order in which the purchases were made:
Find the lowest ID of a row whose
cumulativecost
exceeds the dollar amount to sell.Flag or delete all the rows with a lower ID, as they will need to be sold in their entirety to realise the dollar amount.
The dollars in the row with the found ID will need to be sold in whole or in part to realise the remainder of the dollar amount. If it needs to be the whole, delete or flag this row as per step 1, if in part, update the row to reflect the residual quantity of dollars and their cost.
and that's it.
In the following, instead of doing these operations on the live data, I'm going to make a copy of the DollarPurchases and perform the operations on that. I've left a few debugging statements in the code for checking purposes.
-- declare some script variables to use
declare
@dollarstosell float,
@highestrowtosell int,
@dollarsremaining float
select
@dollarstosell = 8000
select * into purchasescopy from dollarpurchases -- create copy of purchases table
select @highestrowtosell = (select min(id) from vwcosts where cumulativecost > @dollarstosell)
select @highestrowtosell -- for debugging
-- calculate how many dollars will remain in the row which will be partially sold
select @dollarsremaining = (select cumulativecost from vwcosts where id = @highestrowtosell) - @dollarstosell
select @dollarsremaining -- for debugging
-- remove the rows which will be sold in toto
delete from purchasescopy where id < @highestrowtosell
--update the row which will be partially sold
update purchasescopy set quantity = @dollarsremaining / price, cost = @dollarsremaining where id = @highestrowtosell
select * from purchasescopy
-- following are optional to tidy up
drop view vwcosts
drop table purchasescopy
This yields
ID Price Quantity Cost
2 1100 0.86 950
3 1500 5 7500
Of course, the above only addresses the case where the highest relevant row is only partially sold, but it would be trivial to deal with it as per step 1 if it is being sold in its entirety.
I imagine that a real SQL expert could do all the above in a single SQL statement, but I hope this step-by-step approach makes it easier to follow what's going on and to debug it.
It's worth mentioning that this could all be done processing the data row-by-row
using a SQL cursor in a while
loop, but maybe that's a bit too much like the Delphi code example you've been given in the other answer.
A translation of the above into Firebird SQL is shown below. It has two main changes compared to the MS SQL Server version:
Ihe computed
cost
column is renamed toavalue
(it would have beenvalue
but for a naming conflict).As Firebird SQL does not support the free use of local variables in the way TransactSQL does, I replaced the variable by entries in a single-row
variables
table. This makes some of the statements a bit wordier but is preferable from my pov to doing it with Firebird'sEXECUTE BLOCK
.
The code:
create table dollarpurchases(id int, price float, quantity float, avalue computed by (price*quantity));
create table purchasescopy(id int, price float, quantity float);
create view vwDollarPurchases as select p1.*, (select sum(avalue) from dollarpurchases p2 where p2.id <= p1.id) as cumulativevalue from dollarpurchases p1;
create table variables(ID int, dollarstosell float, highestrowtosell int, dollarsremaining float);
insert into dollarpurchases(id, price, quantity) values (1, 1000, 4);
insert into dollarpurchases(id, price, quantity) values (2, 1100, 4.5);
insert into dollarpurchases(id, price, quantity) values (3, 1500, 5);
insert into variables(ID, dollarstosell, highestrowtosell, dollarsremaining)
values(1, 8000, 0, 0);
insert into purchasescopy(id, price, quantity) select id, price, quantity from dollarpurchases;
update variables set highestrowtosell = (select min(id) from VWDOLLARPURCHASES where cumulativevalue > dollarstosell) where id = 1;
update variables v1 set v1.dollarsremaining = (select distinct v2.cumulativevalue from VWDOLLARPURCHASES v2 where v2.id = v1.highestrowtosell) - v1.dollarstosell where v1.id = 1;
delete from purchasescopy where id < (select highestrowtosell from variables where id = 1);
update purchasescopy set quantity = (select dollarsremaining from variables where id = 1) / price where id = (select highestrowtosell from variables where id = 1);
select * from purchasescopy;
回答2:
Here is example of Delphi code, Q is TQuery, other variables are Real. You should add correct rounding depending on law regulations in Poland.
USD := 2500; // how much dollars
PLN := 0; // used to store PNL
USDTemp := 0; // temporary variable
Q.Sql.Add('Select * from Table1 WHERE ... ORDER BY ID'); // all awailable dollars
Q.Open;
while not Q.EOF and (USDTemp < USD) do // while there is dollars and not to much
begin
if USDTemp+Q.FieldByName('USD').AsFloat <= USD then // if will not excede needed dollars
begin
PLN := PLN + Q.FieldByName('USD').AsFloat;
USDTemp := USDTemp + Q.FieldByName('USD').AsFloat;
end
else // only part of available dollars is needed
begin
PLN := PLN + ((USD-USDTemp)/Q.FieldByName('USD').AsFloat);
USDTemp := USD;
end;
Q.next;
end;
Q.Close;
ExchangeRate := PLN/USDTemp; // dividing with USDTemp because maybe there was not enough dollars, result is exchange rate, and then you add margin
来源:https://stackoverflow.com/questions/41817224/sql-and-fifo-simple-select