2009-07-30 4 views
1

J'ai une table qui n'a pas de clé primaire et je ne peux pas ajouter une - les colonnes pertinentes de celui-ci sont:Sélectionnez la plus fréquente enregistrements à l'aide de deux ou plusieurs colonnes de regroupement

Department | Category | 
-------------+-----------+ 
0001   | A   | 
0002   | D   | 
0003   | A   | 
0003   | A   | 
0003   | C   | 
0004   | B   | 

Je veux récupérer un ligne unique pour chaque Department, ce qui me donne le code du département et la Category qui apparaît le plus fréquemment dans le tableau, à savoir

Department | Category | 
-------------+-----------+ 
0001   | A   | 
0002   | D   | 
0003   | A   | 
0004   | B   | 

Quelle est la meilleure façon d'y parvenir? Ma tentative actuelle implique une Count(Category) dans une sous-requête à partir de laquelle le Max(CountofCategory) est alors prise, mais l'inclusion du champ Category à ce stade signifie trop de lignes à renvoyer (puisque GROUP BY est appliqué au niveau Category ainsi que Department). Dans le cas d'une égalité, je choisirais simplement le min/max de la catégorie arbitrairement. Idéalement, cela devrait être indépendant de la base de données, mais il est susceptible de fonctionner sur Oracle ou MySQL.

+0

Vous avez déclaré que vous ne pouvez pas modifier la table en ajoutant une clé primaire, mais pouvez-vous créer des vues au-dessus de la table? –

Répondre

3

Works dans Oracle et SQL Server, je crois tous les standards SQL, des normes plus tard:

with T_with_RN as 
    (select Department 
     , Category 
     , row_number() over (partition by Department order by count(*) Desc) as RN 
    from T 
    group by Department, Category) 
select Department, Category 
from T_with_RN 
where RN = 1 

EDIT Je ne sais pas pourquoi je WITH, la solution est sans doute plus facile à lire en utilisant une vue en ligne:

select Department, Category 
from (select Department 
    , Category 
    , row_number() over (partition by Department order by count(*) Desc) as RN 
    from T 
    group by Department, Category) T_with_RN 
where RN = 1 

FIN EDIT

cas de test:

create table T (
    Department varchar(10) null, 
    Category varchar(10) null 
); 

-- Original test case 
insert into T values ('0001', 'A'); 
insert into T values ('0002', 'D'); 
insert into T values ('0003', 'A'); 
insert into T values ('0003', 'A'); 
insert into T values ('0003', 'C'); 
insert into T values ('0004', 'B'); 
-- Null Test cases: 
insert into T values (null, 'A'); 
insert into T values (null, 'B'); 
insert into T values (null, 'B'); 
insert into T values ('0005', null); 
insert into T values ('0005', null); 
insert into T values ('0005', 'X'); 
-- Tie Test case 
insert into T values ('0006', 'O'); 
insert into T values ('0006', 'P'); 
+0

La question initiale ne mentionne pas comment gérer les liens. Votre solution sélectionne l'une des lignes liées, de manière aléatoire. Je pense qu'il vaudrait mieux utiliser RANK() au lieu de ROW_NUMBER(), car cela retourne toutes les lignes qui sont liées pour la première place. – APC

+0

@APC: Il mentionne des cravates: "En cas d'égalité, je choisirais arbitrairement le min/max de la catégorie." Ce que j'ai pris pour signifier qu'il ne se souciait pas de laquelle des catégories liées a été choisi. Vous avez raison de dire que rank() retournera des liens. –

0

Vous devez nettoyer ce si vous êtes mieux avec les sous-requêtes que je suis, mais dans mon essai ce produit le résultat souhaité:

SELECT 
    main.Department as Department, 
    (SELECT 
    Category 
    FROM yourtable 
    WHERE Department=main.Department 
    GROUP BY Category 
    ORDER BY COUNT(Category) DESC 
    LIMIT 1) AS Category 
FROM yourtable main 
GROUP BY main.Department 

L'astuce est juste pour obtenir une ligne de la sous-requête pour retourner la valeur maximale que vous voulez avec ORDER BY et la "LIMIT 1"

+0

Cette syntaxe ne fonctionne pas avec Oracle. – APC

0

Il existe un moyen plus simple:

select department, stats_mode(category) from T ; 

fonctionne très bien quand seulement la valeur la plus fréquente est nécessaire, quand vous avez besoin de la 2ème, 3ème ... le plus souvent vous devez faire le comptage comme ci-dessus.

0

Vous pouvez également essayer ce qui suit. La fenêtre renvoie ici les catégories ordonnées par leur fréquence de correspondance décroissante par département. FIRST_VALUE() choisit le premier.

SELECT DISTINCT (department), 
    FIRST_VALUE(category) OVER 
    (PARTITION BY department ORDER BY count(*) DESC ROWS UNBOUNDED PRECEDING) 
FROM T 
GROUP BY department, category; 
Questions connexes