2016-02-26 2 views
2

Я работаю с набором таблиц, который устанавливает денормализованную структуру местоположения со следующей схемой:найти самый верхний элемент в иерархии денормализованного

Location  (Name,Id) 
Sublocation1 (Name,Id,LocationId) 
Sublocation2 (Name,Id,Sublocation1Id) 
Sublocation3 (Name,Id,Sublocation2Id) 

И таблицей, которая отслеживает связь между пользователем и каждым уровень:

UserLocation (User,LocationId,Sublocation1Id,Sublocation2Id,Sublocation3Id) 

доступ к более высокому уровню грантов места доступа к любому уровню под ней, так что вторая строка в следующем примере является излишней, но третий ряд не является:

User Location  Sublocation1 Sublocation2 Sublocation3 
---------------------------------------------------------------- 
Joe Houston Plant West Building NULL   NULL 
Joe Houston Plant West Building Third Floor  Room 42 
Joe Houston Plant East Building Second Floor Room 21 

Третья строка дает Джо доступ к только номера 21, но не другие Sublocation3s под вторым этажом


Вопрос: Как я могу найти все записи, которые предоставляют самый высокий уровень доступа без предоставление дополнительных прав Джо? Моя цель - убрать все эти посторонние записи из моей базы данных.

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

+0

Таким образом, запрос должен возвращать только второй ряд, поскольку он предоставляет доступ к комнате 42, к которой у Джо есть доступ уже из-за первого ряда, что дает ему доступ ко всем этажам «Западного здания» и всех комнат там. Правильно ли я понял вас? Не могли бы вы добавить еще несколько строк в пример данных с объяснением, какие из них должны и не должны возвращаться запросом. Если бы это помогло понять требуемую логику. –

ответ

1

Учитывая следующую таблицу:

CREATE TABLE UserLocation(
    UserId int, 
    LocationId int, 
    Sublocation1Id int, 
    Sublocation2Id int, 
    Sublocation3Id int 
) 

Следующий запрос дает высокий уровень доступов:

-- Highest level access: 

SELECT * -- Level 3 grants with no higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NOT NULL 
    AND NOT EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND (UL1.Sublocation2Id IS NULL OR UL1.Sublocation2Id = UL.Sublocation2Id) 
      AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id) 
      AND (UL1.LocationId = UL.LocationId) 
    ) 
UNION ALL 
SELECT * -- Level 2 grants with no higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NULL 
    AND UL.Sublocation2Id IS NOT NULL 
    AND NOT EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND UL1.Sublocation2Id IS NULL 
      AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id) 
      AND (UL1.LocationId = UL.LocationId) 
    ) 
UNION ALL 
SELECT * -- Level 1 grants with no higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NULL 
    AND UL.Sublocation2Id IS NULL 
    AND UL.Sublocation1Id IS NOT NULL 
    AND NOT EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND UL1.Sublocation2Id IS NULL 
      AND UL1.Sublocation1Id IS NULL 
      AND (UL1.LocationId = UL.LocationId) 
    ) 
SELECT * -- Level 0 grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NULL 
    AND UL.Sublocation2Id IS NULL 
    AND UL.Sublocation1Id IS NULL 

Следующий запрос показывает вам superflous гранты:

-- Superflous grants (there is higher level grants) 

SELECT * -- Level 3 grants with higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NOT NULL 
    AND EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND (UL1.Sublocation2Id IS NULL OR UL1.Sublocation2Id = UL.Sublocation2Id) 
      AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id) 
      AND (UL1.LocationId = UL.LocationId) 
    ) 
UNION ALL 
SELECT * -- Level 2 grants with higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NULL 
    AND UL.Sublocation2Id IS NOT NULL 
    AND EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND UL1.Sublocation2Id IS NULL 
      AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id) 
      AND (UL1.LocationId = UL.LocationId) 
    ) 
UNION ALL 
SELECT * -- Level 1 grants with higher level grants 
FROM UserLocation UL 
WHERE 
    UL.Sublocation3Id IS NULL 
    AND UL.Sublocation2Id IS NULL 
    AND UL.Sublocation1Id IS NOT NULL 
    AND EXISTS ( 
     SELECT * -- higher level grant 
     FROM UserLocation UL1 
     WHERE 
      UL1.Sublocation3Id IS NULL 
      AND UL1.Sublocation2Id IS NULL 
      AND UL1.Sublocation1Id IS NULL 
      AND (UL1.LocationId = UL.LocationId) 
    ) 

Я предполагаю, что если идентификатор уровня местоположения L null, то идентификационный номер L+1 не имеет знака.

+0

Спасибо! Это было именно то, что мне нужно, чтобы мой запрос был на ходу. Я не уверен, что это делает разницу в производительности, но я переупорядочил логику, чтобы начать с самого высокого уровня местоположения (0). –

1

Предположим, что таблица UserLocation имеет столбец Id. Если это не так, вы можете создать его с помощью ROW_NUMBER.

Для определения правил strong (минимальный набор существующих правил доступа, эквивалентных всем существующим правилам) или weak правил (набор существующих правил, которые являются избыточными, поскольку они также покрываются другими «более сильными» правилами) вы можете присоединиться к UserLocation таблицу с собой:

with access as (
    select * from (values 
    (1, 'Joe', 'Houston Plant', 'West Building', NULL,    NULL ), 
    (2, 'Joe', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'), 
    (3, 'Joe', 'Houston Plant', 'East Building', 'Second Floor', 'Room 21'), 

    (4, 'Mark', 'Houston Plant', 'West Building', NULL,    NULL ), 
    (5, 'Mark', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'), 
    (6, 'Mark', 'Houston Plant', 'West Building', 'Second Floor', 'Room 21'), 

    (7, 'Bob', null,    null,    NULL,    NULL ), 
    (8, 'Bob', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'), 
    (9, 'Bob', 'Houston Plant', 'West Building', 'Second Floor', 'Room 21') 
    ) as v(Id, Usr, Location1, Location2, Location3, Location4) 
) 
select distinct 
     weak.* -- or strong.* 
from access strong 
     inner JOIN 
     access weak on weak.Usr = strong.Usr 
        and weak.Id <> strong.Id 
        and (strong.Location1 is null or weak.Location1 = strong.Location1) 
        and (strong.Location2 is null or weak.Location2 = strong.Location2) 
        and (strong.Location3 is null or weak.Location3 = strong.Location3) 
        and (strong.Location4 is null or weak.Location4 = strong.Location4) 

Edit:

я понял, что там могут быть правила, которые являются уникальными для пользователя, как:

(10, 'John', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'), 
(11, 'Ana', 'Houston Plant', 'West Building', NULL,   NULL ) 

Поскольку выше запрос использует INNER JOIN эти правила не сообщается в любом наборе (weak или strong), но если вы думаете об этом вы можете считать, что эти правила neutral так сказать, потому что:

  • они не заменяют другие правила
  • они не перекрываются другими правилами
+0

Это действительно впечатляет своей компактностью. Я добавил фильтр для уровня местоположения 0, но в остальном это кажется правильным для SQL. Большое спасибо за отзыв! –

Смежные вопросы