elw*_*ood 0 tree database-design hierarchical-data
我目前正在开发一个基于MySQL的网站,但如果有必要,转换到另一个数据库不是问题(如CTE左右).
我正在寻找最好的数据库数据结构(如果可能的话,还有一些SQL代码片段)来处理我当前的项目,如下所示:
产品的"树"结构非常稳定,因此更新/插入/删除查询的速度并不重要.
最终目标是能够尽快在SELECT查询中检索指定的两个日期之间可用的所有产品的列表.
这意味着:
顺便说一句,在实际情况下,我的产品永远不会有超过5级的深度.将所有父ID存储在单独的列中可能是一个很好(但非常难看)的想法.
您所描述的数据结构是不是一个树(这要求每个节点都只有一个父,但没有父根):确切地说,它是一个更一般的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上看到它.
| 归档时间: |
|
| 查看次数: |
1339 次 |
| 最近记录: |