有没有一种方法可以仅计算sqlite中现有产品的平均值?

时间:2019-11-01 10:28:56

标签: python sqlite

我正在尝试创建一个商店程序,但是遇到了需要您帮助解决的问题。该程序将具有以下信息的行插入数据库StoringTF中:

  • 商店地点代码(适用于公司)
  • 商店的名称(根据代码)
  • 入境日期
  • 产品代码
  • 产品名称
  • 收货数量
  • 单位购买价格
  • 总购买价
  • 寄出数量
  • 单位售价
  • 总售价
  • 条目说明

我希望将单价自动设置为同一产品的单价的平均值 我尝试使用此代码

c.execute("""WITH cte AS (
    SELECT Product_Name, AVG(Unit_Buying_Price) AS AveragePrice 
FROM StoringTF 
GROUP BY Product_Name
)
UPDATE StoringTF
SET Unit_Selling_Price = (
SELECT AveragePrice
FROM cte
WHERE Product_Name = StoringTF.Product_Name 
)""") 

但是它做错了两件事。

  1. 它更新表中同一产品的所有先前值 这是不正确的。
  2. 它计算不存在产品的平均值,这使得 储值错误。

我希望输出是这样的。

  1. 我希望它为仅插入而不是插入的行插入它 编辑上一行
  2. 第二个我希望它仅针对现有产品进行计算

例如: 如果我3个月前以1000美元的价格购买了一台显示器 然后我卖掉了(所以平均价格是1000美元,这很好) 然后今天我花了2000美元买了同一个显示器。 我希望平均值是2000美元而不是1500美元。

这是表的架构,可以使事情变得更清楚。

c.execute("""
CREATE TABLE StoringTF (
Store_code INTEGER,
Store TEXT,
Product_Date TEXT,
Permission INTEGER,
Product_Code INTEGER,
Product_Name TEXT,
Incoming INTEGER,
Unit_Buying_Price INTEGER,
Total_Buying_Price INTEGER,
Outgoing INTEGER,
Unit_Sell_Price INTEGER,
Total_Sell_Price INTEGER,
Description TEXT)
        """)


2 个答案:

答案 0 :(得分:1)

  
      
  1. 它会为同一产品更新表中的所有先前值,这是不正确的。
  2.   

您必须准确指定要更新的记录 。即使您具有日期字段或具有相同的Product_Name的多行,数据库也不具有“上一个”值的自动概念。该语句完全按照您的指示进行操作...根据WHERE Product_Name = StoringTF.Product_Name更新与名称匹配的任何和所有行。您为什么期望它做其他任何事情?

  
      
  1. 它计算不存在产品的平均值,这会使商店价值错误。
  2.   

从本质上讲,这与第一个问题完全相同:数据库将包含与您的条件匹配的所有行。您说过仅对Product_Name进行分组,这就是它所做的。再一次,没有“不存在”产品的自动概念。 您必须在WHERE子句中添加某些内容和/或更新GROUP BY子句,以区分现有产品和不存在的产品。您甚至没有提供足够的详细信息来让其他人确定该事实,因此如何数据库是否知道要排除“不存在”的产品?

  
      
  1. 我希望它只为要插入的行插入它,而不要编辑先前的行
  2.   

您的代码执行UPDATE语句。如果要插入新行,则需要执行以下操作:执行INSERT语句。 UPDATE语句更新现有行。 INSERT语句插入新行。

  
      
  1. 第二个我希望它仅针对现有产品进行计算
  2.   

前两点的答案相同。


我建议研究数据规范化。有关数据规范化的基本思想是避免冗余和重复的信息。在关系数据库中,这是通过创建与主键和外键链接的多个表来完成的。

例如,在一个表中,您定义的产品仅包含不会随时间变化的信息...类似Product_Name或Product_Code的信息,并为每一行分配唯一的ProductID值。为每个商店定义一个单独的表,其中包含各种商店详细信息和唯一的主键StoreID值。

在另一个表中,您可以存储诸如买卖之类的交易。事务表将包含外键ProductID和StoreID列。您实际上并没有在交易表中存储产品或存储明细,而仅是美元金额和其他交易明细。通过外键ID值检索有关产品和商店的所有详细信息。更好的是将销售和购买划分到单独的表中,但这是更高级的一步。

更多建议开始超出这一问题的范围,但是还有其他方法可以标准化交易数据,以便更轻松地获取最新平均值并选择“仅当前产品”等。 / p>


建议的部分解决方案

尽管我做出了更好的判断,但我还是发布了更多细节,希望对您有所帮助。一般来说,对于发布长期的完整解决方案,StackOverflow变得更加开放和容忍。

以下内容无论如何都不是完整的解决方案,但其中包含示例架构和查询,它们可以用作完整解决方案的一部分。该问题尚不清楚所有必要的细节,但以下内容展示了一定程度的规范化。我当然不包括对现有数据的任何迁移查询,因为此类工作和细节应由OP处理。

这仍然不能回答选择“仅现有产品”的问题,因为您需要进一步定义。我不知道“仅现有产品意味着什么”。您只说存货吗?从表模式中还不清楚您是要在每行中存储项目总数还是每行是否是单个事务。

CREATE TABLE Stores (
    Store_code INTEGER PRIMARY KEY, 
    Store TEXT NOT NULL UNIQUE
)

CREATE TABLE Products ( 
    Product_Code INTEGER PRIMARY KEY,
    Product_Name TEXT NOT NULL UNIQUE,
    Description TEXT, 
    Product_Date TEXT -- Is this the transaction date? 
)

-- Not exactly sure what these columns are for, 
-- so I don’t know precisely where they fit in a normalized schema
    -- Permission INTEGER, -- Not sure what this is for 
    -- Incoming INTEGER, -- Same as purchased quantity?  
    -- Outgoing INTEGER, -- Same as sold quantity? 

CREATE TABLE Sales (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
    Store_code INTEGER NOT NULL REFERENCES Stores(Store_code),
    Product_Code INTEGER NOT NULL REFERENCES Products(Product_Code),
    TransactionDate AS DATETIME,
    Unit_Sell_Price CURRENCY NOT NULL,
    Quantity INTEGER NOT NULL
)

CREATE TABLE Purchases (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
    Store_code INTEGER NOT NULL REFERENCES Stores(Store_code),
    Product_Code INTEGER NOT NULL REFERENCES Products(Product_Code),
    TransactionDate AS DATETIME,
    Unit_Buying_Price CURRENCY NOT NULL,
    Quantity INTEGER NOT NULL
)

以下查询演示了如何插入新购买的产品。该INSERT语句使用SQL参数语法表示它需要SQLite数据库外部语言/环境中的输入值。 (有关如何正确执行这样的语句的详细信息(包括如何传递输入值的信息,此处不再赘述,应单独研究)。

INSERT INTO Sales (Store_code, Product_Code, TransactionDate, Unit_Sell_Price, Quantity)
    VALUES (@storecode, @productcode, @trandate, 
        (SELECT AVG(Unit_Buying_Price) AS AveragePrice  
         FROM Purchases 
         WHERE Store_code= @storecode AND Product_Code = @productcode), 
        @quantity)

请注意,总价未存储在SalesPurchases交易表中,而是通过类似以下查询的方式动态计算总价。

CREATE VIEW PurchaseDetails AS
    SELECT *, Unit_Buying_Price * Quantity AS Total_Buying_Price
    FROM Purchases

答案 1 :(得分:0)

仅在构造了规范化的架构并编写了其他答案并获得OP澄清的注释后,我才最终意识到了原始问题的意图。我不为其他答案道歉,因为很容易假设所有问题都与非规范化表有关,并且误解了“现有”的含义。关于更新vs插入以及存储计算的数据而不是在规范化表上使用查询等也存在混淆。最终,我意识到“现有”的意思是“现货”或“现货”产品...

最初的问题是获取仅未售出商品的平均价格(即现货)。或换句话说,仅获取最近购买的商品的平均价格。

这不是一个琐碎的查询,因为购买和销售的数量可能不同,并且库存中的剩余物品可能会“划分”单个购买交易。除了这一特殊挑战之外,一个关键思想是必须使用多个子查询完成此操作。对于sqlite,我们可以使用本质上名为子查询的通用表表达式(CTE):

  1. 一组子查询获取总购买(传入)数量和总销售(外出)数量,其中两者之差为库存(即“现有”)数量。
  2. 另一个查询必须计算一个总和,以确定先前所有库存已售出的关键交易。这样的临界值很可能不会完全落在交易边界上,因此临界购买交易将在一些已售数量和一些库存数量之间分配。尽管可以使用经典的SQL构造来获得运行总和,但sqlite支持我使用的窗口函数(即OVER子句)。

数学注释:无法通过计算多个部分平均值的简单平均值来获得整个样本的平均值。取而代之的是,要么对部分平均值进行加权,要么对整个样本求和,然后直接计算平均值。这里最简单的方法是简单地将价值(即价格)和数量分别相加,以直接计算总体平均值。这种方法避免了必须分别计算各个平均值的权重并在查询之间传递它们。

最后,在进行实际查询之前,我基于架构和数据的不确定性做出了一些假设。以下代码:

  • 假定每个Product_Date有一个传入交易。如果不正确,则该表需要更多信息(唯一的ID或时间戳)才能正确运行 对事务进行排序和区分,以使多个事务不会 被选为确定“现有”(即库存)产品的关键点。
  • 假定从任何特定的Product_Date / TransactionDate开始,总的出库数量永远不会大于总的入库数量。如果根据实际数据这是错误的,则结果肯定是不准确的,并且可能会返回假金额。

以下内容旨在处理问题的原始架构。 仅此查询仅提供所需的平均购买价格。如果您要使用此查询插入一个以平均值为单位销售价格的新收货/销售记录,则必须将此查询与另一个插入内容结合使用查询。有多种方法可以做到这一点,包括将其保存为sqlite View 或继续添加CTE链以供INSERT语句引用。这项练习留给您。

WITH sums AS (
        -- First get quantity sums for calculating stocked quantities
        SELECT Store_code, Product_Code, 
               ifnull(sum(Outgoing), 0) AS sold, sum(Incoming) AS purchased,
               sum(Incoming) - ifnull(sum(Outgoing), 0) AS stocked
        FROM StoringTF
        GROUP BY Store_code, Product_Code 
     ),
     runningIncomingD AS (
        -- Get a running sum of most-recently purchased items
        SELECT Store_code, Product_Code, Product_Date, Incoming, Unit_Buying_Price,
               sum(Incoming) OVER winStocked as purchased
        FROM StoringTF
        WHERE Incoming > 0 -- Purchased rows only
        WINDOW winStocked AS (PARTITION BY Store_code, Product_Code ORDER BY Product_Date DESC)
        ORDER BY Store_code, Product_Code, Product_Date DESC
     ),
     instockAll AS (
       SELECT r.Store_code, r.Product_Code,
              -- First case is for transactions that are purely in stock (not sold)
              -- Else case is for the critical transaction with partially sold items
              CASE WHEN r.purchased <= s.stocked  THEN r.Incoming
                   ELSE (s.stocked - (r.purchased - r.incoming)) END
              AS stocked,
              CASE WHEN r.purchased <= s.stocked  THEN r.Incoming * r.Unit_Buying_Price
                   ELSE (s.stocked - (r.purchased - r.incoming)) * r.Unit_Buying_Price END
              AS total_price              
       FROM runningIncomingD r INNER JOIN sums s
              ON s.Store_code == r.Store_code AND s.Product_Code == r.Product_Code

       -- Select only transactions that contain quantities not yet sold ("existing")
       WHERE r.purchased - s.stocked < r.incoming
       ORDER BY r.Store_code, r.Product_Code, r.Product_Date DESC
     )
SELECT Store_code, Product_Code,
       -- Get sums of both prices and quantities, then calculate the overall average
       sum(stocked) AS stocked, round(sum(total_price) / sum(stocked), 2) AS ave_price
FROM instockAll
GROUP BY Store_code, Product_Code
ORDER BY Store_code, Product_Code

以下是我在另一个问题中发布的标准化架构的等效查询。几乎相同,只是必须将两个“销售”和“购买”交易表分别相加。

WITH salesSums AS (
        SELECT Store_code, Product_Code, sum(Quantity) AS sold
        FROM Sales
        GROUP BY Store_code, Product_Code 
     ),
     purchaseSums AS (
        SELECT Store_code, Product_Code, sum(Quantity) AS purchased
        FROM Purchases
        GROUP BY Store_code, Product_Code 
     ),
     sums AS (
        SELECT p.Store_code, p.Product_Code, 
        ifnull(s.sold, 0), p.purchased, p.purchased - ifnull(s.sold, 0) AS stocked
        FROM purchaseSums p LEFT JOIN salesSums s
             ON p.Store_code = s.Store_code AND p.Product_Code = s.Product_Code
     ),
     runningIncomingD AS (
        SELECT Store_code, Product_Code, TransactionDate, Quantity, Unit_Buying_Price,
               sum(Quantity) OVER winStocked as purchased
        FROM Purchases
        WINDOW winStocked AS (PARTITION BY Store_code, Product_Code ORDER BY TransactionDate DESC)
        ORDER BY Store_code, Product_Code, TransactionDate DESC
     ),
     instockAll AS (
       SELECT r.Store_code, r.Product_Code,
              CASE WHEN r.purchased <= s.stocked  THEN r.Quantity
                   ELSE (s.stocked - (r.purchased - r.Quantity)) END
              AS stocked,
              CASE WHEN r.purchased <= s.stocked  THEN r.Quantity * r.Unit_Buying_Price
                   ELSE (s.stocked - (r.purchased - r.Quantity)) * r.Unit_Buying_Price END
              AS total_price              
       FROM runningIncomingD r INNER JOIN sums s
              ON s.Store_code == r.Store_code AND s.Product_Code == r.Product_Code
       WHERE r.purchased - s.stocked < r.Quantity
       ORDER BY r.Store_code, r.Product_Code, r.TransactionDate DESC
     )
SELECT Store_code, Product_Code,
       sum(stocked) AS stocked, round(sum(total_price) / sum(stocked), 2) AS ave_price
FROM instockAll
GROUP BY Store_code, Product_Code
ORDER BY Store_code, Product_Code