根据多行的约束选择行

时间:2012-05-30 05:35:27

标签: mysql hierarchical-data

问题:

我正在使用闭包表来跟踪用户文件权限,并且在JOIN之后,这会导致多行read个布尔值。如果所有连接的行都是可读的,我只想选择一行。使用UNION显然无法做到这一点。

详细说明:

文件夹表:

CREATE TABLE IF NOT EXISTS folders
(
   id                    INT NOT NULL AUTO_INCREMENT,
   path                  VARCHAR(500) NOT NULL,
   r                     BOOL NOT NULL DEFAULT FALSE,
   PRIMARY KEY ( id )
)engine=innodb;

文件表:

CREATE TABLE IF NOT EXISTS files
(
   id                    INT NOT NULL AUTO_INCREMENT,
   parent_folder_id      INT NOT NULL ,
   path                  VARCHAR(500) NOT NULL,
   r                     BOOL NOT NULL DEFAULT FALSE,
   FOREIGN KEY ( parent_folder_id ) REFERENCES folders ( id ),
   PRIMARY KEY ( id )
)engine=innodb;

文件夹 - 父文件夹关闭表:

CREATE TABLE IF NOT EXISTS parent_folders
(
   id                INT NOT NULL AUTO_INCREMENT,
   folder_id         INT NOT NULL,
   parent_folder_id  INT NOT NULL,
   FOREIGN KEY ( folder_id ) REFERENCES folders ( id ),
   FOREIGN KEY ( parent_folder_id ) REFERENCES folders ( id ),
   PRIMARY KEY ( id )
)engine=innodb;

现在,如果我想获取所有可读文件(忽略我完全省略用户的那一刻),我会这样开始

SELECT 
    F.id, F.path, F.r, P.parent_folder_id, D.path, D.r
FROM
    files AS F 
    LEFT JOIN parent_folders AS P 
        ON F.parent_folder_id = P.folder_id 
    LEFT JOIN folders AS D  
        ON P.parent_folder_id = D.id;

这将显示每个文件ID,路径和读取权限的表格,可以从其每个父文件夹访问,如此

id   path                  r     id  path          r
......
0   /home/joe/foo/bar.txt  True  1   /home/joe/foo True
1   /home/joe/foo/bar.txt  True  2   /home/joe     True
1   /home/joe/foo/bar.txt  True  3   /home         True
1   /home/joe/foo/bar.txt  True  4   /             True
2   /home/jim/foo/bar.txt  True  5   /home/jim/foo True
2   /home/jim/foo/bar.txt  True  6   /home/jim     False
2   /home/jim/foo/bar.txt  True  7   /home         True
2   /home/jim/foo/bar.txt  True  8   /             True
....

在这种情况下,我想SELECT /home/joe/foo/bar.txt因为每个父文件夹都是可读的,但我不想SELECT /home/jim/foo/bar.txt因为一个它的父文件夹不可读。

编辑:或者,我可以这样重写一下这样的问题:“我可以AND跨多行显示一列的值吗?”

3 个答案:

答案 0 :(得分:1)

可以使用非标准SQL来完成,但这取决于您的数据库供应商。例如,您可能希望使用Oracle中的CONNECT BY子句检查分层查询。 MySQL可能有类似的东西。但是,出于三个原因,我建议反对这样的解决方案:

  1. 供应商锁定。
  2. 目前尚不清楚这些查询的效率如何,或者如何优化它们。
  3. 如果您想要更多这样的规则(例如可继承的用户权限),复杂性会迅速增加。
  4. 相反,我会建议我在几个大中型项目中使用以下方法:

    1. 对于r字段,使用三态布尔值(TRUEFALSENULL),其中NULL代表“继承” ”。

    2. 为每个文件添加一个新字段effective_r(可能为每个文件夹添加)。这将包含应用所有继承规则的结果,并且只能是TRUEFALSE。当然,您必须在层次结构的每次更改时计算此字段,但这更快,因为更新不经常发生,当它们发生时,它们只影响层次结构的一部分。

    3. 定义自上而下的传播规则。在这种情况下很容易:

      parent effective_r       child r        child effective_r
      ---------------------    ------------   ---------------------
      <ROOT>                   NULL           TRUE
      <ROOT>                   TRUE           TRUE
      <ROOT>                   FALSE          FALSE
      TRUE                     NULL           TRUE
      FALSE                    NULL           FALSE
      TRUE|FALSE               TRUE           TRUE
      TRUE|FALSE               FALSE          FALSE
      

      对于用户权限,规则可能更加复杂和复杂。

答案 1 :(得分:0)

添加WHERE子句

WHERE D.r = TRUE

修改

根据需要进行改进

SELECT F.id, F.path, F.r, P.parent_folder_id, D.path, D.r
FROM files AS F
LEFT JOIN parent_folders AS P ON F.parent_folder_id = P.folder_id
LEFT JOIN folders AS D ON P.parent_folder_id = D.id
WHERE F.path NOT IN
    (SELECT A.path
     FROM files AS A
     LEFT JOIN parent_folders AS B ON A.parent_folder_id = B.folder_id
     LEFT JOIN folders AS C ON B.parent_folder_id = C.id
     WHERE C.r = FALSE)

答案 2 :(得分:0)

select a.path
from (
    SELECT 
        F.path, D.r
    FROM
        files AS F 
        LEFT JOIN parent_folders AS P 
            ON F.parent_folder_id = P.folder_id 
        LEFT JOIN folders AS D  
            ON P.parent_folder_id = D.id
) a
left join (
    SELECT 
        F.path, D.r
    FROM
        files AS F 
        LEFT JOIN parent_folders AS P 
            ON F.parent_folder_id = P.folder_id 
        LEFT JOIN folders AS D  
            ON P.parent_folder_id = D.id
) b on a.path = b.path and b.r = false
where b.r is null
group by path