Как проверить статистику по всем буквам в алфавите - PullRequest
1 голос
/ 25 сентября 2019

Я проверяю некоторые условия, используя таблицу strs.Я могу сделать это для всех букв в алфавите.Но это будет Жесткий Код.Подскажите, пожалуйста, как обобщить процесс проверки?

Я пытался проверять каждую букву вручную.

CREATE TABLE strs
(
    str_ VARCHAR(20)
);


CREATE TABLE final_data
(
    letter                VARCHAR(20),
    total                 int,
    occurance             int,
    max_occurance         int,
    max_occurance_reached int
);


INSERT INTO strs(str_)
VALUES('aa'),
      ('aaaa'),
      ('aab'),
      ('abaaba'),
      ('bbbbb');


INSERT INTO final_data(letter,total,occurance,max_occurance,max_occurance_reached)

SELECT DISTINCT tb1.letter,tb2.Total,tb3.occurance,tb4.max_occurance,tb5.max_occurance_reached FROM
(SELECT 'a' AS letter FROM  strs ) as tb1
LEFT JOIN (SELECT 'a' AS letter, SUM(LEN(str_)-LEN(REPLACE(str_,'a',''))) AS Total FROM strs) AS tb2
ON tb1.letter=tb2.letter
LEFT JOIN (SELECT 'a' AS letter ,COUNT(str_) AS occurance FROM strs WHERE CHARINDEX('a',str_)>0) AS tb3
ON tb2.letter=tb3.letter
LEFT JOIN (SELECT 'a' AS letter, MAX(LEN(str_)-LEN(REPLACE(str_,'a',''))) as max_occurance FROM strs) AS tb4
ON tb3.letter=tb4.letter
LEFT JOIN (SELECT 'a' AS letter ,count(str_) AS max_occurance_reached FROM strs WHERE (LEN(str_)-LEN(REPLACE(str_,'a',''))) in (SELECT MAX(LEN(str_)-LEN(REPLACE(str_,'a',''))) FROM strs)) AS tb5
ON tb4.letter=tb5.letter;

INSERT INTO final_data(letter,total,occurance,max_occurance,max_occurance_reached)

SELECT DISTINCT tb1.letter,tb2.Total,tb3.occurance,tb4.max_occurance,tb5.max_occurance_reached FROM
(SELECT 'b' AS letter FROM  strs ) AS tb1
LEFT JOIN (SELECT 'b' AS letter, sum(LEN(str_)-LEN(REPLACE(str_,'b',''))) AS Total FROM strs) AS tb2
ON tb1.letter=tb2.letter
LEFT JOIN (SELECT 'b' AS letter ,count(str_) AS occurance FROM strs WHERE CHARINDEX('b',str_)>0) AS tb3
ON tb2.letter=tb3.letter
LEFT JOIN (SELECT 'b' AS letter, MAX(LEN(str_)-LEN(REPLACE(str_,'b',''))) AS max_occurance FROM strs) AS tb4
ON tb3.letter=tb4.letter
LEFT JOIN (SELECT 'b' AS letter ,count(str_) AS max_occurance_reached FROM strs WHERE (LEN(str_)-LEN(REPLACE(str_,'b',''))) 
in (SELECT MAX(LEN(str_)-LEN(REPLACE(str_,'b',''))) FROM strs)) as tb5
ON tb4.letter=tb5.letter;

SELECT letter,total,occurance,max_occurance,max_occurance_reached FROM final_data;

Ответы [ 2 ]

1 голос
/ 25 сентября 2019

Если важна производительность, тогда решение на основе множеств - это только способ. Использование циклов или подобных методов будет работать ужасно по сравнению с эквивалентным методом на основе множеств.

Мое решение использует 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;

Теперь давайте сравним планы выполнения с виртуальным индексом и без него.

enter image description here

Бум!Без индекса, без сортировки, без циклов, ух-х!

1 голос
/ 25 сентября 2019

Вы можете попробовать с кодом ниже

Declare @num tinyint, @alphabet char(1)
Declare @i int=0
While @i<26
Begin
    Select @num = 97+@i
    Select @alphabet = char(@num)

    INSERT INTO final_data(letter,total,occurance,max_occurance,max_occurance_reached)
    SELECT DISTINCT tb1.letter,tb2.Total,tb3.occurance,tb4.max_occurance,tb5.max_occurance_reached FROM
    (SELECT @alphabet AS letter FROM  strs ) as tb1
    LEFT JOIN (SELECT @alphabet AS letter, SUM(LEN(str_)-LEN(REPLACE(str_,@alphabet,''))) AS Total FROM strs) AS tb2
    ON tb1.letter=tb2.letter
    LEFT JOIN (SELECT @alphabet AS letter ,COUNT(str_) AS occurance FROM strs WHERE CHARINDEX(@alphabet,str_)>0) AS tb3
    ON tb2.letter=tb3.letter
    LEFT JOIN (SELECT @alphabet AS letter, MAX(LEN(str_)-LEN(REPLACE(str_,@alphabet,''))) as max_occurance FROM strs) AS tb4
    ON tb3.letter=tb4.letter
    LEFT JOIN (SELECT @alphabet AS letter ,count(str_) AS max_occurance_reached FROM strs WHERE (LEN(str_)-LEN(REPLACE(str_,@alphabet,''))) in (SELECT MAX(LEN(str_)-LEN(REPLACE(str_,@alphabet,''))) FROM strs)) AS tb5
    ON tb4.letter=tb5.letter;

    set @i=@i+1
End

SELECT letter,total,occurance,max_occurance,max_occurance_reached FROM final_data;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...