Pergunta

Eu estou tentando criar um sistema de controle de acesso.

Aqui está um exemplo simplificada do que a mesa eu estou tentando controlar o acesso a aparência:

things table:
id   group_id   name
1    1          thing 1
2    1          thing 2
3    1          thing 3
4    1          thing 4
5    2          thing 5

E a tabela de controle de acesso esta aparência:

access table:
user_id  type   object_id  access
1        group  1          50
1        thing  1          10
1        thing  2          100

O acesso pode ser concedida, indicando o identificador do 'coisa' diretamente, ou concedida para um grupo inteiro de coisas, especificando um ID de grupo. No exemplo acima, o usuário 1 foi concedido um nível de acesso de 50 ao grupo 1, que devem ser aplicadas a menos que existam quaisquer outras regras que garantam acesso mais específico para uma coisa individual.

Eu preciso de uma consulta que retorna uma lista de coisas (IDS é apenas ok), juntamente com o nível de acesso de um usuário específico. Então, usando o exemplo acima eu iria querer algo assim para identificação de usuário 1:

desired result:
thing_id   access
1          10
2          100
3          50   (things 3 and 4 have no specific access rule,
4          50   so this '50' is from the group rule)
5               (thing 5 has no rules at all, so although I 
                still want it in the output, there's no access
        level for it)

O mais próximo que eu posso vir acima com é o seguinte:

SELECT * 
FROM things 
LEFT JOIN access ON 
    user_id = 1
    AND (
        (access.type = 'group' AND access.object_id = things.group_id)
        OR (access.type = 'thing' AND access.object_id = things.id)
    )

Mas que retorna várias linhas, quando eu só quero um para cada linha na tabela 'coisas'. Eu não tenho certeza de como chegar até uma única linha para cada 'coisa', ou como priorizar regras 'coisa' sobre as regras 'grupo'.

Se ajudar, o banco de dados que estou usando é PostgreSQL.

Por favor, sinta-se livre para deixar um comentário se há alguma informação que eu perdi.

Agradecemos antecipadamente!

Foi útil?

Solução

Eu não sei o dialeto Postgres SQL, mas talvez algo como:

select thing.*, coalesce ( ( select access
                             from   access
                             where  userid = 1
                             and    type = 'thing'
                             and    object_id = thing.id
                           ),
                           ( select access
                             from   access
                             where  userid = 1
                             and    type = 'group'
                             and    object_id = thing.group_id
                           )
                         )
from things

A propósito, eu não gosto do design. Eu preferiria tabela de acesso a ser dividida em duas:

thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)

Meu consulta torna-se então:

select thing.*, coalesce ( ( select access
                             from   thing_access
                             where  userid = 1
                             and    thing_id = thing.id
                           ),
                           ( select access
                             from   group_access
                             where  userid = 1
                             and    group_id = thing.group_id
                           )
                         )
from things

Eu prefiro isso porque chaves estrangeiras agora pode ser usado nas tabelas de acesso.

Outras dicas

Acabei de ler um artigo na noite passada sobre este assunto. Ele tem algumas idéias sobre como fazer isso. Se você não pode usar o link no título tente usar o Google Scholar na Divulgação limitante na Hipócrates bancos de dados.

Embora existam várias respostas boas, o mais eficiente seria provavelmente algo como isto:

SELECT things.id, things.group_id, things.name, max(access) 
FROM things 
LEFT JOIN access ON 
        user_id = 1 
        AND (
                (access.type = 'group' AND access.object_id = things.group_id)
                OR (access.type = 'thing' AND access.object_id = things.id)
        )
group by things.id, things.group_id, things.name

Que simplesmente usa sumarização adicionado a você consulta para obter o que você está procurando.

Tony:

Nem uma solução ruim, eu gosto, parece funcionar. Aqui está a sua consulta após pequenos ajustes:

SELECT 
    things.*, 
    coalesce ( 
        (   SELECT access 
            FROM access 
            WHERE user_id = 1 
                AND type = 'thing' 
                AND object_id = things.id
        ), 
        (   SELECT access 
            FROM access 
            WHERE user_id = 1 
                AND type = 'group' 
                AND object_id = things.group_id 
        ) 
    ) AS access
    FROM things;

E os resultados parecem corretas:

 id | group_id |  name   | access 
----+----------+---------+--------
  1 |        1 | thing 1 |     10
  2 |        1 | thing 2 |    100
  3 |        1 | thing 3 |     50
  4 |        1 | thing 4 |     50
  5 |        2 | thing 5 |       

eu completamente tomar o ponto sobre ele não ser um esquema ideal. No entanto, estou preso com ele até certo ponto.


Josef:

A sua solução é muito semelhante ao material que eu estava brincando com, e meus instintos (tais como eles são) me dizer que ele deve ser possível fazê-lo dessa forma. Infelizmente, não produz resultados completamente corretas:

 id | group_id |  name   | max 
----+----------+---------+-----
  1 |        1 | thing 1 |  50
  2 |        1 | thing 2 | 100
  3 |        1 | thing 3 |  50
  4 |        1 | thing 4 |  50
  5 |        2 | thing 5 |    

O nível de acesso para 'coisa 1' tomou o valor de acesso 'grupo' maior, ao invés do valor específico de acesso 'coisa' mais de 10, que é o que eu estou atrás. Eu não acho que há uma maneira de corrigir isso dentro de um GROUP BY, mas se alguém tiver alguma sugestão que eu sou mais do que feliz de ser provado errado sobre esse ponto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top