基于项目可用性的多对数树的最佳数据库数据结构

elw*_*ood 0 tree database-design hierarchical-data

我目前正在开发一个基于MySQL的网站,但如果有必要,转换到另一个数据库不是问题(如CTE左右).

我正在寻找最好的数据库数据结构(如果可能的话,还有一些SQL代码片段)来处理我当前的项目,如下所示:

  • 产品可以由其他产品(递归地)制成,因此产品必须在面向树的架构中设计.
  • 产品可用于制造许多产品.这就像嵌套集体系结构之类的东西不起作用,因为节点可以是子节点,也可以是许多节点的父节点,使用NestedSet似乎不可能.
  • 产品具有每天可用的数量,但仅限于它是"叶子"(它不是由其他产品制成).否则,其数量将取决于其直接子项的数量(依此类推,直到递归到达叶子).因此,产品可能链接到表"可用性",其中包含"product_id","date"和"quantity".

产品的"树"结构非常稳定,因此更新/插入/删除查询的速度并不重要.

最终目标是能够尽快在SELECT查询中检索指定的两个日期之间可用的所有产品的列表.

这意味着:

  • 1)如果产品不是由另一个产品制成,则如果两个日期之间的每天数量> 0,则可以使用该产品.
  • 2)如果产品是由其他产品制成的,只有当(1)对所有孩子都适用时才可以使用.如果直接的孩子不是"叶子",他们将没有数量,所以(2)的递归将发生,直到它到达最后的叶子.

顺便说一句,在实际情况下,我的产品永远不会有超过5级的深度.将所有父ID存储在单独的列中可能是一个很好(但非常难看)的想法.

egg*_*yal 6

您所描述的数据结构是不是一个(这要求每个节点都只有一个父,但没有父根):确切地说,它是一个更一般的DAG:

有向无环图

其他RDBMS本身支持分层数据和递归查询,而MySQL没有; 相反,用于在MySQL中存储此类数据的良好通用关系模型是创建图的传递闭包的表.使用//作为语句分隔符:

CREATE TABLE Products (
  SKU         SERIAL                COMMENT 'Stock-Keeping Unit code',
  Name        VARCHAR(15)           COMMENT 'Product name',
  Description VARCHAR(255)          COMMENT 'Descriptive text',
  Price       DECIMAL(6,2)          COMMENT 'Selling price',
  isAtomic    BOOLEAN DEFAULT FALSE COMMENT 'Flag indicating atomicity'
)
  ENGINE  = InnoDB,
  COMMENT = 'Properties relating to each product'
//

CREATE TABLE ProductComponents (
  SKU          BIGINT UNSIGNED NOT NULL COMMENT 'Stock-Keeping Unit Code',
  ComponentSKU BIGINT UNSIGNED NOT NULL COMMENT 'SKU of comprised component',
  PRIMARY KEY (SKU, ComponentSKU),
  INDEX       (ComponentSKU, SKU),
  FOREIGN KEY (         SKU) REFERENCES Products (SKU),
  FOREIGN KEY (ComponentSKU) REFERENCES Products (SKU)
)
  ENGINE  = InnoDB,
  COMMENT = 'Transitive closure of the product DAG'
//
Run Code Online (Sandbox Code Playgroud)

可以使用触发器在后一个表中强制执行原子性:

CREATE TRIGGER ins_atomic BEFORE INSERT ON ProductComponents
FOR EACH ROW IF
  NEW.SKU <> NEW.ComponentSKU
  AND (SELECT isAtomic FROM Products WHERE SKU = NEW.SKU)
THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//

CREATE TRIGGER upd_atomic BEFORE UPDATE ON ProductComponents
FOR EACH ROW IF
  NEW.SKU <> NEW.ComponentSKU
  AND (SELECT isAtomic FROM Products WHERE SKU = NEW.SKU)
THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//
Run Code Online (Sandbox Code Playgroud)

您可能还需要一个类似的触发器来阻止不正确的更新Products.isAtomic:

CREATE TRIGGER upd_prod BEFORE UPDATE ON Products
FOR EACH ROW IF NEW.isAtomic AND EXISTS (
  SELECT * FROM ProductComponents WHERE SKU <> ComponentSKU AND SKU = NEW.SKU
) THEN
  SIGNAL SQLSTATE '45000' SET
    MESSAGE_TEXT = 'Atomic product cannot have a component'
  ;
END IF//
Run Code Online (Sandbox Code Playgroud)

对于上面描述的图表,您的数据如下:

INSERT INTO Products
  (SKU, isAtomic)
VALUES
  (  2, TRUE    ),
  (  3, FALSE   ),
  (  5, FALSE   ),
  (  7, FALSE   ),
  (  8, FALSE   ),
  (  9, TRUE    ),
  ( 10, TRUE    ),
  ( 11, FALSE   )
//

INSERT INTO ProductComponents
  (SKU, ComponentSKU)
VALUES
  (2,2),
  (3,3),   (3,8),   (3,9),   (3,10),
  (5,5),   (5,11),  (5,2),   (5,9),   (5,10),
  (7,7),   (7,8),   (7,9),   (7,11),  (7,2),   (7,10),
  (8,8),   (8,9),
  (9,9),
  (10,10),
  (11,11), (11,2),  (11,9),  (11,10)
//
Run Code Online (Sandbox Code Playgroud)

然后您可以按如下方式存储可用性:

CREATE TABLE ProductAvailability (
  SKU      BIGINT UNSIGNED NOT NULL COMMENT 'Stock-Keeping Unit Code',
  Date     DATE                     COMMENT 'Availability date',
  Quantity INT                      COMMENT 'Available quantity',
  PRIMARY KEY (SKU, Date),
  FOREIGN KEY (SKU) REFERENCES Products (SKU)
)
  ENGINE  = InnoDB,
  COMMENT = 'Available quantities'
//
Run Code Online (Sandbox Code Playgroud)

上述的一些测试数据可能是:

INSERT INTO ProductAvailability
  (SKU, Date        , Quantity)
VALUES
  (  2, '2012-12-13',     NULL),
  (  2, '2012-12-15',       15),
  (  9, '2012-12-13',      234),
  (  9, '2012-12-14',       46),
  (  9, '2012-12-15',        0),
  ( 10, '2012-12-13',        4),
  ( 10, '2012-12-14',        7),
  ( 10, '2012-12-15',        5)
//
Run Code Online (Sandbox Code Playgroud)

你的查询将是:

SELECT   p.*
FROM     Products p
    JOIN ProductComponents c USING (SKU)
    JOIN (
           SELECT   p.SKU AS ComponentSKU,
                    COUNT(*) = DATEDIFF(@end_date, @start_date) + 1 AS available
           FROM     Products p LEFT JOIN ProductAvailability a
                 ON a.SKU = p.SKU
                AND a.Quantity > 0
                AND a.Date BETWEEN @start_date AND @end_date
           WHERE    p.isAtomic
           GROUP BY p.SKU
         ) q USING (ComponentSKU)
GROUP BY p.SKU
HAVING   NOT SUM(q.available = 0)
Run Code Online (Sandbox Code Playgroud)

sqlfiddle上看到它.