Esto es por diversión, ¿verdad?
SQL tiene que ver con el procesamiento de conjuntos de filas, por lo que si podemos convertir una 'palabra' en un conjunto de caracteres como filas, entonces podemos usar las funciones de 'grupo' para hacer cosas útiles.
Usar un 'motor de base de datos relacional' para hacer una manipulación simple de personajes se siente mal. Aún así, ¿es posible responder a su pregunta solo con SQL? Sí lo es...
Ahora, siempre tengo una tabla que tiene una columna de enteros que tiene alrededor de 500 filas que tiene la secuencia ascendente 1 .. 500. Se llama 'integerseries'. Es una tabla realmente pequeña que se usa mucho, por lo que se almacena en caché en la memoria. Está diseñado para reemplazar el from 'select 1 ... union ...
texto en consultas.
Es útil para generar filas secuenciales (una tabla) de cualquier cosa que pueda calcular que se base en un número entero usándolo en una cross join
(también cualquier inner join
). Lo uso para generar días durante un año, analizar cadenas delimitadas por comas, etc.
Ahora, el sql mid
La función se puede utilizar para devolver el carácter en una posición determinada. Al usar la tabla de 'series enteras', puedo convertir 'fácilmente' una 'palabra' en una tabla de caracteres con una fila por carácter. Luego use las funciones de 'grupo'...
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
Salida:
charAtIdx count(charAtIdx)
--------- ------------------
1
d 1
e 1
H 1
l 3
o 2
r 1
W 1
Nota:El número de filas en la salida es el número de caracteres diferentes en la cadena. Por lo tanto, si se cuenta el número de filas de salida, se conocerá el número de "letras diferentes".
Esta observación se utiliza en la consulta final.
La consulta final:
El punto interesante aquí es mover las restricciones de 'unión cruzada' de 'integerseries' (1 .. length(word)) a la 'unión' real en lugar de hacerlo en el where
cláusula. Esto proporciona al optimizador pistas sobre cómo restringir los datos producidos al hacer 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
Salida:
wordId word letterCount
------ -------------------- -------------
1 3333333333 1
2 1113333333 2
3 1112222444 3
4 Hello World 8
5 funny - not so much? 13
Tabla de palabras y datos:
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);
Tabla de series enteras:rango 1 .. 30 para este ejemplo.
CREATE TABLE `integerseries` (
`id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci