Pregunta

Estoy tratando de crear un sistema de control de acceso.

Este es un ejemplo simplificado de cómo se ve la tabla a la que estoy tratando de controlar el acceso:

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

Y la tabla de control de acceso se ve así:

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

El acceso se puede otorgar ya sea especificando el ID de la 'cosa' directamente, o otorgado para un grupo completo de cosas especificando un ID de grupo. En el ejemplo anterior, al usuario 1 se le ha otorgado un nivel de acceso de 50 al grupo 1, que debe aplicarse a menos que haya otras reglas que otorguen un acceso más específico a una cosa individual.

Necesito una consulta que devuelva una lista de cosas (solo identificaciones, está bien) junto con el nivel de acceso para un usuario específico. Entonces, usando el ejemplo anterior, me gustaría algo como esto para el ID de usuario 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)

Lo más cercano que puedo encontrar es esto:

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

Pero eso devuelve varias filas, cuando solo quiero una para cada fila en la tabla "cosas". No estoy seguro de cómo llegar a una sola fila para cada "cosa", o cómo priorizar las reglas de "cosa" sobre las reglas de "grupo".

Si ayuda, la base de datos que estoy usando es PostgreSQL.

Por favor, siéntase libre de dejar un comentario si hay alguna información que haya omitido.

Gracias de antemano!

¿Fue útil?

Solución

No sé el dialecto de Postgres SQL, pero tal vez 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

Por cierto, no me gusta el diseño. Preferiría que la tabla de acceso se divida en dos:

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

Mi consulta se convierte en:

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

Prefiero esto porque ahora se pueden usar claves foráneas en las tablas de acceso.

Otros consejos

Acabo de leer un artículo anoche sobre esto. Tiene algunas ideas sobre cómo hacer esto. Si no puede usar el enlace en el título, intente usar Google Scholar en Limitación de la divulgación en Hipocrático Bases de datos.

Si bien hay varias respuestas buenas, la más eficiente probablemente sería algo como esto:

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 simplemente usa el resumen agregado a su consulta para obtener lo que está buscando.

Tony:

No es una mala solución, me gusta, parece funcionar. Aquí está su consulta después de pequeños 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;

Y los resultados se ven correctos:

 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 |       

Hago completamente entendiendo que no es un esquema ideal. Sin embargo, estoy atascado con él hasta cierto punto.


Josef:

Tu solución es muy similar a las cosas con las que jugaba, y mis instintos (como son) me dicen que debería ser posible hacerlo de esa manera. Desafortunadamente no produce resultados completamente correctos:

 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 |    

El nivel de acceso para 'cosa 1' ha tomado el valor de acceso de 'grupo' más alto, en lugar del valor de acceso de 'cosa' más específico de 10, que es lo que busco. No creo que haya una manera de solucionarlo dentro de un GROUP BY , pero si alguien tiene alguna sugerencia, estoy más que contento de que se me demuestre lo contrario.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top