Tenho o seguinte exemplo de estrutura de dados de cliente que pode fazer parte de vários grupos usando uma tabela de junção e dados:
CREATE TABLE customer(id) AS VALUES (0),(1),(2),(3);
CREATE TABLE groups(id) AS VALUES (1),(3),(5),(6);
CREATE TABLE customers_to_groups(customer_id, group_id) AS
VALUES (0, 1)--customer 0 is in group (5 OR 6) AND (1 OR 3)
,(0, 5)--customer 0 is in group (5 OR 6) AND (1 OR 3)
,(1, 1)
,(1, 90)
,(2, 1)
,(3, 3)--customer 3 is in group (5 OR 6) AND (1 OR 3)
,(3, 5)--customer 3 is in group (5 OR 6) AND (1 OR 3)
,(3, 90);
Preciso obter clientes que tenham grupos específicos dos quais fazem parte e preciso obter uma lista de todos os clientes que fazem parte de pelo menos um grupo em várias listas de grupos. Por exemplo eu quero pegar todos os clientes que estão no grupo (5 OR 6) AND (1 OR 3)
, então por exemplo um cliente nos grupos 5 e 1 seria retornado, mas alguém no grupo 1 e 90 ou apenas no grupo 1 não. Com os dados de amostra fornecidos, obteríamos o cliente com os IDs 0 e 3 apenas se eles estivessem em conformidade com as regras fornecidas acima.
Apenas fazer WHERE group_id IN (5,6) AND group_id IN (1,3)
não parece funcionar, então estou procurando uma alternativa.
Eu tenho isso até agora que funciona:
SELECT DISTINCT c.id
FROM customer c
INNER JOIN customers_to_groups at1 ON c.id = at1.customer_id
INNER JOIN customers_to_groups at2 ON c.id = at2.customer_id
WHERE at1.group_id IN (5, 6)
AND at2.group_id IN (1, 3);
Resultados esperados:
eu ia |
---|
0 |
3 |
Existe uma maneira de fazer isso com mais desempenho?
Você pode obter o resultado desejado com uma consulta mais otimizada usando uma cláusula GROUP BY e HAVING. Essa abordagem evita a necessidade de múltiplas autojunções
Consideração sobre índice: para melhorar ainda mais o desempenho, certifique-se de ter um índice nas colunas customer_id e group_id na tabela clients_to_groups:
Podemos
GROUP BY
identificar o ID do cliente e usar umaHAVING
cláusula. Lá podemos usarCASE
ouFILTER
se o seu RDBMS suportar. Postgres deveria.Lá suas condições serão definidas.
A consulta será:
ou
Nota: As consultas acima pressupõem que você realmente precisa incluir ambas
customers
ascustomers_to_groups
tabelas e juntá-las. Caso não precise incluir acustomers
tabela, basta removê-la e selecionar apenas na tabelacustomers_to_groups
para melhorar o desempenho:Esta demonstração com seus dados de amostra e muitas linhas adicionais mostra as diferenças de desempenho.
Usar
FILTER
orCASE
é muito mais rápido (pois evita um segundoJOIN
na tabela clients_to_groups). A diferença exata de desempenho depende dos dados reais em suas tabelas e de quais índices você usa.Este é um problema de Divisão Relacional com a diferença de que você tem múltiplos divisores. O que você tem não é ruim em termos de eficiência.
Mas se você quiser fazer isso dinamicamente e passar diferentes números de grupos e blocos de grupos, precisará de uma solução diferente.
Você pode passá-lo como um parâmetro de array JSONB
Alternativamente, se você tiver os dados de entrada em uma tabela, poderá usar a Divisão Relacional completa
banco de dados<> violino
Faça uma cláusula
GROUP BY
, useHAVING
para garantir que pelo menos um de (5, 6) e pelo menos um de (1, 3) estejam lá.Você também pode usar
INTERSECT
:se esta tarefa for repetitiva, você pode colocá-la em uma função
Como
customer_id
é provável que seja uma chave estrangeira paracustomer.id
significar que ambas as tabelas possuem o mesmo valor, não vejo motivo para alcançá-lacustomer
.Uma forma bem padrão SQL de fazer isso é usando
INTERSECT
já sugerido pelo @jarlh :ALL
a palavra-chave ignora a desduplicação, que é o comportamento padrão deINTERSECT
(DISTINCT
).Você também pode executar uma auto-junção :
E se você gosta da ideia de @Rahul Jangid e Jonas Metzler
having
, a condição que você procura se simplifica parabool_or()
:Ou você pode usar um
EXISTS
:Neste teste db<>fiddle com
19 000
clientes com1 900 000
atribuições aleatórias a alguns19 000
grupos, aEXISTS
variante ganha o plano mais rápido com Hash Semi Join .Todos estes são melhor atendidos com um índice de cobertura que permite varreduras somente de índice :
Se você pretende reutilizá-lo dinamicamente, envolva-o em uma instrução preparada com parâmetros :
Então