sql和fifo简单选择

时间:2017-01-23 22:59:53

标签: sql delphi select firebird

我有这样的数据:

Id Price Quantity Value
1   1000    4      4000
2   1000    4.5    4500
3   1000    5      5000

当价格是例如2500的总和时,我会要求数据库行。

我希望回答这个问题:

Id Price Quantity Value
1  1000     4      4000
2  1000     4.5    4500
3   500     5      2500

我可以使用while循环,但我认为select会更聪明。 我的数据库是Firebird。

我在数据库中有3行,例如购买美元。

  • 首先,我为4 PLN买了1000美元,
  • 第二次我为4.5 PLN买了1000美元,
  • 和第三个我为5 PLN买了1000美元。

(PLN是波兰货币)。

现在我想卖2500美元,当然我想知道PLN的美元成本是多少。

在我看来,这笔交易费用:(1000 * 4)+(1000 * 4.5)+(500 * 5)= 12000 PLN。

了解PLN对波兰会计师的成本是多少非常重要。

2 个答案:

答案 0 :(得分:3)

以下显示了如何在SQL中操作美元购买和销售的FIFO模型的基本要素。我已经为MS SQL Server编写并测试了它,将其转换为Firebird SQL并对其进行了测试

因此,请从DollarPurchases表开始,如下所示

CREATE TABLE [dbo].[DollarPurchases](
    [ID] [int] NULL,
    [Price] [float] NULL,
    [Quantity] [float] NULL,
    [Cost]  AS ([Price]*[Quantity])
) 

并向其添加几行

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)

然后,我们可以创建一个包含运行总计

的视图
create view vwcosts as
select 
  * ,
  cumulativecost = (select sum(Cost) from dollarpurchases p2 where p2.id <= p1.id)
from 
  dollarpurchases p1

视图的内容如下所示

ID   Price      Quantity   Cost   cumulativecost
1    1000          4       4000      4000
2    1100          4.5     4950      8950
3    1500          5       7500     16450

现在,假设我们想卖出一定数量的美元。执行此操作的算法可能如下,假设ID列值反映了购买的顺序:

  1. 查找cumulativecost超过要销售的美元金额的行的最低ID。

  2. 标记或删除ID较低的所有行,因为它们需要全部售出以实现金额。

  3. 具有找到的ID的行中的美元将需要全部或部分出售以实现剩余的美元金额。如果需要整体,请按照步骤1删除或标记此行,如果部分更新行,则反映剩余的美元数量及其成本。

  4. 就是这样。

    在下文中,我将不再对实时数据执行这些操作,而是复制DollarPurchases并对其执行操作。我在代码中留下了一些调试语句用于检查目的。

    -- 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  
    

    这会产生

    ID  Price   Quantity    Cost
    2   1100    0.86         950
    3   1500    5           7500
    

    当然,上述内容仅针对最高相关行仅部分销售的情况,但如果将其全部出售,按照步骤1处理它将是微不足道的。

    我认为一个真正的SQL专家可以在一个SQL语句中完成上述所有操作,但我希望这种逐步的方法可以更容易地跟踪正在发生的事情并进行调试。

    值得一提的是,这可以通过逐行处理数据来完成 在while循环中使用SQL游标,但是这可能与在另一个答案中给出的Delphi代码示例有点过分。

    将以上内容翻译成Firebird SQL如下所示。与MS SQL Server版本相比,它有两个主要变化:

    • 我将计算出的cost列重命名为avalue(它本来是value但是出于命名冲突。

    • 由于Firebird SQL不支持以TransactSQL的方式自由使用局部变量,因此我用单行variables表中的条目替换了变量。这使得一些陈述变得更加冗长,但从我的pov到使用Firebird的EXECUTE BLOCK更好。

    代码:

    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;
    

答案 1 :(得分:0)

以下是Delphi代码的示例,Q是TQuery,其他变量是Real。 您应该根据波兰的法律规定添加正确的舍入。

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