Isso é para se divertir certo?
SQL tem tudo a ver com o processamento de conjuntos de linhas, portanto, se pudermos converter uma 'palavra' em um conjunto de caracteres como linhas, podemos usar as funções de 'grupo' para fazer coisas úteis.
Usar um 'mecanismo de banco de dados relacional' para fazer manipulação simples de caracteres parece errado. Ainda assim, é possível responder à sua pergunta apenas com SQL? É sim...
Agora, eu sempre tenho uma tabela que tem uma coluna inteira que tem cerca de 500 linhas que tem a seqüência ascendente 1 .. 500. Ela é chamada de 'integerseries'. É uma tabela muito pequena que usou muito, por isso fica armazenada em cache na memória. Ele é projetado para substituir o
from 'select 1 ... union ...
texto nas consultas. É útil para gerar linhas sequenciais (uma tabela) de qualquer coisa que você possa calcular com base em um inteiro usando-o em uma
cross join
(também qualquer inner join
). Eu o uso para gerar dias por um ano, analisar strings delimitadas por vírgulas etc. Agora, o sql
mid
A função pode ser usada para retornar o caractere em uma determinada posição. Usando a tabela 'integerseries' eu posso 'facilmente' converter uma 'palavra' em uma tabela de caracteres com uma linha por caractere. Em seguida, use as funções '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
Saída:
charAtIdx count(charAtIdx)
--------- ------------------
1
d 1
e 1
H 1
l 3
o 2
r 1
W 1
Nota:O número de linhas na saída é o número de caracteres diferentes na string. Portanto, se o número de linhas de saída for contado, o número de 'letras diferentes' será conhecido.
Essa observação é usada na consulta final.
A consulta final:
O ponto interessante aqui é mover as restrições de 'cross join' 'integerseries' (1 .. length(word)) para o 'join' real em vez de fazê-lo no
where
cláusula. Isso fornece ao otimizador dicas sobre como restringir os dados produzidos ao fazer o 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
Saída:
wordId word letterCount
------ -------------------- -------------
1 3333333333 1
2 1113333333 2
3 1112222444 3
4 Hello World 8
5 funny - not so much? 13
Tabela de palavras e dados:
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);
Tabela de séries inteiras:intervalo 1 .. 30 para este exemplo.
CREATE TABLE `integerseries` (
`id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci