【问题标题】:fetching data from same table in multiple join hierarchy从多个连接层次结构中的同一个表中获取数据
【发布时间】:2018-05-24 08:10:13
【问题描述】:

我有一个用户项目,放置在某种层次结构树中。一个用户可以有多个孩子和多个父母。组织结构图如下所示:

此层次结构使用数据库中的 2 个表,userposition。这是重现带有少量固定装置的表的 SQL,以重现下面提供的组织图像:

-- phpMyAdmin SQL Dump
-- version 4.8.0
-- https://www.phpmyadmin.net/
--
-- Hôte : db
-- Généré le :  jeu. 24 mai 2018 à 07:39
-- Version du serveur :  10.2.14-MariaDB-10.2.14+maria~jessie
-- Version de PHP :  7.2.4

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";

CREATE DATABASE IF NOT EXISTS `hierarchy` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `hierarchy`;

DROP TABLE IF EXISTS `position`;
CREATE TABLE `position` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '(DC2Type:guid)',
  `type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `position` (`id`, `user_id`, `parent_id`, `uuid`, `type`) VALUES
(2, 3, NULL, '7db54d6a-6a89-3fe2-87ba-c0dd96d4f664', 'TYPE_HR_PLUS'),
(3, 4, 3, 'adadab97-c0b8-3f89-a245-4edb077a4579', 'TYPE_HR_PLUS'),
(4, 5, 3, '4efec484-8a1f-3c27-a48f-a8c8412e18db', 'TYPE_HR_PLUS'),
(5, 6, 4, '8027b0cb-3e34-357a-8bff-80e4abbf60ef', 'TYPE_HR_PLUS'),
(6, 7, 4, 'e588ec20-9b0e-3ec6-8164-bdfafdf4b440', 'TYPE_HR'),
(7, 8, 5, '61e537d7-5ed6-36b3-8969-1913cdacb42b', 'TYPE_HR'),
(8, 9, 5, '6c241f8f-1a0a-34dc-8042-591352a764ea', 'TYPE_HR_PLUS'),
(9, 9, 3, 'e03998c4-d95a-3410-9727-099f0c83b3d5', 'TYPE_COLLABORATOR'),
(10, 10, 6, '67de2883-a7b0-3260-bb2a-67d3ffa3dc40', 'TYPE_HR'),
(11, 11, 7, '07d37612-01bf-3eec-b249-41c20d756144', 'TYPE_COLLABORATOR'),
(12, 11, 5, '09f7f6f1-59e2-37c0-a2e4-ae9b58ba6c93', 'TYPE_HR'),
(13, 12, 9, '724b8185-b6eb-3d35-b71f-1d076b3020ed', 'TYPE_HR'),
(14, 13, 9, '97d90e5c-7872-3e1e-be65-0f6b5329f54c', 'TYPE_HR_PLUS'),
(15, 14, 10, '3c357b9d-e162-316b-921c-25157d523e70', 'TYPE_COLLABORATOR'),
(16, 14, 3, '542235c6-462c-4922-a629-303430701000', 'TYPE_COLLABORATOR'),
(17, 15, 10, '2d208936-a7e9-32c1-963f-0df7f57ae463', 'TYPE_COLLABORATOR'),
(18, 16, 11, '00b678a2-54cd-3f37-9381-0cd92acea079', 'TYPE_COLLABORATOR'),
(19, 17, 8, 'ec4b8cad-fbce-3692-8d4c-f48f7ffa6452', 'TYPE_COLLABORATOR'),
(20, 18, 12, '48f0c85c-3801-3275-8978-5b2573fd7e0b', 'TYPE_COLLABORATOR'),
(21, 18, 10, 'd6129dec-8823-3304-8dfe-819118073d1f', 'TYPE_COLLABORATOR'),
(22, 19, 13, '1ec9c202-97d7-3311-9119-f6e9f55d058b', 'TYPE_COLLABORATOR'),
(23, 20, 9, '3ab32120-520b-3d41-80f0-56fd3876eecb', 'TYPE_COLLABORATOR');

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '(DC2Type:guid)',
  `first_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
  `last_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `user` (`id`, `uuid`, `first_name`, `last_name`) VALUES
(3, 'fb7304f6-5ade-4c06-83e5-36827a4d7904', 'Albus', 'Dumbledore'),
(4, '60af55ca-e9cd-3d8a-bc8a-c9f7ec99baa1', 'Minerva', 'McGonagall'),
(5, 'a422b364-b68f-324b-8836-d38ca62001f5', 'Severus', 'Rogue'),
(6, 'cbdf94a8-f4e3-360b-ba7f-10ea7e59b85d', 'Filius', 'Flitwick'),
(7, '9bce88c9-7b7d-3db3-8780-b6397bb1263d', 'Remus', 'Lupin'),
(8, 'b9d85cfa-3c46-33c1-8750-53be1c281111', 'Pomona', 'Chourave'),
(9, 'c07d10d7-333f-327a-ab32-201209127b7a', 'Rolanda', 'Bibine'),
(10, '8a7b76a1-5f0c-31ff-a93a-ca478598682b', 'Gilderoy', 'Lockhart'),
(11, '7302700a-0bc5-3ed0-84fc-857e975a5771', 'Alastor', 'Maugrey'),
(12, '7b8722f3-1570-3351-864a-ede9ee3a90e6', 'Sybille', 'Trelawney'),
(13, '12a7683a-00da-46a6-b7fd-a6eae5b10e3c', 'Dolores', 'Ombrage'),
(14, 'f054bdeb-a8b8-41e5-be78-b00d1b9377f3', 'Harry', 'Potter'),
(15, 'b1f696ad-cfa3-3830-8685-c45ff4a4ffda', 'Ronald', 'Weasley'),
(16, '8b2a64e9-ed28-363d-a731-3e15e255cd1d', 'Hermione', 'Granger'),
(17, 'f6aa74f9-2a4a-346d-bf75-3f8b02614c19', 'Neville', 'Londubat'),
(18, '8fbbc431-2555-3718-8055-ad8637260732', 'Drago', 'Malefoy'),
(19, '78a58546-8ae1-330d-9426-c23c74b71e78', 'Luna', 'Lovegood'),
(20, '4739a44b-d1b7-3390-9bbb-cbd2e39b4ec0', 'Ginny', 'Weasley');

ALTER TABLE `position`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `uuid_idx` (`uuid`),
  ADD KEY `IDX_462CE4F5727ACA70` (`parent_id`),
  ADD KEY `IDX_462CE4F5A76ED395` (`user_id`);

ALTER TABLE `user`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `uuid_idx` (`uuid`);

ALTER TABLE `position`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1024;

ALTER TABLE `user`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1027;

ALTER TABLE `position`
  ADD CONSTRAINT `FK_462CE4F5727ACA70` FOREIGN KEY (`parent_id`) REFERENCES `user` (`id`),
  ADD CONSTRAINT `FK_462CE4F5A76ED395` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`);


COMMIT;

需要注意的重要一点是position 表表示用户与其父级之间的关系,并且它还存储了一个定义关系类型的字符串(实际上它表示该关系中子级的用户类型)。

编辑:我还没有提到的一件非常重要的事情是这棵树中没有递归,最多只能有 5 个级别,如组织图像所示:1 HR_PLUS Head of Department,然后是最多 3 级 HR_PLUS 或 HR,其次是 1 级 COLLAB

我想要完成的是:

对于给定的position uuid,我想获取该关系下的每个子用户。

例如,Minerva McGonagall 和 Albus Dumbledore 之间的关系是位置 #3,uuid adadab97-c0b8-3f89-a245-4edb077a4579。鉴于此 uuid,我想获取 Minerva McGonagall 以下的每个用户:

+---------------------------------------+
| id    | first_name    | last_name     |
+---------------------------------------+
| 6     | Filius        | Flitwick      |
| 7     | Remus         | Lupin         |
| 10    | Gilderoy      | Lockhart      |
| 11    | Alastor       | Maugrey       |
| 14    | Harry         | Potter        |
| 15    | Ronald        | Weasley       |
| 16    | Hermione      | Granger       |
| 18    | Drago         | Malefoy       |
+---------------------------------------+

到目前为止我写的查询如下:

SELECT u2.id, u2.first_name, u2.last_name 
FROM `user` u1 

JOIN position p1 ON p1.user_id = u1.id
JOIN position p2 ON p2.parent_id = p1.user_id
JOIN user u2 ON u2.id = p2.user_id

WHERE p1.uuid = 'adadab97-c0b8-3f89-a245-4edb077a4579'
AND p1.type IN ('TYPE_HR', 'TYPE_HR_PLUS')

我在type 属性上添加了一个过滤器,因为如果type 不是HRHR_PLUS,我不希望查询返回任何内容。 现在,查询返回直接位于 Minerva McGonagall 下方的用户(Filius 和 Remus)。而且我不知道如何返回他们下面的其余用户。

任何帮助将不胜感激。

我只是成功地使用UNION 获取预期数据,因为这不是递归树:

SELECT u2.id, u2.first_name, u2.last_name 
FROM `user` u1 

JOIN position p1 ON p1.user_id = u1.id
JOIN position p2 ON p2.parent_id = p1.user_id
JOIN user u2 ON u2.id = p2.user_id

WHERE p1.uuid = 'adadab97-c0b8-3f89-a245-4edb077a4579'
AND p1.type IN ('TYPE_HR', 'TYPE_HR_PLUS')

UNION

SELECT u2.id, u2.first_name, u2.last_name 
FROM `user` u1 

JOIN position p1 ON p1.user_id = u1.id
JOIN position p2 ON p2.parent_id = p1.user_id
JOIN position p3 ON p3.parent_id = p2.user_id
JOIN user u2 ON u2.id = p3.user_id

WHERE p1.uuid = 'adadab97-c0b8-3f89-a245-4edb077a4579'
AND p1.type IN ('TYPE_HR', 'TYPE_HR_PLUS')

UNION

SELECT u2.id, u2.first_name, u2.last_name 
FROM `user` u1 

JOIN position p1 ON p1.user_id = u1.id
JOIN position p2 ON p2.parent_id = p1.user_id
JOIN position p3 ON p3.parent_id = p2.user_id
JOIN position p4 ON p4.parent_id = p3.user_id
JOIN user u2 ON u2.id = p4.user_id

WHERE p1.uuid = 'adadab97-c0b8-3f89-a245-4edb077a4579'
AND p1.type IN ('TYPE_HR', 'TYPE_HR_PLUS')

但这看起来很重,我很确定有一个更简单的方法来做到这一点。

【问题讨论】:

  • 这并不容易。满足表中任意数量数据的通用解决方案本质上是树遍历算法,并且需要循环结构。使用游标或 while 循环似乎很难避免。
  • 也添加你正在使用的sql语言的标签
  • 一个非常重要的事情我没有提到是这棵树中没有递归,最多只能有5个级别,如组织图像所示:1 HR_PLUS部门主管,后跟最多 3 个级别的 HR_PLUS 或 HR,然后是 1 个级别的 COLLAB
  • 请注意,您的查询中实际上不需要 u1。保存一个加入;)
  • 我不是 MariaDB 的人,但在 SQL Server 中,我们会使用递归 CTE 来解决此类问题。看起来 MariaDB 支持类似的东西:mariadb.com/kb/en/library/….

标签: sql join mariadb hierarchy


【解决方案1】:

既然您已经找到联合解决方案,这里有一个使用循环(和伪代码)的替代方案:

创建一个表格来保存你的结果,我们称之为 r。

Insert into r the results of your first query, along with a field named level with a value of 1.
set i=2
while (at least a line of r exists where level=i-1) do the following:
insert into r (level=i)
   a modification of your select based on selecting positions from r 
   where level=i-1

【讨论】:

    【解决方案2】:

    这就是你要找的吗? DB Fiddle

    with recursive cte as (
      select `user_id` as `ancestor_id`
      /* , 0 as `degreesOfSeperation` */
      , `user_id` 
      from `position`
      union all
      select c.`ancestor_id` 
      /* , c.`degreesOfSeperation` + 1 */
      , p.`user_id` 
      from `cte` as c
      inner join `position` as p
      on p.`parent_id` = c.`user_id`
    )
    select u.id, u.first_name, u.last_name
    from cte c
    inner join `user` a on a.`id` = c.`ancestor_id`
    inner join `position` p on p.`user_id` = a.`id`
    inner join `user` u on u.`id` = c.`user_id`
    /* WHERE c.`degreesOfSeperation` > 0 */
    WHERE c.`user_id` != c.`ancestor_id` 
    AND p.`uuid` = 'adadab97-c0b8-3f89-a245-4edb077a4579'
    AND p.`type` IN ('TYPE_HR', 'TYPE_HR_PLUS')
    order by c.`degreesOfSeperation`
    ;
    

    输出

    id first_name last_name

    Id | First_Name | Last_Name
    ---+------------+----------  
     6 | Filius     | Flitwick 
     7 | Remus      | Lupin 
    10 | Gilderoy   | Lockhart 
    11 | Alastor    | Maugrey 
    18 | Drago      | Malefoy 
    14 | Harry      | Potter 
    15 | Ronald     | Weasley 
    16 | Hermione   | Granger 
    

    您可能已经熟悉其中的大部分内容。 处理层次结构的逻辑是recursive cte。 这有 2 个部分:

    第 1 部分

    选择user_idancestor_id , 0 为degreesOfSeperation , user_id 来自position

    这会从位置表中获取所有用户,将他们的记录设置为ancestor 记录;即我们将种植树的根。我们将 degreeOfSeperation 设置为 0 以表明这是根记录;即用户和祖先是一回事。

    第 2 部分

      select c.`ancestor_id` 
      , c.`degreesOfSeperation` + 1
      , p.`user_id` 
      from `cte` as c
      inner join `position` as p
      on p.`parent_id` = c.`user_id`
    

    这是递归部分;对于已经添加到树中的每个用户(即从根语句;或从填充 cte 表的最后一次迭代),我们现在获取该位置的父级与前一次迭代的用户匹配的任何其他位置。我们将一个添加到degreesOfSeparation,这样我们就可以知道这条记录离根祖先有多远。

    DegressOfSeparation 实际上并不是必需的(事实上,在上面的声明中我已经注释掉了);但我倾向于包含它,因为它对于调试/查看正在发生的事情很有用。以下是 cte 在没有任何过滤器的情况下的样子,以帮助您了解发生了什么:

    With recursive cte as (
      select `user_id` as `ancestor_id`
      , 0 as `degreesOfSeperation`
      , `user_id` 
      from `position`
      union all
      select c.`ancestor_id` 
      , c.`degreesOfSeperation` + 1
      , p.`user_id` 
      from `cte` as c
      inner join `position` as p
      on p.`parent_id` = c.`user_id`
    )
    select *
    from cte c
    order by c.`ancestor_id`
    , c.`degreesOfSeperation`
    , c.`user_id`
    ;
    

    Results here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-13
      • 2019-09-07
      • 2014-10-25
      • 1970-01-01
      • 2018-08-31
      • 1970-01-01
      相关资源
      最近更新 更多