Mysql
 sql >> Base de données >  >> RDS >> Mysql

Compter le nombre de caractères uniques dans une chaîne

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