用于计算“保留”库存项目的 SQL 查询

Posted

技术标签:

【中文标题】用于计算“保留”库存项目的 SQL 查询【英文标题】:SQL Query to calculates 'reserved' inventory items 【发布时间】:2021-07-20 07:20:02 【问题描述】:

我们正在为名为 readoutprobes 和 readoutprobekits 的物品创建一个库存系统。下面的架构被简化了,使用了itemsitemkits这两个词。

itemkit,是一个或多个项目的预定义集合,即一个工具包。在套件中,特定类型的物品只能出现一次。一个套件,通常包含约 40 件物品。 itemkit_item 表捕获了套件中项目的定义。套件的库存记录在 itemkit_containers 表中。

itemkit_container 跟踪物理物品容器。相反,它假设一个物理项目包是正确“组装”的,使用一组物理项目,但我们不知道是哪些。填充后,itemkit_containers 记录中的“填充”字段设置为 true。

items 的库存由 item_containers 表跟踪。它的存在由容器卷监控。当volume为0时,容器被认为是空的。

从 item_container 表中获取特定项目的体积 > 0 的物理项目容器的计数,对于套件也是如此

我们希望为每个项目获得一个“保留计数”编号,以反映套件库存。

例如,假设我们有一个名为 A 的项目,其计数为 42。如果我们正在创建一个包含名为 A 的项目和一个相应的 itemkit_container 的 itemkit,我们希望“reserved”的计数为 1 , 对于项目 A。

项目的“主查询”如下所示:

SELECT items.*,         
    ic.item_count
FROM items
LEFT JOIN (
    SELECT p.id, COUNT(*) item_count, ic.item_id
    FROM  items AS p, item_containers AS ic
    WHERE p.id = ic.item_id AND ic.volume > 0
    GROUP BY p.id
    ) AS ic   
    ON ic.item_id = items.id        
GROUP BY items.id    
ORDER BY items.id;

项目表中的数据:

item_containers 表中的数据:

itemkits 表中的数据:

itemkit_item 表中的数据:

以及itemkit_containers中的数据:

可以看出,itemkit 及其库存的唯一记录包含项目 ID = 1,3的项目

这个问题是要找出如何查询“免费”(或保留)物理项目的数量,即 item_containers 库存,在任何一个时间点。

上面的查询,返回这个结果:

我们想要一个额外的字段,指示每个项目的“保留”计数,反映项目和 itemkits 的实际库存状态。

对于上面的数据,这将是

A -> Reserved = 1
B -> Reserved = 0
C -> Reserved = 1
D -> Reserved = 0

创建和填充上述表格的 db fiddle 在这里: DB Fiddle

我们使用的是 mysql 8.0。

注意:下面的答案接近正确。但是,它不会将 item_containers(实际库存)与 itemkit_container 记录相关联,而是将 itemkit 记录相关联。通过将 itemkit_containers 表中的填充字段切换为“0”,这一点变得清晰。即:

即使不再填充套件,输出也会显示相同的“保留”计数。在这种情况下,保留应等于“0”。 这是该案例的小提琴:Fiddle where Reserved should be all '0'

【问题讨论】:

为所有表提供 CREATE TABLE,一些示例数据作为 INSERT INTO 和期望的结果。 @Akina 我用表格和数据更新了问题。我也简化了措辞。用于创建和填充表的 SQL 也是问题的一部分。 请说明您使用的是哪个版本的mysql。 我不明白“填充”列。如果项目 1 有三个容器,体积分别为 0、10、15,那么它有 25 件库存。如果物品 1 是套件 10、套件 20 和套件 30 的一部分,其中套件 10 和 20 被填充而套件 30 没有,那么物品 1 有多少件被保留,多少件是免费的? @Thorsten Kettner。好问题。任何体积 > 0 的物品容器都是“有效的”并且可以成为套件的一部分。体积 > 0 的项目容器被计为“1”,无论其体积如何。在 kit_containers 表中找到的填充标志表示套件已“填充”,并带有相关的物品容器。但是,填充套件不会显示使用了哪个物品容器,只会显示其类型。 【参考方案1】:

首先计算总计并从适当的容器中保留,然后离开连接总计并保留给项目。

SELECT items.id, items.name,
    coalesce(total.v, 0) AS Total_volume,
    coalesce(rsvd.v, 0) AS Reserved
FROM items
LEFT JOIN ( /* item total in item_containers  */
       SELECT item_id, SUM(volume) v
       FROM item_containers 
       GROUP BY item_id
    ) AS total ON items.id = total.item_id
LEFT JOIN( /* item reservation by itemkit_containers  */
       SELECT iki.item_id, count(*) v
       FROM itemkit_containers AS ic
       JOIN itemkit_item AS iki
       ON ic.itemkit_id = iki.itemkit_id AND ic.Populated = 1
       GROUP BY iki.item_id 
    ) AS rsvd ON items.id = rsvd.item_id
ORDER BY items.id;

db-fiddle

【讨论】:

这看起来比我给出的答案要短一些。【参考方案2】:

感谢您提供如此详细的描述和所有必要的示例数据。

正如您已经在查询中尝试过的那样,您可以通过加入 items 和 item_containers 表来获得带有数量的项目。为了计算免费或保留项目,您需要离开连接 itemkit_containsers 表,因为套件中项目的库存存储在那里。因此,只需计算 itemkit_containers 中任何项目的数量,然后您就得到了您的保留数量,并通过从 item_containsers 表的 item_count 中减去它,将为您提供该项目的免费数量。

架构和插入语句:

 CREATE TABLE `items` (
   `id` int NOT NULL AUTO_INCREMENT,
   `name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'oligoname + fluorophore wavelength',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='ReadoutProbes for mFISH Survey';
 
 CREATE TABLE `item_containers` (
   `id` int NOT NULL AUTO_INCREMENT,
   `item_id` int NOT NULL COMMENT 'content of tube',
   `volume` float(12,2) NOT NULL COMMENT 'volume in micro liter (uL)',
   PRIMARY KEY (`id`),
   KEY `fk_item_containers_items` (`item_id`),
   CONSTRAINT `fk_item_containers_items` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=764 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Physical tubes received from vendor';
 
 CREATE TABLE `itemkits` (
   `id` int NOT NULL AUTO_INCREMENT,
   `name` varchar(100) DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `name` (`name`),
   UNIQUE KEY `Unique` (`name`)
 ) ENGINE=InnoDB AUTO_INCREMENT=1030 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='A readout kit is a collection of readouts, and defined in a codebook';
 
 CREATE TABLE `itemkit_containers` (
   `id` int NOT NULL AUTO_INCREMENT,
   `itemkit_id` int NOT NULL,
   `populated` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Field used for checking in checking out a tray',
   PRIMARY KEY (`id`),
   KEY `fk_readoutkit_tray_readoutkits` (`itemkit_id`),
   CONSTRAINT `fk_readoutkit_tray_readoutkits` FOREIGN KEY (`itemkit_id`) REFERENCES `itemkits` (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=1027 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Physical readoutkit_tray';
 
 CREATE TABLE `itemkit_item` (
   `itemkit_id` int NOT NULL,
   `item_id` int NOT NULL,
   UNIQUE KEY `Uniqueness` (`itemkit_id`,`item_id`),
   KEY `fk_readoutkit_item_readout_probes` (`item_id`),
   CONSTRAINT `fk_readoutkit_item_readout_probes` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`),
   CONSTRAINT `fk_readoutkit_item_readoutkits` FOREIGN KEY (`itemkit_id`) REFERENCES `itemkits` (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='associations table for definition of a readout kit';
       
 insert  into `items`(`id`,`name`) values 
 (1,'A'),
 (2,'B'),
 (3,'C'),
 (4,'D');
 
 insert  into `itemkits`(`id`,`name`) values 
 (1,'Kit_1');
 
 insert  into `itemkit_containers`(`itemkit_id`,`populated`) values 
 (1,0);
 
 insert  into `itemkit_item`(`itemkit_id`,`item_id`) values 
 (1,1),
 (1,3);
 
 insert  into `item_containers`(`item_id`,`volume`) values 
 (1,1.00),
 (2,1.00),
 (3,1.00),
 (4,1.00),
 (1,1.00);
 

查询:

select i.id,i.name,sum(ic.volume) as total_volume,
      sum(coalesce(ii.item_count,0)) as Reserved 
      from items i inner join item_containers ic on i.id=ic.item_id
      left join (select item_id,count(*) as item_count from itemkit_containers ic
      inner join itemkit_item i on ic.itemkit_id =i.itemkit_id and ic.populated=1
      group by item_id) ii
      on i.id=ii.item_id
      group by i.id,i.name
      order by i.id,i.name

输出:

id name total_volume Reserved
1 A 2.00 0
2 B 1.00 0
3 C 1.00 0
4 D 1.00 0

dbhere

带有填充和未填充 itemkit_containsers 的 Db-Fiddle:

选择查询(示例数据):

 SELECT * from items;
 SELECT item_id, volume from item_containers;
 SELECT * FROM itemkits;
 SELECT itemkit_id, populated FROM itemkit_containers;
 SELECT * FROM itemkit_item;

输出:

id name
1 A
2 B
3 C
4 D
item_id volume
1 1.00
2 1.00
3 1.00
4 1.00
1 1.00
id name
1 Kit_1
2 Kit_2
itemkit_id populated
1 0
2 1
itemkit_id item_id
1 1
2 2
1 3

查询:

      select i.id,i.name,sum(ic.volume) as total_volume,
      sum(coalesce(ii.item_count,0)) as Reserved 
      from items i inner join item_containers ic on i.id=ic.item_id
      left join (select item_id,count(*) as item_count from itemkit_containers ic
      inner join itemkit_item i on ic.itemkit_id =i.itemkit_id and ic.populated=1
      group by item_id) ii
      on i.id=ii.item_id
      group by i.id,i.name
      order by i.id,i.name

输出:

id name total_volume Reserved
1 A 2.00 0
2 B 1.00 1
3 C 1.00 0
4 D 1.00 0

dbhere

【讨论】:

请查看这个答案,如果我遗漏了什么,请告诉我 我的回复延迟了。这是问题的答案,但是我认为我的问题和输入数据缺少重要的一点。我会更新这个问题并用一些更新的数据做一个小提琴。尽管如此,肯定会奖励赏金! 我将问题更新为更清晰。问题是与 itemkits 相关的 itemcontainers 的答案,而不是 itemkit_containers。我从问题中删除了“免费”的概念,因为“保留”是这里的主要问题。 @TotteKarlsson 感谢您的提琴。我已经改变了我的答案。请看一看。 @TotteKarlsson 我还创建了一个小提琴,我在其中创建了两个 itemkit,其中一个填充状态为 1,另一个在 itemkit_containsers 中填充状态为 0。请检查查询和输出。希望这是您正在寻找的。我认为没有必要这么多的左加入。如果我遗漏了什么,请告诉我。【参考方案3】:

添加了考虑populated column of itemkit_containers 并为reserved counts 提供正确输出的sql 语句。

查询:

SELECT items.*,         
    ic.*,
    v.total_volume,
    COALESCE(item_in_kit.item_count,0) AS Reserved
FROM items
LEFT JOIN (
    SELECT i.id, COUNT(*) item_count, ic.item_id
    FROM  items AS i, item_containers AS ic
    WHERE i.id = ic.item_id AND ic.volume > 0
    GROUP BY i.id
    ) AS ic   
    ON ic.item_id = items.id        
    
LEFT JOIN (
    SELECT items.id, COALESCE(SUM(ic.volume),0) total_volume
    FROM items, item_containers AS ic
    WHERE items.id = ic.item_id
    GROUP BY items.id
    ) AS v
    ON items.id = v.id
    
LEFT JOIN item_containers 
    ON item_containers.item_id = items.id
    
LEFT JOIN (
    SELECT item_id, COUNT(*) AS item_count 
    FROM itemkit_item where itemkit_id not in
    (select itemkit_id from itemkit_containers where populated = 0)
    GROUP BY item_id
    ) item_in_kit
    ON items.id = item_in_kit.item_id        
    
GROUP BY items.id    
ORDER BY items.id;

输出:

id name id item_count item_id total_volume Reserved
1 A 1 2 1 2.00 0
2 B 2 1 2 1.00 0
3 C 3 1 3 1.00 0
4 D 4 1 4 1.00 0

Fiddle with correct reserved

【讨论】:

以上是关于用于计算“保留”库存项目的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server内部加入同一张表

库存 SQL 查询出现歧义错误,其中两个字段应相等以进行计算。 MS-ACCESS

用于根据时间戳计算时间跨度的 SQL 查询

MS Access 2010 查询多次提取相同的记录,sql 挑战

如何为需要从以前的订单计算的库存数量编写 SQL

使用 SQL 查询(MySQL 数据库)获取不同项目的最新“事件”