调整查询以按关键字返回类别层次结构

时间:2014-06-18 11:58:09

标签: mysql sql

之前我问过这个问题,然后有人建议这是另一个先前回答的问题的副本。然而,尽管做了3个小时的尝试,我还是无法将这个解决方案应用到我需要的地方。

所以,我的新问题是如何根据自己的需要调整该解决方案。

我的类别/子类别数据库模式的简化版本如下所示:

tblAllCategories

record_id  title                    level   parent_cat_id   parent_id   keywords
-------------------------------------------------------------------------------------------
1          Antiques & Collectables  0       NULL              NULL          junk
2          Art                      0       NULL              NULL          
25         Furniture                1       1                 1             
59         Office Furniture         2       1                 25            retro,shabby chic
101        Chairs                   3       1                 59            

注意:

  • 等级0 =顶级类别,等级1 =第二等等
  • parent_cat_id是顶级类别(即具有级别0)
  • parent_id指的是相关级别正上方的级别

我添加了关键字列来辅助关键字搜索,以便在用户输入关键字但未选择要深入研究的类别时,会返回某些相关类别中的项目。

所以,在前端,在用户输入关键字后,例如" Retro",我不仅需要返回具有术语" retro"的类别。在其关键字列中,也包括所有更高级别的类别。因此,根据上面的架构,搜索" retro"将返回59类及其超级类别--25和1。

查询应按级别排序,以便前端搜索结果看起来像这样(在必要的编码之后):

enter image description here

提供的解决方案来自this question

查询如下:

SELECT T2.id, T2.title,T2.controller,T2.method,T2.url
FROM (
    SELECT
        @r AS _id,
        (SELECT @r := parent_id FROM menu WHERE id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := 31, @l := 0) vars,
        menu m
    WHERE @r <> 0) T1
JOIN menu T2
ON T1._id = T2.id
ORDER BY T1.lvl DESC;

我需要调整此查询以处理传递的关键字,而不是ID。

3 个答案:

答案 0 :(得分:1)

编辑vars子查询,使@r等于具有关键字的行的record_id,如

SELECT T2.record_id, T2.title,T2.level,T2.keywords
FROM (SELECT @r AS _id
           , (SELECT @r := parent_id 
              FROM   tblAllCategories 
              WHERE record_id = _id) AS parent_id
           , @l := @l + 1 AS lvl
      FROM   (SELECT @r := record_id, @l := 0
              FROM   tblAllCategories
              WHERE  keywords like '%retro%') vars
           , tblAllCategories m
      WHERE  @r <> 0) T1
     JOIN tblAllCategories T2 ON T1._id = T2.record_id
ORDER BY T1.lvl DESC;

SQLFiddle demo

将keywork作为逗号分隔值并不是最好的,此表与关键字表(使用强制联结表)之间的多对多关系会更好,因为它将避免使用LIKE 。在此示例中,如果有另一个类别包含关键字&#39; retrobike&#39;该类别及其所有等级也将出现在结果中。

答案 1 :(得分:1)

这需要一段时间才能喝点咖啡。

有很多好的资源可用于分层开发。您将在下面看到的大多数内容来自this这样的网站,并且它会引用您推荐给Celko,我很难推荐。

您要做的第一件事就是删除关键字字段。开发,使用和维护方面的额外努力远不及收到的好处。我将在稍后向您展示如何实施它。

在此设计中,将行视为节点。每个节点都有两个值,左边界和右边界。这些形成了影响的范围或范围。如果节点具有1:4的边界而另一个节点具有2:3,则第二节点是第一节点的子节点,因为其跨度包含在第一节点的范围内。此外,由于第二个节点的边界是连续的,因此它下面不能有节点,因此它必须是叶节点。这可能听起来很复杂,特别是考虑到许多级别的节点时,你会看到SQL如何相对容易编写,并且表的维护工作量很小。

完整的脚本是here

CREATE TABLE categories (
    id      INT not null auto_increment PRIMARY KEY,
    name    VARCHAR( 50 )   NOT NULL,
    lBound  INT             NOT NULL,
    rBound  INT             NOT NULL,
    -- MySQL does not implement check constraints. These are here for illustration.
    -- The functionality will be implemented via trigger.
    CONSTRAINT cat_ptr_incr_chk CHECK ( lBound < rBound ),  -- basic integrity check
    CONSTRAINT cat_ptr_root_chk CHECK ( lBound >= 0 )     -- eliminates negative values
);
create unique index ndx_cat_lBound on categories( lBound );
create unique index ndx_cat_rBound on categories( rBound );

请注意,这里没有任何内容可以说&#34;我是叶子节点&#34;,&#34;我是根&#34;或者&#34;我的根节点是这样的。&#34;此信息全部包含在lBound和rBound(左边界,右边界)值中。让我们构建一些节点,以便我们看到它的样子。

INSERT INTO categories( name, lBound, rBound )
values( 'Categories', 0, 1 );

ID  name        lBound  rBound
==  ==========  ======  ======
 1  Categories       0       1

我们在创建表上的触发器之前执行此操作。这真的是插入触发器不必具有必须识别第一行(整个结构的根节点)时必须识别的特殊代码。该代码仅在插入第一行时执行,而不再执行。现在我们不必担心它。

所以现在我有了结构的根源。请注意,它的边界是0和1. 0和1之间没有任何值,所以这是一个叶节点。树根也是一片叶子。这意味着树是空的。

现在我们编写触发器和dml过程。代码在脚本中,所以我不会在这里复制它,只是说插入和删除触发器不允许任何人发出Insert或Delete语句。任何人都可以发布更新,但只允许更改名称。可以执行插入,删除和完成更新的唯一方法是通过这些过程。考虑到这一点,让我们在根目录下创建第一个节点。

call ins_category( 'Electronics', 1 );

这会创建一个名为&#39; Electronics&#39;的节点。作为ID = 1(根)的节点的子节点。

ID  name        lBound  rBound
==  ==========  ======  ======
 1  Categories       0       3
 2  Electronics      1       2

注意触发器如何扩展根的右边界以允许新节点。下一个节点将是另一个级别。

call ins_category( 'Televisions', 2 );

节点2是电子设备,因此新节点将成为其子节点。

ID  name        lBound  rBound
==  ==========  ======  ======
 1  Categories       0       5
 2  Electronics      1       4
 3  Televisions      2       3

让我们创建一个新的上层节点 - 它仍然必须位于根目录下,但它将是电子旁边的子树的开头。

call ins_category( 'Antiques & Collectibles', 1 );

ID  name                    lBound  rBound
==  ==========              ======  ======
 1  Categories                   0       7
 2  Electronics                  1       4
 3  Televisions                  2       3
 4  Antiques & Collectibles      5       6

请注意,除根之外,5-6不适合任何边界范围。所以它是直接位于根目录下的子节点,就像电子一样,但是独立于其他子节点。

用于更清晰地了解结构的SQL并不复杂。在完成具有更多节点的树之后,让我们看看它的样子:

-- Examine the tree or subtree using pre-order traversal. We start at the node
-- specified in the where clause. The root of the entire tree has lBound = 0.
-- Any other ID will show just the subtree starting at that node.
SELECT  n.ID, n.NAME, n.lBound, n.rBound
FROM    categories p
join    categories n
    on  n.lBound BETWEEN p.lBound AND p.rBound
where   p.lBound = 0
ORDER BY n.lBound;

+----+----------------------------+--------+--------+
| id | name                       | lBound | rBound |
+----+----------------------------+--------+--------+
|  1 | >Categories                |      0 |     31 |
|  2 | -->Electronics             |      1 |     20 |
|  3 | ---->Televisions           |      2 |      9 |
|  4 | ------>Tube                |      3 |      4 |
|  5 | ------>LCD                 |      5 |      6 |
|  6 | ------>Plasma              |      7 |      8 |
|  7 | ---->Portable Electronics  |     10 |     19 |
|  8 | ------>MP3 Players         |     11 |     14 |
|  9 | -------->Flash             |     12 |     13 |
| 10 | ------>CD Players          |     15 |     16 |
| 11 | ------>2-Way Radios        |     17 |     18 |
| 12 | -->Antiques & Collectibles |     21 |     28 |
| 14 | ---->Furniture             |     22 |     27 |
| 15 | ------>Office Furniture    |     23 |     26 |
| 16 | -------->Chairs            |     24 |     25 |
| 13 | -->Art                     |     29 |     30 |
+----+----------------------------+--------+--------+

上面的输出实际上来自脚本中定义的视图,但它清楚地显示了层次结构。这可以很容易地转换为一组嵌套菜单或导航节点。

可以进行增强,但它们不需要改变这种基本结构。您会发现它相当容易维护。我开始认为在DBMS(例如Oracle,SQL Server或PostGreSQL)中可以更容易地实现这一点,它允许在视图上触发。然后访问可能仅限于视图,因此触发器将处理所有事情。这将消除对单独存储过程的需要。但这种方式并不是坏事。我很乐意和它一起生活。实际上,使用仅通过视图不可用的存储过程(您无法将参数传递给视图)具有简单性和灵活性。

关键字功能也已定义,但我不会在此处显示。看看剧本。一次执行一次,以清楚地了解正在发生的事情。如果您有任何疑问,请知道在哪里找到我。

[编辑]添加了一些enhancements,包括使用关键字。

答案 2 :(得分:0)

您可以使用此简单方法,通过 HeirarchyID 类型为管理树添加新列:

我们可以使用Microsoft示例CLICK HERE

这是一个示例表

create table [EmployeeTB]
(
employee int identity primary key,
name nvarchar(50),
hourlyrate money,
managerid int -- parent in personnel tree
);


 set identity_insert dbo.[EmployeeTB] on;
 insert into [EmployeeTB] (employee, name, hourlyrate, managerid)
 values
 (1, 'Big Boss', 1000.00, 1),
 (2, 'Joe', 10.00, 1),
 (8, 'Mary', 20.00, 1),
 (14, 'Jack', 15.00, 1),
 (3, 'Jane', 10.00, 2),
 (5, 'Max', 35.00, 2),
 (9, 'Lynn', 15.00, 8),
 (10, 'Miles', 60.00, 8),
 (12, 'Sue', 15.00, 8),
 (15, 'June', 50.00, 14),
 (18, 'Jim', 55.00, 14),
 (19, 'Bob', 40.00, 14),
 (4, 'Jayne', 35.00, 3),
 (6, 'Ann', 45.00, 5),
 (7, 'Art', 10.00, 5),
 (11, 'Al', 70.00, 10),
 (13, 'Mike', 50.00, 12),
 (16, 'Marty', 55.00, 15),
 (17, 'Barb', 60.00, 15),
 (20, 'Bart', 1000.00, 19);
  set identity_insert dbo.[EmployeeTB] off;

select * from [EmployeeTB]
order by managerid


  --Big Boss   / 
  --Joe        /1/
  --Jane       /1/1/
  --Max        /1/2/
  --Ann        /1/2/1/
  --Art        /1/2/2/

现在通过HEIRARCHY添加新列

alter table [EmployeeTB]
add [Chain] hierarchyid;

-- fills all Chains
with sibs
as
(
select managerid, 
employee, 
cast(row_number() over (partition by managerid order by employee) as varchar) + '/' as sib
from [EmployeeTB]
where employee != managerid
) 
--select * from sibs
,[noChain]
as
(
select managerid, employee, hierarchyid::GetRoot() as Chain   from [EmployeeTB]
where employee = managerid
UNION ALL
select P.managerid, P.employee, cast([noChain].Chain.ToString() + sibs.sib as hierarchyid)  as Chain
from [EmployeeTB] as P
join [noChain] on P.managerid = [noChain].employee
join sibs on 
P.employee = sibs.employee
)
--select Chain.ToString(), * from [noChain]
update [EmployeeTB] 
set Chain = [noChain].Chain
 from  [EmployeeTB] as P join [noChain]
 on P.employee = [noChain].employee

 select Chain.ToString(), * from [EmployeeTB]
 order by managerid

在此示例中,我们可以找到任何视图模型。