Changes of product price in database design

Ang*_*aya 7 mysql database database-design

I have some issues about my POS database design, supposed I have products table with column id, product_name, price, and stocks, and then table transactions, and transaction_details if someone bought some products I have one transaction record and some transaction detail record, for now I copy value of price from product table into transaction_details, so if the product price is changed, they can't affect the transaction history/report, but I consider separate prices into another table, let's say product_prices, each product have many prices, and the transaction_details related with product_prices instead direct product itself. Is my approach better or worse correspond data integrity, performance or efficiency about data itself. and I have stock in products table, is it needed to or I just fetch from purchasing transaction subtract unit_price from transaction_details. Thank you for your answers.

数据库设计图像

-- -----------------------------------------------------
-- Table `mydb`.`transaction`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`transaction` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `date` DATETIME NOT NULL,
  `total` DOUBLE UNSIGNED NOT NULL,
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`products`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`products` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `product_name` VARCHAR(255) NOT NULL,
  `description` VARCHAR(1000) NULL,
  `price` DOUBLE UNSIGNED NOT NULL,
  `stocks` INT NOT NULL DEFAULT 0,
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`transaction_details`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`transaction_details` (
  `transaction_id` INT NOT NULL,
  `products_id` INT NOT NULL,
  `discount` DOUBLE NOT NULL,
  `unit_price` DOUBLE NOT NULL,
  `quantity` INT NOT NULL DEFAULT 1,
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`transaction_id`, `products_id`),
  INDEX `fk_transaction_details_products1_idx` (`products_id` ASC),
  CONSTRAINT `fk_transaction_details_transaction`
    FOREIGN KEY (`transaction_id`)
    REFERENCES `mydb`.`transaction` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_transaction_details_products1`
    FOREIGN KEY (`products_id`)
    REFERENCES `mydb`.`products` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`purchasing`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`purchasing` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `products_id` INT NOT NULL,
  `date` DATETIME NOT NULL,
  `purchasing_price` DOUBLE NOT NULL,
  `quantity` INT NOT NULL,
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`, `products_id`),
  INDEX `fk_purchasing_products1_idx` (`products_id` ASC),
  CONSTRAINT `fk_purchasing_products1`
    FOREIGN KEY (`products_id`)
    REFERENCES `mydb`.`products` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;
Run Code Online (Sandbox Code Playgroud)

Joe*_*own 8

我知道看起来您通过将价格保留在交易表上来非规范化价格,并且非规范化感觉“不好”,因为我们喜欢遵循最佳实践。但是,在这里考虑您的问题的更好方法是在这种情况下价格没有被非规范化。

您的产品表(或产品历史记录表)上的价格与交易表中的价格在语义上是不同的。

一种是建议零售价(即“应该是”的价格),一种是支付的(“实际”)价格。是的,这些在大多数情况下都是相同,但这是巧合。

您应该将实际支付的价格保存在交易表中,无论您是否将价格保存在历史表中。这样做的原因是事务表记录了实际发生的事情。在某种程度上,它是一种一次性写入的日志。如果你能证明实际支付的价格不能根据你的代码的工作方式在以后重新表述,审计人员会更喜欢它。在传统的会计系统中,甚至使用冲销交易而不是编辑来应用更正。

另一件需要考虑的事情是价格可能有例外。例如,如果您决定使用优惠券进行促销怎么办?或者,如果您为“开箱”商品提供 20% 的折扣?这些一次性价格很难在价格历史表中跟踪。

由于这些原因,在事务表中保留实际支付的价格是一个有效的设计决策,而不仅仅是性能或代码简单性的权宜之计。


Vla*_*mir 5

我们通过艰难的方式学会了它......你可能会想到除了产品细节本身和交易细节(或基本上是历史)之外,你会在哪里需要价格。如果您有一些关于产品定价的统计数据,或者您的产品价格可能随时间波动很大(货币交易就是一个很好的例子),那么需要历史价格。但如果不是这种情况,您可能应该选择单独的产品和交易细节。否则,您会减慢整个系统的速度,而不是每笔交易有 1 个重复的(在大多数情况下)值。我希望这是有道理的...

更新 #1

但是股票需要不同的方法来记住同样的事情:始终计算和加载尽可能少的数据。如果您想共同计算一些值,请为此创建 Cron 脚本并将结果存储在 db 中,因为在大多数情况下,从数据库加载比从用户请求的历史记录中计算库存等内容要快。

还要记住,产品可以有不同的颜色/尺寸/其他属性,你绝对不希望将相同的 T 恤放在 3 种颜色和 5 种尺寸中,因为 3*5=15 种不同的产品。所以你需要在某处存储组合和属性,并在每个组合上都有 productId(或者作为优化的一部分,你将在组合中存储比在 prodct 表中更多的数据,以便从一个表中选择而不是连接,但这是另一个问题) .

现在,每种颜色/尺寸都有不同的库存,因此每种属性组合都有您的库存价值。我在自己的结构中唯一的问题是我是否想将库存存储在产品表中(如果产品没有变化,我可以通过分配“默认”组合轻松规范结构),还是应该直接在组合表中存储组合的库存并制作 cron 来计算所有组合的总库存并更新产品中的库存。同样,这完全取决于您的数据使用情况,在我的情况下,我想不出任何地方我需要类似于衣服商店的产品库存的东西(不要犯错误,有很多不同的类似产品)。

更新 #2

并且不要忘记未来的代码扩展。我从未见过一个项目没有在以后甚至公开发布后实现新功能。因此,您不仅应该优化数据,还应该为新模块做好准备,并足够灵活以保持快速。不好的例子是我们当前的 CMS 有产品,然后有人添加了仅包含产品详细信息的重要数据的缓存表,但后来有人发现产品需要更多的变量,因此他们返回了产品表。但是现在这还不够,因为缓存包含产品没有的重要缓存数据,所以它们完全连接在一起,现在不是优化缓存加载,我们在其中实现了三重连接和慢左连接......