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
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)
);
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:
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.
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;
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;