Есть ли способ подсчитать среднее только по существующим продуктам в sqlite? - PullRequest
0 голосов
/ 01 ноября 2019

Я пытаюсь создать программу магазина и столкнулся с проблемой, которую мне нужна ваша помощь. Программа вставляет в базу данных строку со следующей информацией в таблицу 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 
)""") 

, но это делает 2 вещи неправильно.

  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 ]

1 голос
/ 01 ноября 2019
  1. Обновляет все предыдущие значения в таблице для того же продукта, что не соответствует действительности.

Необходимо указать точно , какие записичто вы хотите обновить. База данных не имеет автоматического понятия «предыдущие» значения, даже если у вас есть поле даты или несколько строк с одинаковым Product_Name. Оператор делает именно то, что вы сказали ему сделать ... обновить любую и все строки, которые соответствуют именам в соответствии с WHERE Product_Name = StoringTF.Product_Name. Почему вы ожидаете, что он сделает что-то иначе?

Он вычисляет среднее значение для несуществующих продуктов, что приводит к неправильному значению магазина.

По сути, это та же проблема, что и первая: база данных будет содержать все строки. которые соответствуют вашему состоянию. Вы сказали, что группа только на Product_Name, так что это то, что он сделал. Еще раз не существует автоматического понятия «несуществующий» продукт. Вы должны добавить что-то в свое предложение WHERE и / или обновить предложение GROUP BY, чтобы отличить существующие продукты от несуществующих продуктов. Вы даже не предоставили достаточно информации другому человеку, чтобы определить этот факт, так как бызнаете, что база данных исключает «несуществующие» продукты?

  1. Я хочу, чтобы она вставлялась только для этой вставляемой строки и не редактировала предыдущие строки

Ваш код выполняет инструкцию UPDATE. Если вы хотите вставить новую строку, то вам нужно сделать именно это ... выполнить инструкцию INSERT. Оператор UPDATE обновляет существующие строки. Инструкция INSERT вставляет новые строки.

Во-вторых, я хочу рассчитать его только для существующих продуктов

Тот же ответ для первых двух пунктов.


Я предлагаю исследовать нормализацию данных. Основная идея нормализации данных состоит в том, чтобы избежать избыточной и дублирующей информации. В реляционной базе данных это делается путем создания нескольких таблиц, которые связаны с первичными и внешними ключами.

Например, в одной таблице вы определяете продукты только с информацией, которая не меняется со временем ... что-то вроде Product_Name или Product_Code, и присваивает уникальные значения ProductID каждой строке. Определите отдельную таблицу для каждого магазина с различными сведениями о магазине и уникальным значением первичного ключа StoreID.

В другой таблице хранятся такие транзакции, как покупка и продажа. Таблица транзакций будет включать столбцы ProductID и StoreID внешнего ключа. Вы на самом деле не храните информацию о товаре или магазине в таблице транзакций, только суммы в долларах и другие детали транзакции. Все детали о продуктах и ​​магазине извлекаются через значения идентификатора внешнего ключа. Еще лучше разделить продажи и покупки на отдельные таблицы, но это еще один сложный шаг.

Дополнительные предложения начинают выходить за рамки этого одного вопроса, но существуют и другие способы нормализации транзакционных данных, чтобыстановится легче получать последние средние значения и выбирать «только текущие продукты» и т. д.


Предлагаемое частичное решение

Несмотря на мои лучшие суждения, я выкладываю более подробную информациючто я надеюсь будет полезным. 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)

Обратите внимание, что итоговые цены не сохраняются ни в одном из них. Таблицы транзакций Sales или Purchases, скорее итоги рассчитываются динамически с помощью запроса, подобного следующему.

CREATE VIEW PurchaseDetails AS
    SELECT *, Unit_Buying_Price * Quantity AS Total_Buying_Price
    FROM Purchases
0 голосов
/ 03 ноября 2019

Я наконец понял смысл первоначального вопроса только после создания нормализованной схемы и написания моего другого ответа и получения разъясняющих комментариев от ОП. Я не извиняюсь за свой другой ответ, потому что было легко предположить, что все проблемы были связаны с ненормализованными таблицами и неправильным пониманием того, что означало «существующее». Также возникла путаница в отношении обновлений по сравнению со вставками и хранения вычисленных данных вместо использования запросов к нормализованным таблицам и т. Д. В конце концов я понял, что «существующие» означают «в наличии» или «в наличии» продукты ...

Первоначальная проблема заключалась в том, чтобы получить среднюю цену только тех товаров, которые еще не были проданы (то есть на складе, под рукой);или, другими словами, чтобы получить среднюю цену только для самых последних купленных предметов.

Это не тривиальный запрос, поскольку покупки и продажи могут происходить в разных количествах, и на складе могут оставаться оставшиеся товары. что «разделить» одну сделку покупки. Помимо этой конкретной задачи, одна из ключевых идей заключается в том, что это должно быть сделано с помощью нескольких подзапросов . В случае sqlite мы можем использовать Common Table Expressions (CTE), которые по сути называются подзапросами:

  1. Один набор подзапросов получает общее количество покупок (входящее) и общее количество проданных (исходящих) количеств, гдеразница между ними - это запасенное (т.е. «существующее») количество.
  2. Другой запрос должен вычислить промежуточную сумму для определения критической транзакции, при которой весь предыдущий запас был продан. Весьма вероятно, что такие критические значения не будут точно соответствовать границам транзакции, поэтому критическая транзакция покупки будет разделена между некоторыми проданными и некоторыми запасами. Хотя текущие суммы могут быть получены с использованием классических конструкций SQL, sqlite поддерживает используемые мной функции окна (т. Е. Предложение OVER).

Математическое примечание : среднее по целому образцу НЕ МОЖЕТ бытьполучается путем вычисления простого среднего из нескольких частичных средних. Вместо этого либо частичные средние значения должны быть взвешенными , либо вся сумма должна быть суммирована, а среднее значение вычислено напрямую. Самым простым подходом здесь было просто суммировать значения (т.е. цены) и количества отдельно для непосредственного расчета общего среднего. При таком подходе не требуется отдельно рассчитывать весовые коэффициенты для отдельных средних и передавать их между запросами.

Наконец, перед фактическими запросами я сделал некоторые предположения, основанные на неопределенности схемы и данных. Следующий код:

  • предполагает, что единственная входящая транзакция на Product_Date. Если это не так, то таблице требуется больше информации (уникальный идентификатор или временная метка) для правильной сортировки и различения транзакций, так что несколько транзакций не выбираются в качестве критической точки для определения «существующих» (то есть укомплектованных) продуктов.
  • предполагает, что общее исходящее количество никогда не превышает общее поступающее количество на конкретную дату Product_Date / TransactionDate. Если это будет неверно в соответствии с фактическими данными, результаты, безусловно, будут неточными и могут вернуть поддельные суммы.

Следующее предназначено для работы с исходной схемой вопроса. Только этот запрос предоставляет только желаемую среднюю цену покупки. Если вы хотите использовать эту функцию для вставки новой записи входящих / продаж со средним значением в качестве цены продажи за единицу, то вы должны объединить этот запрос с другим запросом на вставку. ,Есть несколько способов сделать это, включая , сохраняя это как представление sqlite или, возможно, продолжая цепочку 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...