Được rồi, tôi đã viết các lớp PHP mở rộng các lớp bảng, hàng và tập hợp hàng của Zend Framework DB. Tôi vẫn đang phát triển cái này vì tôi đang nói chuyện tại PHP Tek-X trong vài tuần nữa về mô hình dữ liệu phân cấp.
Tôi không muốn đăng tất cả mã của mình lên Stack Overflow vì chúng mặc nhiên được cấp phép theo Creative Commons nếu tôi làm điều đó. cập nhật: Tôi đã cam kết mã của mình với vườn ươm phần bổ sung Zend Framework và bản trình bày của tôi là Mô hình cho Dữ liệu phân cấp với SQL và PHP tại slideshare.
Tôi sẽ mô tả giải pháp bằng mã giả. Tôi đang sử dụng phân loại động vật học làm dữ liệu thử nghiệm, được tải xuống từ ITIS.gov
. Bảng là longnames
:
CREATE TABLE `longnames` (
`tsn` int(11) NOT NULL,
`completename` varchar(164) NOT NULL,
PRIMARY KEY (`tsn`),
KEY `tsn` (`tsn`,`completename`)
)
Tôi đã tạo bảng đóng cho các đường dẫn trong hệ thống phân loại phân loại:
CREATE TABLE `closure` (
`a` int(11) NOT NULL DEFAULT '0', -- ancestor
`d` int(11) NOT NULL DEFAULT '0', -- descendant
`l` tinyint(3) unsigned NOT NULL, -- levels between a and d
PRIMARY KEY (`a`,`d`),
CONSTRAINT `closure_ibfk_1` FOREIGN KEY (`a`) REFERENCES `longnames` (`tsn`),
CONSTRAINT `closure_ibfk_2` FOREIGN KEY (`d`) REFERENCES `longnames` (`tsn`)
)
Với khóa chính của một nút, bạn có thể lấy tất cả các nút con của nó theo cách này:
SELECT d.*, p.a AS `_parent`
FROM longnames AS a
JOIN closure AS c ON (c.a = a.tsn)
JOIN longnames AS d ON (c.d = d.tsn)
LEFT OUTER JOIN closure AS p ON (p.d = d.tsn AND p.l = 1)
WHERE a.tsn = ? AND c.l <= ?
ORDER BY c.l;
Tham gia để closure AS p
là bao gồm id cha của mỗi nút.
Truy vấn sử dụng khá tốt các chỉ mục:
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
| 1 | SIMPLE | a | const | PRIMARY,tsn | PRIMARY | 4 | const | 1 | Using index; Using filesort |
| 1 | SIMPLE | c | ref | PRIMARY,d | PRIMARY | 4 | const | 5346 | Using where |
| 1 | SIMPLE | d | eq_ref | PRIMARY,tsn | PRIMARY | 4 | itis.c.d | 1 | |
| 1 | SIMPLE | p | ref | d | d | 4 | itis.c.d | 3 | |
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
Và cho rằng tôi có 490.032 hàng trong longnames
và 4.299.883 hàng trong closure
, nó chạy trong thời gian khá tốt:
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| starting | 0.000257 |
| Opening tables | 0.000028 |
| System lock | 0.000009 |
| Table lock | 0.000013 |
| init | 0.000048 |
| optimizing | 0.000032 |
| statistics | 0.000142 |
| preparing | 0.000048 |
| executing | 0.000008 |
| Sorting result | 0.034102 |
| Sending data | 0.001300 |
| end | 0.000018 |
| query end | 0.000005 |
| freeing items | 0.012191 |
| logging slow query | 0.000008 |
| cleaning up | 0.000007 |
+--------------------+----------+
Bây giờ tôi xử lý hậu kết quả của truy vấn SQL ở trên, sắp xếp các hàng thành các tập hợp con theo cấu trúc phân cấp (mã giả):
while ($rowData = fetch()) {
$row = new RowObject($rowData);
$nodes[$row["tsn"]] = $row;
if (array_key_exists($row["_parent"], $nodes)) {
$nodes[$row["_parent"]]->addChildRow($row);
} else {
$top = $row;
}
}
return $top;
Tôi cũng định nghĩa các lớp cho Rows và Rowsets. Rowset về cơ bản là một mảng các hàng. Một Hàng chứa một mảng dữ liệu hàng liên kết và cũng chứa một Tập hợp các hàng con của nó. Rowset con cho một nút lá trống.
Rows và Rowsets cũng xác định các phương thức được gọi là toArrayDeep()
kết xuất nội dung dữ liệu của chúng một cách đệ quy dưới dạng một mảng thuần túy.
Sau đó, tôi có thể sử dụng toàn bộ hệ thống với nhau như thế này:
// Get an instance of the taxonomy table data gateway
$tax = new Taxonomy();
// query tree starting at Rodentia (id 180130), to a depth of 2
$tree = $tax->fetchTree(180130, 2);
// dump out the array
var_export($tree->toArrayDeep());
Kết quả như sau:
array (
'tsn' => '180130',
'completename' => 'Rodentia',
'_parent' => '179925',
'_children' =>
array (
0 =>
array (
'tsn' => '584569',
'completename' => 'Hystricognatha',
'_parent' => '180130',
'_children' =>
array (
0 =>
array (
'tsn' => '552299',
'completename' => 'Hystricognathi',
'_parent' => '584569',
),
),
),
1 =>
array (
'tsn' => '180134',
'completename' => 'Sciuromorpha',
'_parent' => '180130',
'_children' =>
array (
0 =>
array (
'tsn' => '180210',
'completename' => 'Castoridae',
'_parent' => '180134',
),
1 =>
array (
'tsn' => '180135',
'completename' => 'Sciuridae',
'_parent' => '180134',
),
2 =>
array (
'tsn' => '180131',
'completename' => 'Aplodontiidae',
'_parent' => '180134',
),
),
),
2 =>
array (
'tsn' => '573166',
'completename' => 'Anomaluromorpha',
'_parent' => '180130',
'_children' =>
array (
0 =>
array (
'tsn' => '573168',
'completename' => 'Anomaluridae',
'_parent' => '573166',
),
1 =>
array (
'tsn' => '573169',
'completename' => 'Pedetidae',
'_parent' => '573166',
),
),
),
3 =>
array (
'tsn' => '180273',
'completename' => 'Myomorpha',
'_parent' => '180130',
'_children' =>
array (
0 =>
array (
'tsn' => '180399',
'completename' => 'Dipodidae',
'_parent' => '180273',
),
1 =>
array (
'tsn' => '180360',
'completename' => 'Muridae',
'_parent' => '180273',
),
2 =>
array (
'tsn' => '180231',
'completename' => 'Heteromyidae',
'_parent' => '180273',
),
3 =>
array (
'tsn' => '180213',
'completename' => 'Geomyidae',
'_parent' => '180273',
),
4 =>
array (
'tsn' => '584940',
'completename' => 'Myoxidae',
'_parent' => '180273',
),
),
),
4 =>
array (
'tsn' => '573167',
'completename' => 'Sciuravida',
'_parent' => '180130',
'_children' =>
array (
0 =>
array (
'tsn' => '573170',
'completename' => 'Ctenodactylidae',
'_parent' => '573167',
),
),
),
),
)
Nhận xét của bạn về việc tính toán độ sâu - hoặc độ dài thực sự của mỗi đường dẫn.
Giả sử bạn vừa chèn một nút mới vào bảng chứa các nút thực tế (longnames
trong ví dụ trên), id của nút mới được trả về bởi LAST_INSERT_ID()
trong MySQL hoặc bằng cách nào khác bạn có thể lấy nó.
INSERT INTO Closure (a, d, l)
SELECT a, LAST_INSERT_ID(), l+1 FROM Closure
WHERE d = 5 -- the intended parent of your new node
UNION ALL SELECT LAST_INSERT_ID(), LAST_INSERT_ID(), 0;