Подсчет записей в трех таблицах в зависимости от месяца в SQL или LINQ

Я хотел бы извлечь некоторые данные из трех таблиц в базе данных SQL Server 2005. Хотя это, безусловно, можно сделать в коде, кажется, что это можно сделать достаточно хорошо в SQL (бонусные баллы за LINQ!).

По сути, я хотел бы знать за каждый месяц, сколько звонков и встреч провел каждый сотрудник с каждым из наших клиентов. Что-то вроде этого:

Employee GUID   Customer GUID   Jan calls   Jan mtgs   Feb calls      Feb mtgs...
[a guid]        [another guid]  5           0          7              3

Данные распределены по трем таблицам. Для простоты давайте просто покажем соответствующие столбцы:

Таблица связи

[CommunicationId]  (PK, uniqueidentifier)
[Type]             (nvarchar(1)) ('C' for call, 'M' for meeting, etc.)
[Date]             (datetime)

Таблица общения между людьми

[PersonId]         (PK, FK, uniqueidentifier) (Can contain GUIDs for employees or clients, see Person Table below)
[CommunicationId]  (PK, FK, uniqueidentifier)

Таблица лиц

[PersonId]         (PK, uniqueidentifier)
[Type]             (nvarchar(1)) ('E' for employee, 'C' for customer)

Итак, вопросы:

  1. Можно ли это сделать в SQL без ужасного кода или больших проблем с производительностью?
  2. Если да, то как? Я бы даже согласился на хорошую высокоуровневую стратегию. Я предполагаю, что большую роль здесь будут играть стержни (особенно «Сложный пример PIVOT»). DATEPART(MONTH, Date) кажется хорошим методом разделения сообщения по месяцам по линиям:
SELECT DATEPART(MONTH, Date), COUNT(*) 
FROM [CommunicationTable]
WHERE DATEPART(YEAR, Date) = '2009'
GROUP BY DATEPART(MONTH, Date)
ORDER BY DATEPART(MONTH, Date)

... что дает мне количество сообщений в каждом месяце в 2009 году:

1    2871
2    2639
3    3654
4    2751
5    1773
6    2575
7    2906
8    2398
9    2621
10   2638
11   1705
12   2290

person Kevin L.    schedule 24.02.2010    source источник
comment
@Hogan: тип данных uniqueidentifier не подразумевает ограничение UNIQUE.   -  person Quassnoi    schedule 24.02.2010
comment
@Hogan: uniqueidentifier - это термин SQL Server для GUID. Несколько человек могут быть связаны с несколькими сообщениями (но только один раз, например, три раза быть связанным с телефонным звонком бессмысленно и недопустимо).   -  person Kevin L.    schedule 24.02.2010
comment
Ой! Я беру это обратно... неважно.   -  person Hogan    schedule 24.02.2010
comment
Я думаю, вы можете заменить тип данных nvarchar(1) на char(1): вам не нужно сохранять длину и определенно не нужно поддерживать больше, чем ASCII. Тем более, что вы выполняете поиск по этим столбцам, у вас могут быть индексы, включающие их, размер которых будет влиять на производительность (и, как правило, размер таблицы).   -  person van    schedule 24.02.2010


Ответы (3)


Non PIVOT, CASE с использованием синтаксиса:

WITH summary AS (
      SELECT emp.personid AS emp_guid,
             cust.personid AS cust_guid,
             DATEPART(MONTH, ct.date) AS mon, 
             ct.type,
             COUNT(*) AS num_count
        FROM COMMUNICATIONTABLE ct
   LEFT JOIN PERSON_COMMUNICATION pc ON pc.communicationid = ct.communicationid
        JOIN PERSON emp ON emp.personid = pc.personid
                       AND emp.type = 'E'
        JOIN PERSON cust ON cust.personid = p.personid
                        AND cust.type = 'C'
       WHERE ct.date BETWEEN '2009-01-01' AND '2009-12-31'
    GROUP BY emp.personid, cust.personid, DATEPART(MONTH, ct.ate), ct.type)
SELECT s.emp_guid,
       s.cust_guid,
       MAX(CASE WHEN s.mon = 1 AND s.type = 'C' THEN s.num_count ELSE 0 END) AS "Jan calls",
       MAX(CASE WHEN s.mon = 1 AND s.type = 'M' THEN s.num_count ELSE 0 END) AS "Jan mtgs",
       ... --Copy/Paste two lines, update the month check... and the col alias
  FROM summary s
GROUP BY s.emp_guid, s.cust_guid

Используйте WHERE ct.date BETWEEN '2009-01-01' AND '2009-12-31', потому что WHERE DATEPART(YEAR, Date) = '2009' не может использовать индекс, если он существует в столбце date.

person OMG Ponies    schedule 24.02.2010
comment
@OMG Ponies: Смотри мой ответ - не будут ли итоговые данные основаны на человеке и покупателе, или я что-то напутал? - person Hogan; 24.02.2010
comment
@OMG Ponies: Да, в таблице Communications это есть, но не в таблице PersonCommunication - наверное, я не понимаю, что вы подразумеваете под суммированием. - person Hogan; 24.02.2010
comment
@OMG Ponies: вам нужна еще одна группа для внешнего выбора (group by emp_guid, cust_guid) и sum() для каждого случая, иначе вы получите много строк, когда захотите, вы получите одну строку для общего количества вызовов и одну строку для общего количества mtg для каждый месяц в диапазоне. - person Hogan; 24.02.2010
comment
@Hogan: Совсем не сарказм. Я не могу стать лучше, если нет обратной связи. - person OMG Ponies; 24.02.2010

Это должно помочь вам начать. Я сделал для вас один месяц в течение одного года, вы также можете добавить ограничения диапазона дат:

SELECT PE.PersonID as EmployeeID,PC2.PersonID as CustomerID,
 SUM(CASE WHEN DATEPART(MONTH, C.[Date]) = 1 
           AND DATEPART(YEAR,C.[Date]) = 2009 
           AND C.[type] = 'C' THEN 1 ELSE 0 END) AS [Jan 2009 Calls]
FROM PersonTable PE
JOIN PersonCommunicationTable PC ON PE.PersonID = PC.PersonID
JOIN CommunicationsTable C ON PC.CommunicationID = C.CommunicationID
JOIN PersonCommunicationTable PC2 ON PC.CommunicationID = PC2.CommunicationID AND NOT PC2.PersonID = PC.PersonID 
WHERE PE.Type = 'E'
person Hogan    schedule 24.02.2010

Вот достаточно эквивалентное решение с использованием Pivot.

Declare @Comm TABLE
(
    [CommunicationId] uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
    [Type]  nvarchar(1), -- ('C' for call, 'M' for meeting, etc.)
    [Date] datetime
)

Declare @Person TABLE
(
    [PersonId]  uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
    [Type]      Nvarchar(1) -- ('E' for employee, 'C' for customer)
)
Declare @PersonComm TABLE
(
    [PersonId] uniqueidentifier, -- (Can contain GUIDs for employees or clients, see Person Table below)
    [CommunicationId] uniqueidentifier
)

INSERT INTO @Person(Type)
Select 'C' UNION ALL Select 'E'  UNION ALL Select 'C' UNION ALL Select 'E'

INSERT INTO @Comm([Type],[Date])
Select 'C', '01/04/2010' UNION ALL Select 'C', '01/04/2010'
UNION ALL Select 'C', '04/04/2010' UNION ALL Select 'C', '05/01/2010'
UNION ALL Select 'C', '08/04/2009' UNION ALL Select 'C', '09/01/2009'
UNION ALL Select 'M', '01/04/2010' UNION ALL Select 'M', '03/20/2010'
UNION ALL Select 'M', '04/04/2010' UNION ALL Select 'M', '06/01/2010'
UNION ALL Select 'M', '04/10/2009' UNION ALL Select 'M', '04/10/2009'

INSERT INTO @PersonComm
Select  E.PersonID ,  Comm.[CommunicationId]
FROM    @Person E 
        ,@Comm Comm
Where E.[Type] = 'E' 

INSERT INTO @PersonComm
Select  E.PersonID ,  Comm.[CommunicationId]
FROM    @Person E 
        ,@Comm Comm
Where E.[Type] = 'C' 

Select  EmployeeID, 
        ClientID,
        Year, 
        [JanuaryC] AS [Jan Calls], 
        [JanuaryM] AS [Jan Meetings],
        [FebruaryC], 
        [FebruaryM],
        [MarchC], 
        [MarchM], 
        [AprilC], 
        [AprilM], 
        [MayC], 
        [MayM], 
        [JuneC], 
        [JuneM], 
        [JulyC], 
        [JulyM],
        [AugustC], 
        [AugustM],
        [SeptemberC] ,
        [SeptemberM],
        [OctoberC] ,
        [OctoberM],
        [NovemberC],
        [NovemberM],
        [DecemberC], 
        [DecemberM]

FROM 
(
Select P.PersonId EmployeeID, Client.PersonId ClientID, YEAR(C.Date) Year, DateName(m,C.Date) Month,  COUNT(*) Amount, C.Type CommType,
       DateName(m,C.Date) + C.Type PivotColumn -- JanuaryC
FROM    @Comm C
        INNER JOIN @PersonComm PC
            ON PC.CommunicationId = C.CommunicationId
        INNER JOIN @Person P
            ON P.PersonId = PC.PersonId 
        INNER JOIN @PersonComm PCC
            ON PCC.CommunicationId = PC.CommunicationId
        INNER JOIN @Person Client
            ON Client.PersonId = PCC.PersonId AND Client.Type = 'C' 
Where P.Type = 'E'      
Group By P.PersonId, CLient.PersonId, YEAR(C.Date), DateName(m,C.Date), C.Type
) SourceTable
PIVOT (
MAX(Amount)
FOR PivotColumn IN 
    ([JanuaryC], [JanuaryM],[FebruaryC], [FebruaryM],[MarchC], [MarchM], [AprilC], [AprilM], [MayC], [MayM], [JuneC], [JuneM], [JulyC], [JulyM],
     [AugustC], [AugustM],[SeptemberC] , [SeptemberM],[OctoberC] ,[OctoberM],[NovemberC], [NovemberM], [DecemberC], [DecemberM]

)
)As PivotTable
person Ben Dempsey    schedule 25.02.2010