Det er for sjov ikke?
SQL handler om at behandle sæt af rækker, så hvis vi kan konvertere et 'ord' til et sæt tegn som rækker, så kan vi bruge 'gruppe'-funktionerne til at lave nyttige ting.
At bruge en 'relationel databasemotor' til at lave simpel karaktermanipulation føles forkert. Er det stadig muligt at besvare dit spørgsmål med kun SQL? Ja det er...
Nu har jeg altid en tabel, der har en heltalskolonne, der har omkring 500 rækker i den, der har den stigende rækkefølge 1 .. 500. Det kaldes 'heltalsserier'. Det er en rigtig lille tabel, der har brugt meget, så den bliver cachelagret i hukommelsen. Den er designet til at erstatte from 'select 1 ... union ...
tekst i forespørgsler.
Det er nyttigt til at generere sekventielle rækker (en tabel) af alt, som du kan beregne, der er baseret på et heltal ved at bruge det i en cross join
(også enhver inner join
). Jeg bruger den til at generere dage i et år, parsing af kommaseparerede strenge osv.
Nu, sql mid
funktion kan bruges til at returnere tegnet på en given position. Ved at bruge 'integerseries'-tabellen kan jeg 'let' konvertere et 'ord' til en tegntabel med en række pr. tegn. Brug derefter 'gruppe'-funktionerne...
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
Output:
charAtIdx count(charAtIdx)
--------- ------------------
1
d 1
e 1
H 1
l 3
o 2
r 1
W 1
Bemærk:Antallet af rækker i outputtet er antallet af forskellige tegn i strengen. Så hvis antallet af outputrækker tælles, vil antallet af 'forskellige bogstaver' være kendt.
Denne observation bruges i den endelige forespørgsel.
Den sidste forespørgsel:
Det interessante her er at flytte 'integerseries' 'cross join'-begrænsningerne (1 .. length(word)) ind i den faktiske 'join' i stedet for at gøre det i where
klausul. Dette giver optimeringsværktøjet ledetråde til, hvordan man begrænser de data, der produceres, når du udfører 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
Output:
wordId word letterCount
------ -------------------- -------------
1 3333333333 1
2 1113333333 2
3 1112222444 3
4 Hello World 8
5 funny - not so much? 13
Ordtabel og data:
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);
Integerseries tabel:interval 1 .. 30 for dette eksempel.
CREATE TABLE `integerseries` (
`id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci