2010-09-16 15 views
2

Supposons que j'ai trois tables: user, group et xref, une table qui leur donne un RI plusieurs-à-plusieurs.Comment puis-je vérifier une certaine valeur dans toutes les lignes agrégées?

je pourrais vouloir voir comment les groupes chaque utilisateur appartient à:

select 
    user.user_id, 
    user.user_name, 
    count(*) as group_count 
from 
    user 
     inner join xref on user.user_id = xref.user_id 
     inner join group on group.group_id = xref.group_id 
group by user.user_id, user.user_name 

Tout va bien jusqu'à présent. Mais, si je veux des informations supplémentaires? Je signale, et je veux savoir si chaque utilisateur est un développeur ou un gestionnaire de contenu. Maintenant, un ressort anti-modèle:

select 
    user.user_id, 
    user.user_name, 
    count(*) as group_count, 
    max(case group.group_name when 'Developers' then 'Y' else null end) 
     as is_dev 
    max(case group.group_name when 'Content Management' then 'Y' else null end) 
     as is_cm 
from 
    user 
     inner join xref on user.user_id = xref.user_id 
     inner join group on group.group_id = xref.group_id 
group by user.user_id, user.user_name 

Cela fonctionne, et produit les résultats escomptés, mais il se sent très mal. Ce que je veux à demander Oracle est la suivante:

« Pour chaque utilisateur, montrez-moi combien de groupes ils sont en outre, pour tous les noms de groupe par utilisateur, me montrer si « développeurs » est l'un des. les valeurs."

Ce que je suis en fait demander est ceci:.

« Pour chaque utilisateur, montrez-moi combien de groupes ils sont en outre, pour tous les noms de groupe par utilisateur, montrez-moi la valeur la plus élevée produite par cette expression case. "

La raison pour laquelle c'est un anti-modèle est que je suis essentiellement compter sur le fait que Yarrive à « bulle » ci-dessus null lors de l'évaluation avec max(). Si quelqu'un voulait copier ou augmenter cette requête, il pourrait facilement oublier l'anti-pattern et changer accidentellement les valeurs de retour en quelque chose qui n'utilise pas la même coïncidence non intuitive.

Au fond, la requête que je voudrais pouvoir écrire est la suivante:

select 
    user.user_id, 
    user.user_name, 
    count(*) as group_count, 
    any(group.group_name, 'Developers', 'Y', null) as is_dev, 
    any(group.group_name, 'Content Management', 'Y', null) as is_cm 
from 
    user 
     inner join xref on user.user_id = xref.user_id 
     inner join group on group.group_id = xref.group_id 
group by user.user_id, user.user_name 

J'ai tamiser autour des options, et il semble qu'il y ait quelques potentiels:

  • first_value pourrait travail, mais je ne peux pas comprendre comment limiter la fenêtre correspondante partition aux bonnes lignes.
  • fonctions analytiques avec une clause over pourrait fonctionner, mais je ne veulent s'effondrer les colonnes je regroupement par, il ne semble pas être un ajustement parfait. De manière exaspérante, il semble y avoir une fonction any documentée here, mais elle n'existe que dans un mystérieux dialecte appelé Oracle OLAP DML, que je ne pense pas pouvoir accéder en utilisant uniquement SQL sur 10g. Mais, il semble faire exactement ce que je veux.

C'est tout ce que j'ai. Des idées?

Je reconnais qu'il y a deux idées très simples, "Do it in code" ou "Do it in PL/SQL", mais c'est de la triche.:-)

+0

peut appartenir à l'utilisateur à la fois les développeurs et les gestionnaires de contenu? est-ce la raison pour laquelle vous avez besoin de deux colonnes distinctes? –

+0

Au lieu de mettre [SQL] dans le titre, je me fierais simplement à la balise «sql». –

+0

@be ici maintenant- Oui, c'est pourquoi. –

Répondre

3

Je passerais de MAX à SUM (avec 1 plutôt que Y) donc vous dites "Comptez le nombre de groupes dans lesquels cette personne se trouve lorsque le nom du groupe est Développeurs". Ensuite, la tendance est similaire à «compter le nombre de ventes pour lesquelles la valeur d'achat était supérieure à 30 dollars».

Vous pouvez, si vous le souhaitez, ajouter une autre expression pour dire "Si le compte est supérieur à zéro, alors 'oui' cette personne est un développeur". Très explicite et probablement inutile cependant.

+0

Upvoted parce que SUM() 'semble moins d'une solution de contournement. Mais, je tiens à accepter des réponses pour le moment. Merci! –

2
SELECT user.user_id, 
     user.user_name, 
     COUNT(*) group_count, 
     COUNT(DISTINCT DECODE(group_name, 'Developers', 'Y', NULL)) AS is_developer 
     COUNT(DISTINCT DECODE(group_name, 'Content Management', 'Y', NULL)) AS is_content_manager 
FROM the_query 

Quant au ANY, il est un prédicat similaire à IN, pas une fonction:

SELECT * 
FROM dual 
WHERE 'baz' = ANY('foo', 'bar', 'baz') 
0

Je préfère Gary's answer, mais si vous voulez coller avec un retour booléenne que vous pouvez faire la commande plus explicite en retournant 'N' au lieu de null.

select 
    user.user_id, 
    user.user_name, 
    count(*) as group_count, 
    max(case group.group_name when 'Developers' then 'Y' else 'N' end) 
     as is_dev 
    max(case group.group_name when 'Content Management' then 'Y' else 'N' end) 
     as is_cm 
from 
    user 
     inner join xref on user.user_id = xref.user_id 
     inner join group on group.group_id = xref.group_id 
group by user.user_id, user.user_name 

(+1 pour la question bien écrite)

+0

Merci pour le +1 et répondre. Malheureusement, le «N» est victime du même problème que celui que j'ai mentionné dans ma question, à savoir que vous vous fiez au fait que «N» se trouve trier plus haut que «Y». Par exemple, si au lieu de 'Y' et 'N', vous utilisiez 'Toujours' et 'Jamais', alors chaque cellule serait 'Jamais', parce que 'max' le ramassera toujours. C'est un "getcha" non intuitif qui, je crois, fait partie de ce qui en fait un véritable anti-pattern. –

+0

Comme je l'ai dit, je préfère le comptage, mais je pense que vous êtes assez sûr d'utiliser Y et N comme un drapeau. –