Det här är för skojs skull eller hur?
SQL handlar om att bearbeta uppsättningar av rader, så om vi kan konvertera ett "ord" till en uppsättning tecken som rader så kan vi använda "grupp"-funktionerna för att göra användbara saker.
Att använda en "relationell databasmotor" för att göra enkel karaktärsmanipulation känns fel. Är det ändå möjligt att svara på din fråga med bara SQL? Ja det är det...
Nu har jag alltid en tabell som har en heltalskolumn som har cirka 500 rader i den som har den stigande sekvensen 1 .. 500. Det kallas 'heltalsserier'. Det är en riktigt liten tabell som använts mycket så den lagras i minnet. Den är utformad för att ersätta from 'select 1 ... union ...
text i frågor.
Det är användbart för att generera sekventiella rader (en tabell) av allt som du kan beräkna som är baserat på ett heltal genom att använda det i en cross join
(även någon inner join
). Jag använder den för att generera dagar för ett år, analysera kommaavgränsade strängar etc.
Nu, sql mid
funktion kan användas för att returnera tecknet på en given position. Genom att använda 'integerseries'-tabellen kan jag 'enkelt' konvertera ett 'ord' till en teckentabell med en rad per tecken. Använd sedan 'grupp'-funktionerna...
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
Utdata:
charAtIdx count(charAtIdx)
--------- ------------------
1
d 1
e 1
H 1
l 3
o 2
r 1
W 1
Obs! Antalet rader i utdata är antalet olika tecken i strängen. Så om antalet utdatarader räknas kommer antalet "olika bokstäver" att vara känt.
Denna observation används i den sista frågan.
Den sista frågan:
Det intressanta här är att flytta "integerseries" "cross join"-begränsningarna (1 .. length(word)) till den faktiska "join" istället för att göra det i where
klausul. Detta ger optimeraren ledtrådar om hur man begränsar data som produceras när man gör 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
Utdata:
wordId word letterCount
------ -------------------- -------------
1 3333333333 1
2 1113333333 2
3 1112222444 3
4 Hello World 8
5 funny - not so much? 13
Ordtabell och 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);
Heltalstabell:intervall 1 .. 30 för detta exempel.
CREATE TABLE `integerseries` (
`id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci