web-dev-qa-db-fra.com

Sélectionnez les lignes pour lesquelles au moins une ligne par jeu remplit une condition

J'ai le tableau suivant:

create table test (
  company_id integer not null, 
  client_id integer not null, 
  client_status text,
  unique (company_id, client_id)
);

insert into test values
  (1, 1, 'y'),    -- company1

  (2, 2, null),   -- company2

  (3, 3, 'n'),    -- company3

  (4, 4, 'y'),    -- company4
  (4, 5, 'n'),

  (5, 6, null),   -- company5
  (5, 7, 'n')
;

Fondamentalement, il existe 5 sociétés différentes, chacune a un ou plusieurs clients et chaque client a le statut: "y" ou "n" (peut également être nul).

Ce que je dois faire, c'est sélectionner toutes les paires (company_id, client_id) pour toutes les entreprises pour lesquelles il existe au moins un client dont le statut n'est pas 'n' ('y' ou null). Ainsi, pour les données d'exemple ci-dessus, la sortie doit être:

company_id;client_id
1;1
2;2
4;4
4;5
5;6
5;7

J'ai essayé quelque chose avec des fonctions de fenêtre, mais je ne sais pas comment comparer le nombre de TOUS les clients avec le nombre de clients avec STATUS = 'n'.

select company_id,
count(*) over (partition by company_id) as all_clients_count
from test
-- where all_clients_count != ... ?

J'ai compris comment faire cela, mais je ne sais pas si c'est la bonne façon:

select sub.company_id, unnest(sub.client_ids)
from (
  select company_id, array_agg(client_id) as client_ids
  from test
  group by company_id
  having count(*) != count( (case when client_status = 'n' then 1 else null end) )
) sub
7
user606521

Fondamentalement vous recherchez l'expression:

client_status IS DISTINCT FROM 'n'

La colonne client_status Doit vraiment être du type de données boolean , pas text, ce qui permettrait l'expression plus simple:

client_status IS NOT FALSE

Le manuel contient des détails dans le chapitre Opérateurs de comparaison .


En supposant que votre table réelle a une contrainte UNIQUE ou PK , nous arrivons à:

CREATE TABLE test (
  company_id    integer NOT NULL, 
  client_id     integer NOT NULL, 
  client_status boolean,
  PRIMARY KEY (company_id, client_id)
);

Requêtes

Tous ces éléments font la même chose (ce que vous avez demandé), ce qui dépend le plus rapidement de la distribution des données:

SELECT company_id, client_id
FROM   test t
WHERE  EXISTS (
   SELECT 1 FROM test
   WHERE  company_id = t.company_id
   AND    client_status IS NOT FALSE
   );

Ou:

SELECT company_id, client_id
FROM   test t
JOIN  (
   SELECT company_id
   FROM   test t
   GROUP  BY 1
   HAVING bool_or(client_status IS NOT FALSE)
   ) c USING (company_id);

Ou:

SELECT company_id, client_id
FROM   test t
JOIN  (
   SELECT DISTINCT company_id, client_status 
   FROM   test t
   ORDER  BY company_id, client_status DESC
   ) c USING (company_id)
WHERE  c.client_status IS NOT FALSE;

Les valeurs booléennes trient FALSE -> TRUE -> NULL dans l'ordre de tri croissant. Donc FALSE vient en dernier dans l'ordre décroissant. S'il y a n'importe quel autre valeur disponible, celle-là est choisie en premier ...

Le PK ajouté est implémenté avec un index utile pour ces requêtes. Si vous voulez encore plus rapidement, ajoutez un index partiel pour la requête 1:

CREATE INDEX test_special_idx ON test (company_id, client_id)
WHERE  client_status IS NOT FALSE;

Vous pourriez utiliser également les fonctions de la fenêtre, mais ce serait plus lent. Exemple avec first_value():

SELECT company_id, client_id
FROM  (
   SELECT company_id, client_id
        , first_value(client_status) OVER (PARTITION BY company_id
                                           ORDER BY client_status DESC) AS stat
   FROM   test t
   ) sub
WHERE stat IS NOT FALSE;

Pour lots de lignes par company_id, L'une de ces techniques peut être plus rapide, toujours:

5

Je vous ai peut-être mal compris mais j'imagine quelque chose comme:

 select * 
 from test x 
 where exists ( 
     select 1 
     from test y 
     where x.company_id = y.company_id 
       and coalesce(client_status, 'y') <> 'n'
 );

marchera. coalesce est utilisé pour mapper null à "y", mais tout autre chose que "n" devrait faire

L'utilisation d'une fonction OLAP peut nous faire économiser une "jointure":

select company_id, client_id 
from (
    select x.*
         , count(nullif(coalesce(client_status,'y'),'n')) 
               over (partition by company_id) as cnt 
    from test x
) 
where cnt > 0;

Ici, nous mappons null -> 'y' et 'n' -> null. Puisque count (x) comptera les lignes où x n'est pas nul, nous comptons les lignes où client_status <> 'n'. J'ai utilisé une fonction OLAP pour éviter GROUP BY, ce qui signifie que nous n'avons besoin de référencer la table qu'une seule fois.

2
Lennart

Je pense que cela peut être un peu simplifié:

select company_id 
from test 
group by company_id 
having count(*) filter (where client_status!='n' or client_status is null) > 0;
1
Károly Nagy

Une requête SQL standard ci-dessous devrait fonctionner

select
  company_id,
  client_id
from test
where client_status!='n' or client_status is null;
0
Sahap Asci