SQL: join: encuentra todos los objetos que satisfacen algunos requisitos

8

Tengo una tabla que contiene una lista de objetos y qué requisitos cumplen. Luego tengo una tabla que contiene una lista de tareas y qué requisitos debe cumplir un objeto para poder realizar la tarea. Me gustaría consultar: dada una tarea, muéstrame todos los objetos que pueden realizar esa tarea, y dado un objeto, muéstrame todas las tareas que ese objeto puede realizar:

Ejemplo:

tabla task_req

tasks   |    reqs
-----------------
taskA   |    req1
taskA   |    req2
taskA   |    req3
taskB   |    req4
taskB   |    req5
taskB   |    req6

Entonces, esta tabla dice que para realizar la tarea A, se necesitan los requisitos req1, req2 y req3.

tabla obj_reqs

object  |   reqs
----------------
obj1    |   req3
obj1    |   req4
obj2    |   req1
obj2    |   req2
obj2    |   req3
obj2    |   req4

Entonces podría hacer la pregunta: ¿qué objetos pueden realizar la tarea A? La respuesta debería ser solo una fila:

tasks   |   objects
-------------------
taskA   |   object2

porque obj2 es el único que cumple los requisitos req1, req2, req3. Pregunta diferente: ¿qué objetos pueden realizar la tarea B? La respuesta es ninguna, porque no hay ningún objeto con los requisitos req4, req5, req6. La consulta debe ser lógica de manejo donde una tarea puede ser realizada por múltiples objetos devolviendo múltiples filas.

La pregunta es: ¿qué consulta hace esto?

Mi problema es que he logrado encontrar esa consulta, pero me parece demasiado complicado. La consulta básicamente hace: A) tabla de unión interna task_reqs con tabla obj_reqs, grupo por tareas y objs y cuenta requisitos distintos, B) selecciona tareas, cuenta (distinto (reqs)) del grupo task_reqs por tareas, C) unión interna A y B tanto en la tarea como en el recuento (distinto (requisitos)).

Seguramente hay una manera más fácil de hacer esta consulta, ¿verdad?

Estoy pegando debajo del código SQL para generar las tablas y mi consulta.

create table task_reqs (task varchar, req varchar);
create table obj_reqs (object varchar, req varchar);
insert into task_reqs values ('taskA', 'req1');
insert into task_reqs values ('taskA', 'req2');
insert into task_reqs values ('taskA', 'req3');
insert into task_reqs values ('taskB', 'req4');
insert into task_reqs values ('taskB', 'req5');
insert into task_reqs values ('taskB', 'req6');
insert into obj_reqs values ('obj1','req1');
insert into obj_reqs values ('obj1','req3');
insert into obj_reqs values ('obj2','req1');
insert into obj_reqs values ('obj2','req2');
insert into obj_reqs values ('obj2','req3');
insert into obj_reqs values ('obj2','req4');

y mi consulta:

select t.task,t.object,n.n_reqs
from (
    select task,object,count(distinct(obj_reqs.req)) as n_reqs
    from task_reqs
    inner join obj_reqs on task_reqs.req=obj_reqs.req
    group by task,object
) t
inner join (
    select task,count(distinct(req)) as n_reqs
    from task_reqs
    group by task
) n
on n.n_reqs=t.n_reqs and n.task=t.task;

que devuelve:

 task  | object | n_reqs 
-------+--------+--------
 taskA | obj2   |      3

Seguramente hay una manera más simple.

cheif
fuente
Sin relación con su problema, pero: nodistinct es una función. El encerrar una columna utilizada entre paréntesis no cambiará nada y es inútil. es lo mismo quedistinctcount(distinct (a))count(distinct a)
a_horse_with_no_name

Respuestas:

1

Aquí hay una posible forma más simple:

select t.task, o.object, count(t.req) n_reqs
  from task_reqs t left join obj_reqs o on t.req = o.req
  group by t.task, o.object
  having o.object is not null and count(t.req) = (select count(req) from task_reqs where 
  task = t.task)

Manifestación

rad
fuente
Esta es mi solución preferida. ¿Agregaría un ejemplo de violín SQL, como hicieron los otros respondedores, por favor?
cheif
Por supuesto. Ahora se agrega.
rad
2

Puede hacerlo con una combinación cruzada de las tablas:

select t.task, o.object, count(distinct t.req) n_reqs 
from task_reqs t cross join obj_reqs o
where t.task = 'taskA'
group by t.task, o.object
having count(distinct t.req) = count(case when t.req = o.req then 1 end)

Ver la demo .
Resultados:

| task  | object | n_reqs |
| ----- | ------ | ------ |
| taskA | obj2   | 3      |
forpas
fuente
¿No es realmente cruzado unirse?
cheif
Para 2 mesas masivas es malo.
forpas
1

Tu consulta parece estar bien. Creo que esto será complicado, sin importar cómo lo persigas, ya que los criterios de unión y / o dónde los predicados dependerán de ambos reqy del recuento de reqcoincidencias.

Las funciones de ventana pueden reducir el tiempo de procesamiento aquí, ya que puede eliminar un escaneo de tabla de su consulta original.

SELECT DISTINCT task, object
FROM
  (
    SELECT task, 
      object, 
      COUNT(*) OVER (PARTITION BY task, object) matchCount,
      trqs.reqCount
    FROM (SELECT task, req, count(*) OVER (PARTITION BY task) as reqcount FROM task_reqs) trqs
      INNER JOIN obj_reqs orqs
        ON trqs.req = orqs.req
   ) taskreqcounter
WHERE matchCount = reqCount 

Si tiene un índice obj_reqs.req, creo que esta consulta también es bastante rápida. Si solo le interesa una tarea en particular, puede agregarla a la WHEREcláusula en la subconsulta más interna ( trqs).

SQLFiddle aquí

Invertir esta lógica funciona para la pregunta 2

SELECT DISTINCT task, object
FROM
  (
    SELECT task, 
      object, 
      COUNT(*) OVER (PARTITION BY task, object) matchCount,
      orqs.reqCount
    FROM (SELECT object, req, count(*) OVER (PARTITION BY object) as reqcount FROM obj_reqs) orqs
      INNER JOIN task_reqs trqs
        ON orqs.req = trqs.req
   ) taskreqcounter
WHERE matchCount = reqCount

SQLFiddle aquí

JNevill
fuente