3
votes

Utilisation de CASE pour compter correctement les éléments avec la logique if / else dans SQL

Disons que j'ai une table table sous la forme:

WITH a_users AS 
(
  SELECT
    user, 
    SUM(CASE WHEN class = 'a' THEN 1 ELSE 0 END) AS a_class
  FROM 
    table
  WHERE 
    class in ('a', 'b')
  GROUP BY 
    user
), 

labeled_users as (
SELECT 
  user, 
  CASE WHEN a_class >=1 then 'a' ELSE 'b' END as label 
FROM
  a_users
) 

SELECT 
  label, 
  COUNT(DISTINCT user)
FROM 
  labeled_users;

Il n'y a que deux classes.

Je veux écrire une requête de telle sorte que nous comptions le nombre d'utilisateurs dans chaque classe de telle sorte que tout utilisateur qui a les étiquettes a et b soit trié dans a , tout utilisateur avec seulement a est trié dans a , puis tout utilisateur avec seulement b entre dans b . Si elle était appliquée à l'extrait de tableau ci-dessus, nous obtiendrions:

| a | b |
|---|---|
| 2 | 1 |

La transposition est également acceptable, comme:

| class | count |
|-------|-------|
| a     | 2     |
| b     | 1     |

Mon La solution actuelle implique deux CTE:

| user | class |
|------|-------|
| 1    | a     |
| 1    | b     |
| 1    | b     |
| 2    | b     |
| 3    | a     |

Existe-t-il un (1) moyen plus efficace de résoudre ce problème ou (2) une solution plus concise / lisible?


2 commentaires

Est-ce que un est vraiment un sur-ensemble? c'est-à-dire que toutes les personnes dans b sont également dans a ?


Ouais bon appel, titre de la question édité


4 Réponses :


0
votes

Quelque chose comme ça devrait fonctionner, si a et b sont vraiment vos classes. Sinon, ajustez le min / max au besoin.

; with CTE as (
    Select user, min(class) as Class
    from Labeled_Users
    group by user)
Select Class, count(*)
from CTE
group by Class


2 commentaires

Ouais, je ne suppose pas que - faire semblant qu'ils sont deux chaînes différentes


La logique devrait toujours fonctionner, il vous suffit de choisir min / max selon les besoins pour la classe que vous souhaitez privilégier. Par exemple. si vos cours sont prioritaires et pas prioritaires alors utilisez max au lieu de min , puisque priorité trie moins de pas de priorité .



0
votes

Voici une requête simple pour faire le travail en utilisant une sous-requête et une agrégation conditionnelle. Elle doit renvoyer la deuxième version de votre résultat attendu (pivoté):

SELECT
    SUM(CASE WHEN x.minc <> x.maxc OR  x.maxc = 'a' THEN 1 ELSE 0 END) a,
    SUM(CASE WHEN x.minc =  x.maxc AND x.maxc = 'b' THEN 1 ELSE 0 END) b
FROM ( 
    SELECT user, MAX(class) maxclass, MIN(class) minclass
    FROM mytable 
    GROUP BY user 
) x

La sous-requête calcule la classe minimum et maximum de chaque utilisateur. Ensuite, la requête externe compte séparément les utilisateurs:

  • a : utilisateurs qui appartiennent aux deux classes ou simplement à la classe a
  • b : utilisateurs appartenant à la classe b uniquement

Il s'agit d'une syntaxe SQL standard qui fonctionnera sur la plupart des SGBDR (évidemment, même ceux qui ne supportent pas les CTE, comme les versions MySQL antérieures à la 8.0).


0 commentaires

2
votes

En gros, vous voulez "a" pour un utilisateur qui a "a" du tout. Une sous-requête est la première approche:

select count(distinct case when class = 'a' then user end) as num_as,
       count(distinct user) - count(distinct case when class = 'a' then user end) as num_bs
from t;

Avec une petite astuce, vous pouvez éliminer la sous-requête:

select sum(case when num_as > 0 then 1 else 0 end) as num_class_a,
       sum(case when num_as = 0 then 1 else 0 end) as num_class_b
from (select user, sum(case when class = 'a' then 1 else 0 end) as num_as
      from t
      group by user
     ) t;


1 commentaires

Hey! Vous avez oublié un trait de soulignement dans num_as dans la deuxième ligne de votre premier extrait de code



0
votes

Utilisation de String_agg():

with usr_class as(
SELECT DISTINCT usr,
   string_agg(txt,':') as all_class
   FROM abc
   GROUP BY usr 
)
select count(usr),
    case when POSITION('a' in all_class)>0 THEN 'a'
         ELSE 'b'
    END AS CLASS 
    FROM usr_class 
    GROUP BY case when POSITION('a' in all_class)>0 THEN 'a'
             ELSE 'b'
             END;


0 commentaires