J'ai le tableau suivant ( voir sur SQL Fiddle ) (j'ai créé pour décomposer mon problème):
| ID | Word |
----------------
| 5 | "Hello" |
| 6 | NULL |
| 7 | "World" |
| 8 | "World" |
Maintenant, je veux compter le nombre d'occurrences de chaque mot en utilisant GROUP BY Word WITH ROLLUP
. Le NULL dans la colonne Word de la ligne générée par le ROLLUP doit être remplacé par "total":
SELECT
ID,
ifnull(Word, "total") as Word,
count(*) as occurrences
FROM test
GROUP BY Word WITH ROLLUP;
Le problème est qu'il remplace également le NULL
dans l'enregistrement par le nombre de lignes où les mots sont NULL:
| ID | Word | occurrences |
|----|-------|-------------|
| 6 | total | 1 | <- Here lies the problem
| 5 | Hello | 1 |
| 7 | world | 2 |
| 7 | total | 4 |
Donc, j'utilise maintenant count(Word)
pour distinguer s'il s'agit d'un NULL de mes données ou d'un NULL créé par ROLLUP afin que je puisse le remplacer par "vide" ou "total":
SELECT
ID,
if(count(Word) = 0, "empty", ifnull(Word, "total")) as Word,
count(*) as occurrences
FROM test
group by Word with ROLLUP;
Cela fonctionne pour mon cas d'utilisation mais il a toujours un défaut: si Word est NULL dans toutes les lignes, count(Word)
est également 0 dans la dernière ligne affichant le total: ( SQL Fiddle avec des données différentes (seulement enregistrement # 6) )
| ID | Word | occurrences |
|----|-------|-------------|
| 6 | empty | 1 |
| 6 | empty | 1 | <- Word should be "total"
La déclaration suivante fournit le résultat que je souhaite:
SELECT
ID,
ifnull(Word, "total") as Word,
count(*) as occurrences
FROM
(
SELECT
ID,
ifnull(Word, "empty") as Word
FROM test
) tmp
GROUP BY Word WITH ROLLUP
Mais comme dans la plupart des réponses ici, l'utilisation des sous-requêtes semble découragée, je me demande s'il existe une meilleure solution.
vous avez une "solution", donc en disant
Je me demande s'il y a une meilleure solution.
Je pense que vous voulez en parler. Permettez-moi de me concentrer sur plusieurs aspects:
SELECT ID [...] GROUP BY Word
, indépendamment du fait d'avoir ou non un WITH ROLLUP
est faux. Vous sélectionnez un champ dont la valeur est indéterminée . En particulier, avec MySQL, vous êtes exposé à renvoyer une valeur aléatoire; ou si vous utilisez un mode strict (recommandé et par défaut dans les dernières versions de MySQL), la requête échoue: 'db_9_b0ff7.test.ID' n'est pas dans GROUP BY
Je ne vois en principe aucun problème avec votre requête finale, si nous exécutons expliquer sur les deux requêtes (sans ID), nous voyons:
MariaDB [test]> EXPLAIN SELECT if(count(Word) = 0, "empty", ifnull(Word, "total")) as Word, count(*) as occurrences FROM test group by Word with ROLLUP\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 4
Extra: Using filesort
1 row in set, 1 warning (0.00 sec)
Warning (Code 1052): Column 'Word' in group statement is ambiguous
MariaDB [test]> EXPLAIN SELECT ifnull(Word, "total") as Word, count(*) as occurrences FROM ( SELECT ifnull(Word, "empty") as Word FROM test ) tmp GROUP BY Word WITH ROLLUP\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 4
Extra: Using filesort
1 row in set, 1 warning (0.01 sec)
Normalement, la haine envers MySQL est due à ses limites sur les premières versions de MySQL. Je ne vais pas dire qu'il est maintenant parfait, il y aura toujours des problèmes, comme le développement de MySQL, comme il l'a toujours été, axé sur un SGBDR rapide, facile à entrer et à sortir, et non un moteur relationnel complet , mais une sous-requête au bon endroit est correcte (après tout, certaines requêtes nécessiteront une sous-requête, peu importe quoi. Votre utilisation est correcte - elle seulement avoir une surcharge de parcourir les lignes résumées à nouveau et je suppose que sur la vraie affaire, celles-ci ne seront pas significatives par rapport à l'ensemble complet.
Remarque - sur mon instance ici, la sous-requête n'est pas affichée, mais vous pouvez la voir avec des sous-requêtes matérialisées en 5.6 ici: http://sqlfiddle.com/#!9/b0ff7/7 (It exécutera la requête interne puis la partie externe, sans problème). Un index pourrait certainement aider ici (en raison du port de fichiers), mais cela dépend du nombre final d'enregistrements. La sous-requête peut nuire aux performances si un index (qui n'existe pas actuellement) ne peut pas être utilisé car il. Vous devez tester votre version spécifique de mysql et créer un tel index.
Maintenant, la dernière question est, est-il possible de le faire sans sous-requête sur MySQL, et sera-t-il meilleur/moins laid? Je ne vois pas vraiment de moyen - nous pourrions faire une jointure, mais ce sera toujours une sous-requête. Je peux penser à ne alternative , qui est:
SELECT * FROM (
SELECT Word,
count(*) as occurrences
FROM test
GROUP BY Word with rollup)
tmp
ORDER BY occurrences;
Cela garantit que la dernière ligne contient le résumé (et s'il y a un autre null, c'est la valeur null réelle). L'ordre se produirait même dans le cas de valeurs nulles uniquement. Au contraire, il peut même être plus lent car il nécessite une commande, et encore une fois, vous pouvez avoir besoin d'un index, et cet index peut ou non être utilisé.
Vous pouvez éviter les sous-requêtes et optimiser les caractères saisis à l'aide de cette requête:
SELECT
ifnull(Word, "empty") as Word,
count(*) as occurrences
FROM test
GROUP BY ifnull(Word, "empty") WITH ROLLUP
;
Cela remplace essentiellement les valeurs de null
par votre marqueur "vide" avant d'effectuer le regroupement:
null
valeur dans la colonne de regroupementUne approche similaire peut être utilisée pour PostgreSQL, cela ne devrait pas être nécessaire pour SQL Server car il fournit une fonction spéciale GROUPING
.