Помогите улучшить этот запрос SQL UNION - PullRequest
1 голос
/ 21 марта 2011

Хорошо, у меня есть система контроля доступа, которая имитирует NT DACL.По сути, у меня есть пользователи, группы, сопоставление членства между ними и списки ACL с любым количеством ACE, ссылающихся на пользователей или группы.

(Например, это позволяет группе «Отдел маркетинга» получить доступ к чему-либо,но Джо, работающий в области маркетинга, является проблемным ребенком, так что вы можете отказать ему в доступе к чему-то, и ему будет отказано, но всем остальным в группе будет разрешено)

Мне нужно перечислить списокобъекты, которыми управляет данный ACL - в этом случае «управляемые объекты» сами являются объектами пользователя.Например, скажем, пользователь Боб (с uid=1) хочет удалить другого пользователя из системы, и я хочу, чтобы список пользователей показал Бобу, над которым он может выполнить это действие.Если пользователь (обозначенный здесь WHERE usr.id = 1 ("1" будет кэшироваться приложением PHP, в которое он встроен)) имеет доступ к данному объекту, я хочу показать его, и если он (а) он делаетнет, тогда он не должен существовать в наборе результатов.

Вот лучшее, что я придумал до сих пор:

SELECT `acelist`.id, `acelist`.first_name, `acelist`.last_name, `acelist`.acl
FROM
(
  (
    SELECT `usResult`.id, `usResult`.first_name, `usResult`.last_name, `usResult`.acl, `ace`.`allowed`
    FROM `user` usResult
    INNER JOIN access_control_list acl ON usResult.acl = acl.id
    INNER JOIN group_access_control_entry ace ON acl.id = ace.acl
    INNER JOIN `group` gp ON ace.gid = gp.id
    INNER JOIN group_membership ON gp.id = group_membership.gid
    INNER JOIN `user` usr ON group_membership.uid = usr.id
    WHERE usr.id = 1
  )
  UNION ALL
  (
    SELECT `usResult`.id, `usResult`.first_name, `usResult`.last_name, `usResult`.acl, `ace`.`allowed`
    FROM `user` usResult
    INNER JOIN access_control_list acl ON usResult.acl = acl.id
    INNER JOIN user_access_control_entry ace ON acl.id = ace.acl
    INNER JOIN `user` usr ON ace.uid = usr.id
    WHERE usr.id = 1
  )
) AS acelist
GROUP BY `acelist`.id
HAVING COUNT(acelist.allowed) = SUM(acelist.allowed)

А вот схема, с которой я работаю:

# Generated by Propel ORM
# This is a fix for InnoDB in MySQL >= 4.1.x
# It "suspends judgement" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;

-- ---------------------------------------------------------------------
-- user
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user`
(
    `id` INTEGER NOT NULL,
    `first_name` VARCHAR(255) NOT NULL,
    `last_name` VARCHAR(255) NOT NULL,
    `direct_login` INTEGER,
    `acl` INTEGER NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `user_FI_1` (`acl`),
    CONSTRAINT `user_FK_1`
        FOREIGN KEY (`acl`)
        REFERENCES `access_control_list` (`id`)
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- case_id_user
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `case_id_user`;

CREATE TABLE `case_id_user`
(
    `uid` INTEGER NOT NULL,
    `case_id` VARCHAR(8),
    PRIMARY KEY (`uid`),
    UNIQUE INDEX `case_id_user_U_1` (`case_id`),
    CONSTRAINT `case_id_user_FK_1`
        FOREIGN KEY (`uid`)
        REFERENCES `user` (`id`)
        ON UPDATE CASCADE
        ON DELETE CASCADE
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- direct_login_user
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `direct_login_user`;

CREATE TABLE `direct_login_user`
(
    `uid` INTEGER NOT NULL,
    `passhash` CHAR(60) NOT NULL,
    `email` VARCHAR(255) NOT NULL,
    `user_name` VARCHAR(45) NOT NULL,
    PRIMARY KEY (`uid`),
    UNIQUE INDEX `direct_login_user_U_1` (`user_name`),
    CONSTRAINT `direct_login_user_FK_1`
        FOREIGN KEY (`uid`)
        REFERENCES `user` (`id`)
        ON UPDATE CASCADE
        ON DELETE CASCADE
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- group
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `group`;

CREATE TABLE `group`
(
    `id` INTEGER NOT NULL,
    `name` VARCHAR(45) NOT NULL,
    `description` TEXT NOT NULL,
    `acl` INTEGER NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `group_U_1` (`name`),
    INDEX `group_FI_1` (`acl`),
    CONSTRAINT `group_FK_1`
        FOREIGN KEY (`acl`)
        REFERENCES `access_control_list` (`id`)
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- privilege
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `privilege`;

CREATE TABLE `privilege`
(
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(45) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `privilege_U_1` (`name`)
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- access_control_list
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `access_control_list`;

CREATE TABLE `access_control_list`
(
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- user_access_control_entry
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `user_access_control_entry`;

CREATE TABLE `user_access_control_entry`
(
    `acl` INTEGER NOT NULL,
    `uid` INTEGER NOT NULL,
    `privilege_id` INTEGER NOT NULL,
    `allowed` TINYINT NOT NULL,
    PRIMARY KEY (`acl`,`uid`,`privilege_id`,`allowed`),
    INDEX `user_access_control_entry_FI_1` (`privilege_id`),
    INDEX `user_access_control_entry_FI_2` (`uid`),
    CONSTRAINT `user_access_control_entry_FK_1`
        FOREIGN KEY (`privilege_id`)
        REFERENCES `privilege` (`id`)
        ON UPDATE CASCADE
        ON DELETE CASCADE,
    CONSTRAINT `user_access_control_entry_FK_2`
        FOREIGN KEY (`uid`)
        REFERENCES `user` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE,
    CONSTRAINT `user_access_control_entry_FK_3`
        FOREIGN KEY (`acl`)
        REFERENCES `access_control_list` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- group_access_control_entry
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `group_access_control_entry`;

CREATE TABLE `group_access_control_entry`
(
    `acl` INTEGER NOT NULL,
    `gid` INTEGER NOT NULL,
    `privilege_id` INTEGER NOT NULL,
    `allowed` TINYINT NOT NULL,
    PRIMARY KEY (`acl`,`gid`,`privilege_id`,`allowed`),
    INDEX `group_access_control_entry_FI_1` (`privilege_id`),
    INDEX `group_access_control_entry_FI_2` (`gid`),
    CONSTRAINT `group_access_control_entry_FK_1`
        FOREIGN KEY (`privilege_id`)
        REFERENCES `privilege` (`id`)
        ON UPDATE CASCADE
        ON DELETE CASCADE,
    CONSTRAINT `group_access_control_entry_FK_2`
        FOREIGN KEY (`gid`)
        REFERENCES `group` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE,
    CONSTRAINT `group_access_control_entry_FK_3`
        FOREIGN KEY (`acl`)
        REFERENCES `access_control_list` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE
) ENGINE=InnoDB;

-- ---------------------------------------------------------------------
-- group_membership
-- ---------------------------------------------------------------------

DROP TABLE IF EXISTS `group_membership`;

CREATE TABLE `group_membership`
(
    `uid` INTEGER NOT NULL,
    `gid` INTEGER NOT NULL,
    PRIMARY KEY (`uid`,`gid`),
    INDEX `group_membership_FI_2` (`gid`),
    CONSTRAINT `group_membership_FK_1`
        FOREIGN KEY (`uid`)
        REFERENCES `user` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE,
    CONSTRAINT `group_membership_FK_2`
        FOREIGN KEY (`gid`)
        REFERENCES `group` (`id`)
        ON UPDATE RESTRICT
        ON DELETE CASCADE
) ENGINE=InnoDB;

# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;

Кто-нибудь из присутствующих здесь гуру SQL видит что-нибудь, что я мог бы сделать, чтобы либо упростить запрос, либо сделать его более быстрым?

РЕДАКТИРОВАТЬ : В идеалеЯ каким-то образом смогу сделать сложные биты этой работы для любого объекта ACL, чтобы избежать необходимости писать такой запрос для каждого объекта ACL в базе данных ...

Ответы [ 2 ]

1 голос
/ 21 марта 2011

Конечно, не красивее, но может быть быстрее, потому что меньше данных для манипулирования.

SELECT  `acelist`.id, `acelist`.first_name, `acelist`.last_name, `acelist`.acl 
FROM    `usResult` acl
        INNER JOIN (
          SELECT  `usResult`.id
          FROM    (      
                    SELECT  `usResult`.id
                             , SUM(`ace`.`allowed`) AS SumAllowed
                             , COUNT(`ace`.`allowed`) AS CountAllowed
                    FROM    `user` usResult
                            INNER JOIN access_control_list acl ON usResult.acl = acl.id
                            INNER JOIN group_access_control_entry ace ON acl.id = ace.acl
                            INNER JOIN `group` gp ON ace.gid = gp.id
                            INNER JOIN group_membership ON gp.id = group_membership.gid
                            INNER JOIN `user` usr ON group_membership.uid = usr.id
                    WHERE   usr.id = 1
                    GROUP BY
                            `usResult`.id
                    UNION ALL 
                    SELECT  `usResult`.id
                             , SUM(`ace`.`allowed`) AS SumAllowed
                             , COUNT(`ace`.`allowed`) AS CountAllowed
                    FROM    `user` usResult
                            INNER JOIN access_control_list acl ON usResult.acl = acl.id
                            INNER JOIN user_access_control_entry ace ON acl.id = ace.acl
                            INNER JOIN `user` usr ON ace.uid = usr.id
                    WHERE usr.id = 1
                    GROUP BY
                            `usResult`.id
                  ) results
          GROUP BY
                  results.id
          HAVING  SUM(results.SumAllowed) = SUM(results.CountAllowed)
      ) r ON r.id = acl.id
0 голосов
/ 21 марта 2011

В зависимости от количества ваших пользователей и частоты авторизованных действий вы можете рассмотреть возможность кэширования всего поддерева разрешений для конкретного пользователя.

Вы можете настаивать на кешировании ACID, отслеживая последний раз, когдатаблицы разрешений были обновлены, и это дает вам быстрый запрос, который может определить, нужно ли выполнять медленнее.

В противном случае ваши запросы выглядят нормально;Мне не нравится COUNT (разрешено) = SUM (разрешено), чтобы установить, что нет нулей.Вариант NOT EXIST должен быть в состоянии оптимизировать по сравнению с этим подходом, который должен вычислять COUNT () и SUM () по всем записям.Однако при условии разумного распределения данных я бы ожидал гораздо больший выигрышный коэффициент для кэширования разрешений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...