【问题标题】:Calculate the exact margin from sales orders and purchase orders根据销售订单和采购订单计算准确的利润
【发布时间】:2018-01-24 14:22:44
【问题描述】:

我正在尝试生成一份报告,该报告从以下数据库中计算保证金。问题是产品的成本(存在于 purchase_order_products 表中)可能会发生变化。

id 为 4022 的产品在 2017-06-08 的成本是 1110,但在 2017-07-25 的成本是 1094。这令人困惑。我无法获得所售每种产品的确切成本。

我编写了一个 PHP 算法,它遍历所有订单和采购订单,并使用最旧的成本到最新的成本。但该算法的时间复杂度非常高。这可以仅使用 mysql 查询来完成吗?

请检查以下场景:

公司在第 1 天为产品 X 创建了一个采购订单:数量 3,成本 10。

客户购买了 2 件产品 X 售价:第 1 天 12 件(库存中仍有 1 件商品,成本为 10)

公司在第 2 天为产品 X 创建了采购订单:数量 4,成本 9。

客户在第 2 天购买了 3 件产品 X 售价:12 件

客户在第 3 天购买了 2 件产品 X 售价:12 件

公司在第 3 天为产品 X 创建了采购订单:数量 2,成本 11。

客户在第 3 天购买了 2 件产品 X 售价:12 件

报告:

第 1 天: 以 12 的价格售出 2 个产品 X,成本 10,利润:2 * (12 - 10)

第 2 天: 以 12 的价格售出 3 个产品 X,1 件商品的成本为 10,2 件商品的成本为 9,

利润:1 * (12 - 10) + 2 * (12 - 9)

第 3 天: 以 12 的价格售出 2 个产品 X,成本 9,利润:2 * (12 - 9)

以 12 的价格售出 2 个产品 X,成本 11,利润:2 * (12 - 11)

因此,新销售产品的利润是根据其相应的成本计算的。希望你明白我的意思。

数据库结构:

数据库中的 4 个产品

产品 上述产品的采购订单

已售产品

Dump File Attached here

【问题讨论】:

  • 你为什么使用最旧的成本?只是想更好地了解您在此处寻找的确切内容。
  • 我已经更新了这个问题。请检查场景。
  • 好的,这确实更清楚了。您的库存记录是否在购买后从 purchase_order_products 表中删除?
  • 这只是一厢情愿,我认为这可以通过任何一种方式进行查询。我会试着把一些东西放在一起。
  • 这不是问题的症结所在:“产品 X 在 Y 天的成本是多少?”如果你能有效地发现这个价值,其余的难道不是“微不足道的”吗?如果是,那么我将解决这个问题。如果不是,请详细说明。

标签: php mysql


【解决方案1】:

您为什么不放轻松,在订单表中添加一个利润列,该列是在客户购买产品时实时计算的。这样您就可以仅根据销售订单计算您的利润,因为这实际上已经以某种方式计算出来以产生售价。当然,这仅适用于未来的销售,但您可以使用现有代码稍加修改来填充旧记录的利润列,并且在更新前您将只为旧交易运行一次此代码。
详细说明:
更改表 "sales_order" 添加 "profit" 列。通过这种方式,您可以使用其他相关列 (total_paid, total_refund, total_due, grand_total) 计算总和,因为您可能希望通过在计算中根据需要包含这些货币字段来更好地控制报告,例如仅使用 total_payed 生成报告,不包括 tota_due 或将其包含在不同类型的报告中,换句话说,您可以仅从该表生成多种报告类型,而不会通过仅添加这一列来压倒数据库系统。
编辑:强>
您还可以在此表中添加成本列以实现快速检索,并最大限度地减少对其他表的连接和查询,如果您想更进一步,您可以为报告添加专用表,这将非常有帮助,例如生成上个月的缺失报告并检查旧订单状态。

【讨论】:

  • 我想我会选择这个解决方案,因为它更简单,更准确,你能解释一下吗?
  • 我想你知道MYsql SUM(profit)函数。
  • 我认为一个关键点是如何选择产品? “先进先出”或通过更低的价格或相反的方式,或者如果它的食品饮料可能是过期日期,或者可能为忠诚的客户保留廉价产品等,但是当客户实际购买产品时会做出这个决定。
  • 使用 FIFO 选择产品。因为最好在我们的库存中保留最新的物品。
  • 我在下面得到了更多的赞成票,我知道为什么。 +1 考虑到这一点,而不会将自己限制在确切的情况/想出一个全面的更好的方法。
【解决方案2】:

一些免责声明:

  • 这是一个辅助逻辑的尝试,所以是粗略的代码(容易受到 SQL 注入攻击,所以不要复制粘贴)
  • 我无法测试此查询,因此其中可能存在错误,只是试图让您走上正确的轨道(和/或将进行后续编辑)
  • 如果您需要每个订单的利润,这将不起作用,仅用于每个产品的利润。如果需要,您可能会得到一个带有 BETWEEN 子句的日期范围。

话虽如此,我认为这样的事情应该适合你:

    $productsIds = array('4022', '4023', '4160', '4548', '4601');
    foreach($productIds as $pid){
        $sql = "SELECT (soi.revenue - sum(pop.cost)) AS profit, sum(pop.cost) AS total_cost, sum(pop.quantity) AS total_purchased, soi.revenue, soi.total_sold 
                    FROM purchase_order_products pop 
                    JOIN (SELECT sum(price) AS revenue, sum(quantity_ordred) AS total_sold FROM sales_order_item WHERE product_id = ".$pid.") AS soi ON soi.product_id = pop.product_id
                    WHERE pop.product_id = ".$pid." GROUP BY pop.product_id HAVING sum(pop.quantity) < soi.total_sold ORDER BY pop.created_at ASC;";
        $conn->query($sql);
        //do what you want with results
    }

这里的关键是在 GROUP BY 之后使用 HAVING 子句来确定您在哪里切断查找购买成本的总和。只要它们在该范围内,您就可以将它们全部相加,并且您可以获得按 created_at 排序的正确日期。

同样,我无法对此进行测试,也不建议按原样使用此代码,只是希望“这里是如何实现这一点的总体思路”有所帮助。

如果我有时间重新创建您的数据库,或者如果您提供带有示例数据的 sql 转储文件,我可以尝试为您提供一个工作示例。

【讨论】:

  • 感谢 TCooper 的努力。该报告称为每日销售报告。所以它应该计算每天的利润。此外,在循环中执行 mysql 查询,肯定会使 mysql 服务器停机。数据库中有大约 10000 种产品。如果你愿意,我可以给你发送一个 sql 转储。
  • 是的,如果您想发送一份样本数据转储,我不介意再看一次。我确实喜欢这样的问题。我会记住循环限制。尽管这可能是有限的。这个报告需要实时生成吗?如果您可以每晚生成一次并存储第二天的结果,您可以在循环中设置一个计时器,以便在下一次查询之前给您的服务器足够的时间。
  • 我已经更新了我的问题。我添加了一个包含一些数据的转储文件的链接。报告需要实时生成。但是,如果这不可能,我可以将其限制为每天一次。
【解决方案3】:

一个对象在给定时间的价格由公式给出:“股票总价/股票总数”。

为此,您需要执行两个查询:

  • 第一个知道已售商品数量(总价和数量)的人:

sql:

SELECT SUM(row_total) sale_total_cost, SUM(quantity_ordered) sale_total_number
    FROM sales_order_item soi
    JOIN sales_order so ON soi.sales_order_id=so.id
    WHERE so.purchase_date<'2017-06-07 15:03:30'
    AND soi.product_id=4160;
  • 第二个知道你买了多少产品

sql:

SELECT SUM(pop.cost * pop.quantity) purchase_total_price, SUM(pop.quantity) purchase_total_number
    FROM purchase_order_products pop
    JOIN purchase_order po ON pop.purchase_order_id=po.id
    WHERE po.created_at<'2017-06-07 15:03:30'
        AND pop.product_id=4160;
  • 2017-02-01 14:23:35处产品4160的价格为:

(purchase_total_price - sale_total_cost) / (purchase_total_number - sale_total_number)

问题是您的“sales_order”表从2017-02-01 14:23:35 开始,而您的“purchase_order”表从2017-06-07 08:55:48 开始。因此,只要您无法从一开始就跟踪所有购买,结果就会不连贯。


编辑:

如果您可以修改表结构并且只对未来的销售感兴趣。

purchase_order_products 表中添加已售商品的数量

您必须修改purchase_order_products 以获得每个产品的消费量:

ALTER TABLE `purchase_order_products` ADD COLUMN sold_items INT DEFAULT 0;

初始化数据

为了让它工作,你必须让 sold_items 列反映你的真实库存

您应该使用以下请求初始化您的表 UPDATE `purchase_order_products` SET sold_items=quantity;

然后使用每个产品的确切库存手动更新表格(这意味着 quantity_ordered-sold_items 必须反映您的实际库存。

这只能做一次。

将采购价格添加到 sales_order_item 表中

ALTER TABLE sales_order_item ADD total_purchase_price INT DEFAULT NULL

输入新的销售订单

当您输入新的销售订单时,您必须使用以下命令获取最旧的采购订单以及剩余物品:

SELECT * FROM `purchase_order_products` WHERE quantity!=sold_items where product_id=4160 ORDER BY `purchase_order_id` LIMIT 1;

然后您必须增加已售商品的值,并计算总购买价格(总和)以填充 total_purchase_price 列。

计算边距

使用 sales_order_item 表中 row_total 和 total_purchase_price 之间的差值可以轻松计算出保证金

【讨论】:

  • 那不准确
  • @Mohammad 这正是我在帖子中所解释的。 结果将是不连贯的,那因为您没有跟踪所有购买。否则,您将获得在给定时间的确切价格,而您没有其他解决方案。
  • 更新数据库没有问题。我也可以从新的未来日期开始,没问题。我只需要一个解决方案来申请并继续使用它。
  • @Mohammad 好的。我会相应地改变我的答案
  • 太好了,谢谢你
【解决方案4】:

感谢您使用 FIFO 方法管理库存。但是,这并不意味着您需要使用 FIFO 来计算保证金。文章https://en.wikipedia.org/wiki/Inventory_valuation 概述了这些选项。 (您所在国家/地区的法规可能会排除某些选项。)

我认为,用于计算单个销售的 FIFO 保证金的可重复解决方案很复杂。期初余额、退货、分批发货、分批发货、乱序处理、盘点调整、货物损坏等都很复杂。

您问题中的数据库结构似乎没有解决这些问题。

通常,这些问题是通过计算该期间库存价值的变化来计算一个期间(日、月等)的利润/利润来解决的。

如果你可以使用平均成本法,你可以用纯SQL计算保证金。我相信其他方法似乎需要一些迭代,因为 SQL 中没有固有的顺序。 (您可以通过创建一个新表并存储之前的周期值来提高性能。)

我不会太担心将整个解决方案放在 SQL 中,因为这似乎不会降低问题的计算复杂性。在数据库引擎中进行尽可能多的计算仍然可能具有速度优势,尤其是在数据集很大的情况下。

您可能会觉得这篇文章很有趣:Set-based Speed Phreakery: The FIFO Stock Inventory SQL Problem。 (那里有一些聪明人!)

【讨论】:

  • 非常有用的答案
猜你喜欢
  • 2016-10-14
  • 1970-01-01
  • 1970-01-01
  • 2020-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-13
  • 2018-05-09
相关资源
最近更新 更多