Количество различных символов в столбце

Скажем, у меня есть следующий набор данных

Column1 (VarChar(50 or something))
Elias
Sails
Pails
Plane
Games

То, что я хотел бы получить из этого столбца, это следующий набор:

LETTER     COUNT
E          3
L          4
I          3
A          5
S          5
And So On...

Одним из решений, о котором я подумал, было объединение всех строк в одну строку, а затем подсчет каждого экземпляра буквы в этой строке, но это выглядит неаккуратно.

Это скорее проявление любопытства, чем что-либо еще, но есть ли способ получить количество всех различных букв в наборе данных с помощью SQL?


person Elias    schedule 23.06.2014    source источник


Ответы (5)


Я бы сделал это, создав таблицу ваших писем, похожую на:

CREATE TABLE tblLetter
(
  letter varchar(1)
);

INSERT INTO tblLetter ([letter])
VALUES
    ('a'),
    ('b'),
    ('c'),
    ('d'); -- etc

Затем вы можете присоединиться к letters к своей таблице, где ваши данные похожи на письмо:

select l.letter, count(n.col) Total
from tblLetter l
inner join names n
  on n.col like '%'+l.letter+'%'
group by l.letter;

См. SQL Fiddle с демонстрацией. Это даст результат:

| LETTER | TOTAL |
|--------|-------|
|      a |     5 |
|      e |     3 |
|      g |     1 |
|      i |     3 |
|      l |     4 |
|      m |     1 |
|      p |     2 |
|      s |     4 |
person Taryn    schedule 23.06.2014

Если вы создадите таблицу букв, например:

create table letter (ch char(1));
insert into letter(ch) values ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H')
,('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P')
,('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z');

вы можете сделать это с помощью перекрестного соединения, например:

select ch, SUM(len(str) - len(replace(str,ch,'')))
from letter
cross join test -- <<== test is the name of the table with the string
group by ch
having SUM(len(str) - len(replace(str,ch,''))) <> 0

Вот работающая демонстрация sqlfiddle.

Вы можете сделать это без определения таблицы, встроив список букв в сам запрос, но идея перекрестного соединения и группировки по букве останется прежней.

Примечание: см. этот ответ для объяснения выражения внутри SUM.

person Sergey Kalinichenko    schedule 23.06.2014
comment
Но почему вы отфильтровываете нерелевантные результаты только после группировки, а не до? ... WHERE len(str) - len(replace(str,ch,'')) <> 0 GROUP BY ch может оказаться быстрее. - person Andriy M; 24.06.2014
comment
@AndriyM Это не должно иметь большого значения: в любом случае выражение будет оцениваться один раз для каждой пары слово/символ, чтобы войти в SUM; вот где реальный расход. Отфильтровать нули после GROUP BY очень дешево. Фактически, это может оказаться дешевле, чем предложение WHERE, потому что это приводит к меньшему количеству нулевых проверок (точно 26 вместо 26 * wordCount). - person Sergey Kalinichenko; 24.06.2014

Для меня это проблема, почти адаптированная для CTE (спасибо, Николас Кэри, за оригинал, моя скрипка здесь: http://sqlfiddle.com/#!3/44f77/8):

WITH cteLetters
AS
(
    SELECT
              1 AS CharPos,
              str,
              MAX(LEN(str)) AS MaxLen,
              SUBSTRING(str, 1, 1) AS Letter
    FROM
              test
    GROUP BY
              str,
              SUBSTRING(str, 1, 1)

    UNION ALL

    SELECT
              CharPos + 1,
              str,
              MaxLen,
              SUBSTRING(str, CharPos + 1, 1) AS Letter
    FROM
              cteLetters
    WHERE
              CharPos + 1 <= MaxLen
)

SELECT
          UPPER(Letter) AS Letter,
          COUNT(*) CountOfLetters
FROM
          cteLetters
GROUP BY
          Letter
ORDER BY
          Letter;

Используйте CTE для вычисления позиций символов и деконструкции каждой строки. Затем вы можете просто агрегировать из самого CTE. Нет необходимости в дополнительных столах или чем-то еще.

person VBlades    schedule 23.06.2014
comment
Привет, я пробую ваше решение, но все еще не уверен, откуда вы берете символы при запросе cteLetters. Я попробовал ваш пример sqlfiddle, но он не работает - person noor h; 05.01.2020
comment
@noorh Привет, письма приходят из таблицы test, которая настроена в части сборки. Проверьте ссылку sqlfiddle, которая теперь обновлена ​​для использования механизма SQL Server 2017 (хотя код идентичен): sqlfiddle. com/#!18/0ccb8/1 - person VBlades; 09.01.2020

Это должно работать, даже если у вас включена чувствительность к регистру.

Установка:

CREATE TABLE _test ( Column1 VARCHAR (50) )

INSERT _test (Column1) VALUES ('Elias'),('Sails'),('Pails'),('Plane'),('Games')

Работа:

DECLARE @counter AS INT
DECLARE @results TABLE (LETTER VARCHAR(1),[COUNT] INT)

SET @counter=65  --ascii value for 'A'

WHILE ( @counter <=90 )  -- ascii value for 'Z'
BEGIN
    INSERT @results (LETTER,[COUNT])
    SELECT CHAR(@counter),SUM(LEN(UPPER(Column1)) - LEN(REPLACE(UPPER(Column1), CHAR(@counter),''))) FROM _test
    SET @counter=@counter+1
END

SELECT * FROM @results WHERE [Count]>0
person John Turner    schedule 23.06.2014

Часто полезно иметь таблицу диапазонов или последовательностей, которая дает вам источник больших наборов последовательных последовательных чисел, таких как этот, покрывающий диапазон -100 000 + 100 000.

drop table dbo.range
go
create table dbo.range
(
  id int not null primary key clustered ,
)
go

set nocount on
go

declare @i int = -100000
while ( @i <= +100000 )
begin
  if ( @i > 0 and @i % 1000 = 0 ) print convert(varchar,@i) + ' rows'
  insert dbo.range values ( @i )
  set @i = @i + 1
end
go

set nocount off
go

Если у вас есть такая таблица, вы можете сделать что-то вроде этого:

select character = substring( t.some_column , r.id , 1 ) ,
       frequency = count(*)
from dbo.some_table t
join dbo.range      r on r.id between 1 and len( t.some_column )
group by substring( t.some_column , r.id , 1 )
order by 1

Если вы хотите обеспечить нечувствительность к регистру, просто добавьте нужные upper() или lower():

select character = upper( substring( t.some_column , r.id , 1 ) ) ,
       frequency = count(*)
from dbo.some_table t
join dbo.range      r on r.id between 1 and len( t.some_column )
group by upper( substring( t.some_column , r.id , 1 ) )
order by 1

Учитывая ваши образцы данных:

create table dbo.some_table
(
  some_column varchar(50) not null
)
go

insert dbo.some_table values ( 'Elias' )
insert dbo.some_table values ( 'Sails' )
insert dbo.some_table values ( 'Pails' )
insert dbo.some_table values ( 'Plane' )
insert dbo.some_table values ( 'Games' )
go

Последний запрос выше дает следующие результаты:

character frequency
    A         5
    E         3
    G         1
    I         3
    L         4
    M         1
    N         1
    P         2
    S         5
person Nicholas Carey    schedule 23.06.2014