Выбор наиболее конкретного результата из набора результатов SQL - PullRequest
2 голосов
/ 26 мая 2011

Ниже приведены упрощенные DDL и DML для представления чего-то, над чем я сжигал серьезные объемы мозгового вещества. Длинный таймер, первый постер и, надеюсь, не нарушать этикета SO и не использовать слишком много чернил в этом посте.

Существуют ресурсы, которые можно включать или отключать (разрешать) для определенных подразделений в бизнес-организации. Я выбрал ярлык ниже и только что определил таблицу разделов, но также будет таблица компаний и отделов.

Я могу установить разрешения для ресурса, используя любую комбинацию Company, Division и Section.

Набор разрешений только для Секции будет превосходить набор разрешений только для Подразделения или Компании.

Установка разрешения Y с NULL для компании, подразделения и отдела означает, что если нет разрешения, специфичного для этого подразделения, то они получат доступ к ресурсу на основе этого значения по умолчанию.

В настоящий момент я нахожу наиболее подходящее разрешение для бизнес-единицы, выполняя сначала несколько SELECT с наиболее конкретным предложением WHERE (ища ResourcePermission с Company, Division и Section, равной таковой для поставляемого бизнес-подразделения) наименее конкретный (NULL для всех трех). Всего восемь SELECT.

Если позднее будет добавлено больше бизнес-уровней (Department, UserGroup ...), SELECT примут привычки разведения кроликов.

Есть ли лучший способ добиться этого в SQL или лучше подходит для выполнения этого в процедурном коде.

Запуск финального SELECT ниже даст вам 9 разрешений на ресурсы. Я просто хочу, чтобы три, которые являются наиболее определенными для определенной бизнес-единицы.

CREATE TABLE Resources (
  ResourceID varchar(20) NOT NULL PRIMARY KEY NONCLUSTERED,
  ResourceName varchar(100) NOT NULL)
GO

CREATE TABLE ResourcePermissions (
  PermissionID int identity(1,1) PRIMARY KEY NONCLUSTERED,
  ResourceID varchar(20) CONSTRAINT [FK_Resources] FOREIGN KEY REFERENCES Resources(ResourceID),
  Company varchar(10) NULL,
  Division varchar(10) NULL,
  Section varchar(20) NULL,
  Permitted char(1) NOT NULL)
GO

CREATE TABLE Sections (
  Company varchar(10) NOT NULL,
  Division varchar(10) NOT NULL,
  Section varchar(20) NOT NULL,
  SectionName varchar(50) NOT NULL,
  CONSTRAINT PK_Sections PRIMARY KEY (Company, Division, Section) )
GO

INSERT INTO Sections VALUES('Company 1','Division A','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 1','Division A','Blue Section','Bluejays')
INSERT INTO Sections VALUES ('Company 1','Division B','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 1','Division B','Blue Section','Bluejays')
INSERT INTO Sections VALUES ('Company 1','Division C','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 1','Division C','Blue Section','Bluejays')
INSERT INTO Sections VALUES('Company 2','Division A','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 2','Division A','Blue Section','Bluejays')
INSERT INTO Sections VALUES ('Company 2','Division B','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 2','Division B','Blue Section','Bluejays')
INSERT INTO Sections VALUES ('Company 2','Division C','Red Section','Redskins')
INSERT INTO Sections VALUES ('Company 2','Division C','Blue Section','Bluejays')

INSERT INTO Resources VALUES('Irish','Irish Resource')
INSERT INTO Resources VALUES('English','English Resource')
INSERT INTO Resources VALUES('French','French Resource')

INSERT INTO ResourcePermissions VALUES('Irish', NULL, NULL, NULL, 'Y')
INSERT INTO ResourcePermissions VALUES('Irish', NULL, NULL, 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('Irish', NULL, 'Division A', 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('Irish', 'Company 1', 'Division A', NULL, 'N')
INSERT INTO ResourcePermissions VALUES('French', NULL, 'Division B', 'Blue Section', 'Y')
INSERT INTO ResourcePermissions VALUES('French', 'Company 2', NULL, 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('French', 'Company 1', NULL, 'Blue Section', 'Y')
INSERT INTO ResourcePermissions VALUES('French', NULL, NULL, 'Blue Section', 'Y')
INSERT INTO ResourcePermissions VALUES('French', NULL, 'Division B', 'Red Section', 'N')
INSERT INTO ResourcePermissions VALUES('French', NULL, 'Division C', 'Red Section', 'Y')
INSERT INTO ResourcePermissions VALUES('English', NULL, 'Division B', 'Blue Section', 'Y')
INSERT INTO ResourcePermissions VALUES('English', 'Company 2', NULL, 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('English', NULL, 'Division A', 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('English', NULL, NULL, 'Blue Section', 'N')
INSERT INTO ResourcePermissions VALUES('English', 'Company 1', 'Division A', 'Blue Section', 'Y')

Запрос:

  SELECT ResourceID, Company, Division, Section, Permitted
    FROM ResourcePermissions
   WHERE (Company = 'Company 1' OR Company IS NULL)
     AND (Division = 'Division A' OR Division IS NULL)
     AND (Section = 'Blue Section' OR Section IS NULL)
ORDER BY ResourceID

Ответы [ 2 ]

1 голос
/ 26 мая 2011

Это можно сделать одним запросом, если вы используете аналитические запросы, но я бы лично использовал временные таблицы и несколько запросов.

CREATE TEMPORARY TABLE _ResourceDetail AS
SELECT ResourceID
  , Company
  , Division
  , Section
  , Permitted
  , CASE WHEN Company IS NULL THEN 0 ELSE 1 END
    + CASE WHEN Division IS NULL THEN 0 ELSE 2 END
    + CASE WHEN Section IS NULL THEN 0 ELSE 4 END
    AS Priority
FROM ResourcePermissions
WHERE (Company = 'Company 1' OR Company IS NULL)
  AND (Division = 'Division A' OR Division IS NULL)
  AND (Section = 'Blue Section' OR Section IS NULL);

CREATE TEMPORARY TABLE _BestResource AS
SELECT ResourceID, max(Priority) as MaxPriority
FROM _ResourceDetail
GROUP BY ResourceID;

SELECT d.ResourceID
  , d.Company
  , d.Division
  , d.Section
  , d.Permitted
FROM _ResourceDetail d
  JOIN _BestResource b
    ON d.ResourceID = b.ResourceID
      AND d.Priority = b.MaxPriority
ORDER BY d.ResourceID;

В качестве альтернативы вы можете просто поставить ORDER BY в первый запрос и тривиально отфильтровать максимальный приоритет в цикле. (Или даже вытолкните расчет Priority из базы данных.)

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

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

0 голосов
/ 26 мая 2011

Я не думаю, что это простая задача для установки, но она усложняется из-за очень денормализованной структуры схемы. Я предполагаю, что на самом деле существует иерархическая структура, в которой каждая компания имеет несколько отделов, а каждое подразделение имеет несколько разделов. Тогда у вас должно быть три таблицы: Companies, Divisions, Sections, а таблица Sections будет иметь только FK для своего деления. (Его компания может быть определена из этого.)

Однако Companies, Divisions и Sections имеют два (как минимум) общих атрибута. У них есть родители (кроме самого высокого уровня), и они могут появиться в ResourcePermissions. Итак, то, что мы имеем здесь, концептуально призывает к наследованию . Поскольку поддержка наследования в большинстве RDBMS довольно слабая (некоторые в Postgresql, я не знаю о SQL Server), вам придется выполнять большую часть настройки самостоятельно с помощью триггеров. В корне наследования находится таблица [псевдо DDL]

CorporateElement
element_id SERIAL (PK, AutoIncrement, etc.)
parent int (FK references CorporateElement.id)
level int or enum (Division, Section, etc., but not as text,
                  as an enum or an FK into a list of these)

ResourcePermissions
resource_id int (FK references Resource.resource_id)
element_id int (FK references CorporateElement.element_id)

Ваши Section и другие таблицы наследуют свой ключ от element_id, но их текстовые имена и другие данные являются локальными по отношению к их собственным таблицам. Далее вам понадобятся возможности WITH RECURSIVE SQL Server. Я собираюсь оставить ответ здесь неполным (отредактирую позже), потому что есть несколько способов сделать это - присоединиться до или после RECURSIVE part & mdash; и мне нужно продумать их до конца.

[править] Хорошо, вот пример запроса, но основанный на рефакторинге схемы. Я не проверял это, но он должен дать список всех ресурсов с разрешениями. Незначительные моды могут добавить те, у кого их нет на любом уровне. И эта структура является гибкой в ​​отношении добавления дополнительных уровней корпоративного субъекта.

WITH RECURSIVE permissions_search(resource_id, element_id, parent, permission) AS
(
SELECT resource_id, element_id, parent, permission
FROM resources
JOIN resource_permissions 
ON resources.resource_id= resource_permissions.resource_id
JOIN corporate_elements
ON corporate_elements.elements_id=resource_permissions.elements_id
WHERE corporate_element.level=section /* enum or magic int value */
UNION ALL
SELECT 
resource_id, element_id, permission
FROM permissions_search ps
JOIN resource_permissions 
ON resources.resource_id= resource_permissions.resource_id
JOIN corporate_elements
ON corporate_elements.elements_id=resource_permissions.elements_id
WHERE (corporate_elements.elements_id=ps.parent) AND (ps.permission IS NULL)
)
SELECT * FROM permissions_search WHERE permission IS NOT NULL;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...