Если важна производительность, тогда решение на основе множеств - это только способ. Использование циклов или подобных методов будет работать ужасно по сравнению с эквивалентным методом на основе множеств.
Мое решение использует APPLY и таблицу Tally - две наиболее важные вещи, которые нужно изучить / понять / освоить, если вы хотите правильно выполнить высокопроизводительные запросы SQL,Я знаю, что эти две темы немного продвинуты, поэтому я разобью свое решение на маленькие кусочки.Сначала давайте посмотрим, как это сделать для одного персонажа.
DECLARE @C CHAR(1) = 'a';
SELECT
letter = @C,
occurance = SUM(IIF(f.CCount>0,1,0)),
max_occurance = MAX(f.CCount),
total = SUM(f.CCount)
FROM strs
CROSS APPLY (VALUES(LEN(str_)-LEN(REPLACE(str_,@C,'')))) AS f(CCount);
Возвращает:
letter occurance max_occurance total
------ ----------- ------------- -----------
a 4 4 12
Далее для основанного на множестве способа создания алфавита (или любой последовательности символов)
SELECT i.N, letter = CHAR(i.N+96)
FROM
(
SELECT TOP (26) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(1),(1),(1),(1),(1),(1)) AS a(x)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1)) AS b(x)
) AS i(N);
Возвращает:
N letter
------ ------
1 a
2 b
3 c
...
25 y
26 z
Примечание: вы также можете использовать NGrams8K для создания алфавита;этот запрос вернет то же самое ...
SELECT Letter = ng.token FROM dbo.NGrams8k('abcdefghijklmnopqrstuvwxyz',1) AS ng;
Теперь давайте передадим результаты нашего "алфавитного запроса" нашей логике для анализа текста.
-- Solution that does not get max_occur_reached (full virtual index, no sort!)
SELECT
letter = CHAR(i.N+96),
total = SUM(f.CCount),
occurance = SUM(IIF(f.CCount>0,1,0)),
max_occurance = MAX(f.CCount)
FROM strs
CROSS JOIN
(
SELECT TOP (26) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(1),(1),(1),(1),(1),(1)) AS a(x)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1)) AS b(x)
) AS i(N)
CROSS APPLY (VALUES(LEN(str_)-LEN(REPLACE(str_,CHAR(i.N+96),'')))) AS f(CCount)
GROUP BY i.N;
Возвращает:
letter total occurance max_occurance
------ ----------- ----------- -------------
a 12 4 4
b 8 3 5
c 0 0 0
....
Далее мы добавляем код для получения нашего "max_occurances_reached".
--INSERT final_data (letter,total,occurance,max_occurance,max_occurance_reached)
SELECT
ff.Letter,
total = SUM(ff.CCount),
occurance = SUM(IIF(ff.CCount>0,1,0)),
max_occurance = MAX(ff.max_occur),
max_occurance_reached = SUM(ff.max_reached)
FROM
(
SELECT
Letter = CHAR(i.N+96),
CCount = f.CCount,
max_occur = MAX(f.CCount) OVER (PARTITION BY i.N),
max_reached = IIF(f.CCount=MAX(f.CCount) OVER (PARTITION BY i.N),1,0)
FROM strs
CROSS JOIN
(
SELECT TOP (26) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(1),(1),(1),(1),(1),(1)) AS a(x)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1)) AS b(x)
) AS i(N)
CROSS APPLY (VALUES(LEN(str_)-LEN(REPLACE(str_,CHAR(i.N+96),'')))) AS f(CCount)
) AS ff
GROUP BY ff.Letter;
Это даст вам то, что вам нужно без каких-либо циклов, курсоровили другие низкоэффективные методы.
Бонус:
Можем ли мы сделать еще лучше?Если вы посмотрите на план выполнения, то увидите, что мое решение, приведенное выше, становится своего рода.Сорта дорогие, но часто обязательные.Индексы предварительно сортируют данные, чтобы избежать сортировки во время выполнения.Там сортировка требуется для обработки GROUP BY ff.Letter
.Мы можем удалить эту сортировку, используя Виртуальный индекс , о чем я подробнее расскажу здесь .В этом случае виртуальный индекс существует в iN .Это позволяет мне использовать GROUP BY i.N
, ORDER BY i.N
, SELECT DISTINCT i.N
, PARTITION BY i.N
и другие без сортировки в плане выполнения.Если я смогу реорганизовать запрос, чтобы мои операторы указывали на iN, он был бы свободен от сортировки и быстрее.
SELECT
Letter = CHAR(ff.i+96),
total = SUM(ff.CCount),
occurance = SUM(IIF(ff.CCount>0,1,0)),
max_occurance = MAX(ff.max_occur),
max_occurance_reached = SUM(ff.max_reached)
FROM
(
SELECT
i = i.N,
CCount = f.CCount,
max_occur = MAX(f.CCount) OVER (PARTITION BY i.N),
max_reached = IIF(f.CCount=MAX(f.CCount) OVER (PARTITION BY i.N),1,0)
FROM strs
CROSS JOIN
(
SELECT TOP (26) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(1),(1),(1),(1),(1),(1)) AS a(x)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1)) AS b(x)
) AS i(N)
CROSS APPLY (VALUES(LEN(str_)-LEN(REPLACE(str_,CHAR(i.N+96),'')))) AS f(CCount)
) AS ff
GROUP BY ff.i;
Теперь давайте сравним планы выполнения с виртуальным индексом и без него.
Бум!Без индекса, без сортировки, без циклов, ух-х!