Группировать по SQL-запросу в колонке, объединенной запятой - PullRequest
2 голосов
/ 14 февраля 2011

Моя структура таблицы такая, как показано ниже, Столбец «Почта» может содержать несколько писем, соединенных запятой

Данные (INT)

Почта (VARCHAR (200))

[Данные] [Почта]


1 м1 @ gmail.com, м2 @ hotmail.com

2 м2 @ hotmail.com, м3 @ test.com

и мне нужно сгенерировать отчет, как показано ниже, с учетом каждой строки для каждого электронного письма

[Почта] [Количество]


m1@gmail.com 1

m2@hotmail.com 2

m3@test.com 1

Итак, что будет генерировать запрос sql (сервер), как описано выше? Также я не могу изменить структуру таблицы.

Ответы [ 6 ]

3 голосов
/ 14 февраля 2011

Решение SQL Server

WITH T ([Data], [Mail])
     AS (SELECT 1,'m1@gmail.com,m2@hotmail.com' UNION ALL
         SELECT 2,'m2@hotmail.com,m3@test.com')
SELECT address  AS Mail,
       COUNT(*) AS [Count]
FROM   T
       CROSS APPLY (SELECT CAST('<m>' + REPLACE([Mail], ',', '</m><m>') + '</m>'
                                AS XML
                           ) AS x) ca1
       CROSS APPLY (SELECT T.split.value('.', 'varchar(200)') AS address
                    FROM   x.nodes('/m') T(split)) ca
GROUP  BY address  
2 голосов
/ 14 февраля 2011

SQL Server Использование рекурсивного cte.

declare @Mail table (ID int, Mail varchar(200))

insert into @Mail values
(1, 'm1@gmail.com,m2@hotmail.com'),
(2, 'm2@hotmail.com,m3@test.com'),
(3, 'm2@hotmail.com')

;with cte1 as
(
  select Mail+',' as Mail
  from @Mail
),
cte2
as
(
  select
    left(Mail, charindex(',', Mail)-1) as Mail1,
    right(Mail, len(Mail)-charindex(',', Mail)) as Mail
  from cte1
  union all
  select
    left(Mail, charindex(',', Mail)-1) as Mail1,
    right(Mail, len(Mail)-charindex(',', Mail)) as Mail
  from cte2
  where charindex(',', Mail) > 1
)
select
  Mail1 as Mail,
  count(*) as [Count]
from cte2
group by Mail1

Редактировать 1 То же, что и раньше, но обрабатывает случай, когда в Mail

есть только одно электронное письмо
1 голос
/ 15 февраля 2011

Разделение строк выполняется быстрее, если использовать только CHARINDEX без XML или CTE.

Пример таблицы

create table #tmp ([Data] int, [Mail] varchar(200))
insert #tmp SELECT 1,'m1@gmail.com,m2@hotmail.com,other, longer@test, fifth'
UNION ALL   SELECT 2,'m2@hotmail.com,m3@test.com'
UNION ALL   SELECT 3,'m3@single.com'
UNION ALL   SELECT 4,''
UNION ALL   SELECT 5,null

Запрос

select single, count(*) [Count]
from
(
    select ltrim(rtrim(substring(t.mail, v.number+1,
        isnull(nullif(charindex(',',t.mail,v.number+1),0)-v.number-1,200)))) single
    from #tmp t
    inner join master..spt_values v on v.type='p'
        and v.number <= len(t.Mail)
        and (substring(t.mail,v.number,1) = ',' or v.number=0)
) X
group by single

Единственные поставляемые вами детали

  • # tmp : имя вашей таблицы
  • # mail : имя столбца
1 голос
/ 14 февраля 2011

Правильнее всего было бы добавить связанную таблицу для хранения нескольких электронных писем. Практически всегда плохое проектное решение хранить вещи в списке через запятую, как вы обнаружили, пытаясь его запросить. Как правило, это означает, что вам нужно создать связанную таблицу, поскольку у вас есть отношение один ко многим. Задача, которую вы хотите выполнить, тривиальна, если у вас есть правильно связанные таблицы.

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

1 голос
/ 14 февраля 2011

Очень похоже на ответ Микаэля с незначительными изменениями ...
- Иметь поле с «кэшированным» LEN, чтобы избежать повторного подсчета длины
- Используйте только один UNION для каждой рекурсии, заменив 0 CHARINDEX на NULL

Эти различия будут действительно заметно только для длинных списков, и так с несколькими уровнями рекурсии.


Задача CROSS APPLY - просто сделать SELECT более аккуратным, а не повторять загрузки NULLIF (CHARINDEX) раз.


WITH
  source (
    Data,
    Mail
  )
AS
(
  SELECT 1,'m1@gmail.com,m2@hotmail.com' UNION ALL
  SELECT 2,'m2@hotmail.com,m3@test.com'
)
,
  split_cte
AS
(
  SELECT
    LEFT (mail, ISNULL(comma - 1, LEN(mail)))     AS "current_mail",
    RIGHT(mail, ISNULL(LEN(mail) - comma, 0))     AS "mail_data",
    ISNULL(LEN(mail) - comma, 0)                  AS "chars"
  FROM
    source
  CROSS APPLY
    (SELECT NULLIF(CHARINDEX(',', mail), 0) AS "comma") AS search

  UNION ALL

  SELECT
    LEFT (mail_data, ISNULL(comma - 1, chars))    AS "current_mail",
    RIGHT(mail_data, ISNULL(chars - comma, 0))    AS "mail_data",
    ISNULL(chars - comma, 0)                      AS "chars"
  FROM
    split_cte
  CROSS APPLY
    (SELECT NULLIF(CHARINDEX(',', mail_data), 0) AS "comma") AS search
  WHERE
    chars > 0
)

SELECT
  current_mail     AS "Mail",
  COUNT(*)         AS "Count"
FROM
  split_cte
GROUP BY
  current_mail
0 голосов
/ 16 февраля 2011

Это дополнительный ответ, показывающий эффективность различных опций:

Заполнить таблицу с примерами некоторыми данными

create table tmp1 ([Data] int, [Mail] varchar(200))
insert tmp1 SELECT 1,'m1@gmail.com,m2@hotmail.com,other, longer@test, fifth'
UNION ALL   SELECT 2,'m2@hotmail.com,m3@test.com'
UNION ALL   SELECT 3,'m3@single.com'
UNION ALL   SELECT 4,''
UNION ALL   SELECT 5,null

insert tmp1
select data*10000 + number, mail
from tmp1, master..spt_values v
where v.type='P'

-- total rows: 10245

Тестовый запрос:

set statistics io on
set statistics time on

dbcc dropcleanbuffers dbcc freeproccache

select single, count(*) [Count]
from
(
    select ltrim(rtrim(substring(t.mail, v.number+1,
        isnull(nullif(charindex(',',t.mail,v.number+1),0)-v.number-1,200)))) single
    from tmp1 t
    inner join master..spt_values v on v.type='p'
        and v.number <= len(t.Mail)
        and (substring(t.mail,v.number,1) = ',' or v.number=0)
) X
group by single

dbcc dropcleanbuffers dbcc freeproccache

;with cte1 as
(
  select Mail+',' as Mail
  from tmp1
),
cte2
as
(
  select
    left(Mail, charindex(',', Mail)-1) as Mail1,
    right(Mail, len(Mail)-charindex(',', Mail)) as Mail
  from cte1
  union all
  select
    left(Mail, charindex(',', Mail)-1) as Mail1,
    right(Mail, len(Mail)-charindex(',', Mail)) as Mail
  from cte2
  where charindex(',', Mail) > 1
)
select
  Mail1 as Mail,
  count(*) as [Count]
from cte2
group by Mail1

dbcc dropcleanbuffers dbcc freeproccache

--SET ANSI_DEFAULTS ON
--SET ANSI_NULLS ON
;
SELECT address  AS Mail,
       COUNT(*) AS [Count]
FROM   tmp1
       CROSS APPLY (SELECT CAST('<m>' + REPLACE([Mail], ',', '</m><m>') + '</m>'
                                AS XML
                           ) AS x) ca1
       CROSS APPLY (SELECT T.split.value('.', 'varchar(200)') AS address
                    FROM   x.nodes('/m') T(split)) ca
GROUP  BY address  

Статистика

Проведите несколько раз, чтобы почувствовать средние значения

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'spt_values'. Scan count 8196, logical reads 26637, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tmp1'. Scan count 3, logical reads 43, physical reads 0, read-ahead reads 14, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 641 ms,  elapsed time = 412 ms.

Table 'Worktable'. Scan count 2, logical reads 103271, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tmp1'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 14, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 609 ms,  elapsed time = 614 ms.

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tmp1'. Scan count 3, logical reads 43, physical reads 0, read-ahead reads 14, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 2798 ms,  elapsed time = 1421 ms.

Table 'Worktable'. Scan count 2, logical reads 103334, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tmp1'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 14, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 734 ms,  elapsed time = 742 ms.

Резюме

Первый (CHARINDEX) : время ЦП = 344 мс, прошедшее время = 198 мс.
Секунда (CTE) : время ЦП = 594 мс, прошедшее время = 613 мс
Третий (XML) : время ЦП = 2812 мс, прошедшее время = 1418 мс.
Четвертый (CTE2) : время ЦП = 719 мс, прошедшее время = 750 мс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...