MySQL-多层产品数据库

时间:2018-09-23 01:25:38

标签: php mysql database mysqli

我目前正在为我的一位客户构建一个基于商店的模块。这些项目具有可以输入的许多不同类型的类别。我将使用的示例是收音机。

它可以是手持移动类型的无线电。我们在以下路径下列出了收音机:

store -> radios -> brand -> mobile / handheld -> item page

如果所有内容的布局都相同,则存储起来非常简单,但是可惜不是那么容易。他们可能有一个适用于所有收音机和品牌的配件,在这种情况下,它将像这样存放:

store -> radios -> accessory -> item page

这是收音机下面的完整类别,因此对于每个项目组,编写 select 都必须有所不同,这不尽人意。还应该从共享的父类别中访问这些项目,例如,如果我要访问store -> radios,则应该看到那些子类别下的所有项目。这是我计划中的当前数据库结构,也是我认为存在最多问题的地方(PS:ID类别是唯一,主要和自动递增的):

+===================================================+
| ID | Category Name  | Path            | Parent ID |
+===================================================+
| 1  | radios         | radios          | 0         |
| 2  | accessory      | misc            | 1         |
| 3  | motorola       | motorola        | 1         |
| 4  | handheld       | handheld        | 3         |
| 5  | mobile         | mobile          | 3         |
+===================================================+

然后我将产品上的category_id设置为“ .. 5”,以添加到产品页面的移动无线电。如果我访问产品页面,则需要获取要在页面上显示的每个父类别的路径,例如

store/radios/motorola/mobile/product-page-here.html

我必须使用path字段从上述数据库中构建该路径,并加入所有父类的类以获得正确的路径。现在,我正在执行许多select * FROM tblname WHERE id = parent,然后在该命令的while循环中,以相同的方式进行另一次选择,直到返回父表为止。必须有一种更好的方法,尤其是如果我只深入3个级别,并且客户添加了4级深的子类别,那么它永远不会走上正确的道路。

最后一个问题是,使用上面的示例,如果我改为去store/radios/motorola,它将尝试获取category_id的{​​{1}}下列出的所有项目,而不是{{1 }},并且该项目不会显示在3的常规选项卡下,因为它是5表的子项,是motorola表的间接子项。通过为他们提供多个子类别选项,它使整个系统的规划成为一种皇家痛苦。

无论如何,感谢您的阅读,在下面留下您的评论或建议。顺便说一句,这些都存储在MySQL中,并通过PHP脚本调用。

2 个答案:

答案 0 :(得分:1)

您是severely limited by your current choice of schema.(实际上,我很想关闭您的问题,因为它是我在此处链接的重复项,但是鉴于您的问题描述,我建议对实现路径进行更改:

与其从描述性属性的层次结构开始,不如将它们视为与每个项目相关联的词云,并考虑由一个或多个或多个词组成的每条可能路径。例如

 radios/sku1
 motorola/sku1
 mobile/sku1
 radios/Motorola/sku1
 radios/mobile/sku1
 motorola/radios/sku1
 motorola/mobile/sku1
 mobile/radios/sku1
 mobile/Motorola/sku1
 radios/Motorola/mobile/sku1
 radios/mobile/motorola/sku1
 (etc)

您是否存储该路径由您决定-如上所示,条目数量可以快速升级。但是您可以即时生成它。例如

Create table userpath (
   request int, 
   word varchar(30),
   Primary key (request, word)
);
Create table product (
   SKU int auto-increment,
   ...make, model, price, description...
);
Create table labels (
   SKU int,
   Word varchar(20),
   Primary key (SKU, word),
   Uniques key (word, SKU)
);

要解析路径中的项目,请将其拆分为单词,然后将单词放入用户路径,然后:

Select p.*
From products p
Join labels l
On p.sku=l.sku
Join userpath u
On l.word=u.word
Where request = ?

您可以从以下位置获取当前路径的子类别:

Select l2.word, count(*)
From labels L2
join products p
on l2.sku=p.sku
Join labels l
On p.sku=l.sku
Join userpath u
On l.word=u.word
Left join userpath U2
On l2.word=u2.word
Where u2.word is null
And request=?
Order by count(*) desc

(可以通过加权和元数据进行增强)

答案 1 :(得分:1)

正如@symcbean所提到的,这种模式是相当有限的,将您的品牌存储为类别可能不是您想要的。但是,使用现有工具,这里有一些有关如何解决问题的建议。

除非您可以为嵌套类别的深度设置硬限制,否则这些问题在应用程序代码中可能比SQL更容易解决。编写这样的查询很容易,它可以从像这样的表中提取出给定深度的嵌套类别。像这样:

SELECT t1.name, t2.name, t3.name FROM accessory_categories AS t1
LEFT JOIN accessory_categories AS t2 ON t2.parent_id=t1.id
LEFT JOIN accessory_categories AS t3 ON t3.parent_id=t2.id
WHERE t1.parent_id!=1
AND t1.id!=1;

但是正如您所说,一旦有人创建了第四级,您就会遇到麻烦。

只要这是一家类别数量合理的小型商店,您最好的选择可能是创建几个数据结构以将类别映射到其父级,反之亦然,然后将其填充到缓存中例如Redis或Memcache,因此您不必为每个请求提取整个类别表。

<?php
//Mock category records, would come from the DB in the real world
$categoryRecords = [
    ['id' => 1, 'title' => 'Radios', 'slug'=>'radios', 'parent_id' => 0],
    ['id' => 2, 'title' => 'Accessories', 'slug'=>'misc', 'parent_id' => 1],
    ['id' => 3, 'title' => 'Motorola', 'slug'=>'motorola', 'parent_id' => 1],
    ['id' => 4, 'title' => 'Handheld', 'slug'=>'handheld', 'parent_id' => 3],
    ['id' => 5, 'title' => 'Mobile', 'slug'=>'mobile', 'parent_id' => 3],
    ['id' => 6, 'title' => 'Level 3', 'slug'=>'level-3', 'parent_id' => 5],
    ['id' => 7, 'title' => 'Level 4', 'slug'=>'level-4', 'parent_id' => 6]
];

//Create an array that maps parent IDs to primary keys
$idToParentIdMap = [];
$parentIdToIdMap = [];

foreach($categoryRecords as $currRecord)
{
    $idToParentIdMap[$currRecord['id']] = $currRecord['parent_id'];
    $parentIdToIdMap[$currRecord['parent_id']][] = $currRecord['id'];
}

/*
 * Now would be a good time to cache these maps in something like Redis or Memcache so you don't have to pull
 * the whole category table during every request.
 */


/**
 * This function will traverse the category tree and return an array of IDs belonging to all categories below
 * the category identified by the $parentId
 * @param int $parentId Primary key of category to find children for
 * @param array $childIds Pass previously found IDs for recursion
 * @return array
 */
function findChildIds($parentId, $childIds=[])
{
    global $parentIdToIdMap;

    if(array_key_exists($parentId, $parentIdToIdMap))
    {
        $immediateChildIds = $parentIdToIdMap[$parentId];

        foreach($immediateChildIds as $currChildId)
        {
            $childIds[] = $currChildId;

            $childIds = findChildIds($currChildId, $childIds);
        }
    }

    return $childIds;
}

/**
 * Return an array of parent IDs for a given category ID
 * @param int $categoryId
 * @return array
 */
function findParentIds($categoryId)
{
    global $idToParentIdMap;

    $parentIds=[];

    while(array_key_exists($categoryId, $idToParentIdMap))
    {
        $currParentId = $idToParentIdMap[$categoryId];
        $parentIds[] = $currParentId;

        $categoryId = $currParentId;
    }

    $parentIds = array_reverse($parentIds);

    return $parentIds;
}

//ID of category requested
$requestedId = 3;

/*
 * Now you can use these IDs to find all of the sub categories and products under the requested category
 */
$categoryIds = findChildIds($requestedId);
$categoryIds[] = $requestedId;

/*
 * Now you can use these IDs to get your parent categories
 */
$parentIds = findParentIds($requestedId);