SQL: join: encontre todos os objetos que atendem a alguns requisitos

8

Eu tenho uma tabela que contém uma lista de objetos e quais requisitos eles atendem. Então, eu tenho uma tabela que contém uma lista de tarefas e quais requisitos um objeto deve atender para poder executar a tarefa. Gostaria de consultar: dada uma tarefa, mostre-me todos os objetos que podem executar essa tarefa e, dado um objeto, mostre-me todas as tarefas que esse objeto pode executar:

Exemplo:

tabela task_req

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

Portanto, esta tabela diz que, para executar a tarefa A, são necessários os requisitos req1, req2 e req3.

tabela obj_reqs

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

Para que eu pudesse fazer a pergunta: quais objetos podem executar a tarefaA? A resposta deve ser apenas uma linha:

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

porque obj2 é o único que atende aos requisitos req1, req2, req3. Pergunta diferente: quais objetos podem executar a tarefa B? A resposta é nenhuma, porque não há objeto com os requisitos req4, req5, req6. A consulta deve lidar com lógica em que uma tarefa pode ser executada por vários objetos, retornando várias linhas.

A questão é: qual consulta faz isso?

Meu problema é que consegui encontrar essa consulta, mas me parece muito complicado. A consulta basicamente faz: A) junção interna da tabela task_reqs com a tabela obj_reqs, agrupa por tarefas e objs e conta requisitos distintos, B) seleciona tarefas, conta (distintas (reqs)) do task_reqs agrupa por tarefas, C) junta interna A e B na tarefa e na contagem (distinto (reqs)).

Certamente, existe uma maneira mais fácil de fazer essa consulta, certo?

Estou colando abaixo do código SQL para gerar as tabelas e minha 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');

e minha 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 retorna:

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

Certamente há uma maneira mais simples.

cheif
fonte
Não relacionado ao seu problema, mas: nãodistinct é uma função. O fechamento de uma coluna usada entre parênteses não muda nada e é inútil. é o mesmo quedistinctcount(distinct (a))count(distinct a)
a_horse_with_no_name 18/12/19

Respostas:

1

Aqui está uma maneira mais simples possível:

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)

Demo

rad
fonte
Esta é a minha solução preferida. Você poderia adicionar um exemplo de violino de SQL, como os outros respondentes, por favor?
cheif
Certo. Agora é adicionado.
rad
2

Você pode fazer isso com uma junção cruzada das tabelas:

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)

Veja a demonstração .
Resultados:

| task  | object | n_reqs |
| ----- | ------ | ------ |
| taskA | obj2   | 3      |
forpas
fonte
A junção cruzada não é realmente ruim?
cheif
Para 2 mesas enormes, é ruim.
forpas 18/12/19
1

Sua consulta parece boa. Acredito que isso seja complicado, não importa como você o siga, pois os critérios de junção e / ou onde os predicados dependerão de ambos reqe da contagem de reqcorrespondências.

As funções da janela podem reduzir o tempo de processamento aqui, pois você pode eliminar uma varredura de tabela da sua 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 

Se você tiver um índice obj_reqs.req, acho que você também achará essa consulta bastante rápida. Se você estiver interessado em apenas uma tarefa específica, poderá adicioná-la à WHEREcláusula na subconsulta mais interna ( trqs).

SQLFiddle aqui

Inverter essa lógica funciona para a pergunta 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 aqui

JNevill
fonte