Domanda

Sto cercando di creare un sistema di controllo degli accessi.

Ecco un esempio ridotto di come appare la tabella a cui sto cercando di controllare l'accesso:

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 la tabella di controllo degli accessi è simile alla seguente:

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

L'accesso può essere concesso specificando direttamente l'id della "cosa" oppure concedendo un intero gruppo di cose specificando un ID del gruppo. Nell'esempio sopra, all'utente 1 è stato concesso un livello di accesso di 50 al gruppo 1, che dovrebbe applicarsi a meno che non ci siano altre regole che garantiscano un accesso più specifico a una singola cosa.

Ho bisogno di una query che ritorni un elenco di cose (solo ids va bene) insieme al livello di accesso per un utente specifico. Quindi, usando l'esempio sopra, vorrei qualcosa di simile per l'id utente 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)

Il più vicino che posso trovare è questo:

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)
    )

Ma questo restituisce più righe, quando ne voglio solo una per ogni riga nella tabella "cose". Non sono sicuro di come passare a una singola riga per ogni 'cosa', o come dare priorità alle regole 'cosa' rispetto alle regole 'gruppo'.

Se aiuta, il database che sto usando è PostgreSQL.

Non esitate a lasciare un commento se ci sono informazioni che mi sono perso.

Grazie in anticipo!

È stato utile?

Soluzione

Non conosco il dialetto SQL di Postgres, ma forse qualcosa del tipo:

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

Per inciso, non mi piace il design. Preferirei che la tabella di accesso fosse divisa in due:

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

La mia query diventa quindi:

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

Preferisco questo perché ora le chiavi esterne possono essere utilizzate nelle tabelle di accesso.

Altri suggerimenti

Ho appena letto un articolo ieri sera su questo. Ha alcune idee su come farlo. Se non riesci a utilizzare il link sul titolo, prova a utilizzare Google Scholar su Limitare la divulgazione in Ippocratico Basi di dati.

Mentre ci sono molte buone risposte, la più efficace sarebbe probabilmente qualcosa del genere:

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

Che utilizza semplicemente il riepilogo aggiunto alla tua query per ottenere ciò che stai cercando.

Tony:

Non è una cattiva soluzione, mi piace, sembra funzionare. Ecco la tua domanda dopo una piccola modifica:

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 i risultati sembrano corretti:

 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 |       

Faccio completamente il fatto che non sia uno schema ideale. Tuttavia, ne sono bloccato in una certa misura.


Josef:

La tua soluzione è molto simile alle cose con cui stavo giocando, e i miei istinti (come sono) mi dicono che dovrebbe essere possibile farlo in quel modo. Purtroppo non produce risultati completamente corretti:

 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 |    

Il livello di accesso per 'cosa 1' ha preso il valore di accesso 'gruppo' più alto, piuttosto che il valore di accesso 'cosa' più specifico di 10, che è quello che sto cercando. Non credo che ci sia un modo per risolverlo all'interno di un GROUP BY , ma se qualcuno ha qualche suggerimento sono più che felice di essermi dimostrato errato su quel punto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top