C'est pour s'amuser, non ?
SQL consiste à traiter des ensembles de lignes, donc si nous pouvons convertir un "mot" en un ensemble de caractères sous forme de lignes, nous pouvons utiliser les fonctions "groupe" pour faire des choses utiles.
L'utilisation d'un "moteur de base de données relationnelle" pour effectuer de simples manipulations de caractères semble malsaine. Pourtant, est-il possible de répondre à votre question avec juste SQL ? Oui c'est...
Maintenant, j'ai toujours une table qui a une colonne d'entiers qui contient environ 500 lignes et qui a la séquence ascendante 1 .. 500. Elle s'appelle 'integerseries'. C'est une très petite table qui a beaucoup utilisé, elle est donc mise en cache en mémoire. Il est conçu pour remplacer le from 'select 1 ... union ...
texte dans les requêtes.
Il est utile pour générer des lignes séquentielles (une table) de tout ce que vous pouvez calculer et qui est basé sur un entier en l'utilisant dans une cross join
(également toute inner join
). Je l'utilise pour générer des jours pendant un an, analyser des chaînes délimitées par des virgules, etc.
Maintenant, le sql mid
La fonction peut être utilisée pour renvoyer le caractère à une position donnée. En utilisant la table 'integerseries', je peux 'facilement' convertir un 'mot' en une table de caractères avec une ligne par caractère. Utilisez ensuite les fonctions 'groupe'...
SET @word='Hello World';
SELECT charAtIdx, COUNT(charAtIdx)
FROM (SELECT charIdx.id,
MID(@word, charIdx.id, 1) AS charAtIdx
FROM integerseries AS charIdx
WHERE charIdx.id <= LENGTH(@word)
ORDER BY charIdx.id ASC
) wordLetters
GROUP BY
wordLetters.charAtIdx
ORDER BY charAtIdx ASC
Sortie :
charAtIdx count(charAtIdx)
--------- ------------------
1
d 1
e 1
H 1
l 3
o 2
r 1
W 1
Remarque :Le nombre de lignes dans la sortie correspond au nombre de caractères différents dans la chaîne. Ainsi, si le nombre de lignes de sortie est compté, le nombre de "lettres différentes" sera connu.
Cette observation est utilisée dans la requête finale.
La question finale :
Le point intéressant ici est de déplacer les restrictions 'integerseries' 'cross join' (1 .. length(word)) dans la 'join' réelle plutôt que de le faire dans le where
clause. Cela fournit à l'optimiseur des indices sur la façon de restreindre les données produites lors de la join
.
SELECT
wordLetterCounts.wordId,
wordLetterCounts.word,
COUNT(wordLetterCounts.wordId) AS letterCount
FROM
(SELECT words.id AS wordId,
words.word AS word,
iseq.id AS charPos,
MID(words.word, iseq.id, 1) AS charAtPos,
COUNT(MID(words.word, iseq.id, 1)) AS charAtPosCount
FROM
words
JOIN integerseries AS iseq
ON iseq.id BETWEEN 1 AND words.wordlen
GROUP BY
words.id,
MID(words.word, iseq.id, 1)
) AS wordLetterCounts
GROUP BY
wordLetterCounts.wordId
Sortie :
wordId word letterCount
------ -------------------- -------------
1 3333333333 1
2 1113333333 2
3 1112222444 3
4 Hello World 8
5 funny - not so much? 13
Tableau de mots et données :
CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
`wordlen` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*Data for the table `words` */
insert into `words`(`id`,`word`,`wordlen`) values (1,'3333333333',10);
insert into `words`(`id`,`word`,`wordlen`) values (2,'1113333333',10);
insert into `words`(`id`,`word`,`wordlen`) values (3,'1112222444',10);
insert into `words`(`id`,`word`,`wordlen`) values (4,'Hello World',11);
insert into `words`(`id`,`word`,`wordlen`) values (5,'funny - not so much?',20);
Table de séries entières :plage 1 .. 30 pour cet exemple.
CREATE TABLE `integerseries` (
`id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci